From 2e46354396c886c5fce2008f591170aaad712617 Mon Sep 17 00:00:00 2001 From: Johannes Obermair Date: Thu, 10 Oct 2024 14:38:39 +0200 Subject: [PATCH] Add support for literal arrays to block meta String, number, boolean, and JSON arrays can be defined by setting `array: true`. **Example** ```ts class NewsListBlockData { @BlockField({ type: "string", array: true }) newsIds: string[]; } ``` --- .changeset/spicy-panthers-bake.md | 17 ++++++++++++++++ demo/api/block-meta.json | 10 ++++++---- demo/api/src/news/blocks/news-list.block.ts | 4 ++-- .../src/news/blocks/NewsListBlock.loader.ts | 2 +- packages/api/blocks-api/src/blocks-meta.ts | 1 + packages/api/blocks-api/src/blocks/block.ts | 1 + .../blocks-api/src/blocks/decorators/field.ts | 20 ++++++++++++++----- .../cli/src/commands/generate-block-types.ts | 17 ++++++++++++++++ 8 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 .changeset/spicy-panthers-bake.md diff --git a/.changeset/spicy-panthers-bake.md b/.changeset/spicy-panthers-bake.md new file mode 100644 index 0000000000..d3c00e72f5 --- /dev/null +++ b/.changeset/spicy-panthers-bake.md @@ -0,0 +1,17 @@ +--- +"@comet/blocks-api": minor +"@comet/cli": minor +--- + +Add support for literal arrays to block meta + +String, number, boolean, and JSON arrays can be defined by setting `array: true`. + +**Example** + +```ts +class NewsListBlockData { + @BlockField({ type: "string", array: true }) + newsIds: string[]; +} +``` diff --git a/demo/api/block-meta.json b/demo/api/block-meta.json index f8fccc4426..920d54e29c 100644 --- a/demo/api/block-meta.json +++ b/demo/api/block-meta.json @@ -1194,15 +1194,17 @@ "fields": [ { "name": "ids", - "kind": "Json", - "nullable": false + "kind": "String", + "nullable": false, + "array": true } ], "inputFields": [ { "name": "ids", - "kind": "Json", - "nullable": false + "kind": "String", + "nullable": false, + "array": true } ] }, diff --git a/demo/api/src/news/blocks/news-list.block.ts b/demo/api/src/news/blocks/news-list.block.ts index 8ca5b7bc68..d10a7e5427 100644 --- a/demo/api/src/news/blocks/news-list.block.ts +++ b/demo/api/src/news/blocks/news-list.block.ts @@ -2,12 +2,12 @@ import { BlockData, BlockField, BlockInput, createBlock, inputToData } from "@co import { IsUUID } from "class-validator"; export class NewsListBlockData extends BlockData { - @BlockField({ type: "json" }) + @BlockField({ type: "string", array: true }) ids: string[]; } class NewsListBlockInput extends BlockInput { - @BlockField({ type: "json" }) + @BlockField({ type: "string", array: true }) @IsUUID(undefined, { each: true }) ids: string[]; diff --git a/demo/site/src/news/blocks/NewsListBlock.loader.ts b/demo/site/src/news/blocks/NewsListBlock.loader.ts index f8244ac682..0078fa232d 100644 --- a/demo/site/src/news/blocks/NewsListBlock.loader.ts +++ b/demo/site/src/news/blocks/NewsListBlock.loader.ts @@ -9,7 +9,7 @@ export const loader: BlockLoader = async ({ blockData, graphQ const newsList: LoadedData = []; // TODO create a require that allows loading multiple news by IDs at once? - for (const id of blockData.ids as string[]) { + for (const id of blockData.ids) { const data = await graphQLFetch( gql` query NewsListBlock($id: ID!) { diff --git a/packages/api/blocks-api/src/blocks-meta.ts b/packages/api/blocks-api/src/blocks-meta.ts index e8a7b1fdff..1eb0627617 100644 --- a/packages/api/blocks-api/src/blocks-meta.ts +++ b/packages/api/blocks-api/src/blocks-meta.ts @@ -54,6 +54,7 @@ function extractFromBlockMeta(blockMeta: BlockMetaInterface): BlockMetaField[] { name: field.name, kind: field.kind, nullable: field.nullable, + array: field.array, }; } else if (field.kind === BlockMetaFieldKind.Enum) { return { diff --git a/packages/api/blocks-api/src/blocks/block.ts b/packages/api/blocks-api/src/blocks/block.ts index 5d969b6d6c..4bbe28de1f 100644 --- a/packages/api/blocks-api/src/blocks/block.ts +++ b/packages/api/blocks-api/src/blocks/block.ts @@ -198,6 +198,7 @@ export type BlockMetaField = name: string; kind: BlockMetaLiteralFieldKind; nullable: boolean; + array?: boolean; } | { name: string; kind: BlockMetaFieldKind.Enum; enum: string[]; nullable: boolean } | { name: string; kind: BlockMetaFieldKind.Block; block: Block; nullable: boolean } diff --git a/packages/api/blocks-api/src/blocks/decorators/field.ts b/packages/api/blocks-api/src/blocks/decorators/field.ts index be5148856c..872c78954f 100644 --- a/packages/api/blocks-api/src/blocks/decorators/field.ts +++ b/packages/api/blocks-api/src/blocks/decorators/field.ts @@ -19,8 +19,9 @@ type BlockFieldOptions = nullable?: boolean; } | { - type: "json"; + type: "json" | "string" | "number" | "boolean"; nullable?: boolean; + array?: boolean; } | { nullable?: boolean; @@ -53,6 +54,7 @@ type BlockFieldData = | { kind: BlockMetaLiteralFieldKind; nullable: boolean; + array?: boolean; } | { kind: BlockMetaFieldKind.Enum; enum: string[]; nullable: boolean } | { kind: BlockMetaFieldKind.Block; block: Block; nullable: boolean } @@ -68,13 +70,20 @@ export function getBlockFieldData(ctor: { prototype: any }, propertyKey: string) const fieldType = Reflect.getMetadata(`data:fieldType`, ctor.prototype, propertyKey); const nullable = !!(fieldType && fieldType.nullable); + const array: boolean | undefined = fieldType?.array ?? undefined; if (fieldType && fieldType.type) { - if (fieldType.type === "enum") { + if (fieldType.type === "string") { + ret = { kind: BlockMetaFieldKind.String, nullable, array }; + } else if (fieldType.type === "number") { + ret = { kind: BlockMetaFieldKind.Number, nullable, array }; + } else if (fieldType.type === "boolean") { + ret = { kind: BlockMetaFieldKind.Boolean, nullable, array }; + } else if (fieldType.type === "json") { + ret = { kind: BlockMetaFieldKind.Json, nullable, array }; + } else if (fieldType.type === "enum") { const enumValues = Array.isArray(fieldType.enum) ? fieldType.enum : Object.values(fieldType.enum); ret = { kind: BlockMetaFieldKind.Enum, enum: enumValues, nullable }; - } else if (fieldType.type === "json") { - ret = { kind: BlockMetaFieldKind.Json, nullable }; } else if (fieldType.type === "block") { ret = { kind: BlockMetaFieldKind.Block, block: fieldType.block, nullable }; } else { @@ -110,7 +119,7 @@ export function getBlockFieldData(ctor: { prototype: any }, propertyKey: string) break; case "Array": if (!fieldType || !(isBlockDataInterface(fieldType.prototype) || isBlockInputInterface(fieldType.prototype))) { - throw new Error(`In ${designType.name} for ${propertyKey} only SubBlocks implementing BlockDataInterface are allowed`); + throw new Error(`Unknown array type for ${propertyKey}. An explicit type annotation is necessary.`); } ret = { kind: BlockMetaFieldKind.NestedObjectList, object: fieldType, nullable }; @@ -144,6 +153,7 @@ export class AnnotationBlockMeta implements BlockMetaInterface { name, kind: field.kind, nullable: field.nullable, + array: field.array, }); } else if (field.kind === BlockMetaFieldKind.Enum) { ret.push({ diff --git a/packages/cli/src/commands/generate-block-types.ts b/packages/cli/src/commands/generate-block-types.ts index 9a87e54193..81feef3aaa 100644 --- a/packages/cli/src/commands/generate-block-types.ts +++ b/packages/cli/src/commands/generate-block-types.ts @@ -7,6 +7,7 @@ type BlockMetaField = name: string; kind: "String" | "Number" | "Boolean" | "Json"; nullable: boolean; + array?: boolean; } | { name: string; @@ -48,12 +49,28 @@ let content = ""; function writeFieldType(field: BlockMetaField, blockNamePostfix: string) { if (field.kind === "String") { content += "string"; + + if (field.array) { + content += "[]"; + } } else if (field.kind === "Number") { content += "number"; + + if (field.array) { + content += "[]"; + } } else if (field.kind === "Boolean") { content += "boolean"; + + if (field.array) { + content += "[]"; + } } else if (field.kind === "Json") { content += "unknown"; + + if (field.array) { + content += "[]"; + } } else if (field.kind === "Enum") { content += `"${field.enum.join('" | "')}"`; } else if (field.kind === "Block") {