@storyblok/schema 0.1.0-alpha.2 → 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.
- package/dist/generated/types/block.d.cts +6 -0
- package/dist/generated/types/block.d.mts +6 -0
- package/dist/generated/types/field.d.cts +22 -3
- package/dist/generated/types/field.d.mts +22 -3
- package/dist/helpers/define-block.cjs +6 -1
- package/dist/helpers/define-block.cjs.map +1 -1
- package/dist/helpers/define-block.d.cts +14 -4
- package/dist/helpers/define-block.d.mts +14 -4
- package/dist/helpers/define-block.mjs +6 -1
- package/dist/helpers/define-block.mjs.map +1 -1
- package/dist/helpers/define-field.cjs +8 -1
- package/dist/helpers/define-field.cjs.map +1 -1
- package/dist/helpers/define-field.d.cts +12 -3
- package/dist/helpers/define-field.d.mts +12 -3
- package/dist/helpers/define-field.mjs +8 -1
- package/dist/helpers/define-field.mjs.map +1 -1
- package/dist/helpers/define-folder.cjs +19 -0
- package/dist/helpers/define-folder.cjs.map +1 -0
- package/dist/helpers/define-folder.d.cts +39 -0
- package/dist/helpers/define-folder.d.mts +39 -0
- package/dist/helpers/define-folder.mjs +18 -0
- package/dist/helpers/define-folder.mjs.map +1 -0
- package/dist/helpers/define-schema.cjs +1 -0
- package/dist/helpers/define-schema.cjs.map +1 -1
- package/dist/helpers/define-schema.d.cts +3 -0
- package/dist/helpers/define-schema.d.mts +3 -0
- package/dist/helpers/define-schema.mjs +1 -0
- package/dist/helpers/define-schema.mjs.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/utils/slugify-folder-path.cjs +21 -0
- package/dist/utils/slugify-folder-path.cjs.map +1 -0
- package/dist/utils/slugify-folder-path.mjs +20 -0
- package/dist/utils/slugify-folder-path.mjs.map +1 -0
- package/dist/validators/shapes.cjs.map +1 -1
- package/dist/validators/shapes.d.cts +6 -2
- package/dist/validators/shapes.d.mts +6 -2
- package/dist/validators/shapes.mjs.map +1 -1
- package/dist/validators/validate-story.cjs +60 -24
- package/dist/validators/validate-story.cjs.map +1 -1
- package/dist/validators/validate-story.mjs +60 -24
- package/dist/validators/validate-story.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Block } from "../generated/types/block.cjs";
|
|
2
|
+
import { BlockFolder } from "./define-folder.cjs";
|
|
2
3
|
import { Datasource } from "./define-datasource.cjs";
|
|
3
4
|
import { FieldPlugin } from "./define-field-plugin.cjs";
|
|
4
5
|
|
|
@@ -6,6 +7,7 @@ import { FieldPlugin } from "./define-field-plugin.cjs";
|
|
|
6
7
|
/** Minimal shape accepted by {@link defineSchema}; extra keys pass through unchanged. */
|
|
7
8
|
interface SchemaConfig {
|
|
8
9
|
blocks: Record<string, Block>;
|
|
10
|
+
folders?: Record<string, BlockFolder>;
|
|
9
11
|
datasources?: Record<string, Datasource>;
|
|
10
12
|
fieldPlugins?: Record<string, FieldPlugin>;
|
|
11
13
|
}
|
|
@@ -18,6 +20,7 @@ interface SchemaConfig {
|
|
|
18
20
|
* @example
|
|
19
21
|
* export const schema = defineSchema({
|
|
20
22
|
* blocks: { pageBlock, heroBlock },
|
|
23
|
+
* folders: { layout },
|
|
21
24
|
* datasources: { colorsDatasource },
|
|
22
25
|
* fieldPlugins: { colorPicker },
|
|
23
26
|
* });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Block } from "../generated/types/block.mjs";
|
|
2
|
+
import { BlockFolder } from "./define-folder.mjs";
|
|
2
3
|
import { Datasource } from "./define-datasource.mjs";
|
|
3
4
|
import { FieldPlugin } from "./define-field-plugin.mjs";
|
|
4
5
|
|
|
@@ -6,6 +7,7 @@ import { FieldPlugin } from "./define-field-plugin.mjs";
|
|
|
6
7
|
/** Minimal shape accepted by {@link defineSchema}; extra keys pass through unchanged. */
|
|
7
8
|
interface SchemaConfig {
|
|
8
9
|
blocks: Record<string, Block>;
|
|
10
|
+
folders?: Record<string, BlockFolder>;
|
|
9
11
|
datasources?: Record<string, Datasource>;
|
|
10
12
|
fieldPlugins?: Record<string, FieldPlugin>;
|
|
11
13
|
}
|
|
@@ -18,6 +20,7 @@ interface SchemaConfig {
|
|
|
18
20
|
* @example
|
|
19
21
|
* export const schema = defineSchema({
|
|
20
22
|
* blocks: { pageBlock, heroBlock },
|
|
23
|
+
* folders: { layout },
|
|
21
24
|
* datasources: { colorsDatasource },
|
|
22
25
|
* fieldPlugins: { colorPicker },
|
|
23
26
|
* });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-schema.mjs","names":[],"sources":["../../src/helpers/define-schema.ts"],"sourcesContent":["import type { Block } from './define-block';\nimport type { Datasource } from './define-datasource';\nimport type { FieldPlugin } from './define-field-plugin';\n\n/** Minimal shape accepted by {@link defineSchema}; extra keys pass through unchanged. */\nexport interface SchemaConfig {\n blocks: Record<string, Block>;\n datasources?: Record<string, Datasource>;\n fieldPlugins?: Record<string, FieldPlugin>;\n}\n\n/**\n * Typed identity wrapper for a schema object, consistent with the other\n * `define*` helpers. Preserves the exact literal shape via `const` inference so\n * {@link Schema} can derive block, datasource, and field-plugin types from\n * `typeof schema`. Does not validate or transform.\n *\n * @example\n * export const schema = defineSchema({\n * blocks: { pageBlock, heroBlock },\n * datasources: { colorsDatasource },\n * fieldPlugins: { colorPicker },\n * });\n */\nexport function defineSchema<const T extends SchemaConfig>(schema: T): T {\n return schema;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"define-schema.mjs","names":[],"sources":["../../src/helpers/define-schema.ts"],"sourcesContent":["import type { Block } from './define-block';\nimport type { Datasource } from './define-datasource';\nimport type { BlockFolder } from './define-folder';\nimport type { FieldPlugin } from './define-field-plugin';\n\n/** Minimal shape accepted by {@link defineSchema}; extra keys pass through unchanged. */\nexport interface SchemaConfig {\n blocks: Record<string, Block>;\n folders?: Record<string, BlockFolder>;\n datasources?: Record<string, Datasource>;\n fieldPlugins?: Record<string, FieldPlugin>;\n}\n\n/**\n * Typed identity wrapper for a schema object, consistent with the other\n * `define*` helpers. Preserves the exact literal shape via `const` inference so\n * {@link Schema} can derive block, datasource, and field-plugin types from\n * `typeof schema`. Does not validate or transform.\n *\n * @example\n * export const schema = defineSchema({\n * blocks: { pageBlock, heroBlock },\n * folders: { layout },\n * datasources: { colorsDatasource },\n * fieldPlugins: { colorPicker },\n * });\n */\nexport function defineSchema<const T extends SchemaConfig>(schema: T): T {\n return schema;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,SAAgB,aAA2C,QAAc;AACvE,QAAO"}
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ 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
5
|
const require_define_field_plugin = require('./helpers/define-field-plugin.cjs');
|
|
6
|
+
const require_define_folder = require('./helpers/define-folder.cjs');
|
|
6
7
|
const require_define_schema = require('./helpers/define-schema.cjs');
|
|
7
8
|
const require_validate_story = require('./validators/validate-story.cjs');
|
|
8
9
|
const require_create_story_validator = require('./validators/create-story-validator.cjs');
|
|
@@ -13,6 +14,7 @@ exports.defineBlock = require_define_block.defineBlock;
|
|
|
13
14
|
exports.defineDatasource = require_define_datasource.defineDatasource;
|
|
14
15
|
exports.defineField = require_define_field.defineField;
|
|
15
16
|
exports.defineFieldPlugin = require_define_field_plugin.defineFieldPlugin;
|
|
17
|
+
exports.defineFolder = require_define_folder.defineFolder;
|
|
16
18
|
exports.defineSchema = require_define_schema.defineSchema;
|
|
17
19
|
exports.validateSchema = require_validate_schema.validateSchema;
|
|
18
20
|
exports.validateStory = require_validate_story.validateStory;
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,7 @@ 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";
|
|
@@ -13,4 +14,4 @@ import { createStoryValidator } from "./validators/create-story-validator.cjs";
|
|
|
13
14
|
import { ValidationIssue, ValidationResult, ValidationSeverity } from "./validators/types.cjs";
|
|
14
15
|
import { validateSchema } from "./validators/validate-schema.cjs";
|
|
15
16
|
import { validateStory } from "./validators/validate-story.cjs";
|
|
16
|
-
export { type AssetFieldValue, type Block, type BlockContent, type BlockContentInput, type BlockFields, 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, defineSchema, 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,6 +3,7 @@ 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";
|
|
@@ -13,4 +14,4 @@ import { createStoryValidator } from "./validators/create-story-validator.mjs";
|
|
|
13
14
|
import { ValidationIssue, ValidationResult, ValidationSeverity } from "./validators/types.mjs";
|
|
14
15
|
import { validateSchema } from "./validators/validate-schema.mjs";
|
|
15
16
|
import { validateStory } from "./validators/validate-story.mjs";
|
|
16
|
-
export { type AssetFieldValue, type Block, type BlockContent, type BlockContentInput, type BlockFields, 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, defineSchema, 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
|
@@ -2,9 +2,10 @@ 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
4
|
import { defineFieldPlugin } from "./helpers/define-field-plugin.mjs";
|
|
5
|
+
import { defineFolder } from "./helpers/define-folder.mjs";
|
|
5
6
|
import { defineSchema } from "./helpers/define-schema.mjs";
|
|
6
7
|
import { validateStory } from "./validators/validate-story.mjs";
|
|
7
8
|
import { createStoryValidator } from "./validators/create-story-validator.mjs";
|
|
8
9
|
import { validateSchema } from "./validators/validate-schema.mjs";
|
|
9
10
|
|
|
10
|
-
export { createStoryValidator, defineBlock, defineDatasource, defineField, defineFieldPlugin, defineSchema, 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 +1 @@
|
|
|
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 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/** 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":";;;;
|
|
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"}
|
|
@@ -15,8 +15,10 @@ interface SchemaFieldLike {
|
|
|
15
15
|
/** `custom`: the field plugin discriminant, matched against registered `fieldPlugins`. */
|
|
16
16
|
field_type?: string;
|
|
17
17
|
required?: boolean;
|
|
18
|
-
/** Normalized block-name references for `bloks` fields. */
|
|
19
|
-
allow?: readonly string
|
|
18
|
+
/** Normalized block-name or folder-path references for `bloks` fields. */
|
|
19
|
+
allow?: readonly (string | {
|
|
20
|
+
folder: string;
|
|
21
|
+
})[];
|
|
20
22
|
/** Normalized datasource slug for option/options fields. */
|
|
21
23
|
datasource?: string;
|
|
22
24
|
/** `text`/`textarea`/`markdown`: maximum string length. */
|
|
@@ -50,6 +52,8 @@ interface SchemaFieldLike {
|
|
|
50
52
|
interface SchemaBlockLike {
|
|
51
53
|
name: string;
|
|
52
54
|
fields?: readonly SchemaFieldLike[];
|
|
55
|
+
/** Display path of the folder (component group) this block belongs to, if any. */
|
|
56
|
+
folder?: string | null;
|
|
53
57
|
}
|
|
54
58
|
/** A datasource definition (`defineDatasource` result). */
|
|
55
59
|
interface SchemaDatasourceLike {
|
|
@@ -15,8 +15,10 @@ interface SchemaFieldLike {
|
|
|
15
15
|
/** `custom`: the field plugin discriminant, matched against registered `fieldPlugins`. */
|
|
16
16
|
field_type?: string;
|
|
17
17
|
required?: boolean;
|
|
18
|
-
/** Normalized block-name references for `bloks` fields. */
|
|
19
|
-
allow?: readonly string
|
|
18
|
+
/** Normalized block-name or folder-path references for `bloks` fields. */
|
|
19
|
+
allow?: readonly (string | {
|
|
20
|
+
folder: string;
|
|
21
|
+
})[];
|
|
20
22
|
/** Normalized datasource slug for option/options fields. */
|
|
21
23
|
datasource?: string;
|
|
22
24
|
/** `text`/`textarea`/`markdown`: maximum string length. */
|
|
@@ -50,6 +52,8 @@ interface SchemaFieldLike {
|
|
|
50
52
|
interface SchemaBlockLike {
|
|
51
53
|
name: string;
|
|
52
54
|
fields?: readonly SchemaFieldLike[];
|
|
55
|
+
/** Display path of the folder (component group) this block belongs to, if any. */
|
|
56
|
+
folder?: string | null;
|
|
53
57
|
}
|
|
54
58
|
/** A datasource definition (`defineDatasource` result). */
|
|
55
59
|
interface SchemaDatasourceLike {
|
|
@@ -1 +1 @@
|
|
|
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 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/** 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":";;;;
|
|
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"}
|
|
@@ -3,6 +3,7 @@ const require_is_record = require('../utils/is-record.cjs');
|
|
|
3
3
|
const require_shapes = require('./shapes.cjs');
|
|
4
4
|
const require_zod_gen = require('../generated/overlay/zod.gen.cjs');
|
|
5
5
|
require('./internal-schemas.cjs');
|
|
6
|
+
const require_slugify_folder_path = require('../utils/slugify-folder-path.cjs');
|
|
6
7
|
let zod = require("zod");
|
|
7
8
|
|
|
8
9
|
//#region src/validators/validate-story.ts
|
|
@@ -67,7 +68,7 @@ function validateFieldValue(field, value, blocksByName, fieldPluginsByType, path
|
|
|
67
68
|
break;
|
|
68
69
|
case "richtext":
|
|
69
70
|
checkValue(require_zod_gen.zRichtextFieldValue, value, path, entity, issues);
|
|
70
|
-
validateRichtextBloks(value, blocksByName, fieldPluginsByType, path, issues);
|
|
71
|
+
validateRichtextBloks(value, field, blocksByName, fieldPluginsByType, path, entity, issues);
|
|
71
72
|
break;
|
|
72
73
|
case "custom": {
|
|
73
74
|
checkValue(zPluginEnvelope, value, path, entity, issues);
|
|
@@ -85,17 +86,7 @@ function validateFieldValue(field, value, blocksByName, fieldPluginsByType, path
|
|
|
85
86
|
}
|
|
86
87
|
checkCount(value.length, field.minimum, field.maximum, "block(s)", path, entity, issues);
|
|
87
88
|
value.forEach((item, index) => {
|
|
88
|
-
|
|
89
|
-
severity: "error",
|
|
90
|
-
code: "disallowed_component",
|
|
91
|
-
path: [
|
|
92
|
-
...path,
|
|
93
|
-
index,
|
|
94
|
-
"component"
|
|
95
|
-
],
|
|
96
|
-
entity,
|
|
97
|
-
message: `Component "${item.component}" is not allowed in field "${field.name}"; allowed: ${field.allow.join(", ")}.`
|
|
98
|
-
});
|
|
89
|
+
checkComponentAllowed(field, item, [...path, index], blocksByName, entity, issues);
|
|
99
90
|
validateBlokContent(item, blocksByName, fieldPluginsByType, [...path, index], issues);
|
|
100
91
|
});
|
|
101
92
|
break;
|
|
@@ -142,6 +133,42 @@ function validateFieldValue(field, value, blocksByName, fieldPluginsByType, path
|
|
|
142
133
|
break;
|
|
143
134
|
}
|
|
144
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Enforces a field's `allow` list for one embedded blok. Shared by the `bloks`
|
|
138
|
+
* case and the richtext walk so both apply the same rule: `mapFieldToWire`
|
|
139
|
+
* pushes folder/name `allow` as an editor/API restriction on *both* field types,
|
|
140
|
+
* so validation must reject the same components the editor and API would.
|
|
141
|
+
*
|
|
142
|
+
* `itemPath` is the path to the blok item (its index); the reported issue points
|
|
143
|
+
* at that item's `component` key. A component is allowed when it is named
|
|
144
|
+
* directly in `allow` or its block sits in (or under) an allowed folder — both
|
|
145
|
+
* sides canonicalized to slug space so a folder referenced two ways (a
|
|
146
|
+
* `defineFolder` ref vs. a string shorthand with different casing/separators)
|
|
147
|
+
* matches the way the CLI/editor group it.
|
|
148
|
+
*/
|
|
149
|
+
function checkComponentAllowed(field, item, itemPath, blocksByName, entity, issues) {
|
|
150
|
+
const allowEntries = field.allow ?? [];
|
|
151
|
+
if (allowEntries.length === 0 || !require_is_record.isRecord(item) || typeof item.component !== "string") return;
|
|
152
|
+
const blockNamesAllowed = allowEntries.filter((entry) => typeof entry === "string");
|
|
153
|
+
const folderPathsAllowed = allowEntries.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.folder === "string");
|
|
154
|
+
const itemBlockFolder = blocksByName.get(item.component)?.folder;
|
|
155
|
+
const allowedByName = blockNamesAllowed.includes(item.component);
|
|
156
|
+
const itemFolderSlug = typeof itemBlockFolder === "string" ? require_slugify_folder_path.slugifyFolderPath(itemBlockFolder) : void 0;
|
|
157
|
+
const allowedByFolder = itemFolderSlug !== void 0 && folderPathsAllowed.some(({ folder }) => {
|
|
158
|
+
const allowedSlug = require_slugify_folder_path.slugifyFolderPath(folder);
|
|
159
|
+
return itemFolderSlug === allowedSlug || itemFolderSlug.startsWith(`${allowedSlug}/`);
|
|
160
|
+
});
|
|
161
|
+
if (!allowedByName && !allowedByFolder) {
|
|
162
|
+
const allowedList = allowEntries.map((entry) => typeof entry === "string" ? entry : `folder:${entry.folder}`).join(", ");
|
|
163
|
+
issues.push({
|
|
164
|
+
severity: "error",
|
|
165
|
+
code: "disallowed_component",
|
|
166
|
+
path: [...itemPath, "component"],
|
|
167
|
+
entity,
|
|
168
|
+
message: `Component "${item.component}" is not allowed in field "${field.name}"; allowed: ${allowedList}.`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
145
172
|
/** Reports a constraint (bound/length/count) violation as an error issue. */
|
|
146
173
|
function pushConstraint(message, path, entity, issues) {
|
|
147
174
|
issues.push({
|
|
@@ -191,24 +218,33 @@ function pushTypeIssue(value, expected, path, entity, issues) {
|
|
|
191
218
|
message: `Expected ${expected}, received ${value === null ? "null" : typeof value}.`
|
|
192
219
|
});
|
|
193
220
|
}
|
|
194
|
-
/**
|
|
195
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Walks richtext `content` nodes and validates embedded bloks (`type: 'blok'`).
|
|
223
|
+
* `field` is the owning richtext field: each embedded blok is checked against its
|
|
224
|
+
* `allow` list ({@link checkComponentAllowed}), matching the group/name
|
|
225
|
+
* restriction `mapFieldToWire` pushes for richtext fields.
|
|
226
|
+
*/
|
|
227
|
+
function validateRichtextBloks(value, field, blocksByName, fieldPluginsByType, path, entity, issues) {
|
|
196
228
|
if (!require_is_record.isRecord(value) || !Array.isArray(value.content)) return;
|
|
197
229
|
value.content.forEach((node, index) => {
|
|
198
230
|
if (!require_is_record.isRecord(node)) return;
|
|
199
|
-
if (node.type === "blok" && require_is_record.isRecord(node.attrs) && Array.isArray(node.attrs.body)) node.attrs.body.forEach((blok, blokIndex) =>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
231
|
+
if (node.type === "blok" && require_is_record.isRecord(node.attrs) && Array.isArray(node.attrs.body)) node.attrs.body.forEach((blok, blokIndex) => {
|
|
232
|
+
const blokPath = [
|
|
233
|
+
...path,
|
|
234
|
+
"content",
|
|
235
|
+
index,
|
|
236
|
+
"attrs",
|
|
237
|
+
"body",
|
|
238
|
+
blokIndex
|
|
239
|
+
];
|
|
240
|
+
checkComponentAllowed(field, blok, blokPath, blocksByName, entity, issues);
|
|
241
|
+
validateBlokContent(blok, blocksByName, fieldPluginsByType, blokPath, issues);
|
|
242
|
+
});
|
|
243
|
+
else if (Array.isArray(node.content)) validateRichtextBloks(node, field, blocksByName, fieldPluginsByType, [
|
|
208
244
|
...path,
|
|
209
245
|
"content",
|
|
210
246
|
index
|
|
211
|
-
], issues);
|
|
247
|
+
], entity, issues);
|
|
212
248
|
});
|
|
213
249
|
}
|
|
214
250
|
/** Validates a single blok content object against its component definition. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-story.cjs","names":["z","zAssetFieldValue","zMultilinkFieldValue","zTableFieldValue","zRichtextFieldValue","isRecord","toValues"],"sources":["../../src/validators/validate-story.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { SchemaBlockLike, SchemaFieldLike, SchemaLike } from './shapes';\nimport type { ValidationIssue, ValidationResult } from './types';\nimport {\n zAssetFieldValue,\n zMultilinkFieldValue,\n zRichtextFieldValue,\n zTableFieldValue,\n} from './internal-schemas';\nimport { isRecord, toValues } from './shapes';\n\n/** Field-content keys that are not user-defined fields. */\nconst RESERVED_KEYS = new Set(['_uid', 'component', '_editable']);\n\n/**\n * Relaxed plugin envelope used by the `custom` case. Mirrors the generated\n * `zPluginFieldValue` but relaxes `_uid` from a UUID to a plain string, matching\n * the CMS, which persists arbitrary `_uid` strings. Kept local so a codegen\n * regenerate cannot revert it.\n */\nconst zPluginEnvelope = z.object({ plugin: z.string(), _uid: z.optional(z.string()) });\n\n/** Maps a Standard Schema validator to a {@link ValidationIssue} reporter at `path`. */\nfunction checkValue(\n schema: StandardSchemaV1,\n value: unknown,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n const result = schema['~standard'].validate(value);\n // `validateStory` is synchronous. The internal Zod schemas never return a\n // thenable, but a registered field plugin may ship an async validator — which\n // cannot be awaited here. Surface it as an error instead of silently passing,\n // which would report a false `ok: true`.\n if (result instanceof Promise) {\n issues.push({\n severity: 'error',\n code: 'async_validator_unsupported',\n path,\n entity,\n message: 'Field plugin validator is asynchronous; validateStory runs synchronously and cannot await it.',\n });\n return;\n }\n if (result.issues) {\n for (const issue of result.issues) {\n const issuePath = (issue.path ?? []).map(segment =>\n (typeof segment === 'object' && segment !== null ? String(segment.key) : (segment as string | number)),\n );\n issues.push({\n severity: 'error',\n code: 'invalid_value',\n path: [...path, ...issuePath],\n entity,\n message: issue.message,\n });\n }\n }\n}\n\nfunction validateFieldValue(\n field: SchemaFieldLike,\n value: unknown,\n blocksByName: Map<string, SchemaBlockLike>,\n fieldPluginsByType: Map<string, StandardSchemaV1>,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n switch (field.type) {\n case 'asset':\n checkValue(zAssetFieldValue, value, path, entity, issues);\n break;\n case 'multiasset':\n if (!Array.isArray(value)) {\n pushTypeIssue(value, 'array', path, entity, issues);\n break;\n }\n value.forEach((item, index) => checkValue(zAssetFieldValue, item, [...path, index], entity, issues));\n checkCount(value.length, field.minimum_entries, field.maximum_entries, 'asset(s)', path, entity, issues);\n break;\n case 'multilink':\n checkValue(zMultilinkFieldValue, value, path, entity, issues);\n break;\n case 'table':\n checkValue(zTableFieldValue, value, path, entity, issues);\n break;\n case 'richtext':\n checkValue(zRichtextFieldValue, value, path, entity, issues);\n validateRichtextBloks(value, blocksByName, fieldPluginsByType, path, issues);\n break;\n case 'custom': {\n checkValue(zPluginEnvelope, value, path, entity, issues);\n const validator = field.field_type ? fieldPluginsByType.get(field.field_type) : undefined;\n if (validator && isRecord(value)) {\n // Envelope keys sit alongside the plugin's own keys; strip them so the\n // plugin validator sees only its value. Sibling keys keep issue paths\n // accurate (an issue at ['color'] maps to [...path, 'color']).\n const { plugin: _plugin, _uid, ...pluginValue } = value;\n checkValue(validator, pluginValue, path, entity, issues);\n }\n break;\n }\n case 'bloks':\n if (!Array.isArray(value)) {\n pushTypeIssue(value, 'array', path, entity, issues);\n break;\n }\n checkCount(value.length, field.minimum, field.maximum, 'block(s)', path, entity, issues);\n value.forEach((item, index) => {\n if (\n field.allow && field.allow.length > 0\n && isRecord(item) && typeof item.component === 'string'\n && !field.allow.includes(item.component)\n ) {\n issues.push({\n severity: 'error',\n code: 'disallowed_component',\n path: [...path, index, 'component'],\n entity,\n message: `Component \"${item.component}\" is not allowed in field \"${field.name}\"; allowed: ${field.allow.join(', ')}.`,\n });\n }\n validateBlokContent(item, blocksByName, fieldPluginsByType, [...path, index], issues);\n });\n break;\n case 'text':\n case 'textarea':\n case 'markdown':\n if (typeof value !== 'string') {\n pushTypeIssue(value, 'string', path, entity, issues);\n break;\n }\n checkStringLength(field, value, path, entity, issues);\n break;\n case 'option':\n case 'datetime':\n if (typeof value !== 'string') {\n pushTypeIssue(value, 'string', path, entity, issues);\n }\n break;\n case 'number':\n if (typeof value !== 'number') {\n pushTypeIssue(value, 'number', path, entity, issues);\n break;\n }\n if (field.min_value != null && value < field.min_value) {\n pushConstraint(`Value ${value} is below the minimum of ${field.min_value}.`, path, entity, issues);\n }\n if (field.max_value != null && value > field.max_value) {\n pushConstraint(`Value ${value} exceeds the maximum of ${field.max_value}.`, path, entity, issues);\n }\n if (field.decimals != null && decimalPlaces(value) > field.decimals) {\n pushConstraint(`Value ${value} has more than ${field.decimals} decimal place(s).`, path, entity, issues);\n }\n if (field.steps != null && field.steps > 0 && !isMultipleOf(value, field.steps, field.min_value ?? 0)) {\n const base = field.min_value ?? 0;\n pushConstraint(\n `Value ${value} is not a multiple of the step ${field.steps}${base ? ` (offset from ${base})` : ''}.`,\n path,\n entity,\n issues,\n );\n }\n break;\n case 'boolean':\n if (typeof value !== 'boolean') {\n pushTypeIssue(value, 'boolean', path, entity, issues);\n }\n break;\n case 'options':\n if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {\n pushTypeIssue(value, 'string[]', path, entity, issues);\n break;\n }\n checkCount(value.length, toCount(field.min_options), toCount(field.max_options), 'option(s)', path, entity, issues);\n break;\n case 'section':\n case 'tab':\n // Layout-only field types carry no content value.\n break;\n default:\n // Exhaustiveness guard: when a new `FieldType` is added, this fails to\n // compile until the field type is handled (or explicitly skipped) above.\n field.type satisfies never;\n break;\n }\n}\n\n/** Reports a constraint (bound/length/count) violation as an error issue. */\nfunction pushConstraint(\n message: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n issues.push({ severity: 'error', code: 'constraint_violation', path, entity, message });\n}\n\n/** Checks an array length against optional inclusive `min`/`max` bounds. */\nfunction checkCount(\n length: number,\n min: number | undefined,\n max: number | undefined,\n noun: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n if (min != null && length < min) {\n pushConstraint(`Expected at least ${min} ${noun}, received ${length}.`, path, entity, issues);\n }\n if (max != null && length > max) {\n pushConstraint(`Expected at most ${max} ${noun}, received ${length}.`, path, entity, issues);\n }\n}\n\n/** Checks a string against optional `max_length`/`maxlength` and `minlength` bounds. */\nfunction checkStringLength(\n field: SchemaFieldLike,\n value: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n const max = field.max_length ?? field.maxlength;\n if (max != null && value.length > max) {\n pushConstraint(`Text length ${value.length} exceeds the maximum of ${max}.`, path, entity, issues);\n }\n if (field.minlength != null && value.length < field.minlength) {\n pushConstraint(`Text length ${value.length} is below the minimum of ${field.minlength}.`, path, entity, issues);\n }\n}\n\n/** Counts a number's fractional digits, handling exponential notation (e.g. `1e-7` → 7). */\nfunction decimalPlaces(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n const text = String(value).toLowerCase();\n const [mantissa, exponent] = text.split('e');\n const fractionDigits = mantissa.includes('.') ? mantissa.split('.')[1].length : 0;\n // A negative exponent (e.g. `1e-7`) adds that many fractional digits.\n return exponent ? Math.max(0, fractionDigits - Number(exponent)) : fractionDigits;\n}\n\n/** Whether `value` lands on a `step` increment offset from `base`, with float tolerance. */\nfunction isMultipleOf(value: number, step: number, base: number): boolean {\n const ratio = (value - base) / step;\n const tolerance = 1e-9 * Math.max(1, Math.abs(ratio));\n return Math.abs(ratio - Math.round(ratio)) <= tolerance;\n}\n\n/** Parses a numeric constraint stored as a string (e.g. `min_options`). Empty/non-numeric → undefined. */\nfunction toCount(value: string | undefined): number | undefined {\n if (value == null || value === '') {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction pushTypeIssue(\n value: unknown,\n expected: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n issues.push({\n severity: 'error',\n code: 'invalid_value',\n path,\n entity,\n message: `Expected ${expected}, received ${value === null ? 'null' : typeof value}.`,\n });\n}\n\n/** Walks richtext `content` nodes and validates embedded bloks (`type: 'blok'`). */\nfunction validateRichtextBloks(\n value: unknown,\n blocksByName: Map<string, SchemaBlockLike>,\n fieldPluginsByType: Map<string, StandardSchemaV1>,\n path: (string | number)[],\n issues: ValidationIssue[],\n): void {\n if (!isRecord(value) || !Array.isArray(value.content)) {\n return;\n }\n value.content.forEach((node, index) => {\n if (!isRecord(node)) {\n return;\n }\n if (node.type === 'blok' && isRecord(node.attrs) && Array.isArray(node.attrs.body)) {\n node.attrs.body.forEach((blok, blokIndex) =>\n validateBlokContent(blok, blocksByName, fieldPluginsByType, [...path, 'content', index, 'attrs', 'body', blokIndex], issues),\n );\n }\n else if (Array.isArray(node.content)) {\n // Recurse into nested marks/nodes that may themselves embed bloks.\n validateRichtextBloks(node, blocksByName, fieldPluginsByType, [...path, 'content', index], issues);\n }\n });\n}\n\n/** Validates a single blok content object against its component definition. */\nfunction validateBlokContent(\n content: unknown,\n blocksByName: Map<string, SchemaBlockLike>,\n fieldPluginsByType: Map<string, StandardSchemaV1>,\n path: (string | number)[],\n issues: ValidationIssue[],\n): void {\n if (!isRecord(content)) {\n issues.push({\n severity: 'error',\n code: 'invalid_content',\n path,\n entity: 'story',\n message: 'Expected a block content object.',\n });\n return;\n }\n\n const component = content.component;\n const block = typeof component === 'string' ? blocksByName.get(component) : undefined;\n if (!block) {\n issues.push({\n severity: 'error',\n code: 'unknown_component',\n path: [...path, 'component'],\n entity: 'story',\n message: `Unknown component \"${String(component)}\".`,\n });\n return;\n }\n\n const entity = `block:${block.name}`;\n const fields = block.fields ?? [];\n const fieldsByName = new Map(fields.map(field => [field.name, field]));\n\n for (const key of Object.keys(content)) {\n if (!RESERVED_KEYS.has(key) && !fieldsByName.has(key)) {\n issues.push({\n severity: 'warning',\n code: 'unknown_field',\n path: [...path, key],\n entity,\n message: `Unknown field \"${key}\" on component \"${block.name}\".`,\n });\n }\n }\n\n for (const field of fields) {\n const value = content[field.name];\n if (value === undefined || value === null) {\n if (field.required) {\n issues.push({\n severity: 'error',\n code: 'missing_required_field',\n path: [...path, field.name],\n entity,\n message: `Missing required field \"${field.name}\" on component \"${block.name}\".`,\n });\n }\n continue;\n }\n validateFieldValue(field, value, blocksByName, fieldPluginsByType, [...path, field.name], entity, issues);\n }\n}\n\n/**\n * Validates a story's content against a schema without throwing. Reports unknown\n * components (error), unknown fields (warning), missing required fields (error),\n * and invalid field-value shapes (error), recursing into nested `bloks` and\n * richtext-embedded bloks.\n *\n * @example\n * const result = validateStory(story, { blocks: { page, hero } });\n */\nexport function validateStory(story: unknown, schema: SchemaLike): ValidationResult {\n const issues: ValidationIssue[] = [];\n const blocksByName = new Map(toValues(schema.blocks).map(block => [block.name, block]));\n const fieldPluginsByType = new Map(\n toValues(schema.fieldPlugins).map(plugin => [plugin.fieldType, plugin.value]),\n );\n const content = isRecord(story) ? story.content : undefined;\n validateBlokContent(content, blocksByName, fieldPluginsByType, ['content'], issues);\n return { ok: issues.every(issue => issue.severity !== 'error'), issues };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validate-story.cjs","names":["z","zAssetFieldValue","zMultilinkFieldValue","zTableFieldValue","zRichtextFieldValue","isRecord","slugifyFolderPath","toValues"],"sources":["../../src/validators/validate-story.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { SchemaBlockLike, SchemaFieldLike, SchemaLike } from './shapes';\nimport type { ValidationIssue, ValidationResult } from './types';\nimport {\n zAssetFieldValue,\n zMultilinkFieldValue,\n zRichtextFieldValue,\n zTableFieldValue,\n} from './internal-schemas';\nimport { isRecord, toValues } from './shapes';\nimport { slugifyFolderPath } from '../utils/slugify-folder-path';\n\n/** Field-content keys that are not user-defined fields. */\nconst RESERVED_KEYS = new Set(['_uid', 'component', '_editable']);\n\n/**\n * Relaxed plugin envelope used by the `custom` case. Mirrors the generated\n * `zPluginFieldValue` but relaxes `_uid` from a UUID to a plain string, matching\n * the CMS, which persists arbitrary `_uid` strings. Kept local so a codegen\n * regenerate cannot revert it.\n */\nconst zPluginEnvelope = z.object({ plugin: z.string(), _uid: z.optional(z.string()) });\n\n/** Maps a Standard Schema validator to a {@link ValidationIssue} reporter at `path`. */\nfunction checkValue(\n schema: StandardSchemaV1,\n value: unknown,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n const result = schema['~standard'].validate(value);\n // `validateStory` is synchronous. The internal Zod schemas never return a\n // thenable, but a registered field plugin may ship an async validator — which\n // cannot be awaited here. Surface it as an error instead of silently passing,\n // which would report a false `ok: true`.\n if (result instanceof Promise) {\n issues.push({\n severity: 'error',\n code: 'async_validator_unsupported',\n path,\n entity,\n message: 'Field plugin validator is asynchronous; validateStory runs synchronously and cannot await it.',\n });\n return;\n }\n if (result.issues) {\n for (const issue of result.issues) {\n const issuePath = (issue.path ?? []).map(segment =>\n (typeof segment === 'object' && segment !== null ? String(segment.key) : (segment as string | number)),\n );\n issues.push({\n severity: 'error',\n code: 'invalid_value',\n path: [...path, ...issuePath],\n entity,\n message: issue.message,\n });\n }\n }\n}\n\nfunction validateFieldValue(\n field: SchemaFieldLike,\n value: unknown,\n blocksByName: Map<string, SchemaBlockLike>,\n fieldPluginsByType: Map<string, StandardSchemaV1>,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n switch (field.type) {\n case 'asset':\n checkValue(zAssetFieldValue, value, path, entity, issues);\n break;\n case 'multiasset':\n if (!Array.isArray(value)) {\n pushTypeIssue(value, 'array', path, entity, issues);\n break;\n }\n value.forEach((item, index) => checkValue(zAssetFieldValue, item, [...path, index], entity, issues));\n checkCount(value.length, field.minimum_entries, field.maximum_entries, 'asset(s)', path, entity, issues);\n break;\n case 'multilink':\n checkValue(zMultilinkFieldValue, value, path, entity, issues);\n break;\n case 'table':\n checkValue(zTableFieldValue, value, path, entity, issues);\n break;\n case 'richtext':\n checkValue(zRichtextFieldValue, value, path, entity, issues);\n validateRichtextBloks(value, field, blocksByName, fieldPluginsByType, path, entity, issues);\n break;\n case 'custom': {\n checkValue(zPluginEnvelope, value, path, entity, issues);\n const validator = field.field_type ? fieldPluginsByType.get(field.field_type) : undefined;\n if (validator && isRecord(value)) {\n // Envelope keys sit alongside the plugin's own keys; strip them so the\n // plugin validator sees only its value. Sibling keys keep issue paths\n // accurate (an issue at ['color'] maps to [...path, 'color']).\n const { plugin: _plugin, _uid, ...pluginValue } = value;\n checkValue(validator, pluginValue, path, entity, issues);\n }\n break;\n }\n case 'bloks':\n if (!Array.isArray(value)) {\n pushTypeIssue(value, 'array', path, entity, issues);\n break;\n }\n checkCount(value.length, field.minimum, field.maximum, 'block(s)', path, entity, issues);\n value.forEach((item, index) => {\n checkComponentAllowed(field, item, [...path, index], blocksByName, entity, issues);\n validateBlokContent(item, blocksByName, fieldPluginsByType, [...path, index], issues);\n });\n break;\n case 'text':\n case 'textarea':\n case 'markdown':\n if (typeof value !== 'string') {\n pushTypeIssue(value, 'string', path, entity, issues);\n break;\n }\n checkStringLength(field, value, path, entity, issues);\n break;\n case 'option':\n case 'datetime':\n if (typeof value !== 'string') {\n pushTypeIssue(value, 'string', path, entity, issues);\n }\n break;\n case 'number':\n if (typeof value !== 'number') {\n pushTypeIssue(value, 'number', path, entity, issues);\n break;\n }\n if (field.min_value != null && value < field.min_value) {\n pushConstraint(`Value ${value} is below the minimum of ${field.min_value}.`, path, entity, issues);\n }\n if (field.max_value != null && value > field.max_value) {\n pushConstraint(`Value ${value} exceeds the maximum of ${field.max_value}.`, path, entity, issues);\n }\n if (field.decimals != null && decimalPlaces(value) > field.decimals) {\n pushConstraint(`Value ${value} has more than ${field.decimals} decimal place(s).`, path, entity, issues);\n }\n if (field.steps != null && field.steps > 0 && !isMultipleOf(value, field.steps, field.min_value ?? 0)) {\n const base = field.min_value ?? 0;\n pushConstraint(\n `Value ${value} is not a multiple of the step ${field.steps}${base ? ` (offset from ${base})` : ''}.`,\n path,\n entity,\n issues,\n );\n }\n break;\n case 'boolean':\n if (typeof value !== 'boolean') {\n pushTypeIssue(value, 'boolean', path, entity, issues);\n }\n break;\n case 'options':\n if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {\n pushTypeIssue(value, 'string[]', path, entity, issues);\n break;\n }\n checkCount(value.length, toCount(field.min_options), toCount(field.max_options), 'option(s)', path, entity, issues);\n break;\n case 'section':\n case 'tab':\n // Layout-only field types carry no content value.\n break;\n default:\n // Exhaustiveness guard: when a new `FieldType` is added, this fails to\n // compile until the field type is handled (or explicitly skipped) above.\n field.type satisfies never;\n break;\n }\n}\n\n/**\n * Enforces a field's `allow` list for one embedded blok. Shared by the `bloks`\n * case and the richtext walk so both apply the same rule: `mapFieldToWire`\n * pushes folder/name `allow` as an editor/API restriction on *both* field types,\n * so validation must reject the same components the editor and API would.\n *\n * `itemPath` is the path to the blok item (its index); the reported issue points\n * at that item's `component` key. A component is allowed when it is named\n * directly in `allow` or its block sits in (or under) an allowed folder — both\n * sides canonicalized to slug space so a folder referenced two ways (a\n * `defineFolder` ref vs. a string shorthand with different casing/separators)\n * matches the way the CLI/editor group it.\n */\nfunction checkComponentAllowed(\n field: SchemaFieldLike,\n item: unknown,\n itemPath: (string | number)[],\n blocksByName: Map<string, SchemaBlockLike>,\n entity: string,\n issues: ValidationIssue[],\n): void {\n const allowEntries = field.allow ?? [];\n if (allowEntries.length === 0 || !isRecord(item) || typeof item.component !== 'string') {\n return;\n }\n const blockNamesAllowed = allowEntries.filter((entry): entry is string => typeof entry === 'string');\n const folderPathsAllowed = allowEntries.filter(\n (entry): entry is { folder: string } =>\n typeof entry === 'object' && entry !== null && typeof entry.folder === 'string',\n );\n const itemBlock = blocksByName.get(item.component);\n const itemBlockFolder = itemBlock?.folder;\n const allowedByName = blockNamesAllowed.includes(item.component);\n const itemFolderSlug = typeof itemBlockFolder === 'string' ? slugifyFolderPath(itemBlockFolder) : undefined;\n const allowedByFolder = itemFolderSlug !== undefined\n && folderPathsAllowed.some(({ folder }) => {\n const allowedSlug = slugifyFolderPath(folder);\n return itemFolderSlug === allowedSlug || itemFolderSlug.startsWith(`${allowedSlug}/`);\n });\n if (!allowedByName && !allowedByFolder) {\n const allowedList = allowEntries\n .map(entry => (typeof entry === 'string' ? entry : `folder:${entry.folder}`))\n .join(', ');\n issues.push({\n severity: 'error',\n code: 'disallowed_component',\n path: [...itemPath, 'component'],\n entity,\n message: `Component \"${item.component}\" is not allowed in field \"${field.name}\"; allowed: ${allowedList}.`,\n });\n }\n}\n\n/** Reports a constraint (bound/length/count) violation as an error issue. */\nfunction pushConstraint(\n message: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n issues.push({ severity: 'error', code: 'constraint_violation', path, entity, message });\n}\n\n/** Checks an array length against optional inclusive `min`/`max` bounds. */\nfunction checkCount(\n length: number,\n min: number | undefined,\n max: number | undefined,\n noun: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n if (min != null && length < min) {\n pushConstraint(`Expected at least ${min} ${noun}, received ${length}.`, path, entity, issues);\n }\n if (max != null && length > max) {\n pushConstraint(`Expected at most ${max} ${noun}, received ${length}.`, path, entity, issues);\n }\n}\n\n/** Checks a string against optional `max_length`/`maxlength` and `minlength` bounds. */\nfunction checkStringLength(\n field: SchemaFieldLike,\n value: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n const max = field.max_length ?? field.maxlength;\n if (max != null && value.length > max) {\n pushConstraint(`Text length ${value.length} exceeds the maximum of ${max}.`, path, entity, issues);\n }\n if (field.minlength != null && value.length < field.minlength) {\n pushConstraint(`Text length ${value.length} is below the minimum of ${field.minlength}.`, path, entity, issues);\n }\n}\n\n/** Counts a number's fractional digits, handling exponential notation (e.g. `1e-7` → 7). */\nfunction decimalPlaces(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n const text = String(value).toLowerCase();\n const [mantissa, exponent] = text.split('e');\n const fractionDigits = mantissa.includes('.') ? mantissa.split('.')[1].length : 0;\n // A negative exponent (e.g. `1e-7`) adds that many fractional digits.\n return exponent ? Math.max(0, fractionDigits - Number(exponent)) : fractionDigits;\n}\n\n/** Whether `value` lands on a `step` increment offset from `base`, with float tolerance. */\nfunction isMultipleOf(value: number, step: number, base: number): boolean {\n const ratio = (value - base) / step;\n const tolerance = 1e-9 * Math.max(1, Math.abs(ratio));\n return Math.abs(ratio - Math.round(ratio)) <= tolerance;\n}\n\n/** Parses a numeric constraint stored as a string (e.g. `min_options`). Empty/non-numeric → undefined. */\nfunction toCount(value: string | undefined): number | undefined {\n if (value == null || value === '') {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction pushTypeIssue(\n value: unknown,\n expected: string,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n issues.push({\n severity: 'error',\n code: 'invalid_value',\n path,\n entity,\n message: `Expected ${expected}, received ${value === null ? 'null' : typeof value}.`,\n });\n}\n\n/**\n * Walks richtext `content` nodes and validates embedded bloks (`type: 'blok'`).\n * `field` is the owning richtext field: each embedded blok is checked against its\n * `allow` list ({@link checkComponentAllowed}), matching the group/name\n * restriction `mapFieldToWire` pushes for richtext fields.\n */\nfunction validateRichtextBloks(\n value: unknown,\n field: SchemaFieldLike,\n blocksByName: Map<string, SchemaBlockLike>,\n fieldPluginsByType: Map<string, StandardSchemaV1>,\n path: (string | number)[],\n entity: string,\n issues: ValidationIssue[],\n): void {\n if (!isRecord(value) || !Array.isArray(value.content)) {\n return;\n }\n value.content.forEach((node, index) => {\n if (!isRecord(node)) {\n return;\n }\n if (node.type === 'blok' && isRecord(node.attrs) && Array.isArray(node.attrs.body)) {\n node.attrs.body.forEach((blok, blokIndex) => {\n const blokPath = [...path, 'content', index, 'attrs', 'body', blokIndex];\n checkComponentAllowed(field, blok, blokPath, blocksByName, entity, issues);\n validateBlokContent(blok, blocksByName, fieldPluginsByType, blokPath, issues);\n });\n }\n else if (Array.isArray(node.content)) {\n // Recurse into nested marks/nodes that may themselves embed bloks.\n validateRichtextBloks(node, field, blocksByName, fieldPluginsByType, [...path, 'content', index], entity, issues);\n }\n });\n}\n\n/** Validates a single blok content object against its component definition. */\nfunction validateBlokContent(\n content: unknown,\n blocksByName: Map<string, SchemaBlockLike>,\n fieldPluginsByType: Map<string, StandardSchemaV1>,\n path: (string | number)[],\n issues: ValidationIssue[],\n): void {\n if (!isRecord(content)) {\n issues.push({\n severity: 'error',\n code: 'invalid_content',\n path,\n entity: 'story',\n message: 'Expected a block content object.',\n });\n return;\n }\n\n const component = content.component;\n const block = typeof component === 'string' ? blocksByName.get(component) : undefined;\n if (!block) {\n issues.push({\n severity: 'error',\n code: 'unknown_component',\n path: [...path, 'component'],\n entity: 'story',\n message: `Unknown component \"${String(component)}\".`,\n });\n return;\n }\n\n const entity = `block:${block.name}`;\n const fields = block.fields ?? [];\n const fieldsByName = new Map(fields.map(field => [field.name, field]));\n\n for (const key of Object.keys(content)) {\n if (!RESERVED_KEYS.has(key) && !fieldsByName.has(key)) {\n issues.push({\n severity: 'warning',\n code: 'unknown_field',\n path: [...path, key],\n entity,\n message: `Unknown field \"${key}\" on component \"${block.name}\".`,\n });\n }\n }\n\n for (const field of fields) {\n const value = content[field.name];\n if (value === undefined || value === null) {\n if (field.required) {\n issues.push({\n severity: 'error',\n code: 'missing_required_field',\n path: [...path, field.name],\n entity,\n message: `Missing required field \"${field.name}\" on component \"${block.name}\".`,\n });\n }\n continue;\n }\n validateFieldValue(field, value, blocksByName, fieldPluginsByType, [...path, field.name], entity, issues);\n }\n}\n\n/**\n * Validates a story's content against a schema without throwing. Reports unknown\n * components (error), unknown fields (warning), missing required fields (error),\n * and invalid field-value shapes (error), recursing into nested `bloks` and\n * richtext-embedded bloks.\n *\n * @example\n * const result = validateStory(story, { blocks: { page, hero } });\n */\nexport function validateStory(story: unknown, schema: SchemaLike): ValidationResult {\n const issues: ValidationIssue[] = [];\n const blocksByName = new Map(toValues(schema.blocks).map(block => [block.name, block]));\n const fieldPluginsByType = new Map(\n toValues(schema.fieldPlugins).map(plugin => [plugin.fieldType, plugin.value]),\n );\n const content = isRecord(story) ? story.content : undefined;\n validateBlokContent(content, blocksByName, fieldPluginsByType, ['content'], issues);\n return { ok: issues.every(issue => issue.severity !== 'error'), issues };\n}\n"],"mappings":"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"}
|