@duckpic/content-spec 0.2.2 → 0.2.4

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.
@@ -1,4 +1,23 @@
1
- import type { ZodType } from "zod";
1
+ import type { ZodType, ZodTypeAny } from "zod";
2
+ export interface ComponentIoChannelSpec {
3
+ /**
4
+ * Optional Zod schema to describe the runtime value shape for this channel.
5
+ * This is metadata for wiring / tooling; not validated at authoring time.
6
+ */
7
+ schema?: ZodTypeAny;
8
+ /**
9
+ * Human-friendly type name (e.g. "VocabId[]") the editor can display.
10
+ */
11
+ valueType?: string;
12
+ /**
13
+ * Optional description to surface in UI hints.
14
+ */
15
+ description?: string;
16
+ }
17
+ export interface ComponentIoSpec {
18
+ inputs?: Record<string, ComponentIoChannelSpec>;
19
+ outputs?: Record<string, ComponentIoChannelSpec>;
20
+ }
2
21
  export interface ComponentUiMetadata {
3
22
  /**
4
23
  * Human-friendly label for menus and UI surfaces.
@@ -21,5 +40,9 @@ export interface ComponentSpec {
21
40
  type: string;
22
41
  propsSchema: ZodType<any>;
23
42
  slots: string[];
43
+ /**
44
+ * Optional runtime input/output channels for dynamic data wiring.
45
+ */
46
+ io?: ComponentIoSpec;
24
47
  ui?: ComponentUiMetadata;
25
48
  }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ import type { ComponentSpec } from "./ComponentSpec";
3
+ export declare const VocabularyListPropsSchema: z.ZodObject<{
4
+ title: z.ZodOptional<z.ZodString>;
5
+ staticVocabIds: z.ZodArray<z.ZodNumber>;
6
+ }, z.core.$strip>;
7
+ export declare const VocabularyListComponentSpec: ComponentSpec;
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ export const VocabularyListPropsSchema = z.object({
3
+ title: z.string().optional(),
4
+ staticVocabIds: z.array(z.number())
5
+ });
6
+ export const VocabularyListComponentSpec = {
7
+ type: "VocabularyList",
8
+ propsSchema: VocabularyListPropsSchema,
9
+ slots: [],
10
+ io: {
11
+ inputs: {
12
+ extraVocabIds: {
13
+ schema: z.array(z.number()),
14
+ valueType: "VocabId[]",
15
+ description: "Additional vocab ids injected at runtime (e.g. incorrect answers)."
16
+ }
17
+ },
18
+ outputs: {
19
+ selectedVocabIds: {
20
+ schema: z.array(z.number()),
21
+ valueType: "VocabId[]",
22
+ description: "Ids the student marked or selected in this list."
23
+ }
24
+ }
25
+ },
26
+ ui: {
27
+ label: "Vocabulary list",
28
+ insertable: true,
29
+ insertMenuOrder: 50,
30
+ }
31
+ };
package/dist/index.d.ts CHANGED
@@ -4,8 +4,9 @@ export * from "./components/Layout";
4
4
  export * from "./components/Page";
5
5
  export * from "./components/Multipage";
6
6
  export * from "./components/Video";
7
+ export * from "./components/VocabularyList";
7
8
  export * from "./schema/PageTree";
