@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 @@
1
+ {"version":3,"file":"define-block.js","sourceRoot":"","sources":["../src/define-block.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * defineBlock API\n *\n * Create type-safe custom block definitions with Zod schemas.\n *\n * @module @pwfabric/authoring/define-block\n *\n * @example\n * ```typescript\n * import { defineBlock } from '@pwfabric/authoring'\n * import { z } from 'zod'\n *\n * export const MyBlock = defineBlock({\n * type: 'my-block',\n * name: 'My Block',\n * schema: z.object({ title: z.string() }),\n * defaultProps: { title: 'Default' },\n * component: ({ props }) => <div>{props.title}</div>\n * })\n * ```\n */\n\nimport { z, type ZodSchema, type ZodTypeAny } from 'zod'\nimport { createElement, type ReactNode } from 'react'\nimport type { CustomBlockOptions, CustomBlockDefinition, SchemaProps } from './types.js'\n\n// ============================================================\n// VALIDATION\n// ============================================================\n\n/**\n * Validate block type identifier\n *\n * Must be kebab-case, 2-50 characters, start with letter.\n */\nfunction validateBlockType(type: string): void {\n if (typeof type !== 'string') {\n throw new Error('Block type must be a string')\n }\n\n if (type.length < 2 || type.length > 50) {\n throw new Error('Block type must be 2-50 characters')\n }\n\n const kebabCaseRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/\n if (!kebabCaseRegex.test(type)) {\n throw new Error(\n `Block type \"${type}\" must be kebab-case (lowercase letters, numbers, hyphens, starting with a letter)`\n )\n }\n\n // Reserved types (core block types)\n const reservedTypes = [\n 'container',\n 'grid',\n 'text',\n 'heading',\n 'button',\n 'image',\n 'form',\n 'input',\n 'link',\n 'icon',\n 'badge',\n 'code',\n 'divider',\n 'list',\n 'table',\n 'avatar',\n 'tooltip',\n 'accordion',\n 'tabs',\n 'dropdown',\n 'modal',\n ]\n\n if (reservedTypes.includes(type)) {\n throw new Error(`Block type \"${type}\" is reserved. Use a unique type for custom blocks.`)\n }\n}\n\n/**\n * field-severity policy — `name` is a COSMETIC display label, NOT the\n * block's identity. It must never be a hard/fatal error: coerce a missing or\n * invalid name to the block `type` and warn. (`type` is the identity and stays\n * HARD — see validateBlockType.) This is the runtime SOFT half of the end-to-end\n * stitch; the authoring/install GATE applies the same policy via\n * validateBlockDefinition() below. Matches the already-tolerant render meta path\n * (installed-block-meta.descriptorToSnapshot), removing the \"valid-but-not-\n * renderable\" divergence where this throw crashed a block at render.\n */\nfunction coerceBlockName(name: unknown, type: string): string {\n if (typeof name === 'string' && name.length >= 1 && name.length <= 100) {\n return name\n }\n console.warn(\n `[pwfabric] block \"${type}\": invalid name (${typeof name}) — falling back to type. ` +\n `Names are cosmetic; fix at authoring, never fatal at render.`\n )\n return type\n}\n\n/**\n * Warn (never throw) when defaultProps don't satisfy the schema.\n * the renderer degrades, it does not crash. A mismatch is an authoring defect\n * to flag, not a render-time fatality (the registry already falls back to\n * defaultProps on a failed parse).\n */\nfunction warnOnSchemaMismatch(\n type: string,\n schema: ZodSchema<unknown>,\n defaultProps: Record<string, unknown>\n): void {\n const result = schema.safeParse(defaultProps)\n if (!result.success) {\n const errors = result.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')\n console.warn(`[pwfabric] block \"${type}\": defaultProps do not match schema (${errors}). Fix at authoring.`)\n }\n}\n\n/**\n * render-degrade fallback: a block whose `component` is missing must\n * not white-screen the canvas. Render a visible placeholder instead of throwing.\n */\nfunction placeholderComponent(type: string): () => ReactNode {\n return function MissingBlock(): ReactNode {\n return createElement(\n 'div',\n {\n 'data-pw-placeholder': type,\n style: {\n padding: '8px 12px',\n border: '1px dashed #c0392b',\n borderRadius: '6px',\n color: '#c0392b',\n font: '12px system-ui, sans-serif',\n },\n },\n `Block \"${type}\" has no component`\n )\n }\n}\n\n// ============================================================\n// DEFINE BLOCK\n// ============================================================\n\n/**\n * Define a custom block with type-safe Zod schema validation\n *\n * Creates a reusable block definition that can be registered with\n * CustomBlockRegistry and used with SurfaceRenderer.\n *\n * @param options - Block definition options\n * @returns A frozen, type-safe custom block definition\n *\n * @example\n * ```typescript\n * import { defineBlock } from '@pwfabric/authoring'\n * import { z } from 'zod'\n *\n * // Define a pricing card block\n * const PricingCard = defineBlock({\n * type: 'pricing-card',\n * name: 'Pricing Card',\n * category: 'content',\n * schema: z.object({\n * title: z.string(),\n * price: z.number(),\n * currency: z.string().default('USD'),\n * features: z.array(z.string()),\n * highlighted: z.boolean().default(false),\n * }),\n * defaultProps: {\n * title: 'Basic Plan',\n * price: 9.99,\n * currency: 'USD',\n * features: ['Feature 1', 'Feature 2'],\n * highlighted: false,\n * },\n * component: ({ props }) => (\n * <div className={props.highlighted ? 'highlighted' : ''}>\n * <h3>{props.title}</h3>\n * <p>{props.currency} {props.price}</p>\n * <ul>\n * {props.features.map((f, i) => <li key={i}>{f}</li>)}\n * </ul>\n * </div>\n * ),\n * })\n * ```\n */\nexport function defineBlock<TSchema extends ZodTypeAny>(\n options: CustomBlockOptions<TSchema>\n): CustomBlockDefinition<SchemaProps<TSchema>> {\n type TProps = SchemaProps<TSchema>\n // the renderer DEGRADES, it never throws. `type` is the identity\n // → HARD (throws). Every other required field falls back + warns so a single\n // malformed block can't white-screen the canvas. The authoring/publish gate is\n // where these are rejected early; render stays graceful.\n validateBlockType(options.type)\n const safeName = coerceBlockName(options.name, options.type)\n\n // schema — missing → passthrough (props pass through unvalidated) + warn.\n let schema = options.schema as ZodSchema<TProps> | undefined\n if (!schema) {\n console.warn(\n `[pwfabric] block \"${options.type}\": no schema — using a passthrough (props unvalidated). ` +\n `Add a schema at authoring; this is degraded render, not a fix.`\n )\n schema = z.object({}).passthrough() as unknown as ZodSchema<TProps>\n }\n\n // defaultProps — missing → {} + warn.\n let defaultProps = options.defaultProps\n if (!defaultProps) {\n console.warn(`[pwfabric] block \"${options.type}\": no defaultProps — using {}.`)\n defaultProps = {} as TProps\n }\n\n // component — missing → visible placeholder + warn (not a thrown white-screen).\n let component = options.component\n if (typeof component !== 'function') {\n console.warn(`[pwfabric] block \"${options.type}\": no component — rendering a placeholder.`)\n component = placeholderComponent(options.type) as typeof options.component\n }\n\n // Defaults vs schema: warn, never throw.\n warnOnSchemaMismatch(options.type, schema, defaultProps)\n\n // Build the definition with defaults\n const definition = {\n type: options.type,\n name: safeName,\n category: options.category ?? 'content',\n icon: options.icon ?? '◆',\n schema,\n defaultProps,\n canHaveChildren: options.canHaveChildren ?? false,\n component,\n // Only include optional properties if they're defined\n ...(options.allowedChildren !== undefined && { allowedChildren: options.allowedChildren }),\n ...(options.allowedParents !== undefined && { allowedParents: options.allowedParents }),\n ...(options.traits !== undefined && { traits: options.traits }),\n ...(options.allowedChildTraits !== undefined && {\n allowedChildTraits: options.allowedChildTraits,\n }),\n ...(options.maxInstances !== undefined && { maxInstances: options.maxInstances }),\n } as CustomBlockDefinition<TProps>\n\n // Return a frozen definition to prevent mutations\n return Object.freeze(definition)\n}\n\n// ============================================================\n// DEFINITION GATE (single-source field policy)\n// ============================================================\n\n/** A reported issue from {@link validateBlockDefinition}. */\nexport interface BlockDefinitionIssue {\n field: 'type' | 'name'\n code: string\n severity: 'error' | 'warning'\n message: string\n}\n\n/**\n * Single source of truth for block-definition field severity, used by\n * the AUTHORING / INSTALL gate (packaging, manifest validation, host\n * install). Non-throwing reporter — the SAME stitch defineBlock applies at\n * runtime, surfaced as structured issues so callers can REJECT identity\n * problems (`type`, severity=error) and merely WARN on cosmetic ones (`name`,\n * severity=warning). Keeping this the one place the policy lives is what makes\n * the treatment identical end-to-end (authoring → install → runtime → render).\n */\nexport function validateBlockDefinition(options: { type?: unknown; name?: unknown }): BlockDefinitionIssue[] {\n const issues: BlockDefinitionIssue[] = []\n // type — block IDENTITY → HARD (error). Mirrors validateBlockType's rules.\n const t = options.type\n if (typeof t !== 'string') {\n issues.push({ field: 'type', code: 'BLOCK_TYPE_NOT_STRING', severity: 'error', message: 'Block type must be a string' })\n } else if (t.length < 2 || t.length > 50) {\n issues.push({ field: 'type', code: 'BLOCK_TYPE_LENGTH', severity: 'error', message: 'Block type must be 2-50 characters' })\n } else if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(t)) {\n issues.push({ field: 'type', code: 'BLOCK_TYPE_FORMAT', severity: 'error', message: `Block type \"${t}\" must be kebab-case` })\n }\n // name — cosmetic label → SOFT (warning). Never blocks; coerced to type at runtime.\n const n = options.name\n if (typeof n !== 'string' || n.length < 1 || n.length > 100) {\n issues.push({ field: 'name', code: 'BLOCK_NAME_INVALID', severity: 'warning', message: 'Block name should be a 1-100 char string; falls back to type' })\n }\n return issues\n}\n\n// ============================================================\n// UTILITY FUNCTIONS\n// ============================================================\n\n/**\n * Check if a value is a custom block definition\n */\nexport function isCustomBlockDefinition(value: unknown): value is CustomBlockDefinition {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n const def = value as Partial<CustomBlockDefinition>\n return (\n typeof def.type === 'string' &&\n typeof def.name === 'string' &&\n typeof def.category === 'string' &&\n typeof def.schema === 'object' &&\n typeof def.defaultProps === 'object' &&\n typeof def.component === 'function'\n )\n}\n\n/**\n * Create a block instance from a custom block definition\n *\n * @param definition - The block definition\n * @param id - Unique block ID\n * @param props - Optional props override (merged with defaults)\n * @param children - Optional children blocks\n */\nexport function createBlockInstance<TProps extends Record<string, unknown>>(\n definition: CustomBlockDefinition<TProps>,\n id: string,\n props?: Partial<TProps>,\n children?: readonly { id: string; type: string; props: Record<string, unknown> }[]\n): { id: string; type: string; props: TProps; children?: typeof children } {\n const mergedProps = {\n ...definition.defaultProps,\n ...props,\n } as TProps\n\n // Validate merged props\n const result = definition.schema.safeParse(mergedProps)\n if (!result.success) {\n const errors = result.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')\n throw new Error(`Invalid block props: ${errors}`)\n }\n\n const instance: { id: string; type: string; props: TProps; children?: typeof children } = {\n id,\n type: definition.type,\n props: result.data as TProps,\n }\n\n if (children && children.length > 0) {\n if (!definition.canHaveChildren) {\n throw new Error(`Block type \"${definition.type}\" cannot have children`)\n }\n instance.children = children\n }\n\n return instance\n}\n"]}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * defineFactory API
3
+ *
4
+ * Create factory functions that generate block compositions.
5
+ *
6
+ * @module @pwfabric/authoring/define-factory
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { defineFactory } from '@pwfabric/authoring'
11
+ * import { z } from 'zod'
12
+ *
13
+ * const MyPageFactory = defineFactory({
14
+ * id: 'my-page',
15
+ * name: 'My Page Factory',
16
+ * configSchema: z.object({
17
+ * title: z.string(),
18
+ * subtitle: z.string().optional(),
19
+ * }),
20
+ * build: (config) => [
21
+ * { type: 'heading', props: { text: config.title, level: 'h1' } },
22
+ * { type: 'text', props: { content: config.subtitle ?? '' } },
23
+ * ]
24
+ * })
25
+ * ```
26
+ */
27
+ import type { Block } from '@pwfabric/core';
28
+ import type { FactoryOptions, FactoryDefinition, FactoryConfig } from './types.js';
29
+ /**
30
+ * Reset the ID counter (useful for testing)
31
+ */
32
+ export declare function resetIdCounter(): void;
33
+ /**
34
+ * Define a factory function that generates block compositions
35
+ *
36
+ * Factories allow you to create reusable block patterns that can be
37
+ * customized with configuration. They automatically generate unique
38
+ * block IDs and validate configuration against an optional schema.
39
+ *
40
+ * @param options - Factory definition options
41
+ * @returns A frozen factory definition
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { defineFactory } from '@pwfabric/authoring'
46
+ * import { z } from 'zod'
47
+ *
48
+ * // Define a pricing section factory
49
+ * const PricingSectionFactory = defineFactory({
50
+ * id: 'pricing-section',
51
+ * name: 'Pricing Section',
52
+ * description: 'Create a pricing section with multiple tiers',
53
+ * configSchema: z.object({
54
+ * title: z.string(),
55
+ * tiers: z.array(z.object({
56
+ * name: z.string(),
57
+ * price: z.number(),
58
+ * features: z.array(z.string()),
59
+ * })),
60
+ * }),
61
+ * build: (config) => [
62
+ * {
63
+ * type: 'container',
64
+ * props: { direction: 'column', gap: 32 },
65
+ * children: [
66
+ * { type: 'heading', props: { text: config.title, level: 'h2' } },
67
+ * {
68
+ * type: 'grid',
69
+ * props: { columns: config.tiers.length },
70
+ * children: config.tiers.map((tier) => ({
71
+ * type: 'pricing-card',
72
+ * props: {
73
+ * title: tier.name,
74
+ * price: tier.price,
75
+ * features: tier.features,
76
+ * },
77
+ * })),
78
+ * },
79
+ * ],
80
+ * },
81
+ * ],
82
+ * })
83
+ *
84
+ * // Use the factory
85
+ * const blocks = PricingSectionFactory.build({
86
+ * title: 'Choose Your Plan',
87
+ * tiers: [
88
+ * { name: 'Basic', price: 9, features: ['Feature 1'] },
89
+ * { name: 'Pro', price: 29, features: ['Feature 1', 'Feature 2'] },
90
+ * ],
91
+ * })
92
+ * ```
93
+ */
94
+ export declare function defineFactory<TConfig extends FactoryConfig = FactoryConfig>(options: FactoryOptions<TConfig>): FactoryDefinition<TConfig>;
95
+ /**
96
+ * Check if a value is a factory definition
97
+ */
98
+ export declare function isFactoryDefinition(value: unknown): value is FactoryDefinition;
99
+ /**
100
+ * Compose multiple factory outputs into a single block array
101
+ *
102
+ * @param factories - Array of factory output block arrays
103
+ * @returns Combined block array
104
+ */
105
+ export declare function composeFactoryOutputs(...factories: readonly (readonly Block[])[]): readonly Block[];
106
+ /**
107
+ * Wrap factory output in a container block
108
+ *
109
+ * @param blocks - Factory output blocks
110
+ * @param containerProps - Props for the wrapper container
111
+ * @returns Single container block with children
112
+ */
113
+ export declare function wrapInContainer(blocks: readonly Block[], containerProps?: Record<string, unknown>): Block;
@@ -0,0 +1,247 @@
1
+ /**
2
+ * defineFactory API
3
+ *
4
+ * Create factory functions that generate block compositions.
5
+ *
6
+ * @module @pwfabric/authoring/define-factory
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { defineFactory } from '@pwfabric/authoring'
11
+ * import { z } from 'zod'
12
+ *
13
+ * const MyPageFactory = defineFactory({
14
+ * id: 'my-page',
15
+ * name: 'My Page Factory',
16
+ * configSchema: z.object({
17
+ * title: z.string(),
18
+ * subtitle: z.string().optional(),
19
+ * }),
20
+ * build: (config) => [
21
+ * { type: 'heading', props: { text: config.title, level: 'h1' } },
22
+ * { type: 'text', props: { content: config.subtitle ?? '' } },
23
+ * ]
24
+ * })
25
+ * ```
26
+ */
27
+ import { DEFAULT_BLOCK_LAYOUT } from '@pwfabric/core';
28
+ import { blockRegistry } from '@pwfabric/runtime/blocks';
29
+ // ============================================================
30
+ // ID GENERATION
31
+ // ============================================================
32
+ let idCounter = 0;
33
+ /**
34
+ * Generate a unique block ID
35
+ */
36
+ function generateBlockId(type) {
37
+ idCounter += 1;
38
+ return `${type}-${idCounter}-${Date.now().toString(36)}`;
39
+ }
40
+ /**
41
+ * Reset the ID counter (useful for testing)
42
+ */
43
+ export function resetIdCounter() {
44
+ idCounter = 0;
45
+ }
46
+ // ============================================================
47
+ // VALIDATION
48
+ // ============================================================
49
+ /**
50
+ * Validate factory identifier
51
+ */
52
+ function validateFactoryId(id) {
53
+ if (typeof id !== 'string') {
54
+ throw new Error('Factory id must be a string');
55
+ }
56
+ if (id.length < 2 || id.length > 50) {
57
+ throw new Error('Factory id must be 2-50 characters');
58
+ }
59
+ const kebabCaseRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
60
+ if (!kebabCaseRegex.test(id)) {
61
+ throw new Error(`Factory id "${id}" must be kebab-case (lowercase letters, numbers, hyphens, starting with a letter)`);
62
+ }
63
+ }
64
+ /**
65
+ * Validate factory config against schema
66
+ */
67
+ function validateConfig(config, schema) {
68
+ if (!schema) {
69
+ // No schema, accept any config
70
+ return {
71
+ valid: true,
72
+ config: config,
73
+ };
74
+ }
75
+ const result = schema.safeParse(config);
76
+ if (result.success) {
77
+ return {
78
+ valid: true,
79
+ config: result.data,
80
+ };
81
+ }
82
+ const errors = result.error.errors.map((e) => ({
83
+ path: e.path.join('.'),
84
+ message: e.message,
85
+ }));
86
+ return {
87
+ valid: false,
88
+ errors,
89
+ };
90
+ }
91
+ // ============================================================
92
+ // BLOCK SPEC TO BLOCK
93
+ // ============================================================
94
+ /**
95
+ * Convert a BlockSpec (without id) to a Block (with id)
96
+ */
97
+ function specToBlock(spec) {
98
+ const block = {
99
+ id: generateBlockId(spec.type),
100
+ type: spec.type,
101
+ props: spec.props,
102
+ layout: blockRegistry.get(spec.type)?.defaultLayout ?? DEFAULT_BLOCK_LAYOUT,
103
+ };
104
+ if (spec.children && spec.children.length > 0) {
105
+ return {
106
+ ...block,
107
+ children: spec.children.map(specToBlock),
108
+ };
109
+ }
110
+ return block;
111
+ }
112
+ // ============================================================
113
+ // DEFINE FACTORY
114
+ // ============================================================
115
+ /**
116
+ * Define a factory function that generates block compositions
117
+ *
118
+ * Factories allow you to create reusable block patterns that can be
119
+ * customized with configuration. They automatically generate unique
120
+ * block IDs and validate configuration against an optional schema.
121
+ *
122
+ * @param options - Factory definition options
123
+ * @returns A frozen factory definition
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import { defineFactory } from '@pwfabric/authoring'
128
+ * import { z } from 'zod'
129
+ *
130
+ * // Define a pricing section factory
131
+ * const PricingSectionFactory = defineFactory({
132
+ * id: 'pricing-section',
133
+ * name: 'Pricing Section',
134
+ * description: 'Create a pricing section with multiple tiers',
135
+ * configSchema: z.object({
136
+ * title: z.string(),
137
+ * tiers: z.array(z.object({
138
+ * name: z.string(),
139
+ * price: z.number(),
140
+ * features: z.array(z.string()),
141
+ * })),
142
+ * }),
143
+ * build: (config) => [
144
+ * {
145
+ * type: 'container',
146
+ * props: { direction: 'column', gap: 32 },
147
+ * children: [
148
+ * { type: 'heading', props: { text: config.title, level: 'h2' } },
149
+ * {
150
+ * type: 'grid',
151
+ * props: { columns: config.tiers.length },
152
+ * children: config.tiers.map((tier) => ({
153
+ * type: 'pricing-card',
154
+ * props: {
155
+ * title: tier.name,
156
+ * price: tier.price,
157
+ * features: tier.features,
158
+ * },
159
+ * })),
160
+ * },
161
+ * ],
162
+ * },
163
+ * ],
164
+ * })
165
+ *
166
+ * // Use the factory
167
+ * const blocks = PricingSectionFactory.build({
168
+ * title: 'Choose Your Plan',
169
+ * tiers: [
170
+ * { name: 'Basic', price: 9, features: ['Feature 1'] },
171
+ * { name: 'Pro', price: 29, features: ['Feature 1', 'Feature 2'] },
172
+ * ],
173
+ * })
174
+ * ```
175
+ */
176
+ export function defineFactory(options) {
177
+ // Validate required fields
178
+ validateFactoryId(options.id);
179
+ if (typeof options.build !== 'function') {
180
+ throw new Error('Factory build function is required');
181
+ }
182
+ // Create the build wrapper that converts specs to blocks
183
+ const buildWithIds = (config) => {
184
+ const specs = options.build(config);
185
+ return specs.map(specToBlock);
186
+ };
187
+ // Create the validation function
188
+ const validateConfigFn = (config) => {
189
+ return validateConfig(config, options.configSchema);
190
+ };
191
+ const definition = {
192
+ id: options.id,
193
+ name: options.name ?? options.id,
194
+ description: options.description ?? '',
195
+ build: buildWithIds,
196
+ validateConfig: validateConfigFn,
197
+ // Only include configSchema if it's defined
198
+ ...(options.configSchema !== undefined && { configSchema: options.configSchema }),
199
+ };
200
+ return Object.freeze(definition);
201
+ }
202
+ // ============================================================
203
+ // UTILITY FUNCTIONS
204
+ // ============================================================
205
+ /**
206
+ * Check if a value is a factory definition
207
+ */
208
+ export function isFactoryDefinition(value) {
209
+ if (!value || typeof value !== 'object') {
210
+ return false;
211
+ }
212
+ const def = value;
213
+ return (typeof def.id === 'string' &&
214
+ typeof def.name === 'string' &&
215
+ typeof def.build === 'function' &&
216
+ typeof def.validateConfig === 'function');
217
+ }
218
+ /**
219
+ * Compose multiple factory outputs into a single block array
220
+ *
221
+ * @param factories - Array of factory output block arrays
222
+ * @returns Combined block array
223
+ */
224
+ export function composeFactoryOutputs(...factories) {
225
+ return factories.flat();
226
+ }
227
+ /**
228
+ * Wrap factory output in a container block
229
+ *
230
+ * @param blocks - Factory output blocks
231
+ * @param containerProps - Props for the wrapper container
232
+ * @returns Single container block with children
233
+ */
234
+ export function wrapInContainer(blocks, containerProps = {}) {
235
+ return {
236
+ id: generateBlockId('container'),
237
+ type: 'container',
238
+ props: {
239
+ direction: 'column',
240
+ gap: 24,
241
+ ...containerProps,
242
+ },
243
+ layout: blockRegistry.get('container')?.defaultLayout ?? DEFAULT_BLOCK_LAYOUT,
244
+ children: [...blocks],
245
+ };
246
+ }
247
+ //# sourceMappingURL=define-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-factory.js","sourceRoot":"","sources":["../src/define-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAUxD,+DAA+D;AAC/D,gBAAgB;AAChB,+DAA+D;AAE/D,IAAI,SAAS,GAAG,CAAC,CAAA;AAEjB;;GAEG;AACH,SAAS,eAAe,CAAC,IAAY;IACnC,SAAS,IAAI,CAAC,CAAA;IACd,OAAO,GAAG,IAAI,IAAI,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAA;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,SAAS,GAAG,CAAC,CAAA;AACf,CAAC;AAED,+DAA+D;AAC/D,aAAa;AACb,+DAA+D;AAE/D;;GAEG;AACH,SAAS,iBAAiB,CAAC,EAAU;IACnC,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IAChD,CAAC;IAED,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACvD,CAAC;IAED,MAAM,cAAc,GAAG,+BAA+B,CAAA;IACtD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,eAAe,EAAE,oFAAoF,CACtG,CAAA;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CACrB,MAAe,EACf,MAA2B;IAE3B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,+BAA+B;QAC/B,OAAO;YACL,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,MAAiB;SAC1B,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACvC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO;YACL,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,MAAM,CAAC,IAAI;SACpB,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAA6B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QACtB,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,MAAM;KACP,CAAA;AACH,CAAC;AAED,+DAA+D;AAC/D,sBAAsB;AACtB,+DAA+D;AAE/D;;GAEG;AACH,SAAS,WAAW,CAAC,IAAe;IAClC,MAAM,KAAK,GAAU;QACnB,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAC9B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,aAAa,IAAI,oBAAoB;KAC5E,CAAA;IAED,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO;YACL,GAAG,KAAK;YACR,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC;SACzC,CAAA;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,+DAA+D;AAC/D,iBAAiB;AACjB,+DAA+D;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAgC;IAEhC,2BAA2B;IAC3B,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAE7B,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACvD,CAAC;IAED,yDAAyD;IACzD,MAAM,YAAY,GAAG,CAAC,MAAe,EAAoB,EAAE;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACnC,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC/B,CAAC,CAAA;IAED,iCAAiC;IACjC,MAAM,gBAAgB,GAAG,CAAC,MAAe,EAAoC,EAAE;QAC7E,OAAO,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;IACrD,CAAC,CAAA;IAED,MAAM,UAAU,GAAG;QACjB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE;QAChC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,EAAE;QACtC,KAAK,EAAE,YAAY;QACnB,cAAc,EAAE,gBAAgB;QAChC,4CAA4C;QAC5C,GAAG,CAAC,OAAO,CAAC,YAAY,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;KACpD,CAAA;IAE/B,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;AAClC,CAAC;AAED,+DAA+D;AAC/D,oBAAoB;AACpB,+DAA+D;AAE/D;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,GAAG,GAAG,KAAmC,CAAA;IAC/C,OAAO,CACL,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAC1B,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;QAC5B,OAAO,GAAG,CAAC,KAAK,KAAK,UAAU;QAC/B,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CACzC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAG,SAAwC;IAE3C,OAAO,SAAS,CAAC,IAAI,EAAE,CAAA;AACzB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAwB,EACxB,iBAA0C,EAAE;IAE5C,OAAO;QACL,EAAE,EAAE,eAAe,CAAC,WAAW,CAAC;QAChC,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE;YACL,SAAS,EAAE,QAAQ;YACnB,GAAG,EAAE,EAAE;YACP,GAAG,cAAc;SAClB;QACD,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,aAAa,IAAI,oBAAoB;QAC7E,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC;KACtB,CAAA;AACH,CAAC","sourcesContent":["/**\n * defineFactory API\n *\n * Create factory functions that generate block compositions.\n *\n * @module @pwfabric/authoring/define-factory\n *\n * @example\n * ```typescript\n * import { defineFactory } from '@pwfabric/authoring'\n * import { z } from 'zod'\n *\n * const MyPageFactory = defineFactory({\n * id: 'my-page',\n * name: 'My Page Factory',\n * configSchema: z.object({\n * title: z.string(),\n * subtitle: z.string().optional(),\n * }),\n * build: (config) => [\n * { type: 'heading', props: { text: config.title, level: 'h1' } },\n * { type: 'text', props: { content: config.subtitle ?? '' } },\n * ]\n * })\n * ```\n */\n\nimport type { ZodSchema } from 'zod'\nimport type { Block } from '@pwfabric/core'\nimport { DEFAULT_BLOCK_LAYOUT } from '@pwfabric/core'\nimport { blockRegistry } from '@pwfabric/runtime/blocks'\nimport type {\n FactoryOptions,\n FactoryDefinition,\n FactoryConfig,\n FactoryValidationResult,\n FactoryValidationError,\n BlockSpec,\n} from './types.js'\n\n// ============================================================\n// ID GENERATION\n// ============================================================\n\nlet idCounter = 0\n\n/**\n * Generate a unique block ID\n */\nfunction generateBlockId(type: string): string {\n idCounter += 1\n return `${type}-${idCounter}-${Date.now().toString(36)}`\n}\n\n/**\n * Reset the ID counter (useful for testing)\n */\nexport function resetIdCounter(): void {\n idCounter = 0\n}\n\n// ============================================================\n// VALIDATION\n// ============================================================\n\n/**\n * Validate factory identifier\n */\nfunction validateFactoryId(id: string): void {\n if (typeof id !== 'string') {\n throw new Error('Factory id must be a string')\n }\n\n if (id.length < 2 || id.length > 50) {\n throw new Error('Factory id must be 2-50 characters')\n }\n\n const kebabCaseRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/\n if (!kebabCaseRegex.test(id)) {\n throw new Error(\n `Factory id \"${id}\" must be kebab-case (lowercase letters, numbers, hyphens, starting with a letter)`\n )\n }\n}\n\n/**\n * Validate factory config against schema\n */\nfunction validateConfig<TConfig extends FactoryConfig>(\n config: unknown,\n schema?: ZodSchema<TConfig>\n): FactoryValidationResult<TConfig> {\n if (!schema) {\n // No schema, accept any config\n return {\n valid: true,\n config: config as TConfig,\n }\n }\n\n const result = schema.safeParse(config)\n if (result.success) {\n return {\n valid: true,\n config: result.data,\n }\n }\n\n const errors: FactoryValidationError[] = result.error.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n }))\n\n return {\n valid: false,\n errors,\n }\n}\n\n// ============================================================\n// BLOCK SPEC TO BLOCK\n// ============================================================\n\n/**\n * Convert a BlockSpec (without id) to a Block (with id)\n */\nfunction specToBlock(spec: BlockSpec): Block {\n const block: Block = {\n id: generateBlockId(spec.type),\n type: spec.type,\n props: spec.props,\n layout: blockRegistry.get(spec.type)?.defaultLayout ?? DEFAULT_BLOCK_LAYOUT,\n }\n\n if (spec.children && spec.children.length > 0) {\n return {\n ...block,\n children: spec.children.map(specToBlock),\n }\n }\n\n return block\n}\n\n// ============================================================\n// DEFINE FACTORY\n// ============================================================\n\n/**\n * Define a factory function that generates block compositions\n *\n * Factories allow you to create reusable block patterns that can be\n * customized with configuration. They automatically generate unique\n * block IDs and validate configuration against an optional schema.\n *\n * @param options - Factory definition options\n * @returns A frozen factory definition\n *\n * @example\n * ```typescript\n * import { defineFactory } from '@pwfabric/authoring'\n * import { z } from 'zod'\n *\n * // Define a pricing section factory\n * const PricingSectionFactory = defineFactory({\n * id: 'pricing-section',\n * name: 'Pricing Section',\n * description: 'Create a pricing section with multiple tiers',\n * configSchema: z.object({\n * title: z.string(),\n * tiers: z.array(z.object({\n * name: z.string(),\n * price: z.number(),\n * features: z.array(z.string()),\n * })),\n * }),\n * build: (config) => [\n * {\n * type: 'container',\n * props: { direction: 'column', gap: 32 },\n * children: [\n * { type: 'heading', props: { text: config.title, level: 'h2' } },\n * {\n * type: 'grid',\n * props: { columns: config.tiers.length },\n * children: config.tiers.map((tier) => ({\n * type: 'pricing-card',\n * props: {\n * title: tier.name,\n * price: tier.price,\n * features: tier.features,\n * },\n * })),\n * },\n * ],\n * },\n * ],\n * })\n *\n * // Use the factory\n * const blocks = PricingSectionFactory.build({\n * title: 'Choose Your Plan',\n * tiers: [\n * { name: 'Basic', price: 9, features: ['Feature 1'] },\n * { name: 'Pro', price: 29, features: ['Feature 1', 'Feature 2'] },\n * ],\n * })\n * ```\n */\nexport function defineFactory<TConfig extends FactoryConfig = FactoryConfig>(\n options: FactoryOptions<TConfig>\n): FactoryDefinition<TConfig> {\n // Validate required fields\n validateFactoryId(options.id)\n\n if (typeof options.build !== 'function') {\n throw new Error('Factory build function is required')\n }\n\n // Create the build wrapper that converts specs to blocks\n const buildWithIds = (config: TConfig): readonly Block[] => {\n const specs = options.build(config)\n return specs.map(specToBlock)\n }\n\n // Create the validation function\n const validateConfigFn = (config: unknown): FactoryValidationResult<TConfig> => {\n return validateConfig(config, options.configSchema)\n }\n\n const definition = {\n id: options.id,\n name: options.name ?? options.id,\n description: options.description ?? '',\n build: buildWithIds,\n validateConfig: validateConfigFn,\n // Only include configSchema if it's defined\n ...(options.configSchema !== undefined && { configSchema: options.configSchema }),\n } as FactoryDefinition<TConfig>\n\n return Object.freeze(definition)\n}\n\n// ============================================================\n// UTILITY FUNCTIONS\n// ============================================================\n\n/**\n * Check if a value is a factory definition\n */\nexport function isFactoryDefinition(value: unknown): value is FactoryDefinition {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n const def = value as Partial<FactoryDefinition>\n return (\n typeof def.id === 'string' &&\n typeof def.name === 'string' &&\n typeof def.build === 'function' &&\n typeof def.validateConfig === 'function'\n )\n}\n\n/**\n * Compose multiple factory outputs into a single block array\n *\n * @param factories - Array of factory output block arrays\n * @returns Combined block array\n */\nexport function composeFactoryOutputs(\n ...factories: readonly (readonly Block[])[]\n): readonly Block[] {\n return factories.flat()\n}\n\n/**\n * Wrap factory output in a container block\n *\n * @param blocks - Factory output blocks\n * @param containerProps - Props for the wrapper container\n * @returns Single container block with children\n */\nexport function wrapInContainer(\n blocks: readonly Block[],\n containerProps: Record<string, unknown> = {}\n): Block {\n return {\n id: generateBlockId('container'),\n type: 'container',\n props: {\n direction: 'column',\n gap: 24,\n ...containerProps,\n },\n layout: blockRegistry.get('container')?.defaultLayout ?? DEFAULT_BLOCK_LAYOUT,\n children: [...blocks],\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @pwfabric/authoring
3
+ *
4
+ * The surface a pack author writes against: define a block, compose a factory,
5
+ * declare a capability, register and validate what you built.
6
+ *
7
+ * It holds no HTTP client: talking to a hosting service is that service's
8
+ * own package. The scaffolding templates in the authoring tooling emit
9
+ * `import { defineBlock } from ...`, so this is the module name a third-party
10
+ * author reads first.
11
+ *
12
+ * @module @pwfabric/authoring
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { defineBlock, createCustomBlockRegistry } from '@pwfabric/authoring'
17
+ * import { z } from 'zod'
18
+ *
19
+ * const MyBlock = defineBlock({
20
+ * type: 'my-block',
21
+ * name: 'My Block',
22
+ * schema: z.object({ title: z.string() }),
23
+ * defaultProps: { title: 'Default' },
24
+ * component: ({ props }) => <div>{props.title}</div>
25
+ * })
26
+ *
27
+ * const registry = createCustomBlockRegistry()
28
+ * registry.register(MyBlock)
29
+ * ```
30
+ */
31
+ export * from '@pwfabric/runtime';
32
+ export type { CustomBlockOptions, SchemaProps, CustomBlockComponentProps, CustomBlockComponent, CustomBlockContext, CustomBlockDefinition, FactoryConfig, BlockSpec, FactoryOptions, FactoryDefinition, FactoryValidationResult, FactoryValidationError, ICustomBlockRegistry, BlockValidationResult, BlockValidationError, PropsValidationResult, InferBlockProps, InferFactoryConfig, InferSchemaProps, } from './types.js';
33
+ export { defineBlock, isCustomBlockDefinition, createBlockInstance } from './define-block.js';
34
+ export { validateBlockDefinition } from './define-block.js';
35
+ export type { BlockDefinitionIssue } from './define-block.js';
36
+ export { defineFactory, isFactoryDefinition, composeFactoryOutputs, wrapInContainer, resetIdCounter, } from './define-factory.js';
37
+ export { CapabilityBuilder, defineCapability } from '@pwfabric/contracts';
38
+ export { CustomBlockRegistry, createCustomBlockRegistry, getCustomBlockRegistry, resetGlobalRegistry, mergeRegistries, mergeWithBaseComponents, } from './registry.js';
39
+ export { validateProps, validatePropsWithDefaults, validateBlock, validateBlockTree, createBlockValidator, createPropsValidator, formatValidationErrors, hasErrors, isBlock, isBlockArray, } from './validation.js';
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @pwfabric/authoring
3
+ *
4
+ * The surface a pack author writes against: define a block, compose a factory,
5
+ * declare a capability, register and validate what you built.
6
+ *
7
+ * It holds no HTTP client: talking to a hosting service is that service's
8
+ * own package. The scaffolding templates in the authoring tooling emit
9
+ * `import { defineBlock } from ...`, so this is the module name a third-party
10
+ * author reads first.
11
+ *
12
+ * @module @pwfabric/authoring
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { defineBlock, createCustomBlockRegistry } from '@pwfabric/authoring'
17
+ * import { z } from 'zod'
18
+ *
19
+ * const MyBlock = defineBlock({
20
+ * type: 'my-block',
21
+ * name: 'My Block',
22
+ * schema: z.object({ title: z.string() }),
23
+ * defaultProps: { title: 'Default' },
24
+ * component: ({ props }) => <div>{props.title}</div>
25
+ * })
26
+ *
27
+ * const registry = createCustomBlockRegistry()
28
+ * registry.register(MyBlock)
29
+ * ```
30
+ */
31
+ // Re-export runtime (which includes core and contracts)
32
+ export * from '@pwfabric/runtime';
33
+ // ============================================================
34
+ // DEFINE BLOCK API
35
+ // ============================================================
36
+ export { defineBlock, isCustomBlockDefinition, createBlockInstance } from './define-block.js';
37
+ // single-source field-severity gate for authoring/install.
38
+ export { validateBlockDefinition } from './define-block.js';
39
+ // ============================================================
40
+ // DEFINE FACTORY API
41
+ // ============================================================
42
+ export { defineFactory, isFactoryDefinition, composeFactoryOutputs, wrapInContainer, resetIdCounter, } from './define-factory.js';
43
+ // ============================================================
44
+ // DEFINE CAPABILITY API
45
+ // ============================================================
46
+ export { CapabilityBuilder, defineCapability } from '@pwfabric/contracts';
47
+ // ============================================================
48
+ // REGISTRY API
49
+ // ============================================================
50
+ export { CustomBlockRegistry, createCustomBlockRegistry, getCustomBlockRegistry, resetGlobalRegistry, mergeRegistries, mergeWithBaseComponents, } from './registry.js';
51
+ // ============================================================
52
+ // VALIDATION API
53
+ // ============================================================
54
+ export { validateProps, validatePropsWithDefaults, validateBlock, validateBlockTree, createBlockValidator, createPropsValidator, formatValidationErrors, hasErrors, isBlock, isBlockArray, } from './validation.js';
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,wDAAwD;AACxD,cAAc,mBAAmB,CAAA;AAqCjC,+DAA+D;AAC/D,mBAAmB;AACnB,+DAA+D;AAE/D,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC7F,2DAA2D;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAG3D,+DAA+D;AAC/D,qBAAqB;AACrB,+DAA+D;AAE/D,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,cAAc,GACf,MAAM,qBAAqB,CAAA;AAE5B,+DAA+D;AAC/D,wBAAwB;AACxB,+DAA+D;AAE/D,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEzE,+DAA+D;AAC/D,eAAe;AACf,+DAA+D;AAE/D,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,uBAAuB,GACxB,MAAM,eAAe,CAAA;AAEtB,+DAA+D;AAC/D,iBAAiB;AACjB,+DAA+D;AAE/D,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,SAAS,EACT,OAAO,EACP,YAAY,GACb,MAAM,iBAAiB,CAAA","sourcesContent":["/**\n * @pwfabric/authoring\n *\n * The surface a pack author writes against: define a block, compose a factory,\n * declare a capability, register and validate what you built.\n *\n * It holds no HTTP client: talking to a hosting service is that service's\n * own package. The scaffolding templates in the authoring tooling emit\n * `import { defineBlock } from ...`, so this is the module name a third-party\n * author reads first.\n *\n * @module @pwfabric/authoring\n *\n * @example\n * ```typescript\n * import { defineBlock, createCustomBlockRegistry } from '@pwfabric/authoring'\n * import { z } from 'zod'\n *\n * const MyBlock = defineBlock({\n * type: 'my-block',\n * name: 'My Block',\n * schema: z.object({ title: z.string() }),\n * defaultProps: { title: 'Default' },\n * component: ({ props }) => <div>{props.title}</div>\n * })\n *\n * const registry = createCustomBlockRegistry()\n * registry.register(MyBlock)\n * ```\n */\n\n// Re-export runtime (which includes core and contracts)\nexport * from '@pwfabric/runtime'\n\n// ============================================================\n// TYPES\n// ============================================================\n\nexport type {\n // Custom block types\n CustomBlockOptions,\n SchemaProps,\n CustomBlockComponentProps,\n CustomBlockComponent,\n CustomBlockContext,\n CustomBlockDefinition,\n\n // Factory types\n FactoryConfig,\n BlockSpec,\n FactoryOptions,\n FactoryDefinition,\n FactoryValidationResult,\n FactoryValidationError,\n\n // Registry types\n ICustomBlockRegistry,\n\n // Validation types\n BlockValidationResult,\n BlockValidationError,\n PropsValidationResult,\n\n // Utility types\n InferBlockProps,\n InferFactoryConfig,\n InferSchemaProps,\n} from './types.js'\n\n// ============================================================\n// DEFINE BLOCK API\n// ============================================================\n\nexport { defineBlock, isCustomBlockDefinition, createBlockInstance } from './define-block.js'\n// single-source field-severity gate for authoring/install.\nexport { validateBlockDefinition } from './define-block.js'\nexport type { BlockDefinitionIssue } from './define-block.js'\n\n// ============================================================\n// DEFINE FACTORY API\n// ============================================================\n\nexport {\n defineFactory,\n isFactoryDefinition,\n composeFactoryOutputs,\n wrapInContainer,\n resetIdCounter,\n} from './define-factory.js'\n\n// ============================================================\n// DEFINE CAPABILITY API\n// ============================================================\n\nexport { CapabilityBuilder, defineCapability } from '@pwfabric/contracts'\n\n// ============================================================\n// REGISTRY API\n// ============================================================\n\nexport {\n CustomBlockRegistry,\n createCustomBlockRegistry,\n getCustomBlockRegistry,\n resetGlobalRegistry,\n mergeRegistries,\n mergeWithBaseComponents,\n} from './registry.js'\n\n// ============================================================\n// VALIDATION API\n// ============================================================\n\nexport {\n validateProps,\n validatePropsWithDefaults,\n validateBlock,\n validateBlockTree,\n createBlockValidator,\n createPropsValidator,\n formatValidationErrors,\n hasErrors,\n isBlock,\n isBlockArray,\n} from './validation.js'\n"]}