@pwfabric/authoring 0.0.0-stage → 1.0.0-rc.1

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.
@@ -0,0 +1,281 @@
1
+ /**
2
+ * @pwfabric/authoring Type Definitions
3
+ *
4
+ * Core type definitions for the Custom Block SDK.
5
+ *
6
+ * @module @pwfabric/authoring/types
7
+ */
8
+ import type { ZodSchema, ZodTypeAny, output as ZodOutput } from 'zod';
9
+ import type { Block, BlockCategory } from '@pwfabric/core';
10
+ import type { BlockComponentRegistry } from '@pwfabric/runtime';
11
+ import type { ReactNode } from 'react';
12
+ /**
13
+ * Custom block definition options
14
+ *
15
+ * Used with `defineBlock()` to create a type-safe custom block.
16
+ */
17
+ export type SchemaProps<TSchema extends ZodTypeAny> = ZodOutput<TSchema> extends Record<string, unknown> ? ZodOutput<TSchema> : Record<string, unknown>;
18
+ /**
19
+ * Options for `defineBlock()`. The block's props are the schema's output
20
+ * (`SchemaProps<TSchema>`): they are inferred from `schema` alone, and
21
+ * `defaultProps` and `component` are checked against them.
22
+ */
23
+ export interface CustomBlockOptions<TSchema extends ZodTypeAny = ZodTypeAny, TProps extends Record<string, unknown> = SchemaProps<TSchema>> {
24
+ /** Unique block type identifier (kebab-case) */
25
+ readonly type: string;
26
+ /** Human-readable display name */
27
+ readonly name: string;
28
+ /** Block category for grouping */
29
+ readonly category?: BlockCategory;
30
+ /** Icon character or identifier */
31
+ readonly icon?: string;
32
+ /** Zod schema for props validation */
33
+ readonly schema: TSchema;
34
+ /**
35
+ * Default props for new instances. Checked against the schema, never used to
36
+ * infer the props: a field the schema marks optional stays optional even when
37
+ * it has a default here.
38
+ */
39
+ readonly defaultProps: TProps;
40
+ /** Whether this block can contain children */
41
+ readonly canHaveChildren?: boolean;
42
+ /** Restrict which block types can be children */
43
+ readonly allowedChildren?: readonly string[];
44
+ /** Restrict which block types can be parents */
45
+ readonly allowedParents?: readonly string[];
46
+ /** open trait vocabulary this block carries (kebab-case). */
47
+ readonly traits?: readonly string[];
48
+ /** children must carry ANY of these traits (containers only). */
49
+ readonly allowedChildTraits?: readonly string[];
50
+ /** Maximum instances allowed in a surface */
51
+ readonly maxInstances?: number;
52
+ /** React component to render this block; its props are the schema's output. */
53
+ readonly component: CustomBlockComponent<TProps>;
54
+ }
55
+ /**
56
+ * Custom block context
57
+ *
58
+ * Narrowed view of the runtime BlockContext for custom block authors.
59
+ * Excludes platform internals (capabilities, imageAdapter, basePath).
60
+ */
61
+ export interface CustomBlockContext {
62
+ /** Current theme */
63
+ readonly theme: 'light' | 'dark';
64
+ /** Current viewport breakpoint */
65
+ readonly viewport: string;
66
+ /** Event handler for block-level events */
67
+ readonly onEvent?: ((event: {
68
+ type: string;
69
+ blockId?: string;
70
+ payload?: unknown;
71
+ }) => void) | undefined;
72
+ }
73
+ /**
74
+ * Custom block component props
75
+ */
76
+ export interface CustomBlockComponentProps<TProps = Record<string, unknown>> {
77
+ /** The resolved, type-safe props */
78
+ readonly props: TProps;
79
+ /** Block ID */
80
+ readonly blockId: string;
81
+ /** Rendered children (for container blocks) */
82
+ readonly children?: ReactNode;
83
+ /** Runtime context (theme, viewport, events) */
84
+ readonly context?: CustomBlockContext | undefined;
85
+ }
86
+ /**
87
+ * Custom block component type
88
+ */
89
+ export type CustomBlockComponent<TProps = Record<string, unknown>> = (props: CustomBlockComponentProps<TProps>) => ReactNode;
90
+ /**
91
+ * Custom block definition (result of defineBlock)
92
+ *
93
+ * Contains both the block metadata and the component for rendering.
94
+ */
95
+ export interface CustomBlockDefinition<TProps extends Record<string, unknown> = Record<string, unknown>> {
96
+ /** Unique block type identifier */
97
+ readonly type: string;
98
+ /** Human-readable display name */
99
+ readonly name: string;
100
+ /** Block category */
101
+ readonly category: BlockCategory;
102
+ /** Icon character or identifier */
103
+ readonly icon: string;
104
+ /** Zod schema for validation */
105
+ readonly schema: ZodSchema<TProps>;
106
+ /** Default props */
107
+ readonly defaultProps: TProps;
108
+ /** Whether this block can contain children */
109
+ readonly canHaveChildren: boolean;
110
+ /** Allowed child block types */
111
+ readonly allowedChildren?: readonly string[];
112
+ /** Allowed parent block types */
113
+ readonly allowedParents?: readonly string[];
114
+ /** open trait vocabulary this block carries (kebab-case). */
115
+ readonly traits?: readonly string[];
116
+ /** children must carry ANY of these traits (containers only). */
117
+ readonly allowedChildTraits?: readonly string[];
118
+ /** Maximum instances */
119
+ readonly maxInstances?: number;
120
+ /** React component */
121
+ readonly component: CustomBlockComponent<TProps>;
122
+ }
123
+ /**
124
+ * Factory configuration input
125
+ *
126
+ * Passed to the build function to customize the generated blocks.
127
+ */
128
+ export type FactoryConfig = Record<string, unknown>;
129
+ /**
130
+ * Block specification for factory output
131
+ *
132
+ * Simplified block definition without requiring an id (auto-generated).
133
+ */
134
+ export interface BlockSpec {
135
+ /** Block type */
136
+ readonly type: string;
137
+ /** Block props */
138
+ readonly props: Record<string, unknown>;
139
+ /** Nested children */
140
+ readonly children?: readonly BlockSpec[];
141
+ }
142
+ /**
143
+ * Factory definition options
144
+ *
145
+ * Used with `defineFactory()` to create a factory function.
146
+ */
147
+ export interface FactoryOptions<TConfig extends FactoryConfig = FactoryConfig> {
148
+ /** Unique factory identifier (kebab-case) */
149
+ readonly id: string;
150
+ /** Human-readable factory name */
151
+ readonly name?: string;
152
+ /** Factory description */
153
+ readonly description?: string;
154
+ /** Optional Zod schema for config validation */
155
+ readonly configSchema?: ZodSchema<TConfig>;
156
+ /** Build function that returns block specifications */
157
+ readonly build: (config: TConfig) => readonly BlockSpec[];
158
+ }
159
+ /**
160
+ * Factory definition (result of defineFactory)
161
+ */
162
+ export interface FactoryDefinition<TConfig extends FactoryConfig = FactoryConfig> {
163
+ /** Factory identifier */
164
+ readonly id: string;
165
+ /** Factory name */
166
+ readonly name: string;
167
+ /** Factory description */
168
+ readonly description: string;
169
+ /** Config schema (if provided) */
170
+ readonly configSchema?: ZodSchema<TConfig>;
171
+ /** Build function */
172
+ readonly build: (config: TConfig) => readonly Block[];
173
+ /** Validate config against schema (if provided) */
174
+ readonly validateConfig: (config: unknown) => FactoryValidationResult<TConfig>;
175
+ }
176
+ /**
177
+ * Factory validation result
178
+ */
179
+ export interface FactoryValidationResult<TConfig = FactoryConfig> {
180
+ /** Whether validation passed */
181
+ readonly valid: boolean;
182
+ /** Validated config (if valid) */
183
+ readonly config?: TConfig;
184
+ /** Validation errors (if invalid) */
185
+ readonly errors?: readonly FactoryValidationError[];
186
+ }
187
+ /**
188
+ * Factory validation error
189
+ */
190
+ export interface FactoryValidationError {
191
+ /** Error path in the config */
192
+ readonly path: string;
193
+ /** Error message */
194
+ readonly message: string;
195
+ }
196
+ /**
197
+ * Custom block registry interface
198
+ */
199
+ export interface ICustomBlockRegistry {
200
+ /**
201
+ * Register a custom block definition
202
+ */
203
+ register<TProps extends Record<string, unknown>>(definition: CustomBlockDefinition<TProps>): void;
204
+ /**
205
+ * Get a custom block definition by type
206
+ */
207
+ get(type: string): CustomBlockDefinition | undefined;
208
+ /**
209
+ * Check if a block type is registered
210
+ */
211
+ has(type: string): boolean;
212
+ /**
213
+ * Get all registered custom block definitions
214
+ */
215
+ list(): readonly CustomBlockDefinition[];
216
+ /**
217
+ * Get block definitions by category
218
+ */
219
+ getByCategory(category: BlockCategory): readonly CustomBlockDefinition[];
220
+ /**
221
+ * Get all registered block types
222
+ */
223
+ types(): readonly string[];
224
+ /**
225
+ * Unregister a custom block
226
+ */
227
+ unregister(type: string): boolean;
228
+ /**
229
+ * Clear all registrations
230
+ */
231
+ clear(): void;
232
+ /**
233
+ * Get a component registry for use with SurfaceRenderer
234
+ */
235
+ getComponents(): BlockComponentRegistry;
236
+ }
237
+ /**
238
+ * Block validation result
239
+ */
240
+ export interface BlockValidationResult {
241
+ /** Whether the block is valid */
242
+ readonly valid: boolean;
243
+ /** The validated block (with defaults applied) */
244
+ readonly block?: Block;
245
+ /** Validation errors */
246
+ readonly errors: readonly BlockValidationError[];
247
+ }
248
+ /**
249
+ * Block validation error
250
+ */
251
+ export interface BlockValidationError {
252
+ /** Error code */
253
+ readonly code: string;
254
+ /** Error message */
255
+ readonly message: string;
256
+ /** Path to the invalid property */
257
+ readonly path?: string;
258
+ }
259
+ /**
260
+ * Props validation result
261
+ */
262
+ export interface PropsValidationResult<TProps = Record<string, unknown>> {
263
+ /** Whether the props are valid */
264
+ readonly valid: boolean;
265
+ /** The validated props (with defaults applied) */
266
+ readonly props?: TProps;
267
+ /** Validation errors */
268
+ readonly errors: readonly BlockValidationError[];
269
+ }
270
+ /**
271
+ * Infer props type from a custom block definition
272
+ */
273
+ export type InferBlockProps<T> = T extends CustomBlockDefinition<infer TProps> ? TProps : never;
274
+ /**
275
+ * Infer config type from a factory definition
276
+ */
277
+ export type InferFactoryConfig<T> = T extends FactoryDefinition<infer TConfig> ? TConfig : never;
278
+ /**
279
+ * Infer props type from a Zod schema
280
+ */
281
+ export type InferSchemaProps<T> = T extends ZodSchema<infer TOutput> ? TOutput : never;
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @pwfabric/authoring Type Definitions
3
+ *
4
+ * Core type definitions for the Custom Block SDK.
5
+ *
6
+ * @module @pwfabric/authoring/types
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG","sourcesContent":["/**\n * @pwfabric/authoring Type Definitions\n *\n * Core type definitions for the Custom Block SDK.\n *\n * @module @pwfabric/authoring/types\n */\n\nimport type { ZodSchema, ZodTypeAny, output as ZodOutput } from 'zod'\nimport type { Block, BlockCategory } from '@pwfabric/core'\nimport type { BlockComponentRegistry } from '@pwfabric/runtime'\nimport type { ReactNode } from 'react'\n\n// ============================================================\n// CUSTOM BLOCK DEFINITION\n// ============================================================\n\n/**\n * Custom block definition options\n *\n * Used with `defineBlock()` to create a type-safe custom block.\n */\nexport type SchemaProps<TSchema extends ZodTypeAny> =\n ZodOutput<TSchema> extends Record<string, unknown> ? ZodOutput<TSchema> : Record<string, unknown>\n\n/**\n * Options for `defineBlock()`. The block's props are the schema's output\n * (`SchemaProps<TSchema>`): they are inferred from `schema` alone, and\n * `defaultProps` and `component` are checked against them.\n */\nexport interface CustomBlockOptions<\n TSchema extends ZodTypeAny = ZodTypeAny,\n TProps extends Record<string, unknown> = SchemaProps<TSchema>,\n> {\n /** Unique block type identifier (kebab-case) */\n readonly type: string\n\n /** Human-readable display name */\n readonly name: string\n\n /** Block category for grouping */\n readonly category?: BlockCategory\n\n /** Icon character or identifier */\n readonly icon?: string\n\n /** Zod schema for props validation */\n readonly schema: TSchema\n\n /**\n * Default props for new instances. Checked against the schema, never used to\n * infer the props: a field the schema marks optional stays optional even when\n * it has a default here.\n */\n readonly defaultProps: TProps\n\n /** Whether this block can contain children */\n readonly canHaveChildren?: boolean\n\n /** Restrict which block types can be children */\n readonly allowedChildren?: readonly string[]\n\n /** Restrict which block types can be parents */\n readonly allowedParents?: readonly string[]\n\n /** open trait vocabulary this block carries (kebab-case). */\n readonly traits?: readonly string[]\n\n /** children must carry ANY of these traits (containers only). */\n readonly allowedChildTraits?: readonly string[]\n\n /** Maximum instances allowed in a surface */\n readonly maxInstances?: number\n\n /** React component to render this block; its props are the schema's output. */\n readonly component: CustomBlockComponent<TProps>\n}\n\n/**\n * Custom block context\n *\n * Narrowed view of the runtime BlockContext for custom block authors.\n * Excludes platform internals (capabilities, imageAdapter, basePath).\n */\nexport interface CustomBlockContext {\n /** Current theme */\n readonly theme: 'light' | 'dark'\n /** Current viewport breakpoint */\n readonly viewport: string\n /** Event handler for block-level events */\n readonly onEvent?:\n | ((event: { type: string; blockId?: string; payload?: unknown }) => void)\n | undefined\n}\n\n/**\n * Custom block component props\n */\nexport interface CustomBlockComponentProps<TProps = Record<string, unknown>> {\n /** The resolved, type-safe props */\n readonly props: TProps\n /** Block ID */\n readonly blockId: string\n /** Rendered children (for container blocks) */\n readonly children?: ReactNode\n /** Runtime context (theme, viewport, events) */\n readonly context?: CustomBlockContext | undefined\n}\n\n/**\n * Custom block component type\n */\nexport type CustomBlockComponent<TProps = Record<string, unknown>> = (\n props: CustomBlockComponentProps<TProps>\n) => ReactNode\n\n/**\n * Custom block definition (result of defineBlock)\n *\n * Contains both the block metadata and the component for rendering.\n */\nexport interface CustomBlockDefinition<\n TProps extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Unique block type identifier */\n readonly type: string\n\n /** Human-readable display name */\n readonly name: string\n\n /** Block category */\n readonly category: BlockCategory\n\n /** Icon character or identifier */\n readonly icon: string\n\n /** Zod schema for validation */\n readonly schema: ZodSchema<TProps>\n\n /** Default props */\n readonly defaultProps: TProps\n\n /** Whether this block can contain children */\n readonly canHaveChildren: boolean\n\n /** Allowed child block types */\n readonly allowedChildren?: readonly string[]\n\n /** Allowed parent block types */\n readonly allowedParents?: readonly string[]\n\n /** open trait vocabulary this block carries (kebab-case). */\n readonly traits?: readonly string[]\n\n /** children must carry ANY of these traits (containers only). */\n readonly allowedChildTraits?: readonly string[]\n\n /** Maximum instances */\n readonly maxInstances?: number\n\n /** React component */\n readonly component: CustomBlockComponent<TProps>\n}\n\n// ============================================================\n// FACTORY DEFINITION\n// ============================================================\n\n/**\n * Factory configuration input\n *\n * Passed to the build function to customize the generated blocks.\n */\nexport type FactoryConfig = Record<string, unknown>\n\n/**\n * Block specification for factory output\n *\n * Simplified block definition without requiring an id (auto-generated).\n */\nexport interface BlockSpec {\n /** Block type */\n readonly type: string\n /** Block props */\n readonly props: Record<string, unknown>\n /** Nested children */\n readonly children?: readonly BlockSpec[]\n}\n\n/**\n * Factory definition options\n *\n * Used with `defineFactory()` to create a factory function.\n */\nexport interface FactoryOptions<TConfig extends FactoryConfig = FactoryConfig> {\n /** Unique factory identifier (kebab-case) */\n readonly id: string\n\n /** Human-readable factory name */\n readonly name?: string\n\n /** Factory description */\n readonly description?: string\n\n /** Optional Zod schema for config validation */\n readonly configSchema?: ZodSchema<TConfig>\n\n /** Build function that returns block specifications */\n readonly build: (config: TConfig) => readonly BlockSpec[]\n}\n\n/**\n * Factory definition (result of defineFactory)\n */\nexport interface FactoryDefinition<TConfig extends FactoryConfig = FactoryConfig> {\n /** Factory identifier */\n readonly id: string\n\n /** Factory name */\n readonly name: string\n\n /** Factory description */\n readonly description: string\n\n /** Config schema (if provided) */\n readonly configSchema?: ZodSchema<TConfig>\n\n /** Build function */\n readonly build: (config: TConfig) => readonly Block[]\n\n /** Validate config against schema (if provided) */\n readonly validateConfig: (config: unknown) => FactoryValidationResult<TConfig>\n}\n\n/**\n * Factory validation result\n */\nexport interface FactoryValidationResult<TConfig = FactoryConfig> {\n /** Whether validation passed */\n readonly valid: boolean\n /** Validated config (if valid) */\n readonly config?: TConfig\n /** Validation errors (if invalid) */\n readonly errors?: readonly FactoryValidationError[]\n}\n\n/**\n * Factory validation error\n */\nexport interface FactoryValidationError {\n /** Error path in the config */\n readonly path: string\n /** Error message */\n readonly message: string\n}\n\n// ============================================================\n// REGISTRY TYPES\n// ============================================================\n\n/**\n * Custom block registry interface\n */\nexport interface ICustomBlockRegistry {\n /**\n * Register a custom block definition\n */\n register<TProps extends Record<string, unknown>>(definition: CustomBlockDefinition<TProps>): void\n\n /**\n * Get a custom block definition by type\n */\n get(type: string): CustomBlockDefinition | undefined\n\n /**\n * Check if a block type is registered\n */\n has(type: string): boolean\n\n /**\n * Get all registered custom block definitions\n */\n list(): readonly CustomBlockDefinition[]\n\n /**\n * Get block definitions by category\n */\n getByCategory(category: BlockCategory): readonly CustomBlockDefinition[]\n\n /**\n * Get all registered block types\n */\n types(): readonly string[]\n\n /**\n * Unregister a custom block\n */\n unregister(type: string): boolean\n\n /**\n * Clear all registrations\n */\n clear(): void\n\n /**\n * Get a component registry for use with SurfaceRenderer\n */\n getComponents(): BlockComponentRegistry\n}\n\n// ============================================================\n// VALIDATION TYPES\n// ============================================================\n\n/**\n * Block validation result\n */\nexport interface BlockValidationResult {\n /** Whether the block is valid */\n readonly valid: boolean\n /** The validated block (with defaults applied) */\n readonly block?: Block\n /** Validation errors */\n readonly errors: readonly BlockValidationError[]\n}\n\n/**\n * Block validation error\n */\nexport interface BlockValidationError {\n /** Error code */\n readonly code: string\n /** Error message */\n readonly message: string\n /** Path to the invalid property */\n readonly path?: string\n}\n\n/**\n * Props validation result\n */\nexport interface PropsValidationResult<TProps = Record<string, unknown>> {\n /** Whether the props are valid */\n readonly valid: boolean\n /** The validated props (with defaults applied) */\n readonly props?: TProps\n /** Validation errors */\n readonly errors: readonly BlockValidationError[]\n}\n\n// ============================================================\n// UTILITY TYPES\n// ============================================================\n\n/**\n * Infer props type from a custom block definition\n */\nexport type InferBlockProps<T> = T extends CustomBlockDefinition<infer TProps> ? TProps : never\n\n/**\n * Infer config type from a factory definition\n */\nexport type InferFactoryConfig<T> = T extends FactoryDefinition<infer TConfig> ? TConfig : never\n\n/**\n * Infer props type from a Zod schema\n */\nexport type InferSchemaProps<T> = T extends ZodSchema<infer TOutput> ? TOutput : never\n"]}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Validation Utilities
3
+ *
4
+ * Schema validation utilities for custom blocks.
5
+ *
6
+ * @module @pwfabric/authoring/validation
7
+ */
8
+ import type { ZodSchema } from 'zod';
9
+ import type { Block } from '@pwfabric/core';
10
+ import type { CustomBlockDefinition, BlockValidationResult, BlockValidationError, PropsValidationResult, ICustomBlockRegistry } from './types.js';
11
+ /**
12
+ * Validate props against a Zod schema
13
+ *
14
+ * @param props - The props to validate
15
+ * @param schema - The Zod schema to validate against
16
+ * @returns Validation result with validated props or errors
17
+ */
18
+ export declare function validateProps<TProps extends Record<string, unknown>>(props: unknown, schema: ZodSchema<TProps>): PropsValidationResult<TProps>;
19
+ /**
20
+ * Validate props with defaults applied
21
+ *
22
+ * If validation fails, returns the default props.
23
+ *
24
+ * @param props - The props to validate
25
+ * @param schema - The Zod schema
26
+ * @param defaultProps - Default props to use if validation fails
27
+ * @returns Validated props or defaults
28
+ */
29
+ export declare function validatePropsWithDefaults<TProps extends Record<string, unknown>>(props: unknown, schema: ZodSchema<TProps>, defaultProps: TProps): TProps;
30
+ /**
31
+ * Validate a block against its definition
32
+ *
33
+ * @param block - The block to validate
34
+ * @param definition - The block definition
35
+ * @returns Validation result
36
+ */
37
+ export declare function validateBlock<TProps extends Record<string, unknown>>(block: Block, definition: CustomBlockDefinition<TProps>): BlockValidationResult;
38
+ /**
39
+ * Validate a block tree against a registry of definitions
40
+ *
41
+ * Recursively validates all blocks in the tree.
42
+ *
43
+ * @param blocks - The blocks to validate
44
+ * @param registry - The block registry
45
+ * @returns Array of validation errors (empty if all valid)
46
+ */
47
+ export declare function validateBlockTree(blocks: readonly Block[], registry: ICustomBlockRegistry): readonly BlockValidationError[];
48
+ /**
49
+ * Create a validation function for a specific block type
50
+ *
51
+ * @param definition - The block definition
52
+ * @returns A validation function
53
+ */
54
+ export declare function createBlockValidator<TProps extends Record<string, unknown>>(definition: CustomBlockDefinition<TProps>): (block: Block) => BlockValidationResult;
55
+ /**
56
+ * Create a props validation function for a specific schema
57
+ *
58
+ * @param schema - The Zod schema
59
+ * @returns A validation function
60
+ */
61
+ export declare function createPropsValidator<TProps extends Record<string, unknown>>(schema: ZodSchema<TProps>): (props: unknown) => PropsValidationResult<TProps>;
62
+ /**
63
+ * Format validation errors as a human-readable string
64
+ *
65
+ * @param errors - The validation errors
66
+ * @returns Formatted error string
67
+ */
68
+ export declare function formatValidationErrors(errors: readonly BlockValidationError[]): string;
69
+ /**
70
+ * Check if a validation result has errors
71
+ */
72
+ export declare function hasErrors(result: BlockValidationResult | PropsValidationResult): boolean;
73
+ /**
74
+ * Check if a value is a valid Block
75
+ */
76
+ export declare function isBlock(value: unknown): value is Block;
77
+ /**
78
+ * Check if a value is a valid Block array
79
+ */
80
+ export declare function isBlockArray(value: unknown): value is Block[];
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Validation Utilities
3
+ *
4
+ * Schema validation utilities for custom blocks.
5
+ *
6
+ * @module @pwfabric/authoring/validation
7
+ */
8
+ // ============================================================
9
+ // PROPS VALIDATION
10
+ // ============================================================
11
+ /**
12
+ * Validate props against a Zod schema
13
+ *
14
+ * @param props - The props to validate
15
+ * @param schema - The Zod schema to validate against
16
+ * @returns Validation result with validated props or errors
17
+ */
18
+ export function validateProps(props, schema) {
19
+ const result = schema.safeParse(props);
20
+ if (result.success) {
21
+ return {
22
+ valid: true,
23
+ props: result.data,
24
+ errors: [],
25
+ };
26
+ }
27
+ const errors = result.error.errors.map((e) => ({
28
+ code: 'VALIDATION_ERROR',
29
+ message: e.message,
30
+ path: e.path.join('.'),
31
+ }));
32
+ return {
33
+ valid: false,
34
+ errors,
35
+ };
36
+ }
37
+ /**
38
+ * Validate props with defaults applied
39
+ *
40
+ * If validation fails, returns the default props.
41
+ *
42
+ * @param props - The props to validate
43
+ * @param schema - The Zod schema
44
+ * @param defaultProps - Default props to use if validation fails
45
+ * @returns Validated props or defaults
46
+ */
47
+ export function validatePropsWithDefaults(props, schema, defaultProps) {
48
+ const result = schema.safeParse(props);
49
+ return result.success ? result.data : defaultProps;
50
+ }
51
+ // ============================================================
52
+ // BLOCK VALIDATION
53
+ // ============================================================
54
+ /**
55
+ * Validate a block against its definition
56
+ *
57
+ * @param block - The block to validate
58
+ * @param definition - The block definition
59
+ * @returns Validation result
60
+ */
61
+ export function validateBlock(block, definition) {
62
+ const errors = [];
63
+ // Validate type matches
64
+ if (block.type !== definition.type) {
65
+ errors.push({
66
+ code: 'TYPE_MISMATCH',
67
+ message: `Expected block type "${definition.type}", got "${block.type}"`,
68
+ });
69
+ return { valid: false, errors };
70
+ }
71
+ // Validate props
72
+ const propsResult = validateProps(block.props, definition.schema);
73
+ if (!propsResult.valid) {
74
+ errors.push(...propsResult.errors);
75
+ }
76
+ // Validate children constraints
77
+ if (block.children && block.children.length > 0) {
78
+ if (!definition.canHaveChildren) {
79
+ errors.push({
80
+ code: 'CHILDREN_NOT_ALLOWED',
81
+ message: `Block type "${definition.type}" cannot have children`,
82
+ });
83
+ }
84
+ if (definition.allowedChildren && definition.allowedChildren.length > 0) {
85
+ for (const child of block.children) {
86
+ if (!definition.allowedChildren.includes(child.type)) {
87
+ errors.push({
88
+ code: 'INVALID_CHILD_TYPE',
89
+ message: `Block type "${child.type}" is not allowed as a child of "${definition.type}"`,
90
+ path: `children.${child.id}`,
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }
96
+ if (errors.length > 0) {
97
+ return { valid: false, errors };
98
+ }
99
+ return {
100
+ valid: true,
101
+ block: {
102
+ ...block,
103
+ props: propsResult.props ?? block.props,
104
+ },
105
+ errors: [],
106
+ };
107
+ }
108
+ /**
109
+ * Validate a block tree against a registry of definitions
110
+ *
111
+ * Recursively validates all blocks in the tree.
112
+ *
113
+ * @param blocks - The blocks to validate
114
+ * @param registry - The block registry
115
+ * @returns Array of validation errors (empty if all valid)
116
+ */
117
+ export function validateBlockTree(blocks, registry) {
118
+ const errors = [];
119
+ function validateRecursive(block, path) {
120
+ const definition = registry.get(block.type);
121
+ if (!definition) {
122
+ // Not a custom block, skip validation
123
+ // (could be a built-in block type)
124
+ if (block.children) {
125
+ block.children.forEach((child, index) => {
126
+ validateRecursive(child, `${path}.children[${index}]`);
127
+ });
128
+ }
129
+ return;
130
+ }
131
+ const result = validateBlock(block, definition);
132
+ if (!result.valid) {
133
+ errors.push(...result.errors.map((e) => ({
134
+ ...e,
135
+ path: e.path ? `${path}.${e.path}` : path,
136
+ })));
137
+ }
138
+ if (block.children) {
139
+ block.children.forEach((child, index) => {
140
+ validateRecursive(child, `${path}.children[${index}]`);
141
+ });
142
+ }
143
+ }
144
+ blocks.forEach((block, index) => {
145
+ validateRecursive(block, `blocks[${index}]`);
146
+ });
147
+ return errors;
148
+ }
149
+ // ============================================================
150
+ // SCHEMA UTILITIES
151
+ // ============================================================
152
+ /**
153
+ * Create a validation function for a specific block type
154
+ *
155
+ * @param definition - The block definition
156
+ * @returns A validation function
157
+ */
158
+ export function createBlockValidator(definition) {
159
+ return (block) => validateBlock(block, definition);
160
+ }
161
+ /**
162
+ * Create a props validation function for a specific schema
163
+ *
164
+ * @param schema - The Zod schema
165
+ * @returns A validation function
166
+ */
167
+ export function createPropsValidator(schema) {
168
+ return (props) => validateProps(props, schema);
169
+ }
170
+ // ============================================================
171
+ // ERROR FORMATTING
172
+ // ============================================================
173
+ /**
174
+ * Format validation errors as a human-readable string
175
+ *
176
+ * @param errors - The validation errors
177
+ * @returns Formatted error string
178
+ */
179
+ export function formatValidationErrors(errors) {
180
+ if (errors.length === 0) {
181
+ return 'No errors';
182
+ }
183
+ return errors
184
+ .map((e) => {
185
+ const path = e.path ? ` at ${e.path}` : '';
186
+ return `[${e.code}]${path}: ${e.message}`;
187
+ })
188
+ .join('\n');
189
+ }
190
+ /**
191
+ * Check if a validation result has errors
192
+ */
193
+ export function hasErrors(result) {
194
+ return !result.valid && result.errors.length > 0;
195
+ }
196
+ // ============================================================
197
+ // TYPE GUARDS
198
+ // ============================================================
199
+ /**
200
+ * Check if a value is a valid Block
201
+ */
202
+ export function isBlock(value) {
203
+ if (!value || typeof value !== 'object') {
204
+ return false;
205
+ }
206
+ const block = value;
207
+ return (typeof block.id === 'string' &&
208
+ typeof block.type === 'string' &&
209
+ typeof block.props === 'object');
210
+ }
211
+ /**
212
+ * Check if a value is a valid Block array
213
+ */
214
+ export function isBlockArray(value) {
215
+ return Array.isArray(value) && value.every(isBlock);
216
+ }
217
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validation Utilities\n *\n * Schema validation utilities for custom blocks.\n *\n * @module @pwfabric/authoring/validation\n */\n\nimport type { ZodSchema } from 'zod'\nimport type { Block } from '@pwfabric/core'\nimport type {\n CustomBlockDefinition,\n BlockValidationResult,\n BlockValidationError,\n PropsValidationResult,\n ICustomBlockRegistry,\n} from './types.js'\n\n// ============================================================\n// PROPS VALIDATION\n// ============================================================\n\n/**\n * Validate props against a Zod schema\n *\n * @param props - The props to validate\n * @param schema - The Zod schema to validate against\n * @returns Validation result with validated props or errors\n */\nexport function validateProps<TProps extends Record<string, unknown>>(\n props: unknown,\n schema: ZodSchema<TProps>\n): PropsValidationResult<TProps> {\n const result = schema.safeParse(props)\n\n if (result.success) {\n return {\n valid: true,\n props: result.data,\n errors: [],\n }\n }\n\n const errors: BlockValidationError[] = result.error.errors.map((e) => ({\n code: 'VALIDATION_ERROR',\n message: e.message,\n path: e.path.join('.'),\n }))\n\n return {\n valid: false,\n errors,\n }\n}\n\n/**\n * Validate props with defaults applied\n *\n * If validation fails, returns the default props.\n *\n * @param props - The props to validate\n * @param schema - The Zod schema\n * @param defaultProps - Default props to use if validation fails\n * @returns Validated props or defaults\n */\nexport function validatePropsWithDefaults<TProps extends Record<string, unknown>>(\n props: unknown,\n schema: ZodSchema<TProps>,\n defaultProps: TProps\n): TProps {\n const result = schema.safeParse(props)\n return result.success ? result.data : defaultProps\n}\n\n// ============================================================\n// BLOCK VALIDATION\n// ============================================================\n\n/**\n * Validate a block against its definition\n *\n * @param block - The block to validate\n * @param definition - The block definition\n * @returns Validation result\n */\nexport function validateBlock<TProps extends Record<string, unknown>>(\n block: Block,\n definition: CustomBlockDefinition<TProps>\n): BlockValidationResult {\n const errors: BlockValidationError[] = []\n\n // Validate type matches\n if (block.type !== definition.type) {\n errors.push({\n code: 'TYPE_MISMATCH',\n message: `Expected block type \"${definition.type}\", got \"${block.type}\"`,\n })\n return { valid: false, errors }\n }\n\n // Validate props\n const propsResult = validateProps(block.props, definition.schema)\n if (!propsResult.valid) {\n errors.push(...propsResult.errors)\n }\n\n // Validate children constraints\n if (block.children && block.children.length > 0) {\n if (!definition.canHaveChildren) {\n errors.push({\n code: 'CHILDREN_NOT_ALLOWED',\n message: `Block type \"${definition.type}\" cannot have children`,\n })\n }\n\n if (definition.allowedChildren && definition.allowedChildren.length > 0) {\n for (const child of block.children) {\n if (!definition.allowedChildren.includes(child.type)) {\n errors.push({\n code: 'INVALID_CHILD_TYPE',\n message: `Block type \"${child.type}\" is not allowed as a child of \"${definition.type}\"`,\n path: `children.${child.id}`,\n })\n }\n }\n }\n }\n\n if (errors.length > 0) {\n return { valid: false, errors }\n }\n\n return {\n valid: true,\n block: {\n ...block,\n props: propsResult.props ?? block.props,\n },\n errors: [],\n }\n}\n\n/**\n * Validate a block tree against a registry of definitions\n *\n * Recursively validates all blocks in the tree.\n *\n * @param blocks - The blocks to validate\n * @param registry - The block registry\n * @returns Array of validation errors (empty if all valid)\n */\nexport function validateBlockTree(\n blocks: readonly Block[],\n registry: ICustomBlockRegistry\n): readonly BlockValidationError[] {\n const errors: BlockValidationError[] = []\n\n function validateRecursive(block: Block, path: string): void {\n const definition = registry.get(block.type)\n\n if (!definition) {\n // Not a custom block, skip validation\n // (could be a built-in block type)\n if (block.children) {\n block.children.forEach((child: Block, index: number) => {\n validateRecursive(child, `${path}.children[${index}]`)\n })\n }\n return\n }\n\n const result = validateBlock(block, definition)\n if (!result.valid) {\n errors.push(\n ...result.errors.map((e) => ({\n ...e,\n path: e.path ? `${path}.${e.path}` : path,\n }))\n )\n }\n\n if (block.children) {\n block.children.forEach((child: Block, index: number) => {\n validateRecursive(child, `${path}.children[${index}]`)\n })\n }\n }\n\n blocks.forEach((block, index) => {\n validateRecursive(block, `blocks[${index}]`)\n })\n\n return errors\n}\n\n// ============================================================\n// SCHEMA UTILITIES\n// ============================================================\n\n/**\n * Create a validation function for a specific block type\n *\n * @param definition - The block definition\n * @returns A validation function\n */\nexport function createBlockValidator<TProps extends Record<string, unknown>>(\n definition: CustomBlockDefinition<TProps>\n): (block: Block) => BlockValidationResult {\n return (block: Block) => validateBlock(block, definition)\n}\n\n/**\n * Create a props validation function for a specific schema\n *\n * @param schema - The Zod schema\n * @returns A validation function\n */\nexport function createPropsValidator<TProps extends Record<string, unknown>>(\n schema: ZodSchema<TProps>\n): (props: unknown) => PropsValidationResult<TProps> {\n return (props: unknown) => validateProps(props, schema)\n}\n\n// ============================================================\n// ERROR FORMATTING\n// ============================================================\n\n/**\n * Format validation errors as a human-readable string\n *\n * @param errors - The validation errors\n * @returns Formatted error string\n */\nexport function formatValidationErrors(errors: readonly BlockValidationError[]): string {\n if (errors.length === 0) {\n return 'No errors'\n }\n\n return errors\n .map((e) => {\n const path = e.path ? ` at ${e.path}` : ''\n return `[${e.code}]${path}: ${e.message}`\n })\n .join('\\n')\n}\n\n/**\n * Check if a validation result has errors\n */\nexport function hasErrors(result: BlockValidationResult | PropsValidationResult): boolean {\n return !result.valid && result.errors.length > 0\n}\n\n// ============================================================\n// TYPE GUARDS\n// ============================================================\n\n/**\n * Check if a value is a valid Block\n */\nexport function isBlock(value: unknown): value is Block {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n const block = value as Partial<Block>\n return (\n typeof block.id === 'string' &&\n typeof block.type === 'string' &&\n typeof block.props === 'object'\n )\n}\n\n/**\n * Check if a value is a valid Block array\n */\nexport function isBlockArray(value: unknown): value is Block[] {\n return Array.isArray(value) && value.every(isBlock)\n}\n"]}