@storyblok/schema 0.1.0-alpha.1 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/field-plugins/index.cjs +4 -0
  2. package/dist/field-plugins/index.d.cts +2 -0
  3. package/dist/field-plugins/index.d.mts +2 -0
  4. package/dist/field-plugins/index.mjs +3 -0
  5. package/dist/field-plugins/storyblok-color-field.cjs +32 -0
  6. package/dist/field-plugins/storyblok-color-field.cjs.map +1 -0
  7. package/dist/field-plugins/storyblok-color-field.d.cts +16 -0
  8. package/dist/field-plugins/storyblok-color-field.d.mts +16 -0
  9. package/dist/field-plugins/storyblok-color-field.mjs +32 -0
  10. package/dist/field-plugins/storyblok-color-field.mjs.map +1 -0
  11. package/dist/generated/overlay/zod.gen.cjs +0 -2
  12. package/dist/generated/overlay/zod.gen.cjs.map +1 -1
  13. package/dist/generated/overlay/zod.gen.mjs +228 -229
  14. package/dist/generated/overlay/zod.gen.mjs.map +1 -1
  15. package/dist/generated/types/block.d.cts +6 -0
  16. package/dist/generated/types/block.d.mts +6 -0
  17. package/dist/generated/types/field.d.cts +53 -18
  18. package/dist/generated/types/field.d.mts +53 -18
  19. package/dist/generated/types/mapi-story.d.cts +4 -4
  20. package/dist/generated/types/mapi-story.d.mts +4 -4
  21. package/dist/generated/types/story.d.cts +3 -3
  22. package/dist/generated/types/story.d.mts +3 -3
  23. package/dist/helpers/define-block.cjs +6 -1
  24. package/dist/helpers/define-block.cjs.map +1 -1
  25. package/dist/helpers/define-block.d.cts +14 -4
  26. package/dist/helpers/define-block.d.mts +14 -4
  27. package/dist/helpers/define-block.mjs +6 -1
  28. package/dist/helpers/define-block.mjs.map +1 -1
  29. package/dist/helpers/define-field-plugin.cjs +21 -0
  30. package/dist/helpers/define-field-plugin.cjs.map +1 -0
  31. package/dist/helpers/define-field-plugin.d.cts +33 -0
  32. package/dist/helpers/define-field-plugin.d.mts +33 -0
  33. package/dist/helpers/define-field-plugin.mjs +20 -0
  34. package/dist/helpers/define-field-plugin.mjs.map +1 -0
  35. package/dist/helpers/define-field.cjs +8 -1
  36. package/dist/helpers/define-field.cjs.map +1 -1
  37. package/dist/helpers/define-field.d.cts +12 -3
  38. package/dist/helpers/define-field.d.mts +12 -3
  39. package/dist/helpers/define-field.mjs +8 -1
  40. package/dist/helpers/define-field.mjs.map +1 -1
  41. package/dist/helpers/define-folder.cjs +19 -0
  42. package/dist/helpers/define-folder.cjs.map +1 -0
  43. package/dist/helpers/define-folder.d.cts +39 -0
  44. package/dist/helpers/define-folder.d.mts +39 -0
  45. package/dist/helpers/define-folder.mjs +18 -0
  46. package/dist/helpers/define-folder.mjs.map +1 -0
  47. package/dist/helpers/define-schema.cjs +23 -0
  48. package/dist/helpers/define-schema.cjs.map +1 -0
  49. package/dist/helpers/define-schema.d.cts +31 -0
  50. package/dist/helpers/define-schema.d.mts +31 -0
  51. package/dist/helpers/define-schema.mjs +22 -0
  52. package/dist/helpers/define-schema.mjs.map +1 -0
  53. package/dist/helpers/schema-type.d.cts +16 -8
  54. package/dist/helpers/schema-type.d.mts +16 -8
  55. package/dist/index.cjs +6 -0
  56. package/dist/index.d.cts +4 -1
  57. package/dist/index.d.mts +4 -1
  58. package/dist/index.mjs +4 -1
  59. package/dist/utils/slugify-folder-path.cjs +21 -0
  60. package/dist/utils/slugify-folder-path.cjs.map +1 -0
  61. package/dist/utils/slugify-folder-path.mjs +20 -0
  62. package/dist/utils/slugify-folder-path.mjs.map +1 -0
  63. package/dist/validators/internal-schemas.mjs +1 -1
  64. package/dist/validators/shapes.cjs.map +1 -1
  65. package/dist/validators/shapes.d.cts +16 -3
  66. package/dist/validators/shapes.d.mts +16 -3
  67. package/dist/validators/shapes.mjs.map +1 -1
  68. package/dist/validators/validate-schema.cjs +21 -1
  69. package/dist/validators/validate-schema.cjs.map +1 -1
  70. package/dist/validators/validate-schema.d.cts +2 -1
  71. package/dist/validators/validate-schema.d.mts +2 -1
  72. package/dist/validators/validate-schema.mjs +21 -1
  73. package/dist/validators/validate-schema.mjs.map +1 -1
  74. package/dist/validators/validate-story.cjs +96 -32
  75. package/dist/validators/validate-story.cjs.map +1 -1
  76. package/dist/validators/validate-story.mjs +96 -33
  77. package/dist/validators/validate-story.mjs.map +1 -1
  78. package/package.json +13 -2
@@ -1,7 +1,15 @@
1
- import { Block } from "../generated/types/block.cjs";
2
1
  import { Datasource } from "./define-datasource.cjs";
2
+ import { FieldPlugin } from "./define-field-plugin.cjs";
3
+ import { SchemaConfig } from "./define-schema.cjs";
4
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
5
 
4
6
  //#region src/helpers/schema-type.d.ts
7
+ /**
8
+ * Derives a `fieldType → validator output` map from a `fieldPlugins` record.
9
+ * Record keys are cosmetic labels; the map is keyed by each plugin's
10
+ * `fieldType`. Resolves to `{}` when no plugins are registered.
11
+ */
12
+ type FieldPluginMap<TPlugins> = TPlugins extends Record<string, FieldPlugin> ? { [K in keyof TPlugins as TPlugins[K]['fieldType']]: StandardSchemaV1.InferOutput<TPlugins[K]['value']> } : Record<never, never>;
5
13
  /**
6
14
  * Derives a schema types interface from a schema object.
7
15
  *
@@ -12,24 +20,24 @@ import { Datasource } from "./define-datasource.cjs";
12
20
  *
13
21
  * @example
14
22
  * ```ts
23
+ * import { defineSchema } from '@storyblok/schema';
15
24
  * import type { Schema as InferSchema } from '@storyblok/schema';
16
25
  *
17
- * export const schema = {
26
+ * export const schema = defineSchema({
18
27
  * blocks: { pageBlock, heroBlock },
19
28
  * datasources: { colorsDatasource },
20
- * };
29
+ * fieldPlugins: { storyblokColorField },
30
+ * });
21
31
  *
22
32
  * export type Schema = InferSchema<typeof schema>;
23
33
  * export type Blocks = Schema['blocks'];
24
- * export type Datasources = Schema['datasources'];
34
+ * export type FieldPlugins = Schema['fieldPlugins'];
25
35
  * ```
26
36
  */