8
- import type { ComponentSpec, ComponentUiMetadata } from "./components/ComponentSpec.ts";
9
+ import type { ComponentSpec } from "./components/ComponentSpec.ts";
9
10
  export declare const ComponentRegistry: {
10
11
  Text: ComponentSpec;
11
12
  Quiz: ComponentSpec;
@@ -13,9 +14,10 @@ export declare const ComponentRegistry: {
13
14
  Page: ComponentSpec;
14
15
  Multipage: ComponentSpec;
15
16
  Video: ComponentSpec;
17
+ VocabularyList: ComponentSpec;
16
18
  };
17
19
  export type ComponentType = keyof typeof ComponentRegistry;
18
20
  export declare function getPropsSchema(type: ComponentType): import("zod").ZodType<any, unknown, import("zod/v4/core").$ZodTypeInternals<any, unknown>>;
19
21
  export declare function getInsertableComponentEntries(): Array<[ComponentType, ComponentSpec]>;
20
22
  export declare function getInsertableComponentTypes(): ComponentType[];
21
- export type { ComponentSpec, ComponentUiMetadata };
23
+ export type { ComponentSpec, ComponentUiMetadata, ComponentIoSpec, ComponentIoChannelSpec } from "./components/ComponentSpec";
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export * from "./components/Layout";
4
4
  export * from "./components/Page";
5
5
  export * from "./components/Multipage";
6
6
  export * from "./components/Video";
7
+ export * from "./components/VocabularyList";
7
8
  export * from "./schema/PageTree";
8
9
  import { TextComponentSpec } from "./components/Text";
9
10
  import { QuizComponentSpec } from "./components/Quiz";
@@ -11,6 +12,7 @@ import { LayoutComponentSpec } from "./components/Layout";
11
12
  import { PageComponentSpec } from "./components/Page";
12
13
  import { MultipageComponentSpec } from "./components/Multipage";
13
14
  import { VideoComponentSpec } from "./components/Video";
15
+ import { VocabularyListComponentSpec } from "./components/VocabularyList";
14
16
  export const ComponentRegistry = {
15
17
  Text: TextComponentSpec,
16
18
  Quiz: QuizComponentSpec,
@@ -18,6 +20,7 @@ export const ComponentRegistry = {
18
20
  Page: PageComponentSpec,
19
21
  Multipage: MultipageComponentSpec,
20
22
  Video: VideoComponentSpec,
23
+ VocabularyList: VocabularyListComponentSpec,
21
24
  };
22
25
  // Helper to get a schema by type
23
26
  export function getPropsSchema(type) {
@@ -1,12 +1,35 @@
1
1
  import { z } from "zod";
2
2
  export declare const NodeIdSchema: z.ZodString;
3
+ export declare const RuntimeVariableSchema: z.ZodString;
4
+ export declare const RuntimeBindingSchema: z.ZodObject<{
5
+ var: z.ZodString;
6
+ }, z.core.$strip>;
7
+ export declare const NodeBindingsSchema: z.ZodObject<{
8
+ inputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
9
+ var: z.ZodString;
10
+ }, z.core.$strip>>>;
11
+ outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
12
+ var: z.ZodString;
13
+ }, z.core.$strip>>>;
14
+ }, z.core.$strip>;
3
15
  export declare const ContentNodeSchema: z.ZodObject<{
4
16
  id: z.ZodString;
5
17
  type: z.ZodString;
6
18
  props: z.ZodRecord<z.ZodString, z.ZodAny>;
7
19
  slots: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
20
+ bindings: z.ZodOptional<z.ZodObject<{
21
+ inputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
22
+ var: z.ZodString;
23
+ }, z.core.$strip>>>;
24
+ outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
25
+ var: z.ZodString;
26
+ }, z.core.$strip>>>;
27
+ }, z.core.$strip>>;
8
28
  }, z.core.$strip>;
9
29
  export type ContentNode = z.infer<typeof ContentNodeSchema>;
30
+ export type RuntimeVariable = z.infer<typeof RuntimeVariableSchema>;
31
+ export type RuntimeBinding = z.infer<typeof RuntimeBindingSchema>;
32
+ export type NodeBindings = z.infer<typeof NodeBindingsSchema>;
10
33
  export declare const PageTreeSchema: z.ZodObject<{
11
34
  version: z.ZodString;
12
35
  root: z.ZodString;
@@ -15,6 +38,14 @@ export declare const PageTreeSchema: z.ZodObject<{
15
38
  type: z.ZodString;
16
39
  props: z.ZodRecord<z.ZodString, z.ZodAny>;
17
40
  slots: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
41
+ bindings: z.ZodOptional<z.ZodObject<{
42
+ inputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
43
+ var: z.ZodString;
44
+ }, z.core.$strip>>>;
45
+ outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
46
+ var: z.ZodString;
47
+ }, z.core.$strip>>>;
48
+ }, z.core.$strip>>;
18
49
  }, z.core.$strip>>;
