@q1k-oss/btree-workflows 0.0.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.
Files changed (203) hide show
  1. package/.claude/settings.local.json +31 -0
  2. package/CLAUDE.md +181 -0
  3. package/LICENSE +21 -0
  4. package/README.md +920 -0
  5. package/behaviour-tree-workflows-landing/index.html +16 -0
  6. package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
  7. package/behaviour-tree-workflows-landing/package.json +31 -0
  8. package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
  9. package/behaviour-tree-workflows-landing/src/App.css +103 -0
  10. package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
  11. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
  12. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
  13. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
  14. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
  15. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
  16. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
  17. package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
  18. package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
  19. package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
  20. package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
  21. package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
  22. package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
  23. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
  24. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
  25. package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
  26. package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
  27. package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
  28. package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
  29. package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
  30. package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
  31. package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
  32. package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
  33. package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
  34. package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
  35. package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
  36. package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
  37. package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
  38. package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
  39. package/demo-google-sheets.ts +181 -0
  40. package/demo-runtime-variables.ts +174 -0
  41. package/demo-template.ts +208 -0
  42. package/docs/ARCHITECTURE_SUMMARY.md +613 -0
  43. package/docs/NODE_REFERENCE.md +504 -0
  44. package/docs/README.md +53 -0
  45. package/docs/custom-nodes-architecture.md +826 -0
  46. package/docs/observability.md +175 -0
  47. package/docs/yaml-specification.md +990 -0
  48. package/examples/temporal/README.md +117 -0
  49. package/examples/temporal/activities.ts +373 -0
  50. package/examples/temporal/client.ts +115 -0
  51. package/examples/temporal/python-worker/activities.py +339 -0
  52. package/examples/temporal/python-worker/requirements.txt +12 -0
  53. package/examples/temporal/python-worker/worker.py +106 -0
  54. package/examples/temporal/worker.ts +66 -0
  55. package/examples/temporal/workflows.ts +6 -0
  56. package/examples/temporal/yaml-workflow-loader.ts +105 -0
  57. package/examples/yaml-test.ts +97 -0
  58. package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
  59. package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
  60. package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
  61. package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
  62. package/examples/yaml-workflows/05-order-processing.yaml +146 -0
  63. package/examples/yaml-workflows/06-activity-test.yaml +71 -0
  64. package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
  65. package/examples/yaml-workflows/08-file-processing.yaml +141 -0
  66. package/examples/yaml-workflows/09-http-request.yaml +137 -0
  67. package/examples/yaml-workflows/README.md +211 -0
  68. package/package.json +38 -0
  69. package/src/actions/code-execution.schema.ts +27 -0
  70. package/src/actions/code-execution.ts +218 -0
  71. package/src/actions/generate-file.test.ts +516 -0
  72. package/src/actions/generate-file.ts +166 -0
  73. package/src/actions/http-request.test.ts +784 -0
  74. package/src/actions/http-request.ts +228 -0
  75. package/src/actions/index.ts +20 -0
  76. package/src/actions/parse-file.test.ts +448 -0
  77. package/src/actions/parse-file.ts +139 -0
  78. package/src/actions/python-script.test.ts +439 -0
  79. package/src/actions/python-script.ts +154 -0
  80. package/src/base-node.test.ts +511 -0
  81. package/src/base-node.ts +605 -0
  82. package/src/behavior-tree.test.ts +431 -0
  83. package/src/behavior-tree.ts +283 -0
  84. package/src/blackboard.test.ts +222 -0
  85. package/src/blackboard.ts +192 -0
  86. package/src/composites/conditional.schema.ts +19 -0
  87. package/src/composites/conditional.test.ts +309 -0
  88. package/src/composites/conditional.ts +129 -0
  89. package/src/composites/for-each.schema.ts +23 -0
  90. package/src/composites/for-each.test.ts +254 -0
  91. package/src/composites/for-each.ts +132 -0
  92. package/src/composites/index.ts +15 -0
  93. package/src/composites/memory-sequence.schema.ts +19 -0
  94. package/src/composites/memory-sequence.test.ts +223 -0
  95. package/src/composites/memory-sequence.ts +98 -0
  96. package/src/composites/parallel.schema.ts +28 -0
  97. package/src/composites/parallel.test.ts +502 -0
  98. package/src/composites/parallel.ts +157 -0
  99. package/src/composites/reactive-sequence.schema.ts +19 -0
  100. package/src/composites/reactive-sequence.test.ts +170 -0
  101. package/src/composites/reactive-sequence.ts +85 -0
  102. package/src/composites/recovery.schema.ts +19 -0
  103. package/src/composites/recovery.test.ts +366 -0
  104. package/src/composites/recovery.ts +90 -0
  105. package/src/composites/selector.schema.ts +19 -0
  106. package/src/composites/selector.test.ts +387 -0
  107. package/src/composites/selector.ts +85 -0
  108. package/src/composites/sequence.schema.ts +19 -0
  109. package/src/composites/sequence.test.ts +337 -0
  110. package/src/composites/sequence.ts +72 -0
  111. package/src/composites/sub-tree.schema.ts +21 -0
  112. package/src/composites/sub-tree.test.ts +893 -0
  113. package/src/composites/sub-tree.ts +177 -0
  114. package/src/composites/while.schema.ts +24 -0
  115. package/src/composites/while.test.ts +381 -0
  116. package/src/composites/while.ts +149 -0
  117. package/src/data-store/index.ts +10 -0
  118. package/src/data-store/memory-store.ts +161 -0
  119. package/src/data-store/types.ts +94 -0
  120. package/src/debug/breakpoint.test.ts +47 -0
  121. package/src/debug/breakpoint.ts +30 -0
  122. package/src/debug/index.ts +17 -0
  123. package/src/debug/resume-point.test.ts +49 -0
  124. package/src/debug/resume-point.ts +29 -0
  125. package/src/decorators/delay.schema.ts +21 -0
  126. package/src/decorators/delay.test.ts +261 -0
  127. package/src/decorators/delay.ts +140 -0
  128. package/src/decorators/force-result.schema.ts +32 -0
  129. package/src/decorators/force-result.test.ts +133 -0
  130. package/src/decorators/force-result.ts +63 -0
  131. package/src/decorators/index.ts +13 -0
  132. package/src/decorators/invert.schema.ts +19 -0
  133. package/src/decorators/invert.test.ts +135 -0
  134. package/src/decorators/invert.ts +42 -0
  135. package/src/decorators/keep-running.schema.ts +20 -0
  136. package/src/decorators/keep-running.test.ts +105 -0
  137. package/src/decorators/keep-running.ts +49 -0
  138. package/src/decorators/precondition.schema.ts +19 -0
  139. package/src/decorators/precondition.test.ts +351 -0
  140. package/src/decorators/precondition.ts +139 -0
  141. package/src/decorators/repeat.schema.ts +21 -0
  142. package/src/decorators/repeat.test.ts +187 -0
  143. package/src/decorators/repeat.ts +94 -0
  144. package/src/decorators/run-once.schema.ts +19 -0
  145. package/src/decorators/run-once.test.ts +140 -0
  146. package/src/decorators/run-once.ts +61 -0
  147. package/src/decorators/soft-assert.schema.ts +19 -0
  148. package/src/decorators/soft-assert.test.ts +107 -0
  149. package/src/decorators/soft-assert.ts +68 -0
  150. package/src/decorators/timeout.schema.ts +21 -0
  151. package/src/decorators/timeout.test.ts +274 -0
  152. package/src/decorators/timeout.ts +159 -0
  153. package/src/errors.test.ts +63 -0
  154. package/src/errors.ts +34 -0
  155. package/src/events.test.ts +347 -0
  156. package/src/events.ts +183 -0
  157. package/src/index.ts +80 -0
  158. package/src/integrations/index.ts +30 -0
  159. package/src/integrations/integration-action.test.ts +571 -0
  160. package/src/integrations/integration-action.ts +233 -0
  161. package/src/integrations/piece-executor.ts +320 -0
  162. package/src/observability/execution-tracker.ts +320 -0
  163. package/src/observability/index.ts +23 -0
  164. package/src/observability/sinks.ts +138 -0
  165. package/src/observability/types.ts +130 -0
  166. package/src/registry-utils.ts +147 -0
  167. package/src/registry.test.ts +466 -0
  168. package/src/registry.ts +334 -0
  169. package/src/schemas/base.schema.ts +104 -0
  170. package/src/schemas/index.ts +223 -0
  171. package/src/schemas/integration.test.ts +238 -0
  172. package/src/schemas/tree-definition.schema.ts +170 -0
  173. package/src/schemas/validation.test.ts +146 -0
  174. package/src/schemas/validation.ts +122 -0
  175. package/src/scripting/index.ts +22 -0
  176. package/src/templates/template-loader.test.ts +281 -0
  177. package/src/templates/template-loader.ts +152 -0
  178. package/src/temporal-integration.test.ts +213 -0
  179. package/src/test-nodes.ts +259 -0
  180. package/src/types.ts +503 -0
  181. package/src/utilities/index.ts +17 -0
  182. package/src/utilities/log-message.test.ts +275 -0
  183. package/src/utilities/log-message.ts +134 -0
  184. package/src/utilities/regex-extract.test.ts +138 -0
  185. package/src/utilities/regex-extract.ts +108 -0
  186. package/src/utilities/variable-resolver.test.ts +416 -0
  187. package/src/utilities/variable-resolver.ts +318 -0
  188. package/src/utils/error-handler.test.ts +117 -0
  189. package/src/utils/error-handler.ts +48 -0
  190. package/src/utils/signal-check.test.ts +234 -0
  191. package/src/utils/signal-check.ts +140 -0
  192. package/src/yaml/errors.ts +143 -0
  193. package/src/yaml/index.ts +30 -0
  194. package/src/yaml/loader.ts +39 -0
  195. package/src/yaml/parser.ts +286 -0
  196. package/src/yaml/validation/semantic-validator.ts +196 -0
  197. package/templates/google-sheets/insert-row.yaml +76 -0
  198. package/templates/notification-sender.yaml +33 -0
  199. package/templates/order-validation.yaml +44 -0
  200. package/tsconfig.json +24 -0
  201. package/vitest.config.ts +25 -0
  202. package/workflows/order-processor.yaml +59 -0
  203. package/workflows/process-order-workflow.yaml +142 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Integration tests for schema validation with Registry
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from "vitest";
6
+ import { Registry } from "../registry.js";
7
+ import { Timeout } from "../decorators/timeout.js";
8
+ import { Delay } from "../decorators/delay.js";
9
+ import { Parallel } from "../composites/parallel.js";
10
+ import { Sequence } from "../composites/sequence.js";
11
+ import { ConfigurationError } from "../errors.js";
12
+
13
+ describe("Schema Validation Integration", () => {
14
+ let registry: Registry;
15
+
16
+ beforeEach(() => {
17
+ registry = new Registry();
18
+ registry.register("Timeout", Timeout, { category: "decorator" });
19
+ registry.register("Delay", Delay, { category: "decorator" });
20
+ registry.register("Parallel", Parallel, { category: "composite" });
21
+ registry.register("Sequence", Sequence, { category: "composite" });
22
+ });
23
+
24
+ describe("Timeout validation", () => {
25
+ it("should accept valid timeoutMs", () => {
26
+ const node = registry.create("Timeout", {
27
+ id: "test",
28
+ timeoutMs: 1000,
29
+ });
30
+
31
+ expect(node).toBeInstanceOf(Timeout);
32
+ });
33
+
34
+ it("should reject negative timeoutMs", () => {
35
+ expect(() => {
36
+ registry.create("Timeout", {
37
+ id: "test",
38
+ timeoutMs: -100,
39
+ });
40
+ }).toThrow(ConfigurationError);
41
+ });
42
+
43
+ it("should reject zero timeoutMs", () => {
44
+ expect(() => {
45
+ registry.create("Timeout", {
46
+ id: "test",
47
+ timeoutMs: 0,
48
+ });
49
+ }).toThrow(ConfigurationError);
50
+ });
51
+
52
+ it("should provide helpful error message", () => {
53
+ try {
54
+ registry.create("Timeout", {
55
+ id: "test-timeout",
56
+ timeoutMs: -100,
57
+ });
58
+ expect.fail("Should have thrown");
59
+ } catch (error) {
60
+ expect(error).toBeInstanceOf(ConfigurationError);
61
+ const message = (error as ConfigurationError).message;
62
+ expect(message).toContain("Timeout:test-timeout");
63
+ expect(message).toContain("timeoutMs");
64
+ expect(message).toContain("positive");
65
+ }
66
+ });
67
+ });
68
+
69
+ describe("Delay validation", () => {
70
+ it("should accept positive delayMs", () => {
71
+ const node = registry.create("Delay", {
72
+ id: "test",
73
+ delayMs: 500,
74
+ });
75
+
76
+ expect(node).toBeInstanceOf(Delay);
77
+ });
78
+
79
+ it("should accept zero delayMs", () => {
80
+ const node = registry.create("Delay", {
81
+ id: "test",
82
+ delayMs: 0,
83
+ });
84
+
85
+ expect(node).toBeInstanceOf(Delay);
86
+ });
87
+
88
+ it("should reject negative delayMs", () => {
89
+ expect(() => {
90
+ registry.create("Delay", {
91
+ id: "test",
92
+ delayMs: -100,
93
+ });
94
+ }).toThrow(ConfigurationError);
95
+ });
96
+ });
97
+
98
+ describe("Parallel validation", () => {
99
+ it("should accept valid strategy", () => {
100
+ const node = registry.create("Parallel", {
101
+ id: "test",
102
+ strategy: "strict",
103
+ });
104
+
105
+ expect(node).toBeInstanceOf(Parallel);
106
+ });
107
+
108
+ it("should accept 'any' strategy", () => {
109
+ const node = registry.create("Parallel", {
110
+ id: "test",
111
+ strategy: "any",
112
+ });
113
+
114
+ expect(node).toBeInstanceOf(Parallel);
115
+ });
116
+
117
+ it("should use default strategy if not provided", () => {
118
+ const node = registry.create("Parallel", {
119
+ id: "test",
120
+ });
121
+
122
+ expect(node).toBeInstanceOf(Parallel);
123
+ });
124
+
125
+ it("should reject invalid strategy", () => {
126
+ expect(() => {
127
+ registry.create("Parallel", {
128
+ id: "test",
129
+ strategy: "invalid" as never,
130
+ });
131
+ }).toThrow(ConfigurationError);
132
+ });
133
+
134
+ it("should accept positive thresholds", () => {
135
+ const node = registry.create("Parallel", {
136
+ id: "test",
137
+ successThreshold: 2,
138
+ failureThreshold: 1,
139
+ });
140
+
141
+ expect(node).toBeInstanceOf(Parallel);
142
+ });
143
+ });
144
+
145
+ describe("Tree creation validation", () => {
146
+ it("should validate entire tree structure", () => {
147
+ const definition = {
148
+ type: "Sequence",
149
+ id: "root",
150
+ children: [
151
+ {
152
+ type: "Timeout",
153
+ id: "timeout1",
154
+ props: {
155
+ timeoutMs: 1000,
156
+ },
157
+ children: [
158
+ {
159
+ type: "Delay",
160
+ id: "delay1",
161
+ props: {
162
+ delayMs: 500,
163
+ },
164
+ },
165
+ ],
166
+ },
167
+ ],
168
+ };
169
+
170
+ const tree = registry.createTree(definition);
171
+ expect(tree).toBeInstanceOf(Sequence);
172
+ });
173
+
174
+ it("should catch validation errors in nested nodes", () => {
175
+ const definition = {
176
+ type: "Sequence",
177
+ id: "root",
178
+ children: [
179
+ {
180
+ type: "Timeout",
181
+ id: "timeout1",
182
+ props: {
183
+ timeoutMs: -100, // Invalid!
184
+ },
185
+ },
186
+ ],
187
+ };
188
+
189
+ expect(() => {
190
+ registry.createTree(definition);
191
+ }).toThrow(ConfigurationError);
192
+ });
193
+
194
+ it("should validate tree structure schema", () => {
195
+ const definition = {
196
+ id: "missing-type",
197
+ };
198
+
199
+ expect(() => {
200
+ registry.createTree(definition);
201
+ }).toThrow(/type/);
202
+ });
203
+ });
204
+
205
+ describe("safeCreateTree", () => {
206
+ it("should return success for valid tree", () => {
207
+ const definition = {
208
+ type: "Sequence",
209
+ id: "root",
210
+ };
211
+
212
+ const result = registry.safeCreateTree(definition);
213
+
214
+ expect(result.success).toBe(true);
215
+ if (result.success) {
216
+ expect(result.tree).toBeInstanceOf(Sequence);
217
+ }
218
+ });
219
+
220
+ it("should return error for invalid tree", () => {
221
+ const definition = {
222
+ type: "Timeout",
223
+ id: "invalid",
224
+ props: {
225
+ timeoutMs: -100,
226
+ },
227
+ };
228
+
229
+ const result = registry.safeCreateTree(definition);
230
+
231
+ expect(result.success).toBe(false);
232
+ if (!result.success) {
233
+ expect(result.error).toBeInstanceOf(Error);
234
+ expect(result.error.message).toContain("timeoutMs");
235
+ }
236
+ });
237
+ });
238
+ });
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Tree definition schemas
3
+ * Validates the structure of tree definitions before node creation
4
+ */
5
+
6
+ import { z } from "zod";
7
+
8
+ /**
9
+ * Tree definition type
10
+ * Matches the format expected by Registry.createTree()
11
+ */
12
+ export interface TreeDefinition {
13
+ type: string;
14
+ id?: string;
15
+ name?: string;
16
+ props?: Record<string, unknown>;
17
+ children?: TreeDefinition[];
18
+ }
19
+
20
+ /**
21
+ * Recursive schema for tree definitions
22
+ * Supports nested children and arbitrary props
23
+ *
24
+ * Validates:
25
+ * - type is a non-empty string
26
+ * - id is optional string
27
+ * - name is optional string
28
+ * - props is optional record of unknown values
29
+ * - children is optional array of tree definitions
30
+ */
31
+ const treeDefSchemaObject = z.object({
32
+ type: z.string().min(1, "Node type is required"),
33
+ id: z.string().optional(),
34
+ name: z.string().optional(),
35
+ props: z.record(z.string(), z.unknown()).optional(),
36
+ children: z
37
+ .array(
38
+ z.lazy(() => treeDefinitionSchema) as z.ZodType<TreeDefinition>,
39
+ )
40
+ .optional(),
41
+ });
42
+
43
+ export const treeDefinitionSchema: z.ZodType<TreeDefinition> =
44
+ treeDefSchemaObject;
45
+
46
+ /**
47
+ * Validate tree definition structure (without type-specific validation)
48
+ * Type-specific validation happens in Registry.create()
49
+ *
50
+ * @param definition - Tree definition to validate
51
+ * @returns Validated tree definition
52
+ * @throws ZodError if structure is invalid
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const definition = validateTreeDefinition({
57
+ * type: 'Sequence',
58
+ * id: 'root',
59
+ * children: [
60
+ * { type: 'PrintAction', id: 'action1' }
61
+ * ]
62
+ * });
63
+ * ```
64
+ */
65
+ export function validateTreeDefinition(definition: unknown): TreeDefinition {
66
+ return treeDefinitionSchema.parse(definition) as TreeDefinition;
67
+ }
68
+
69
+ /**
70
+ * Validate decorator has exactly one child
71
+ *
72
+ * @param nodeType - Type of the decorator node
73
+ * @param children - Children array to validate
74
+ * @throws Error if child count is not exactly 1
75
+ */
76
+ export function validateDecoratorChildren(
77
+ nodeType: string,
78
+ children?: TreeDefinition[],
79
+ ): void {
80
+ const childCount = children?.length || 0;
81
+ if (childCount !== 1) {
82
+ throw new Error(
83
+ `Decorator ${nodeType} must have exactly one child (got ${childCount})`,
84
+ );
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Validate composite has at least minimum children
90
+ *
91
+ * @param nodeType - Type of the composite node
92
+ * @param children - Children array to validate
93
+ * @param minChildren - Minimum required children (default: 0)
94
+ * @throws Error if child count is less than minimum
95
+ */
96
+ export function validateCompositeChildren(
97
+ nodeType: string,
98
+ children?: TreeDefinition[],
99
+ minChildren: number = 0,
100
+ ): void {
101
+ const count = children?.length || 0;
102
+ if (count < minChildren) {
103
+ throw new Error(
104
+ `Composite ${nodeType} requires at least ${minChildren} children (got ${count})`,
105
+ );
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Validate specific child count for composites with fixed requirements
111
+ *
112
+ * @param nodeType - Type of the composite node
113
+ * @param children - Children array to validate
114
+ * @param expectedCount - Exact number of expected children
115
+ * @throws Error if child count doesn't match expected
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * // While node requires exactly 2 children (condition, body)
120
+ * validateChildCount('While', children, 2);
121
+ * ```
122
+ */
123
+ export function validateChildCount(
124
+ nodeType: string,
125
+ children?: TreeDefinition[],
126
+ expectedCount: number = 0,
127
+ ): void {
128
+ const count = children?.length || 0;
129
+ if (count !== expectedCount) {
130
+ throw new Error(
131
+ `${nodeType} requires exactly ${expectedCount} children (got ${count})`,
132
+ );
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Validate child count range for composites with flexible requirements
138
+ *
139
+ * @param nodeType - Type of the composite node
140
+ * @param children - Children array to validate
141
+ * @param minChildren - Minimum required children
142
+ * @param maxChildren - Maximum allowed children
143
+ * @throws Error if child count is outside range
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * // Conditional node requires 2-3 children (condition, then, optional else)
148
+ * validateChildCountRange('Conditional', children, 2, 3);
149
+ * ```
150
+ */
151
+ export function validateChildCountRange(
152
+ nodeType: string,
153
+ children?: TreeDefinition[],
154
+ minChildren: number = 0,
155
+ maxChildren?: number,
156
+ ): void {
157
+ const count = children?.length || 0;
158
+
159
+ if (count < minChildren) {
160
+ throw new Error(
161
+ `${nodeType} requires at least ${minChildren} children (got ${count})`,
162
+ );
163
+ }
164
+
165
+ if (maxChildren !== undefined && count > maxChildren) {
166
+ throw new Error(
167
+ `${nodeType} allows at most ${maxChildren} children (got ${count})`,
168
+ );
169
+ }
170
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Tests for schema validation utilities
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+ import { z } from "zod";
7
+ import {
8
+ zodErrorToConfigurationError,
9
+ validateConfiguration,
10
+ safeValidateConfiguration,
11
+ } from "./validation.js";
12
+ import { ConfigurationError } from "../errors.js";
13
+
14
+ describe("Schema Validation Utilities", () => {
15
+ describe("zodErrorToConfigurationError", () => {
16
+ it("should convert Zod error to ConfigurationError", () => {
17
+ const schema = z.object({
18
+ id: z.string(),
19
+ value: z.number().positive(),
20
+ });
21
+
22
+ try {
23
+ schema.parse({ id: "test", value: -5 });
24
+ expect.fail("Should have thrown");
25
+ } catch (error) {
26
+ if (error instanceof z.ZodError) {
27
+ const configError = zodErrorToConfigurationError(
28
+ error,
29
+ "TestNode",
30
+ "test-id",
31
+ );
32
+
33
+ expect(configError).toBeInstanceOf(ConfigurationError);
34
+ expect(configError.message).toContain("TestNode:test-id");
35
+ expect(configError.message).toContain("value");
36
+ }
37
+ }
38
+ });
39
+
40
+ it("should include field path in error message", () => {
41
+ const schema = z.object({
42
+ nested: z.object({
43
+ value: z.number(),
44
+ }),
45
+ });
46
+
47
+ try {
48
+ schema.parse({ nested: { value: "not a number" } });
49
+ expect.fail("Should have thrown");
50
+ } catch (error) {
51
+ if (error instanceof z.ZodError) {
52
+ const configError = zodErrorToConfigurationError(error, "TestNode");
53
+
54
+ expect(configError.message).toContain("nested.value");
55
+ }
56
+ }
57
+ });
58
+ });
59
+
60
+ describe("validateConfiguration", () => {
61
+ const testSchema = z.object({
62
+ id: z.string(),
63
+ count: z.number().int().positive(),
64
+ });
65
+
66
+ it("should validate and return valid configuration", () => {
67
+ const config = { id: "test", count: 5 };
68
+ const result = validateConfiguration(
69
+ testSchema,
70
+ config,
71
+ "TestNode",
72
+ "test-id",
73
+ );
74
+
75
+ expect(result).toEqual(config);
76
+ });
77
+
78
+ it("should throw ConfigurationError for invalid configuration", () => {
79
+ const config = { id: "test", count: -5 };
80
+
81
+ expect(() => {
82
+ validateConfiguration(testSchema, config, "TestNode", "test-id");
83
+ }).toThrow(ConfigurationError);
84
+ });
85
+
86
+ it("should include node type and ID in error message", () => {
87
+ const config = { id: "test", count: 0 };
88
+
89
+ try {
90
+ validateConfiguration(testSchema, config, "TestNode", "test-id");
91
+ expect.fail("Should have thrown");
92
+ } catch (error) {
93
+ expect(error).toBeInstanceOf(ConfigurationError);
94
+ expect((error as ConfigurationError).message).toContain(
95
+ "TestNode:test-id",
96
+ );
97
+ }
98
+ });
99
+ });
100
+
101
+ describe("safeValidateConfiguration", () => {
102
+ const testSchema = z.object({
103
+ id: z.string(),
104
+ value: z.number(),
105
+ });
106
+
107
+ it("should return success result for valid configuration", () => {
108
+ const config = { id: "test", value: 42 };
109
+ const result = safeValidateConfiguration(
110
+ testSchema,
111
+ config,
112
+ "TestNode",
113
+ "test-id",
114
+ );
115
+
116
+ expect(result.success).toBe(true);
117
+ if (result.success) {
118
+ expect(result.data).toEqual(config);
119
+ }
120
+ });
121
+
122
+ it("should return error result for invalid configuration", () => {
123
+ const config = { id: "test", value: "not a number" };
124
+ const result = safeValidateConfiguration(
125
+ testSchema,
126
+ config,
127
+ "TestNode",
128
+ "test-id",
129
+ );
130
+
131
+ expect(result.success).toBe(false);
132
+ if (!result.success) {
133
+ expect(result.error).toBeInstanceOf(ConfigurationError);
134
+ expect(result.error.message).toContain("value");
135
+ }
136
+ });
137
+
138
+ it("should not throw errors", () => {
139
+ const config = { id: 123, value: "invalid" }; // Multiple errors
140
+
141
+ expect(() => {
142
+ safeValidateConfiguration(testSchema, config, "TestNode");
143
+ }).not.toThrow();
144
+ });
145
+ });
146
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Validation utilities and error conversion
3
+ * Converts Zod validation errors to ConfigurationError
4
+ */
5
+
6
+ import { z } from "zod";
7
+ import { ConfigurationError } from "../errors.js";
8
+
9
+ /**
10
+ * Convert Zod validation errors to ConfigurationError
11
+ * Preserves detailed error context and provides helpful hints
12
+ *
13
+ * @param error - Zod validation error
14
+ * @param nodeType - Type of node being validated
15
+ * @param nodeId - Optional node ID for context
16
+ * @returns ConfigurationError with formatted message
17
+ */
18
+ export function zodErrorToConfigurationError(
19
+ error: z.ZodError<unknown>,
20
+ nodeType: string,
21
+ nodeId?: string,
22
+ ): ConfigurationError {
23
+ const nodeIdentifier = nodeId ? `${nodeType}:${nodeId}` : nodeType;
24
+
25
+ // Format Zod errors into readable message
26
+ const issues = error.issues
27
+ .map((issue: z.ZodIssue) => {
28
+ const path = issue.path.join(".");
29
+ return ` - ${path ? path + ": " : ""}${issue.message}`;
30
+ })
31
+ .join("\n");
32
+
33
+ const message = `Invalid configuration for ${nodeIdentifier}:\n${issues}`;
34
+
35
+ // Include hint for common fixes
36
+ const hint =
37
+ "Check the node configuration and ensure all required fields are provided with valid values.";
38
+
39
+ return new ConfigurationError(message, hint);
40
+ }
41
+
42
+ /**
43
+ * Validate and parse configuration with ConfigurationError conversion
44
+ * Throws ConfigurationError if validation fails
45
+ *
46
+ * @param schema - Zod schema to validate against
47
+ * @param config - Configuration object to validate
48
+ * @param nodeType - Type of node being validated
49
+ * @param nodeId - Optional node ID for error context
50
+ * @returns Validated and parsed configuration
51
+ * @throws ConfigurationError if validation fails
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const validatedConfig = validateConfiguration(
56
+ * timeoutSchema,
57
+ * { id: 'test', timeoutMs: 1000 },
58
+ * 'Timeout',
59
+ * 'test'
60
+ * );
61
+ * ```
62
+ */
63
+ export function validateConfiguration<T = unknown>(
64
+ schema: z.ZodSchema<T>,
65
+ config: unknown,
66
+ nodeType: string,
67
+ nodeId?: string,
68
+ ): T {
69
+ try {
70
+ return schema.parse(config);
71
+ } catch (error) {
72
+ if (error instanceof z.ZodError) {
73
+ throw zodErrorToConfigurationError(error, nodeType, nodeId);
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Safe validation that returns result instead of throwing
81
+ * Useful for user-facing tools that need graceful error handling
82
+ *
83
+ * @param schema - Zod schema to validate against
84
+ * @param config - Configuration object to validate
85
+ * @param nodeType - Type of node being validated (for error messages)
86
+ * @param nodeId - Optional node ID for error context
87
+ * @returns Success result with data or failure result with error
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const result = safeValidateConfiguration(
92
+ * timeoutSchema,
93
+ * { id: 'test', timeoutMs: -100 },
94
+ * 'Timeout'
95
+ * );
96
+ *
97
+ * if (result.success) {
98
+ * console.log(result.data);
99
+ * } else {
100
+ * console.error(result.error.message);
101
+ * }
102
+ * ```
103
+ */
104
+ export function safeValidateConfiguration<T>(
105
+ schema: z.ZodSchema<T>,
106
+ config: unknown,
107
+ nodeType: string,
108
+ nodeId?: string,
109
+ ):
110
+ | { success: true; data: T }
111
+ | { success: false; error: ConfigurationError } {
112
+ const result = schema.safeParse(config);
113
+
114
+ if (result.success) {
115
+ return { success: true, data: result.data };
116
+ } else {
117
+ return {
118
+ success: false,
119
+ error: zodErrorToConfigurationError(result.error, nodeType, nodeId),
120
+ };
121
+ }
122
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Scripting module (deprecated)
3
+ *
4
+ * The Script node has been replaced by CodeExecution which uses
5
+ * Microsandbox for secure, isolated code execution.
6
+ *
7
+ * Use CodeExecution instead:
8
+ * @example
9
+ * ```yaml
10
+ * type: CodeExecution
11
+ * id: transform-data
12
+ * props:
13
+ * language: javascript # or 'python'
14
+ * code: |
15
+ * const users = getBB('users');
16
+ * setBB('count', users.length);
17
+ * ```
18
+ *
19
+ * @see CodeExecution in src/actions/code-execution.ts
20
+ */
21
+
22
+ // No exports - use CodeExecution instead