27
- interface Schema<T extends {
28
- blocks: Record<string, Block>;
29
- datasources?: Record<string, Datasource>;
30
- }> {
37
+ interface Schema<T extends SchemaConfig> {
31
38
  blocks: T['blocks'][keyof T['blocks']];
32
39
  datasources: T['datasources'] extends Record<string, Datasource> ? T['datasources'][keyof T['datasources']] : never;
40
+ fieldPlugins: FieldPluginMap<T['fieldPlugins']>;
33
41
  }
34
42
  //#endregion
35
43
  export { Schema };
@@ -1,7 +1,15 @@
1
- import { Block } from "../generated/types/block.mjs";
2
1
  import { Datasource } from "./define-datasource.mjs";
2
+ import { FieldPlugin } from "./define-field-plugin.mjs";
3
+ import { SchemaConfig } from "./define-schema.mjs";
4
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
5
 
4
6
  //#region src/helpers/schema-type.d.ts
7
+ /**
8
+ * Derives a `fieldType → validator output` map from a `fieldPlugins` record.
9
+ * Record keys are cosmetic labels; the map is keyed by each plugin's
10
+ * `fieldType`. Resolves to `{}` when no plugins are registered.
11
+ */
12
+ type FieldPluginMap<TPlugins> = TPlugins extends Record<string, FieldPlugin> ? { [K in keyof TPlugins as TPlugins[K]['fieldType']]: StandardSchemaV1.InferOutput<TPlugins[K]['value']> } : Record<never, never>;
5
13
  /**
6
14
  * Derives a schema types interface from a schema object.
7
15
  *
@@ -12,24 +20,24 @@ import { Datasource } from "./define-datasource.mjs";
12
20
  *
13
21
  * @example
14
22
  * ```ts
23
+ * import { defineSchema } from '@storyblok/schema';
15
24
  * import type { Schema as InferSchema } from '@storyblok/schema';
16
25
  *
17
- * export const schema = {
26
+ * export const schema = defineSchema({
18
27
  * blocks: { pageBlock, heroBlock },
19
28
  * datasources: { colorsDatasource },
20
- * };
29
+ * fieldPlugins: { storyblokColorField },
30
+ * });
21
31
  *
22
32
  * export type Schema = InferSchema<typeof schema>;
23
33
  * export type Blocks = Schema['blocks'];
24
- * export type Datasources = Schema['datasources'];
34
+ * export type FieldPlugins = Schema['fieldPlugins'];
25
35
  * ```
26
36
  */
27
- interface Schema<T extends {
28
- blocks: Record<string, Block>;
29
- datasources?: Record<string, Datasource>;
30
- }> {
37
+ interface Schema<T extends SchemaConfig> {
31
38
  blocks: T['blocks'][keyof T['blocks']];
32
39
  datasources: T['datasources'] extends Record<string, Datasource> ? T['datasources'][keyof T['datasources']] : never;
40
+ fieldPlugins: FieldPluginMap<T['fieldPlugins']>;
33
41
  }
34
42
  //#endregion
35
43
  export { Schema };
package/dist/index.cjs CHANGED
@@ -2,6 +2,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_define_block = require('./helpers/define-block.cjs');
3
3
  const require_define_datasource = require('./helpers/define-datasource.cjs');
4
4
  const require_define_field = require('./helpers/define-field.cjs');
5
+ const require_define_field_plugin = require('./helpers/define-field-plugin.cjs');
6
+ const require_define_folder = require('./helpers/define-folder.cjs');
7
+ const require_define_schema = require('./helpers/define-schema.cjs');
5
8
  const require_validate_story = require('./validators/validate-story.cjs');
6
9
  const require_create_story_validator = require('./validators/create-story-validator.cjs');
7
10
  const require_validate_schema = require('./validators/validate-schema.cjs');
@@ -10,5 +13,8 @@ exports.createStoryValidator = require_create_story_validator.createStoryValidat
10
13
  exports.defineBlock = require_define_block.defineBlock;
11
14
  exports.defineDatasource = require_define_datasource.defineDatasource;
12
15
  exports.defineField = require_define_field.defineField;
16
+ exports.defineFieldPlugin = require_define_field_plugin.defineFieldPlugin;
17
+ exports.defineFolder = require_define_folder.defineFolder;
18
+ exports.defineSchema = require_define_schema.defineSchema;
13
19
  exports.validateSchema = require_validate_schema.validateSchema;
14
20
  exports.validateStory = require_validate_story.validateStory;
package/dist/index.d.cts CHANGED
@@ -3,12 +3,15 @@ import { Block, BlockFields, NestableBlock, RootBlock } from "./generated/types/
3
3
  import { BlockContent, BlockContentInput, BlocksFieldValue, FieldType, FieldValue, FieldValueInput } from "./generated/types/field.cjs";
4
4
  import { MapiStory } from "./generated/types/mapi-story.cjs";
5
5
  import { Story } from "./generated/types/story.cjs";
6
+ import { BlockFolder, defineFolder } from "./helpers/define-folder.cjs";
6
7
  import { defineBlock } from "./helpers/define-block.cjs";
7
8
  import { Datasource, defineDatasource } from "./helpers/define-datasource.cjs";
8
9
  import { DefinedField, FieldInput, defineField } from "./helpers/define-field.cjs";
10
+ import { FieldPlugin, defineFieldPlugin } from "./helpers/define-field-plugin.cjs";
11
+ import { defineSchema } from "./helpers/define-schema.cjs";
9
12
  import { Schema } from "./helpers/schema-type.cjs";
10
13
  import { createStoryValidator } from "./validators/create-story-validator.cjs";
11
14
  import { ValidationIssue, ValidationResult, ValidationSeverity } from "./validators/types.cjs";
12
15
  import { validateSchema } from "./validators/validate-schema.cjs";
13
16
  import { validateStory } from "./validators/validate-story.cjs";
14
- export { type AssetFieldValue, type Block, type BlockContent, type BlockContentInput, type BlockFields, type BlocksFieldValue, type Datasource, type DefinedField, type Field, type FieldInput, type FieldType, type FieldValue, type FieldValueInput, type MapiStory, type MultilinkFieldValue, type NestableBlock, type PluginFieldValue, type RichtextFieldValue, type RootBlock, type Schema, type Story, type TableFieldValue, type ValidationIssue, type ValidationResult, type ValidationSeverity, createStoryValidator, defineBlock, defineDatasource, defineField, validateSchema, validateStory };
17
+ export { type AssetFieldValue, type Block, type BlockContent, type BlockContentInput, type BlockFields, type BlockFolder, type BlocksFieldValue, type Datasource, type DefinedField, type Field, type FieldInput, type FieldPlugin, type FieldType, type FieldValue, type FieldValueInput, type MapiStory, type MultilinkFieldValue, type NestableBlock, type PluginFieldValue, type RichtextFieldValue, type RootBlock, type Schema, type Story, type TableFieldValue, type ValidationIssue, type ValidationResult, type ValidationSeverity, createStoryValidator, defineBlock, defineDatasource, defineField, defineFieldPlugin, defineFolder, defineSchema, validateSchema, validateStory };
package/dist/index.d.mts CHANGED
@@ -3,12 +3,15 @@ import { Block, BlockFields, NestableBlock, RootBlock } from "./generated/types/
3
3
  import { BlockContent, BlockContentInput, BlocksFieldValue, FieldType, FieldValue, FieldValueInput } from "./generated/types/field.mjs";
4
4
  import { MapiStory } from "./generated/types/mapi-story.mjs";
5
5
  import { Story } from "./generated/types/story.mjs";
6
+ import { BlockFolder, defineFolder } from "./helpers/define-folder.mjs";
6
7
  import { defineBlock } from "./helpers/define-block.mjs";
7
8
  import { Datasource, defineDatasource } from "./helpers/define-datasource.mjs";
8
9
  import { DefinedField, FieldInput, defineField } from "./helpers/define-field.mjs";
10
+ import { FieldPlugin, defineFieldPlugin } from "./helpers/define-field-plugin.mjs";
11
+ import { defineSchema } from "./helpers/define-schema.mjs";
9
12
  import { Schema } from "./helpers/schema-type.mjs";
10
13
  import { createStoryValidator } from "./validators/create-story-validator.mjs";
11
14
  import { ValidationIssue, ValidationResult, ValidationSeverity } from "./validators/types.mjs";
12
15
  import { validateSchema } from "./validators/validate-schema.mjs";
13
16
  import { validateStory } from "./validators/validate-story.mjs";
14
- export { type AssetFieldValue, type Block, type BlockContent, type BlockContentInput, type BlockFields, type BlocksFieldValue, type Datasource, type DefinedField, type Field, type FieldInput, type FieldType, type FieldValue, type FieldValueInput, type MapiStory, type MultilinkFieldValue, type NestableBlock, type PluginFieldValue, type RichtextFieldValue, type RootBlock, type Schema, type Story, type TableFieldValue, type ValidationIssue, type ValidationResult, type ValidationSeverity, createStoryValidator, defineBlock, defineDatasource, defineField, validateSchema, validateStory };
17
+ export { type AssetFieldValue, type Block, type BlockContent, type BlockContentInput, type BlockFields, type BlockFolder, type BlocksFieldValue, type Datasource, type DefinedField, type Field, type FieldInput, type FieldPlugin, type FieldType, type FieldValue, type FieldValueInput, type MapiStory, type MultilinkFieldValue, type NestableBlock, type PluginFieldValue, type RichtextFieldValue, type RootBlock, type Schema, type Story, type TableFieldValue, type ValidationIssue, type ValidationResult, type ValidationSeverity, createStoryValidator, defineBlock, defineDatasource, defineField, defineFieldPlugin, defineFolder, defineSchema, validateSchema, validateStory };
package/dist/index.mjs CHANGED
@@ -1,8 +1,11 @@
1
1
  import { defineBlock } from "./helpers/define-block.mjs";
2
2
  import { defineDatasource } from "./helpers/define-datasource.mjs";
3
3
  import { defineField } from "./helpers/define-field.mjs";
4
+ import { defineFieldPlugin } from "./helpers/define-field-plugin.mjs";
5
+ import { defineFolder } from "./helpers/define-folder.mjs";
6
+ import { defineSchema } from "./helpers/define-schema.mjs";
4
7
  import { validateStory } from "./validators/validate-story.mjs";
5
8
  import { createStoryValidator } from "./validators/create-story-validator.mjs";
6
9
  import { validateSchema } from "./validators/validate-schema.mjs";
7
10
 
8
- export { createStoryValidator, defineBlock, defineDatasource, defineField, validateSchema, validateStory };
11
+ export { createStoryValidator, defineBlock, defineDatasource, defineField, defineFieldPlugin, defineFolder, defineSchema, validateSchema, validateStory };
@@ -0,0 +1,21 @@
1
+
2
+ //#region src/utils/slugify-folder-path.ts
3
+ /**
4
+ * Canonicalizes a folder display path (`'My Layout/Heros'`) to its slug-space
5
+ * identity (`'my-layout/heros'`) by slugifying each `/` segment and dropping
6
+ * empty segments. The validators use it so a folder referenced two ways (a
7
+ * `defineFolder` ref vs. a string shorthand with different casing or separators)
8
+ * resolves to the same folder — matching how the CLI/editor group it.
9
+ *
10
+ * The per-segment slug rule is identical to the `storyblok` CLI's `slugify`
11
+ * (used by its `slugifyPath` for folder-path identity). The two live in separate
12
+ * packages (the CLI has no runtime dependency on `@storyblok/schema`) but must
13
+ * stay in sync; both are locked by golden-case tests.
14
+ */
15
+ function slugifyFolderPath(displayPath) {
16
+ return displayPath.split("/").map((segment) => segment.toString().toLowerCase().replace(/\s+/g, "-").replace(/[^\w-]+/g, "").replace(/-{2,}/g, "-").replace(/^-+/, "").replace(/-+$/, "")).filter(Boolean).join("/");
17
+ }
18
+
19
+ //#endregion
20
+ exports.slugifyFolderPath = slugifyFolderPath;
21
+ //# sourceMappingURL=slugify-folder-path.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify-folder-path.cjs","names":[],"sources":["../../src/utils/slugify-folder-path.ts"],"sourcesContent":["/**\n * Canonicalizes a folder display path (`'My Layout/Heros'`) to its slug-space\n * identity (`'my-layout/heros'`) by slugifying each `/` segment and dropping\n * empty segments. The validators use it so a folder referenced two ways (a\n * `defineFolder` ref vs. a string shorthand with different casing or separators)\n * resolves to the same folder — matching how the CLI/editor group it.\n *\n * The per-segment slug rule is identical to the `storyblok` CLI's `slugify`\n * (used by its `slugifyPath` for folder-path identity). The two live in separate\n * packages (the CLI has no runtime dependency on `@storyblok/schema`) but must\n * stay in sync; both are locked by golden-case tests.\n */\nexport function slugifyFolderPath(displayPath: string): string {\n return displayPath\n .split('/')\n .map(segment =>\n segment\n .toString()\n .toLowerCase()\n .replace(/\\s+/g, '-') // spaces → single dash\n .replace(/[^\\w-]+/g, '') // drop non-word chars\n .replace(/-{2,}/g, '-') // collapse repeated dashes\n .replace(/^-+/, '') // trim leading dashes\n .replace(/-+$/, ''), // trim trailing dashes\n )\n .filter(Boolean)\n .join('/');\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,SAAgB,kBAAkB,aAA6B;AAC7D,QAAO,YACJ,MAAM,IAAI,CACV,KAAI,YACH,QACG,UAAU,CACV,aAAa,CACb,QAAQ,QAAQ,IAAI,CACpB,QAAQ,YAAY,GAAG,CACvB,QAAQ,UAAU,IAAI,CACtB,QAAQ,OAAO,GAAG,CAClB,QAAQ,OAAO,GAAG,CACtB,CACA,OAAO,QAAQ,CACf,KAAK,IAAI"}
@@ -0,0 +1,20 @@
1
+ //#region src/utils/slugify-folder-path.ts
2
+ /**
3
+ * Canonicalizes a folder display path (`'My Layout/Heros'`) to its slug-space
4
+ * identity (`'my-layout/heros'`) by slugifying each `/` segment and dropping
5
+ * empty segments. The validators use it so a folder referenced two ways (a
6
+ * `defineFolder` ref vs. a string shorthand with different casing or separators)
7
+ * resolves to the same folder — matching how the CLI/editor group it.
8
+ *
9
+ * The per-segment slug rule is identical to the `storyblok` CLI's `slugify`
10
+ * (used by its `slugifyPath` for folder-path identity). The two live in separate
11
+ * packages (the CLI has no runtime dependency on `@storyblok/schema`) but must
12
+ * stay in sync; both are locked by golden-case tests.
13
+ */
14
+ function slugifyFolderPath(displayPath) {
15
+ return displayPath.split("/").map((segment) => segment.toString().toLowerCase().replace(/\s+/g, "-").replace(/[^\w-]+/g, "").replace(/-{2,}/g, "-").replace(/^-+/, "").replace(/-+$/, "")).filter(Boolean).join("/");
16
+ }
17
+
18
+ //#endregion
19
+ export { slugifyFolderPath };
20
+ //# sourceMappingURL=slugify-folder-path.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify-folder-path.mjs","names":[],"sources":["../../src/utils/slugify-folder-path.ts"],"sourcesContent":["/**\n * Canonicalizes a folder display path (`'My Layout/Heros'`) to its slug-space\n * identity (`'my-layout/heros'`) by slugifying each `/` segment and dropping\n * empty segments. The validators use it so a folder referenced two ways (a\n * `defineFolder` ref vs. a string shorthand with different casing or separators)\n * resolves to the same folder — matching how the CLI/editor group it.\n *\n * The per-segment slug rule is identical to the `storyblok` CLI's `slugify`\n * (used by its `slugifyPath` for folder-path identity). The two live in separate\n * packages (the CLI has no runtime dependency on `@storyblok/schema`) but must\n * stay in sync; both are locked by golden-case tests.\n */\nexport function slugifyFolderPath(displayPath: string): string {\n return displayPath\n .split('/')\n .map(segment =>\n segment\n .toString()\n .toLowerCase()\n .replace(/\\s+/g, '-') // spaces → single dash\n .replace(/[^\\w-]+/g, '') // drop non-word chars\n .replace(/-{2,}/g, '-') // collapse repeated dashes\n .replace(/^-+/, '') // trim leading dashes\n .replace(/-+$/, ''), // trim trailing dashes\n )\n .filter(Boolean)\n .join('/');\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,kBAAkB,aAA6B;AAC7D,QAAO,YACJ,MAAM,IAAI,CACV,KAAI,YACH,QACG,UAAU,CACV,aAAa,CACb,QAAQ,QAAQ,IAAI,CACpB,QAAQ,YAAY,GAAG,CACvB,QAAQ,UAAU,IAAI,CACtB,QAAQ,OAAO,GAAG,CAClB,QAAQ,OAAO,GAAG,CACtB,CACA,OAAO,QAAQ,CACf,KAAK,IAAI"}
@@ -1,3 +1,3 @@
1
- import { zAssetFieldValue, zMultilinkFieldValue, zPluginFieldValue, zRichtextFieldValue, zTableFieldValue } from "../generated/overlay/zod.gen.mjs";
1
+ import { zAssetFieldValue, zMultilinkFieldValue, zRichtextFieldValue, zTableFieldValue } from "../generated/overlay/zod.gen.mjs";
2
2
 
3
3
  export { };
@@ -1 +1 @@
1
- {"version":3,"file":"shapes.cjs","names":[],"sources":["../../src/validators/shapes.ts"],"sourcesContent":["import type { FieldType } from '../generated/types/field';\n\nexport { isRecord } from '../utils/is-record';\n\n/**\n * Loose structural views of the content-shape definitions, used internally by\n * the validators. A real `Block` / `Datasource` (from the `define*` helpers) is\n * structurally assignable to these, but they also tolerate the plain objects a\n * validator may receive at runtime.\n */\n\n/** A single field within a block's `fields` array. */\nexport interface SchemaFieldLike {\n name: string;\n type: FieldType;\n required?: boolean;\n /** Normalized block-name references for `bloks` fields. */\n allow?: readonly string[];\n /** Normalized datasource slug for option/options fields. */\n datasource?: string;\n // Value constraints enforced by `validateStory` (all optional).\n /** `text`/`textarea`/`markdown`: maximum string length. */\n max_length?: number;\n /** `text`/`textarea`: legacy alias for `max_length`. */\n maxlength?: number;\n /** `text`/`textarea`: minimum string length. */\n minlength?: number;\n /** `number`: inclusive lower bound. */\n min_value?: number;\n /** `number`: inclusive upper bound. */\n max_value?: number;\n /** `number`: maximum number of fractional digits. */\n decimals?: number;\n /** `number`: the value must be a multiple of this step (offset from `min_value`, else 0). */\n steps?: number;\n /** `bloks`: minimum number of nested blocks. */\n minimum?: number;\n /** `bloks`: maximum number of nested blocks. */\n maximum?: number;\n /** `multiasset`: minimum number of assets. */\n minimum_entries?: number;\n /** `multiasset`: maximum number of assets. */\n maximum_entries?: number;\n /** `options`: minimum number of selected options (stored as a string). */\n min_options?: string;\n /** `options`: maximum number of selected options (stored as a string). */\n max_options?: string;\n}\n\n/** A block definition (`defineBlock` result). */\nexport interface SchemaBlockLike {\n name: string;\n fields?: readonly SchemaFieldLike[];\n}\n\n/** A datasource definition (`defineDatasource` result). */\nexport interface SchemaDatasourceLike {\n slug?: string;\n}\n\n/** The schema object accepted by the validators: blocks (required) and datasources (optional). */\nexport interface SchemaLike {\n blocks: Record<string, SchemaBlockLike> | readonly SchemaBlockLike[];\n datasources?: Record<string, SchemaDatasourceLike> | readonly SchemaDatasourceLike[];\n}\n\n/** Normalizes a record-or-array of entities to an array of values. */\nexport function toValues<T>(input: Record<string, T> | readonly T[] | undefined): T[] {\n if (!input) {\n return [];\n }\n return Array.isArray(input) ? [...input] : Object.values(input as Record<string, T>);\n}\n"],"mappings":";;;;AAmEA,SAAgB,SAAY,OAA0D;AACpF,KAAI,CAAC,MACH,QAAO,EAAE;AAEX,QAAO,MAAM,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,OAAO,OAAO,MAA2B"}
1
+ {"version":3,"file":"shapes.cjs","names":[],"sources":["../../src/validators/shapes.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { FieldType } from '../generated/types/field';\n\nexport { isRecord } from '../utils/is-record';\n\n/**\n * Loose structural views of the content-shape definitions, used internally by\n * the validators. A real `Block` / `Datasource` (from the `define*` helpers) is\n * structurally assignable to these, but they also tolerate the plain objects a\n * validator may receive at runtime.\n */\n\n/** A single field within a block's `fields` array. */\nexport interface SchemaFieldLike {\n name: string;\n type: FieldType;\n /** `custom`: the field plugin discriminant, matched against registered `fieldPlugins`. */\n field_type?: string;\n required?: boolean;\n /** Normalized block-name or folder-path references for `bloks` fields. */\n allow?: readonly (string | { folder: string })[];\n /** Normalized datasource slug for option/options fields. */\n datasource?: string;\n // Value constraints enforced by `validateStory` (all optional).\n /** `text`/`textarea`/`markdown`: maximum string length. */\n max_length?: number;\n /** `text`/`textarea`: legacy alias for `max_length`. */\n maxlength?: number;\n /** `text`/`textarea`: minimum string length. */\n minlength?: number;\n /** `number`: inclusive lower bound. */\n min_value?: number;\n /** `number`: inclusive upper bound. */\n max_value?: number;\n /** `number`: maximum number of fractional digits. */\n decimals?: number;\n /** `number`: the value must be a multiple of this step (offset from `min_value`, else 0). */\n steps?: number;\n /** `bloks`: minimum number of nested blocks. */\n minimum?: number;\n /** `bloks`: maximum number of nested blocks. */\n maximum?: number;\n /** `multiasset`: minimum number of assets. */\n minimum_entries?: number;\n /** `multiasset`: maximum number of assets. */\n maximum_entries?: number;\n /** `options`: minimum number of selected options (stored as a string). */\n min_options?: string;\n /** `options`: maximum number of selected options (stored as a string). */\n max_options?: string;\n}\n\n/** A block definition (`defineBlock` result). */\nexport interface SchemaBlockLike {\n name: string;\n fields?: readonly SchemaFieldLike[];\n /** Display path of the folder (component group) this block belongs to, if any. */\n folder?: string | null;\n}\n\n/** A datasource definition (`defineDatasource` result). */\nexport interface SchemaDatasourceLike {\n slug?: string;\n}\n\n/** A field plugin registration (`defineFieldPlugin` result). */\nexport interface FieldPluginLike {\n fieldType: string;\n value: StandardSchemaV1;\n}\n\n/** The schema object accepted by the validators: blocks (required), datasources and field plugins (optional). */\nexport interface SchemaLike {\n blocks: Record<string, SchemaBlockLike> | readonly SchemaBlockLike[];\n datasources?: Record<string, SchemaDatasourceLike> | readonly SchemaDatasourceLike[];\n fieldPlugins?: Record<string, FieldPluginLike> | readonly FieldPluginLike[];\n}\n\n/** Normalizes a record-or-array of entities to an array of values. */\nexport function toValues<T>(input: Record<string, T> | readonly T[] | undefined): T[] {\n if (!input) {\n return [];\n }\n return Array.isArray(input) ? [...input] : Object.values(input as Record<string, T>);\n}\n"],"mappings":";;;;AA+EA,SAAgB,SAAY,OAA0D;AACpF,KAAI,CAAC,MACH,QAAO,EAAE;AAEX,QAAO,MAAM,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,OAAO,OAAO,MAA2B"}
@@ -1,4 +1,5 @@
1
1
  import { FieldType } from "../generated/types/field.cjs";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
2
3
 
3
4
  //#region src/validators/shapes.d.ts
4
5
  /**
@@ -11,9 +12,13 @@ import { FieldType } from "../generated/types/field.cjs";
11
12
  interface SchemaFieldLike {
12
13
  name: string;
13
14
  type: FieldType;
15
+ /** `custom`: the field plugin discriminant, matched against registered `fieldPlugins`. */
16
+ field_type?: string;
14
17
  required?: boolean;
15
- /** Normalized block-name references for `bloks` fields. */
16
- allow?: readonly string[];
18
+ /** Normalized block-name or folder-path references for `bloks` fields. */
19
+ allow?: readonly (string | {
20
+ folder: string;
21
+ })[];
17
22
  /** Normalized datasource slug for option/options fields. */
18
23
  datasource?: string;
19
24
  /** `text`/`textarea`/`markdown`: maximum string length. */
@@ -47,15 +52,23 @@ interface SchemaFieldLike {
47
52
  interface SchemaBlockLike {
48
53
  name: string;
49
54
  fields?: readonly SchemaFieldLike[];
55
+ /** Display path of the folder (component group) this block belongs to, if any. */
56
+ folder?: string | null;
50
57
  }
51
58
  /** A datasource definition (`defineDatasource` result). */
52
59
  interface SchemaDatasourceLike {
53
60
  slug?: string;
54
61
  }
55
- /** The schema object accepted by the validators: blocks (required) and datasources (optional). */
62
+ /** A field plugin registration (`defineFieldPlugin` result). */
63
+ interface FieldPluginLike {
64
+ fieldType: string;
65
+ value: StandardSchemaV1;
66
+ }
67
+ /** The schema object accepted by the validators: blocks (required), datasources and field plugins (optional). */
56
68
  interface SchemaLike {
57
69
  blocks: Record<string, SchemaBlockLike> | readonly SchemaBlockLike[];
58
70
  datasources?: Record<string, SchemaDatasourceLike> | readonly SchemaDatasourceLike[];
71
+ fieldPlugins?: Record<string, FieldPluginLike> | readonly FieldPluginLike[];
59
72
  }
60
73
  //#endregion
61
74
  export { SchemaBlockLike, SchemaLike };
@@ -1,4 +1,5 @@
1
1
  import { FieldType } from "../generated/types/field.mjs";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
2
3
 
3
4
  //#region src/validators/shapes.d.ts
4
5
  /**
@@ -11,9 +12,13 @@ import { FieldType } from "../generated/types/field.mjs";
11
12
  interface SchemaFieldLike {
12
13
  name: string;
13
14
  type: FieldType;
15
+ /** `custom`: the field plugin discriminant, matched against registered `fieldPlugins`. */
16
+ field_type?: string;
14
17
  required?: boolean;
15
- /** Normalized block-name references for `bloks` fields. */
16
- allow?: readonly string[];
18
+ /** Normalized block-name or folder-path references for `bloks` fields. */
19
+ allow?: readonly (string | {
20
+ folder: string;
21
+ })[];
17
22
  /** Normalized datasource slug for option/options fields. */
18
23
  datasource?: string;
19
24
  /** `text`/`textarea`/`markdown`: maximum string length. */
@@ -47,15 +52,23 @@ interface SchemaFieldLike {
47
52
  interface SchemaBlockLike {
48
53
  name: string;
49
54
  fields?: readonly SchemaFieldLike[];
55
+ /** Display path of the folder (component group) this block belongs to, if any. */
56
+ folder?: string | null;
50
57
  }
51
58
  /** A datasource definition (`defineDatasource` result). */
52
59
  interface SchemaDatasourceLike {
53
60
  slug?: string;
54
61
  }
55
- /** The schema object accepted by the validators: blocks (required) and datasources (optional). */
62
+ /** A field plugin registration (`defineFieldPlugin` result). */
63
+ interface FieldPluginLike {
64
+ fieldType: string;
65
+ value: StandardSchemaV1;
66
+ }
67
+ /** The schema object accepted by the validators: blocks (required), datasources and field plugins (optional). */
56
68
  interface SchemaLike {
57
69
  blocks: Record<string, SchemaBlockLike> | readonly SchemaBlockLike[];
58
70
  datasources?: Record<string, SchemaDatasourceLike> | readonly SchemaDatasourceLike[];
71
+ fieldPlugins?: Record<string, FieldPluginLike> | readonly FieldPluginLike[];
59
72
  }
60
73
  //#endregion
61
74
  export { SchemaBlockLike, SchemaLike };
@@ -1 +1 @@
1
- {"version":3,"file":"shapes.mjs","names":[],"sources":["../../src/validators/shapes.ts"],"sourcesContent":["import type { FieldType } from '../generated/types/field';\n\nexport { isRecord } from '../utils/is-record';\n\n/**\n * Loose structural views of the content-shape definitions, used internally by\n * the validators. A real `Block` / `Datasource` (from the `define*` helpers) is\n * structurally assignable to these, but they also tolerate the plain objects a\n * validator may receive at runtime.\n */\n\n/** A single field within a block's `fields` array. */\nexport interface SchemaFieldLike {\n name: string;\n type: FieldType;\n required?: boolean;\n /** Normalized block-name references for `bloks` fields. */\n allow?: readonly string[];\n /** Normalized datasource slug for option/options fields. */\n datasource?: string;\n // Value constraints enforced by `validateStory` (all optional).\n /** `text`/`textarea`/`markdown`: maximum string length. */\n max_length?: number;\n /** `text`/`textarea`: legacy alias for `max_length`. */\n maxlength?: number;\n /** `text`/`textarea`: minimum string length. */\n minlength?: number;\n /** `number`: inclusive lower bound. */\n min_value?: number;\n /** `number`: inclusive upper bound. */\n max_value?: number;\n /** `number`: maximum number of fractional digits. */\n decimals?: number;\n /** `number`: the value must be a multiple of this step (offset from `min_value`, else 0). */\n steps?: number;\n /** `bloks`: minimum number of nested blocks. */\n minimum?: number;\n /** `bloks`: maximum number of nested blocks. */\n maximum?: number;\n /** `multiasset`: minimum number of assets. */\n minimum_entries?: number;\n /** `multiasset`: maximum number of assets. */\n maximum_entries?: number;\n /** `options`: minimum number of selected options (stored as a string). */\n min_options?: string;\n /** `options`: maximum number of selected options (stored as a string). */\n max_options?: string;\n}\n\n/** A block definition (`defineBlock` result). */\nexport interface SchemaBlockLike {\n name: string;\n fields?: readonly SchemaFieldLike[];\n}\n\n/** A datasource definition (`defineDatasource` result). */\nexport interface SchemaDatasourceLike {\n slug?: string;\n}\n\n/** The schema object accepted by the validators: blocks (required) and datasources (optional). */\nexport interface SchemaLike {\n blocks: Record<string, SchemaBlockLike> | readonly SchemaBlockLike[];\n datasources?: Record<string, SchemaDatasourceLike> | readonly SchemaDatasourceLike[];\n}\n\n/** Normalizes a record-or-array of entities to an array of values. */\nexport function toValues<T>(input: Record<string, T> | readonly T[] | undefined): T[] {\n if (!input) {\n return [];\n }\n return Array.isArray(input) ? [...input] : Object.values(input as Record<string, T>);\n}\n"],"mappings":";;;;AAmEA,SAAgB,SAAY,OAA0D;AACpF,KAAI,CAAC,MACH,QAAO,EAAE;AAEX,QAAO,MAAM,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,OAAO,OAAO,MAA2B"}
1
+ {"version":3,"file":"shapes.mjs","names":[],"sources":["../../src/validators/shapes.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { FieldType } from '../generated/types/field';\n\nexport { isRecord } from '../utils/is-record';\n\n/**\n * Loose structural views of the content-shape definitions, used internally by\n * the validators. A real `Block` / `Datasource` (from the `define*` helpers) is\n * structurally assignable to these, but they also tolerate the plain objects a\n * validator may receive at runtime.\n */\n\n/** A single field within a block's `fields` array. */\nexport interface SchemaFieldLike {\n name: string;\n type: FieldType;\n /** `custom`: the field plugin discriminant, matched against registered `fieldPlugins`. */\n field_type?: string;\n required?: boolean;\n /** Normalized block-name or folder-path references for `bloks` fields. */\n allow?: readonly (string | { folder: string })[];\n /** Normalized datasource slug for option/options fields. */\n datasource?: string;\n // Value constraints enforced by `validateStory` (all optional).\n /** `text`/`textarea`/`markdown`: maximum string length. */\n max_length?: number;\n /** `text`/`textarea`: legacy alias for `max_length`. */\n maxlength?: number;\n /** `text`/`textarea`: minimum string length. */\n minlength?: number;\n /** `number`: inclusive lower bound. */\n min_value?: number;\n /** `number`: inclusive upper bound. */\n max_value?: number;\n /** `number`: maximum number of fractional digits. */\n decimals?: number;\n /** `number`: the value must be a multiple of this step (offset from `min_value`, else 0). */\n steps?: number;\n /** `bloks`: minimum number of nested blocks. */\n minimum?: number;\n /** `bloks`: maximum number of nested blocks. */\n maximum?: number;\n /** `multiasset`: minimum number of assets. */\n minimum_entries?: number;\n /** `multiasset`: maximum number of assets. */\n maximum_entries?: number;\n /** `options`: minimum number of selected options (stored as a string). */\n min_options?: string;\n /** `options`: maximum number of selected options (stored as a string). */\n max_options?: string;\n}\n\n/** A block definition (`defineBlock` result). */\nexport interface SchemaBlockLike {\n name: string;\n fields?: readonly SchemaFieldLike[];\n /** Display path of the folder (component group) this block belongs to, if any. */\n folder?: string | null;\n}\n\n/** A datasource definition (`defineDatasource` result). */\nexport interface SchemaDatasourceLike {\n slug?: string;\n}\n\n/** A field plugin registration (`defineFieldPlugin` result). */\nexport interface FieldPluginLike {\n fieldType: string;\n value: StandardSchemaV1;\n}\n\n/** The schema object accepted by the validators: blocks (required), datasources and field plugins (optional). */\nexport interface SchemaLike {\n blocks: Record<string, SchemaBlockLike> | readonly SchemaBlockLike[];\n datasources?: Record<string, SchemaDatasourceLike> | readonly SchemaDatasourceLike[];\n fieldPlugins?: Record<string, FieldPluginLike> | readonly FieldPluginLike[];\n}\n\n/** Normalizes a record-or-array of entities to an array of values. */\nexport function toValues<T>(input: Record<string, T> | readonly T[] | undefined): T[] {\n if (!input) {\n return [];\n }\n return Array.isArray(input) ? [...input] : Object.values(input as Record<string, T>);\n}\n"],"mappings":";;;;AA+EA,SAAgB,SAAY,OAA0D;AACpF,KAAI,CAAC,MACH,QAAO,EAAE;AAEX,QAAO,MAAM,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,OAAO,OAAO,MAA2B"}
@@ -6,7 +6,8 @@ const require_shapes = require('./shapes.cjs');
6
6
  * Validates a schema definition without throwing. Checks structural identity
7
7
  * (duplicate block names, field names, and datasource slugs) and cross-references
8
8
  * (every `allow` entry resolves to a defined block; every field `datasource`
9
- * resolves to a defined datasource).
9
+ * resolves to a defined datasource; every `custom` field's `field_type`
10
+ * resolves to a registered field plugin).
10
11
  *
11
12
  * @example
12
13
  * const result = validateSchema({ blocks: { hero }, datasources: { colors } });
@@ -16,6 +17,12 @@ function validateSchema(schema) {
16
17
  const issues = [];
17
18
  const blocks = require_shapes.toValues(schema.blocks);
18
19
  const datasources = require_shapes.toValues(schema.datasources);
20
+ const fieldPlugins = require_shapes.toValues(schema.fieldPlugins);
21
+ const fieldPluginTypes = /* @__PURE__ */ new Set();
22
+ for (const plugin of fieldPlugins) {
23
+ const fieldType = plugin?.fieldType;
24
+ if (typeof fieldType === "string") fieldPluginTypes.add(fieldType);
25
+ }
19
26
  const datasourceSlugs = /* @__PURE__ */ new Set();
20
27
  for (const datasource of datasources) {
21
28
  const slug = datasource?.slug;
@@ -112,6 +119,19 @@ function validateSchema(schema) {
112
119
  entity: `block:${blockName}`,
113
120
  message: `Field "${fieldName}" references unknown datasource "${datasource}".`
114
121
  });
122
+ const fieldType = field.field_type;
123
+ if (field.type === "custom" && typeof fieldType === "string" && !fieldPluginTypes.has(fieldType)) issues.push({
124
+ severity: "error",
125
+ code: "unresolved_field_plugin",
126
+ path: [
127
+ "blocks",
128
+ blockName,
129
+ fieldName ?? "",
130
+ "field_type"
131
+ ],
132
+ entity: `block:${blockName}`,
133
+ message: `Field "${fieldName}" references unregistered field plugin "${fieldType}".`
134
+ });
115
135
  }
116
136
  }
117
137
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"validate-schema.cjs","names":["toValues","isRecord"],"sources":["../../src/validators/validate-schema.ts"],"sourcesContent":["import type { SchemaLike } from './shapes';\nimport type { ValidationIssue, ValidationResult } from './types';\nimport { isRecord, toValues } from './shapes';\n\n/**\n * Validates a schema definition without throwing. Checks structural identity\n * (duplicate block names, field names, and datasource slugs) and cross-references\n * (every `allow` entry resolves to a defined block; every field `datasource`\n * resolves to a defined datasource).\n *\n * @example\n * const result = validateSchema({ blocks: { hero }, datasources: { colors } });\n * if (!result.ok) console.error(result.issues);\n */\nexport function validateSchema(schema: SchemaLike): ValidationResult {\n const issues: ValidationIssue[] = [];\n const blocks = toValues(schema.blocks);\n const datasources = toValues(schema.datasources);\n\n const datasourceSlugs = new Set<string>();\n for (const datasource of datasources) {\n const slug = datasource?.slug;\n if (typeof slug !== 'string') {\n continue;\n }\n if (datasourceSlugs.has(slug)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_datasource_slug',\n path: ['datasources', slug],\n entity: `datasource:${slug}`,\n message: `Duplicate datasource slug \"${slug}\".`,\n });\n }\n datasourceSlugs.add(slug);\n }\n\n const blockNames = new Set<string>();\n for (const block of blocks) {\n const name = block?.name;\n if (typeof name !== 'string') {\n continue;\n }\n if (blockNames.has(name)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_block_name',\n path: ['blocks', name],\n entity: `block:${name}`,\n message: `Duplicate block name \"${name}\".`,\n });\n }\n blockNames.add(name);\n }\n\n // Field-level checks run after the name/slug sets are fully populated so that\n // forward and circular references (resolved by name) validate correctly.\n for (const block of blocks) {\n const blockName = typeof block?.name === 'string' ? block.name : '';\n const fieldNames = new Set<string>();\n const fields = block?.fields ?? [];\n for (let index = 0; index < fields.length; index++) {\n const field = fields[index];\n // Flag malformed fields the wire mapper would otherwise silently drop:\n // a non-object entry, or one without a string `name` (its mapping key).\n if (!isRecord(field)) {\n issues.push({\n severity: 'error',\n code: 'invalid_field',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is not an object.`,\n });\n continue;\n }\n const fieldName = field.name;\n if (typeof fieldName === 'string') {\n if (fieldNames.has(fieldName)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_field_name',\n path: ['blocks', blockName, fieldName],\n entity: `block:${blockName}`,\n message: `Duplicate field name \"${fieldName}\" in block \"${blockName}\".`,\n });\n }\n fieldNames.add(fieldName);\n }\n else {\n issues.push({\n severity: 'error',\n code: 'missing_field_name',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is missing a string \"name\".`,\n });\n }\n\n for (const allowed of field.allow ?? []) {\n if (typeof allowed === 'string' && !blockNames.has(allowed)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_allow',\n path: ['blocks', blockName, fieldName ?? '', 'allow'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" allows unknown block \"${allowed}\".`,\n });\n }\n }\n\n const datasource = field.datasource;\n if (typeof datasource === 'string' && !datasourceSlugs.has(datasource)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_datasource',\n path: ['blocks', blockName, fieldName ?? '', 'datasource'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" references unknown datasource \"${datasource}\".`,\n });\n }\n }\n }\n\n return { ok: issues.every(issue => issue.severity !== 'error'), issues };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,eAAe,QAAsC;CACnE,MAAM,SAA4B,EAAE;CACpC,MAAM,SAASA,wBAAS,OAAO,OAAO;CACtC,MAAM,cAAcA,wBAAS,OAAO,YAAY;CAEhD,MAAM,kCAAkB,IAAI,KAAa;AACzC,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,OAAO,YAAY;AACzB,MAAI,OAAO,SAAS,SAClB;AAEF,MAAI,gBAAgB,IAAI,KAAK,CAC3B,QAAO,KAAK;GACV,UAAU;GACV,MAAM;GACN,MAAM,CAAC,eAAe,KAAK;GAC3B,QAAQ,cAAc;GACtB,SAAS,8BAA8B,KAAK;GAC7C,CAAC;AAEJ,kBAAgB,IAAI,KAAK;;CAG3B,MAAM,6BAAa,IAAI,KAAa;AACpC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,OAAO;AACpB,MAAI,OAAO,SAAS,SAClB;AAEF,MAAI,WAAW,IAAI,KAAK,CACtB,QAAO,KAAK;GACV,UAAU;GACV,MAAM;GACN,MAAM,CAAC,UAAU,KAAK;GACtB,QAAQ,SAAS;GACjB,SAAS,yBAAyB,KAAK;GACxC,CAAC;AAEJ,aAAW,IAAI,KAAK;;AAKtB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,YAAY,OAAO,OAAO,SAAS,WAAW,MAAM,OAAO;EACjE,MAAM,6BAAa,IAAI,KAAa;EACpC,MAAM,SAAS,OAAO,UAAU,EAAE;AAClC,OAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS;GAClD,MAAM,QAAQ,OAAO;AAGrB,OAAI,CAACC,2BAAS,MAAM,EAAE;AACpB,WAAO,KAAK;KACV,UAAU;KACV,MAAM;KACN,MAAM;MAAC;MAAU;MAAW;MAAM;KAClC,QAAQ,SAAS;KACjB,SAAS,kBAAkB,MAAM,aAAa,UAAU;KACzD,CAAC;AACF;;GAEF,MAAM,YAAY,MAAM;AACxB,OAAI,OAAO,cAAc,UAAU;AACjC,QAAI,WAAW,IAAI,UAAU,CAC3B,QAAO,KAAK;KACV,UAAU;KACV,MAAM;KACN,MAAM;MAAC;MAAU;MAAW;MAAU;KACtC,QAAQ,SAAS;KACjB,SAAS,yBAAyB,UAAU,cAAc,UAAU;KACrE,CAAC;AAEJ,eAAW,IAAI,UAAU;SAGzB,QAAO,KAAK;IACV,UAAU;IACV,MAAM;IACN,MAAM;KAAC;KAAU;KAAW;KAAM;IAClC,QAAQ,SAAS;IACjB,SAAS,kBAAkB,MAAM,aAAa,UAAU;IACzD,CAAC;AAGJ,QAAK,MAAM,WAAW,MAAM,SAAS,EAAE,CACrC,KAAI,OAAO,YAAY,YAAY,CAAC,WAAW,IAAI,QAAQ,CACzD,QAAO,KAAK;IACV,UAAU;IACV,MAAM;IACN,MAAM;KAAC;KAAU;KAAW,aAAa;KAAI;KAAQ;IACrD,QAAQ,SAAS;IACjB,SAAS,UAAU,UAAU,0BAA0B,QAAQ;IAChE,CAAC;GAIN,MAAM,aAAa,MAAM;AACzB,OAAI,OAAO,eAAe,YAAY,CAAC,gBAAgB,IAAI,WAAW,CACpE,QAAO,KAAK;IACV,UAAU;IACV,MAAM;IACN,MAAM;KAAC;KAAU;KAAW,aAAa;KAAI;KAAa;IAC1D,QAAQ,SAAS;IACjB,SAAS,UAAU,UAAU,mCAAmC,WAAW;IAC5E,CAAC;;;AAKR,QAAO;EAAE,IAAI,OAAO,OAAM,UAAS,MAAM,aAAa,QAAQ;EAAE;EAAQ"}
1
+ {"version":3,"file":"validate-schema.cjs","names":["toValues","isRecord"],"sources":["../../src/validators/validate-schema.ts"],"sourcesContent":["import type { SchemaLike } from './shapes';\nimport type { ValidationIssue, ValidationResult } from './types';\nimport { isRecord, toValues } from './shapes';\n\n/**\n * Validates a schema definition without throwing. Checks structural identity\n * (duplicate block names, field names, and datasource slugs) and cross-references\n * (every `allow` entry resolves to a defined block; every field `datasource`\n * resolves to a defined datasource; every `custom` field's `field_type`\n * resolves to a registered field plugin).\n *\n * @example\n * const result = validateSchema({ blocks: { hero }, datasources: { colors } });\n * if (!result.ok) console.error(result.issues);\n */\nexport function validateSchema(schema: SchemaLike): ValidationResult {\n const issues: ValidationIssue[] = [];\n const blocks = toValues(schema.blocks);\n const datasources = toValues(schema.datasources);\n const fieldPlugins = toValues(schema.fieldPlugins);\n\n const fieldPluginTypes = new Set<string>();\n for (const plugin of fieldPlugins) {\n const fieldType = plugin?.fieldType;\n if (typeof fieldType === 'string') {\n fieldPluginTypes.add(fieldType);\n }\n }\n\n const datasourceSlugs = new Set<string>();\n for (const datasource of datasources) {\n const slug = datasource?.slug;\n if (typeof slug !== 'string') {\n continue;\n }\n if (datasourceSlugs.has(slug)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_datasource_slug',\n path: ['datasources', slug],\n entity: `datasource:${slug}`,\n message: `Duplicate datasource slug \"${slug}\".`,\n });\n }\n datasourceSlugs.add(slug);\n }\n\n const blockNames = new Set<string>();\n for (const block of blocks) {\n const name = block?.name;\n if (typeof name !== 'string') {\n continue;\n }\n if (blockNames.has(name)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_block_name',\n path: ['blocks', name],\n entity: `block:${name}`,\n message: `Duplicate block name \"${name}\".`,\n });\n }\n blockNames.add(name);\n }\n\n // Field-level checks run after the name/slug sets are fully populated so that\n // forward and circular references (resolved by name) validate correctly.\n for (const block of blocks) {\n const blockName = typeof block?.name === 'string' ? block.name : '';\n const fieldNames = new Set<string>();\n const fields = block?.fields ?? [];\n for (let index = 0; index < fields.length; index++) {\n const field = fields[index];\n // Flag malformed fields the wire mapper would otherwise silently drop:\n // a non-object entry, or one without a string `name` (its mapping key).\n if (!isRecord(field)) {\n issues.push({\n severity: 'error',\n code: 'invalid_field',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is not an object.`,\n });\n continue;\n }\n const fieldName = field.name;\n if (typeof fieldName === 'string') {\n if (fieldNames.has(fieldName)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_field_name',\n path: ['blocks', blockName, fieldName],\n entity: `block:${blockName}`,\n message: `Duplicate field name \"${fieldName}\" in block \"${blockName}\".`,\n });\n }\n fieldNames.add(fieldName);\n }\n else {\n issues.push({\n severity: 'error',\n code: 'missing_field_name',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is missing a string \"name\".`,\n });\n }\n\n for (const allowed of field.allow ?? []) {\n if (typeof allowed === 'string' && !blockNames.has(allowed)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_allow',\n path: ['blocks', blockName, fieldName ?? '', 'allow'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" allows unknown block \"${allowed}\".`,\n });\n }\n }\n\n const datasource = field.datasource;\n if (typeof datasource === 'string' && !datasourceSlugs.has(datasource)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_datasource',\n path: ['blocks', blockName, fieldName ?? '', 'datasource'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" references unknown datasource \"${datasource}\".`,\n });\n }\n\n const fieldType = field.field_type;\n if (field.type === 'custom' && typeof fieldType === 'string' && !fieldPluginTypes.has(fieldType)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_field_plugin',\n path: ['blocks', blockName, fieldName ?? '', 'field_type'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" references unregistered field plugin \"${fieldType}\".`,\n });\n }\n }\n }\n\n return { ok: issues.every(issue => issue.severity !== 'error'), issues };\n}\n"],"mappings":"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"}
@@ -6,7 +6,8 @@ import { ValidationResult } from "./types.cjs";
6
6
  * Validates a schema definition without throwing. Checks structural identity
7
7
  * (duplicate block names, field names, and datasource slugs) and cross-references
8
8
  * (every `allow` entry resolves to a defined block; every field `datasource`
9
- * resolves to a defined datasource).
9
+ * resolves to a defined datasource; every `custom` field's `field_type`
10
+ * resolves to a registered field plugin).
10
11
  *
11
12
  * @example
12
13
  * const result = validateSchema({ blocks: { hero }, datasources: { colors } });
@@ -6,7 +6,8 @@ import { ValidationResult } from "./types.mjs";
6
6
  * Validates a schema definition without throwing. Checks structural identity
7
7
  * (duplicate block names, field names, and datasource slugs) and cross-references
8
8
  * (every `allow` entry resolves to a defined block; every field `datasource`
9
- * resolves to a defined datasource).
9
+ * resolves to a defined datasource; every `custom` field's `field_type`
10
+ * resolves to a registered field plugin).
10
11
  *