19
50
  }, z.core.$strip>;
20
51
  export type PageTree = z.infer<typeof PageTreeSchema>;
@@ -1,11 +1,20 @@
1
1
  import { z } from "zod";
2
2
  export const NodeIdSchema = z.string().min(1);
3
3
  // You can switch to stricter UUID validation later.
4
+ export const RuntimeVariableSchema = z.string().min(1);
5
+ export const RuntimeBindingSchema = z.object({
6
+ var: RuntimeVariableSchema
7
+ });
8
+ export const NodeBindingsSchema = z.object({
9
+ inputs: z.record(z.string().min(1), RuntimeBindingSchema).optional(),
10
+ outputs: z.record(z.string().min(1), RuntimeBindingSchema).optional(),
11
+ });
4
12
  export const ContentNodeSchema = z.object({
5
13
  id: NodeIdSchema,
6
14
  type: z.string(), // we'll refine typing later
7
15
  props: z.record(z.string(), z.any()), // key: string, value: any
8
- slots: z.record(z.string(), z.array(NodeIdSchema))
16
+ slots: z.record(z.string(), z.array(NodeIdSchema)),
17
+ bindings: NodeBindingsSchema.optional()
9
18
  });
10
19
  export const PageTreeSchema = z.object({
11
20
  version: z.string(),
@@ -14,4 +14,26 @@ export function validateNode(node) {
14
14
  throw new Error(`Invalid slot '${slotName}' for component type ${node.type}`);
15
15
  }
16
16
  }
17
+ // 3) Validate runtime bindings
18
+ if (node.bindings) {
19
+ if (!spec.io) {
20
+ throw new Error(`Component type ${node.type} does not support runtime bindings`);
21
+ }
22
+ if (node.bindings.inputs) {
23
+ const allowedInputs = spec.io.inputs ?? {};
24
+ for (const inputName of Object.keys(node.bindings.inputs)) {
25
+ if (!allowedInputs[inputName]) {
26
+ throw new Error(`Invalid input binding '${inputName}' for component type ${node.type}`);
27
+ }
28
+ }
29
+ }
30
+ if (node.bindings.outputs) {
31
+ const allowedOutputs = spec.io.outputs ?? {};
32
+ for (const outputName of Object.keys(node.bindings.outputs)) {
33
+ if (!allowedOutputs[outputName]) {
34
+ throw new Error(`Invalid output binding '${outputName}' for component type ${node.type}`);
35
+ }
36
+ }
37
+ }
38
+ }
17
39
  }
@@ -6,5 +6,13 @@ export declare function validatePageTree(tree: unknown): {
6
6
  type: string;
7
7
  props: Record<string, any>;
8
8
  slots: Record<string, string[]>;
9
+ bindings?: {
10
+ inputs?: Record<string, {
11
+ var: string;
12
+ }> | undefined;
13
+ outputs?: Record<string, {
14
+ var: string;
15
+ }> | undefined;
16
+ } | undefined;
9
17
  }>;
10
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckpic/content-spec",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -1,4 +1,25 @@
1
- import type { ZodType } from "zod";
1
+ import type { ZodType, ZodTypeAny } from "zod";
2
+
3
+ export interface ComponentIoChannelSpec {
4
+ /**
5
+ * Optional Zod schema to describe the runtime value shape for this channel.
6
+ * This is metadata for wiring / tooling; not validated at authoring time.
7
+ */
8
+ schema?: ZodTypeAny;
9
+ /**
10
+ * Human-friendly type name (e.g. "VocabId[]") the editor can display.
11
+ */
12
+ valueType?: string;
13
+ /**
14
+ * Optional description to surface in UI hints.
15
+ */
16
+ description?: string;
17
+ }
18
+
19
+ export interface ComponentIoSpec {
20
+ inputs?: Record<string, ComponentIoChannelSpec>;
21
+ outputs?: Record<string, ComponentIoChannelSpec>;
22
+ }
2
23
 