11
12
  * @example
12
13
  * const result = validateSchema({ blocks: { hero }, datasources: { colors } });
@@ -6,7 +6,8 @@ import { toValues } from "./shapes.mjs";
6
6
  * Validates a schema definition without throwing. Checks structural identity
7
7
  * (duplicate block names, field names, and datasource slugs) and cross-references
8
8
  * (every `allow` entry resolves to a defined block; every field `datasource`
9
- * resolves to a defined datasource).
9
+ * resolves to a defined datasource; every `custom` field's `field_type`
10
+ * resolves to a registered field plugin).
10
11
  *
11
12
  * @example
12
13
  * const result = validateSchema({ blocks: { hero }, datasources: { colors } });
@@ -16,6 +17,12 @@ function validateSchema(schema) {
16
17
  const issues = [];
17
18
  const blocks = toValues(schema.blocks);
18
19
  const datasources = toValues(schema.datasources);
20
+ const fieldPlugins = toValues(schema.fieldPlugins);
21
+ const fieldPluginTypes = /* @__PURE__ */ new Set();
22
+ for (const plugin of fieldPlugins) {
23
+ const fieldType = plugin?.fieldType;
24
+ if (typeof fieldType === "string") fieldPluginTypes.add(fieldType);
25
+ }
19
26
  const datasourceSlugs = /* @__PURE__ */ new Set();
20
27
  for (const datasource of datasources) {
21
28
  const slug = datasource?.slug;
@@ -112,6 +119,19 @@ function validateSchema(schema) {
112
119
  entity: `block:${blockName}`,
113
120
  message: `Field "${fieldName}" references unknown datasource "${datasource}".`
114
121
  });
122
+ const fieldType = field.field_type;
123
+ if (field.type === "custom" && typeof fieldType === "string" && !fieldPluginTypes.has(fieldType)) issues.push({
124
+ severity: "error",
125
+ code: "unresolved_field_plugin",
126
+ path: [
127
+ "blocks",
128
+ blockName,
129
+ fieldName ?? "",
130
+ "field_type"
131
+ ],
132
+ entity: `block:${blockName}`,
133
+ message: `Field "${fieldName}" references unregistered field plugin "${fieldType}".`
134
+ });
115
135
  }