3
24
  export interface ComponentUiMetadata {
4
25
  /**
@@ -23,5 +44,9 @@ export interface ComponentSpec {
23
44
  type: string;
24
45
  propsSchema: ZodType<any>;
25
46
  slots: string[];
47
+ /**
48
+ * Optional runtime input/output channels for dynamic data wiring.
49
+ */
50
+ io?: ComponentIoSpec;
26
51
  ui?: ComponentUiMetadata;
27
52
  }
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import type { ComponentSpec } from "./ComponentSpec";
3
+
4
+ export const VocabularyListPropsSchema = z.object({
5
+ title: z.string().optional(),
6
+ staticVocabIds: z.array(z.number())
7
+ });
8
+
9
+ export const VocabularyListComponentSpec: ComponentSpec = {
10
+ type: "VocabularyList",
11
+ propsSchema: VocabularyListPropsSchema,
12
+ slots: [],
13
+ io: {
14
+ inputs: {
15
+ extraVocabIds: {
16
+ schema: z.array(z.number()),
17
+ valueType: "VocabId[]",
18
+ description: "Additional vocab ids injected at runtime (e.g. incorrect answers)."
19
+ }
20
+ },
21
+ outputs: {
22
+ selectedVocabIds: {
23
+ schema: z.array(z.number()),
24
+ valueType: "VocabId[]",
25
+ description: "Ids the student marked or selected in this list."
26
+ }
27
+ }
28
+ },
29
+ ui: {
30
+ label: "Vocabulary list",
31
+ insertable: true,
32
+ insertMenuOrder: 50,
33
+ }
34
+ };
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./components/Layout";
4
4
  export * from "./components/Page";
5
5
  export * from "./components/Multipage";
6
6
  export * from "./components/Video";
7
+ export * from "./components/VocabularyList";
7
8
  export * from "./schema/PageTree";
8
9
 
9
10
 
@@ -13,6 +14,7 @@ import { LayoutComponentSpec } from "./components/Layout";
13
14
  import { PageComponentSpec } from "./components/Page";
14
15
  import { MultipageComponentSpec } from "./components/Multipage";
15
16
  import { VideoComponentSpec } from "./components/Video";
17
+ import { VocabularyListComponentSpec } from "./components/VocabularyList";
16
18
  import type { ComponentSpec, ComponentUiMetadata } from "./components/ComponentSpec.ts";
17
19
 