116
136
  }
117
137
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"validate-schema.mjs","names":[],"sources":["../../src/validators/validate-schema.ts"],"sourcesContent":["import type { SchemaLike } from './shapes';\nimport type { ValidationIssue, ValidationResult } from './types';\nimport { isRecord, toValues } from './shapes';\n\n/**\n * Validates a schema definition without throwing. Checks structural identity\n * (duplicate block names, field names, and datasource slugs) and cross-references\n * (every `allow` entry resolves to a defined block; every field `datasource`\n * resolves to a defined datasource).\n *\n * @example\n * const result = validateSchema({ blocks: { hero }, datasources: { colors } });\n * if (!result.ok) console.error(result.issues);\n */\nexport function validateSchema(schema: SchemaLike): ValidationResult {\n const issues: ValidationIssue[] = [];\n const blocks = toValues(schema.blocks);\n const datasources = toValues(schema.datasources);\n\n const datasourceSlugs = new Set<string>();\n for (const datasource of datasources) {\n const slug = datasource?.slug;\n if (typeof slug !== 'string') {\n continue;\n }\n if (datasourceSlugs.has(slug)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_datasource_slug',\n path: ['datasources', slug],\n entity: `datasource:${slug}`,\n message: `Duplicate datasource slug \"${slug}\".`,\n });\n }\n datasourceSlugs.add(slug);\n }\n\n const blockNames = new Set<string>();\n for (const block of blocks) {\n const name = block?.name;\n if (typeof name !== 'string') {\n continue;\n }\n if (blockNames.has(name)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_block_name',\n path: ['blocks', name],\n entity: `block:${name}`,\n message: `Duplicate block name \"${name}\".`,\n });\n }\n blockNames.add(name);\n }\n\n // Field-level checks run after the name/slug sets are fully populated so that\n // forward and circular references (resolved by name) validate correctly.\n for (const block of blocks) {\n const blockName = typeof block?.name === 'string' ? block.name : '';\n const fieldNames = new Set<string>();\n const fields = block?.fields ?? [];\n for (let index = 0; index < fields.length; index++) {\n const field = fields[index];\n // Flag malformed fields the wire mapper would otherwise silently drop:\n // a non-object entry, or one without a string `name` (its mapping key).\n if (!isRecord(field)) {\n issues.push({\n severity: 'error',\n code: 'invalid_field',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is not an object.`,\n });\n continue;\n }\n const fieldName = field.name;\n if (typeof fieldName === 'string') {\n if (fieldNames.has(fieldName)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_field_name',\n path: ['blocks', blockName, fieldName],\n entity: `block:${blockName}`,\n message: `Duplicate field name \"${fieldName}\" in block \"${blockName}\".`,\n });\n }\n fieldNames.add(fieldName);\n }\n else {\n issues.push({\n severity: 'error',\n code: 'missing_field_name',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is missing a string \"name\".`,\n });\n }\n\n for (const allowed of field.allow ?? []) {\n if (typeof allowed === 'string' && !blockNames.has(allowed)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_allow',\n path: ['blocks', blockName, fieldName ?? '', 'allow'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" allows unknown block \"${allowed}\".`,\n });\n }\n }\n\n const datasource = field.datasource;\n if (typeof datasource === 'string' && !datasourceSlugs.has(datasource)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_datasource',\n path: ['blocks', blockName, fieldName ?? '', 'datasource'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" references unknown datasource \"${datasource}\".`,\n });\n }\n }\n }\n\n return { ok: issues.every(issue => issue.severity !== 'error'), issues };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,eAAe,QAAsC;CACnE,MAAM,SAA4B,EAAE;CACpC,MAAM,SAAS,SAAS,OAAO,OAAO;CACtC,MAAM,cAAc,SAAS,OAAO,YAAY;CAEhD,MAAM,kCAAkB,IAAI,KAAa;AACzC,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,OAAO,YAAY;AACzB,MAAI,OAAO,SAAS,SAClB;AAEF,MAAI,gBAAgB,IAAI,KAAK,CAC3B,QAAO,KAAK;GACV,UAAU;GACV,MAAM;GACN,MAAM,CAAC,eAAe,KAAK;GAC3B,QAAQ,cAAc;GACtB,SAAS,8BAA8B,KAAK;GAC7C,CAAC;AAEJ,kBAAgB,IAAI,KAAK;;CAG3B,MAAM,6BAAa,IAAI,KAAa;AACpC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,OAAO;AACpB,MAAI,OAAO,SAAS,SAClB;AAEF,MAAI,WAAW,IAAI,KAAK,CACtB,QAAO,KAAK;GACV,UAAU;GACV,MAAM;GACN,MAAM,CAAC,UAAU,KAAK;GACtB,QAAQ,SAAS;GACjB,SAAS,yBAAyB,KAAK;GACxC,CAAC;AAEJ,aAAW,IAAI,KAAK;;AAKtB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,YAAY,OAAO,OAAO,SAAS,WAAW,MAAM,OAAO;EACjE,MAAM,6BAAa,IAAI,KAAa;EACpC,MAAM,SAAS,OAAO,UAAU,EAAE;AAClC,OAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS;GAClD,MAAM,QAAQ,OAAO;AAGrB,OAAI,CAAC,SAAS,MAAM,EAAE;AACpB,WAAO,KAAK;KACV,UAAU;KACV,MAAM;KACN,MAAM;MAAC;MAAU;MAAW;MAAM;KAClC,QAAQ,SAAS;KACjB,SAAS,kBAAkB,MAAM,aAAa,UAAU;KACzD,CAAC;AACF;;GAEF,MAAM,YAAY,MAAM;AACxB,OAAI,OAAO,cAAc,UAAU;AACjC,QAAI,WAAW,IAAI,UAAU,CAC3B,QAAO,KAAK;KACV,UAAU;KACV,MAAM;KACN,MAAM;MAAC;MAAU;MAAW;MAAU;KACtC,QAAQ,SAAS;KACjB,SAAS,yBAAyB,UAAU,cAAc,UAAU;KACrE,CAAC;AAEJ,eAAW,IAAI,UAAU;SAGzB,QAAO,KAAK;IACV,UAAU;IACV,MAAM;IACN,MAAM;KAAC;KAAU;KAAW;KAAM;IAClC,QAAQ,SAAS;IACjB,SAAS,kBAAkB,MAAM,aAAa,UAAU;IACzD,CAAC;AAGJ,QAAK,MAAM,WAAW,MAAM,SAAS,EAAE,CACrC,KAAI,OAAO,YAAY,YAAY,CAAC,WAAW,IAAI,QAAQ,CACzD,QAAO,KAAK;IACV,UAAU;IACV,MAAM;IACN,MAAM;KAAC;KAAU;KAAW,aAAa;KAAI;KAAQ;IACrD,QAAQ,SAAS;IACjB,SAAS,UAAU,UAAU,0BAA0B,QAAQ;IAChE,CAAC;GAIN,MAAM,aAAa,MAAM;AACzB,OAAI,OAAO,eAAe,YAAY,CAAC,gBAAgB,IAAI,WAAW,CACpE,QAAO,KAAK;IACV,UAAU;IACV,MAAM;IACN,MAAM;KAAC;KAAU;KAAW,aAAa;KAAI;KAAa;IAC1D,QAAQ,SAAS;IACjB,SAAS,UAAU,UAAU,mCAAmC,WAAW;IAC5E,CAAC;;;AAKR,QAAO;EAAE,IAAI,OAAO,OAAM,UAAS,MAAM,aAAa,QAAQ;EAAE;EAAQ"}
1
+ {"version":3,"file":"validate-schema.mjs","names":[],"sources":["../../src/validators/validate-schema.ts"],"sourcesContent":["import type { SchemaLike } from './shapes';\nimport type { ValidationIssue, ValidationResult } from './types';\nimport { isRecord, toValues } from './shapes';\n\n/**\n * Validates a schema definition without throwing. Checks structural identity\n * (duplicate block names, field names, and datasource slugs) and cross-references\n * (every `allow` entry resolves to a defined block; every field `datasource`\n * resolves to a defined datasource; every `custom` field's `field_type`\n * resolves to a registered field plugin).\n *\n * @example\n * const result = validateSchema({ blocks: { hero }, datasources: { colors } });\n * if (!result.ok) console.error(result.issues);\n */\nexport function validateSchema(schema: SchemaLike): ValidationResult {\n const issues: ValidationIssue[] = [];\n const blocks = toValues(schema.blocks);\n const datasources = toValues(schema.datasources);\n const fieldPlugins = toValues(schema.fieldPlugins);\n\n const fieldPluginTypes = new Set<string>();\n for (const plugin of fieldPlugins) {\n const fieldType = plugin?.fieldType;\n if (typeof fieldType === 'string') {\n fieldPluginTypes.add(fieldType);\n }\n }\n\n const datasourceSlugs = new Set<string>();\n for (const datasource of datasources) {\n const slug = datasource?.slug;\n if (typeof slug !== 'string') {\n continue;\n }\n if (datasourceSlugs.has(slug)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_datasource_slug',\n path: ['datasources', slug],\n entity: `datasource:${slug}`,\n message: `Duplicate datasource slug \"${slug}\".`,\n });\n }\n datasourceSlugs.add(slug);\n }\n\n const blockNames = new Set<string>();\n for (const block of blocks) {\n const name = block?.name;\n if (typeof name !== 'string') {\n continue;\n }\n if (blockNames.has(name)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_block_name',\n path: ['blocks', name],\n entity: `block:${name}`,\n message: `Duplicate block name \"${name}\".`,\n });\n }\n blockNames.add(name);\n }\n\n // Field-level checks run after the name/slug sets are fully populated so that\n // forward and circular references (resolved by name) validate correctly.\n for (const block of blocks) {\n const blockName = typeof block?.name === 'string' ? block.name : '';\n const fieldNames = new Set<string>();\n const fields = block?.fields ?? [];\n for (let index = 0; index < fields.length; index++) {\n const field = fields[index];\n // Flag malformed fields the wire mapper would otherwise silently drop:\n // a non-object entry, or one without a string `name` (its mapping key).\n if (!isRecord(field)) {\n issues.push({\n severity: 'error',\n code: 'invalid_field',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is not an object.`,\n });\n continue;\n }\n const fieldName = field.name;\n if (typeof fieldName === 'string') {\n if (fieldNames.has(fieldName)) {\n issues.push({\n severity: 'error',\n code: 'duplicate_field_name',\n path: ['blocks', blockName, fieldName],\n entity: `block:${blockName}`,\n message: `Duplicate field name \"${fieldName}\" in block \"${blockName}\".`,\n });\n }\n fieldNames.add(fieldName);\n }\n else {\n issues.push({\n severity: 'error',\n code: 'missing_field_name',\n path: ['blocks', blockName, index],\n entity: `block:${blockName}`,\n message: `Field at index ${index} in block \"${blockName}\" is missing a string \"name\".`,\n });\n }\n\n for (const allowed of field.allow ?? []) {\n if (typeof allowed === 'string' && !blockNames.has(allowed)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_allow',\n path: ['blocks', blockName, fieldName ?? '', 'allow'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" allows unknown block \"${allowed}\".`,\n });\n }\n }\n\n const datasource = field.datasource;\n if (typeof datasource === 'string' && !datasourceSlugs.has(datasource)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_datasource',\n path: ['blocks', blockName, fieldName ?? '', 'datasource'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" references unknown datasource \"${datasource}\".`,\n });\n }\n\n const fieldType = field.field_type;\n if (field.type === 'custom' && typeof fieldType === 'string' && !fieldPluginTypes.has(fieldType)) {\n issues.push({\n severity: 'error',\n code: 'unresolved_field_plugin',\n path: ['blocks', blockName, fieldName ?? '', 'field_type'],\n entity: `block:${blockName}`,\n message: `Field \"${fieldName}\" references unregistered field plugin \"${fieldType}\".`,\n });\n }\n }\n }\n\n return { ok: issues.every(issue => issue.severity !== 'error'), issues };\n}\n"],"mappings":"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"}