18
20
  export const ComponentRegistry = {
@@ -22,6 +24,7 @@ export const ComponentRegistry = {
22
24
  Page: PageComponentSpec,
23
25
  Multipage: MultipageComponentSpec,
24
26
  Video: VideoComponentSpec,
27
+ VocabularyList: VocabularyListComponentSpec,
25
28
  };
26
29
 
27
30
  export type ComponentType = keyof typeof ComponentRegistry;
@@ -60,4 +63,9 @@ export function getInsertableComponentTypes(): ComponentType[] {
60
63
  return getInsertableComponentEntries().map(([type]) => type);
61
64
  }
62
65
 
63
- export type { ComponentSpec, ComponentUiMetadata };
66
+ export type {
67
+ ComponentSpec,
68
+ ComponentUiMetadata,
69
+ ComponentIoSpec,
70
+ ComponentIoChannelSpec
71
+ } from "./components/ComponentSpec";
@@ -3,14 +3,29 @@ import { z } from "zod";
3
3
  export const NodeIdSchema = z.string().min(1);
4
4
  // You can switch to stricter UUID validation later.
5
5
 
6
+ export const RuntimeVariableSchema = z.string().min(1);
7
+
8
+ export const RuntimeBindingSchema = z.object({
9
+ var: RuntimeVariableSchema
10
+ });
11
+
12
+ export const NodeBindingsSchema = z.object({
13
+ inputs: z.record(z.string().min(1), RuntimeBindingSchema).optional(),
14
+ outputs: z.record(z.string().min(1), RuntimeBindingSchema).optional(),
15
+ });
16
+
6
17
  export const ContentNodeSchema = z.object({
7
18
  id: NodeIdSchema,
8
19
  type: z.string(), // we'll refine typing later
9
20
  props: z.record(z.string(), z.any()), // key: string, value: any
10
- slots: z.record(z.string(), z.array(NodeIdSchema))
21
+ slots: z.record(z.string(), z.array(NodeIdSchema)),
22
+ bindings: NodeBindingsSchema.optional()
11
23
  });
12
24
 
13
25
  export type ContentNode = z.infer<typeof ContentNodeSchema>;
26
+ export type RuntimeVariable = z.infer<typeof RuntimeVariableSchema>;
27
+ export type RuntimeBinding = z.infer<typeof RuntimeBindingSchema>;
28
+ export type NodeBindings = z.infer<typeof NodeBindingsSchema>;
14
29
 
15
30
  export const PageTreeSchema = z.object({
16
31
  version: z.string(),
@@ -20,4 +20,35 @@ export function validateNode(node: ContentNode) {
20
20
  );
21
21
  }
22
22
  }
23
+
24
+ // 3) Validate runtime bindings
25
+ if (node.bindings) {
26
+ if (!spec.io) {
27
+ throw new Error(
28
+ `Component type ${node.type} does not support runtime bindings`
29
+ );
30
+ }
31
+
32
+ if (node.bindings.inputs) {
33
+ const allowedInputs = spec.io.inputs ?? {};
34
+ for (const inputName of Object.keys(node.bindings.inputs)) {
35
+ if (!allowedInputs[inputName]) {
36
+ throw new Error(
37
+ `Invalid input binding '${inputName}' for component type ${node.type}`
38
+ );
39
+ }
40
+ }
41
+ }
42
+
43
+ if (node.bindings.outputs) {
44
+ const allowedOutputs = spec.io.outputs ?? {};
45
+ for (const outputName of Object.keys(node.bindings.outputs)) {
46
+ if (!allowedOutputs[outputName]) {
47
+ throw new Error(
48
+ `Invalid output binding '${outputName}' for component type ${node.type}`
49
+ );
50
+ }
51
+ }
52
+ }
53
+ }
23
54
  }
package/dist/files.txt DELETED
@@ -1,265 +0,0 @@
1
- --components/ComponentSpec.d.ts--
2
- import type { ZodType } from "zod";
3
- export interface ComponentSpec {
4
- type: string;
5
- propsSchema: ZodType<any>;
6
- slots: string[];
7
- }
8
-
9
-
10
- --components/ComponentSpec.js--
11
- export {};
12
-
13
-
14
- --components/Layout.d.ts--
15
- import { z } from "zod";
16
- import type { ComponentSpec } from "./ComponentSpec";
17
- export declare const LayoutPropsSchema: z.ZodObject<{
18
- backgroundColor: z.ZodOptional<z.ZodString>;
19
- style: z.ZodOptional<z.ZodObject<{
20
- backgroundColor: z.ZodOptional<z.ZodString>;
21
- padding: z.ZodOptional<z.ZodString>;
22
- borderRadius: z.ZodOptional<z.ZodString>;
23
- }, z.core.$strip>>;
24
- }, z.core.$strip>;
25
- export declare const LayoutComponentSpec: ComponentSpec;
26
-
27
-
28
- --components/Layout.js--
29
- import { z } from "zod";
30
- export const LayoutPropsSchema = z.object({
31
- backgroundColor: z.string().optional(),
32
- style: z.object({
33
- backgroundColor: z.string().optional(),
34
- padding: z.string().optional(),
35
- borderRadius: z.string().optional()
36
- }).optional()
37
- });
38
- export const LayoutComponentSpec = {
39
- type: "Layout",
40
- propsSchema: LayoutPropsSchema,
41
- slots: ["content"]
42
- };
43
-
44
-
45
- --components/Page.d.ts--
46
- import { z } from "zod";
47
- import type { ComponentSpec } from "./ComponentSpec";
48
- export declare const PagePropsSchema: z.ZodObject<{
49
- title: z.ZodOptional<z.ZodString>;
50
- backgroundColor: z.ZodOptional<z.ZodString>;
51
- }, z.core.$strip>;
52
- export declare const PageComponentSpec: ComponentSpec;
53
-
54
-
55
- --components/Page.js--
56
- import { z } from "zod";
57
- export const PagePropsSchema = z.object({
58
- title: z.string().optional(),
59
- backgroundColor: z.string().optional()
60
- });
61
- export const PageComponentSpec = {
62
- type: "Page",
63
- propsSchema: PagePropsSchema,
64
- slots: ["main"]
65
- };
66
-
67
-
68
- --components/Quiz.d.ts--
69
- import { z } from "zod";
70
- import { ComponentSpec } from "./ComponentSpec";
71
- export declare const QuizQuestionSchema: z.ZodObject<{
72
- q: z.ZodString;
73
- a: z.ZodString;
74
- }, z.core.$strip>;
75
- export declare const QuizPropsSchema: z.ZodObject<{
76
- title: z.ZodOptional<z.ZodString>;
77
- questions: z.ZodArray<z.ZodObject<{
78
- q: z.ZodString;
79
- a: z.ZodString;
80
- }, z.core.$strip>>;
81
- backgroundColor: z.ZodOptional<z.ZodString>;
82
- }, z.core.$strip>;
83
- export type QuizProps = z.infer<typeof QuizPropsSchema>;
84
- export declare const QuizComponentSpec: ComponentSpec;
85
-
86
-
87
- --components/Quiz.js--
88
- import { z } from "zod";
89
- export const QuizQuestionSchema = z.object({
90
- q: z.string(),
91
- a: z.string()
92
- });
93
- export const QuizPropsSchema = z.object({
94
- title: z.string().optional(),
95
- questions: z.array(QuizQuestionSchema).min(1),
96
- backgroundColor: z.string().optional()
97
- });
98
- export const QuizComponentSpec = {
99
- type: "Quiz",
100
- propsSchema: QuizPropsSchema,
101
- slots: [] // quizzes don’t have child nodes
102
- };
103
-
104
-
105
- --components/Text.d.ts--
106
- import { z } from "zod";
107
- import type { ComponentSpec } from "./ComponentSpec";
108
- export declare const TextPropsSchema: z.ZodObject<{
109
- variant: z.ZodEnum<{
110
- body: "body";
111
- h1: "h1";
112
- h2: "h2";
113
- h3: "h3";
114
- }>;
115
- text: z.ZodString;
116
- backgroundColor: z.ZodOptional<z.ZodString>;
117
- }, z.core.$strip>;
118
- export declare const TextComponentSpec: ComponentSpec;
119
-
120
-
121
- --components/Text.js--
122
- import { z } from "zod";
123
- export const TextPropsSchema = z.object({
124
- variant: z.enum(["h1", "h2", "h3", "body"]),
125
- text: z.string(),
126
- backgroundColor: z.string().optional()
127
- });
128
- export const TextComponentSpec = {
129
- type: "Text",
130
- propsSchema: TextPropsSchema,
131
- slots: []
132
- };
133
-
134
-
135
- --index.d.ts--
136
- export * from "./components/Text";
137
- export * from "./components/Quiz";
138
- export * from "./components/Layout";
139
- export * from "./components/Page";
140
- export * from "./schema/PageTree";
141
- import type { ComponentSpec } from "./components/ComponentSpec.ts";
142
- export declare const ComponentRegistry: {
143
- Text: ComponentSpec;
144
- Quiz: ComponentSpec;
145
- Layout: ComponentSpec;
146
- Page: ComponentSpec;
147
- };
148
- export type ComponentType = keyof typeof ComponentRegistry;
149
- export declare function getPropsSchema(type: ComponentType): import("zod").ZodType<any, unknown, import("zod/v4/core").$ZodTypeInternals<any, unknown>>;
150
- export type { ComponentSpec };
151
-
152
-
153
- --index.js--
154
- export * from "./components/Text";
155
- export * from "./components/Quiz";
156
- export * from "./components/Layout";
157
- export * from "./components/Page";
158
- export * from "./schema/PageTree";
159
- import { TextComponentSpec } from "./components/Text";
160
- import { QuizComponentSpec } from "./components/Quiz";
161
- import { LayoutComponentSpec } from "./components/Layout";
162
- import { PageComponentSpec } from "./components/Page";
163
- export const ComponentRegistry = {
164
- Text: TextComponentSpec,
165
- Quiz: QuizComponentSpec,
166
- Layout: LayoutComponentSpec,
167
- Page: PageComponentSpec
168
- };
169
- // Helper to get a schema by type
170
- export function getPropsSchema(type) {
171
- return ComponentRegistry[type].propsSchema;
172
- }
173
-
174
-
175
- --schema/PageTree.d.ts--
176
- import { z } from "zod";
177
- export declare const NodeIdSchema: z.ZodString;
178
- export declare const ContentNodeSchema: z.ZodObject<{
179
- id: z.ZodString;
180
- type: z.ZodString;
181
- props: z.ZodRecord<z.ZodString, z.ZodAny>;
182
- slots: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
183
- }, z.core.$strip>;
184
- export type ContentNode = z.infer<typeof ContentNodeSchema>;
185
- export declare const PageTreeSchema: z.ZodObject<{
186
- version: z.ZodString;
187
- root: z.ZodString;
188
- nodes: z.ZodRecord<z.ZodString, z.ZodObject<{
189
- id: z.ZodString;
190
- type: z.ZodString;
191
- props: z.ZodRecord<z.ZodString, z.ZodAny>;
192
- slots: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
193
- }, z.core.$strip>>;
194
- }, z.core.$strip>;
195
- export type PageTree = z.infer<typeof PageTreeSchema>;
196
-
197
-
198
- --schema/PageTree.js--
199
- import { z } from "zod";
200
- export const NodeIdSchema = z.string().min(1);
201
- // You can switch to stricter UUID validation later.
202
- export const ContentNodeSchema = z.object({
203
- id: NodeIdSchema,
204
- type: z.string(), // we'll refine typing later
205
- props: z.record(z.string(), z.any()), // key: string, value: any
206
- slots: z.record(z.string(), z.array(NodeIdSchema))
207
- });
208
- export const PageTreeSchema = z.object({
209
- version: z.string(),
210
- root: NodeIdSchema,
211
- nodes: z.record(z.string(), ContentNodeSchema)
212
- });
213
-
214
-
215
- --validators/validateNode.d.ts--
216
- import { ContentNode } from "..";
217
- export declare function validateNode(node: ContentNode): void;
218
-
219
-
220
- --validators/validateNode.js--
221
- import { ComponentRegistry } from "..";
222
- export function validateNode(node) {
223
- const type = node.type;
224
- const spec = ComponentRegistry[type];
225
- if (!spec) {
226
- throw new Error(`Unknown component type: ${node.type}`);
227
- }
228
- // 1) Validate props
229
- spec.propsSchema.parse(node.props);
230
- // 2) Validate slots
231
- const allowedSlots = (spec.slots ?? []);
232
- for (const slotName of Object.keys(node.slots)) {
233
- if (!allowedSlots.includes(slotName)) {
234
- throw new Error(`Invalid slot '${slotName}' for component type ${node.type}`);
235
- }
236
- }
237
- }
238
-
239
-
240
- --validators/validatePageTree.d.ts--
241
- export declare function validatePageTree(tree: unknown): {
242
- version: string;
243
- root: string;
244
- nodes: Record<string, {
245
- id: string;
246
- type: string;
247
- props: Record<string, any>;
248
- slots: Record<string, string[]>;
249
- }>;
250
- };
251
-
252
-
253
- --validators/validatePageTree.js--
254
- import { PageTreeSchema } from "../schema/PageTree";
255
- import { validateNode } from "./validateNode";
256
- export function validatePageTree(tree) {
257
- const data = PageTreeSchema.parse(tree); // main shape
258
- // Validate nodes individually
259
- for (const node of Object.values(data.nodes)) {
260
- validateNode(node);
261
- }
262
- return data;
263
- }
264
-
265
-