@q1k-oss/btree-workflows 0.0.1 → 0.0.2

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 (206) hide show
  1. package/README.md +13 -13
  2. package/dist/index.cjs +5011 -0
  3. package/dist/index.d.cts +3320 -0
  4. package/dist/index.d.ts +3320 -0
  5. package/dist/index.js +4879 -0
  6. package/package.json +33 -3
  7. package/.claude/settings.local.json +0 -31
  8. package/CLAUDE.md +0 -181
  9. package/behaviour-tree-workflows-landing/index.html +0 -16
  10. package/behaviour-tree-workflows-landing/package-lock.json +0 -2074
  11. package/behaviour-tree-workflows-landing/package.json +0 -31
  12. package/behaviour-tree-workflows-landing/public/favicon.svg +0 -17
  13. package/behaviour-tree-workflows-landing/src/App.css +0 -103
  14. package/behaviour-tree-workflows-landing/src/App.tsx +0 -176
  15. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +0 -89
  16. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +0 -64
  17. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +0 -64
  18. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +0 -34
  19. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +0 -107
  20. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +0 -85
  21. package/behaviour-tree-workflows-landing/src/components/Header.css +0 -50
  22. package/behaviour-tree-workflows-landing/src/components/Header.tsx +0 -26
  23. package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +0 -45
  24. package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +0 -15
  25. package/behaviour-tree-workflows-landing/src/components/Toolbar.css +0 -74
  26. package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +0 -53
  27. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +0 -67
  28. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +0 -192
  29. package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +0 -18
  30. package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +0 -96
  31. package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +0 -11
  32. package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +0 -96
  33. package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +0 -136
  34. package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +0 -549
  35. package/behaviour-tree-workflows-landing/src/main.tsx +0 -9
  36. package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +0 -18
  37. package/behaviour-tree-workflows-landing/src/stubs/fs.ts +0 -24
  38. package/behaviour-tree-workflows-landing/src/stubs/path.ts +0 -16
  39. package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +0 -6
  40. package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +0 -22
  41. package/behaviour-tree-workflows-landing/tsconfig.json +0 -25
  42. package/behaviour-tree-workflows-landing/vite.config.ts +0 -40
  43. package/demo-google-sheets.ts +0 -181
  44. package/demo-runtime-variables.ts +0 -174
  45. package/demo-template.ts +0 -208
  46. package/docs/ARCHITECTURE_SUMMARY.md +0 -613
  47. package/docs/NODE_REFERENCE.md +0 -504
  48. package/docs/README.md +0 -53
  49. package/docs/custom-nodes-architecture.md +0 -826
  50. package/docs/observability.md +0 -175
  51. package/docs/yaml-specification.md +0 -990
  52. package/examples/temporal/README.md +0 -117
  53. package/examples/temporal/activities.ts +0 -373
  54. package/examples/temporal/client.ts +0 -115
  55. package/examples/temporal/python-worker/activities.py +0 -339
  56. package/examples/temporal/python-worker/requirements.txt +0 -12
  57. package/examples/temporal/python-worker/worker.py +0 -106
  58. package/examples/temporal/worker.ts +0 -66
  59. package/examples/temporal/workflows.ts +0 -6
  60. package/examples/temporal/yaml-workflow-loader.ts +0 -105
  61. package/examples/yaml-test.ts +0 -97
  62. package/examples/yaml-workflows/01-simple-sequence.yaml +0 -25
  63. package/examples/yaml-workflows/02-parallel-timeout.yaml +0 -45
  64. package/examples/yaml-workflows/03-ecommerce-checkout.yaml +0 -94
  65. package/examples/yaml-workflows/04-ai-agent-workflow.yaml +0 -346
  66. package/examples/yaml-workflows/05-order-processing.yaml +0 -146
  67. package/examples/yaml-workflows/06-activity-test.yaml +0 -71
  68. package/examples/yaml-workflows/07-activity-simple-test.yaml +0 -43
  69. package/examples/yaml-workflows/08-file-processing.yaml +0 -141
  70. package/examples/yaml-workflows/09-http-request.yaml +0 -137
  71. package/examples/yaml-workflows/README.md +0 -211
  72. package/src/actions/code-execution.schema.ts +0 -27
  73. package/src/actions/code-execution.ts +0 -218
  74. package/src/actions/generate-file.test.ts +0 -516
  75. package/src/actions/generate-file.ts +0 -166
  76. package/src/actions/http-request.test.ts +0 -784
  77. package/src/actions/http-request.ts +0 -228
  78. package/src/actions/index.ts +0 -20
  79. package/src/actions/parse-file.test.ts +0 -448
  80. package/src/actions/parse-file.ts +0 -139
  81. package/src/actions/python-script.test.ts +0 -439
  82. package/src/actions/python-script.ts +0 -154
  83. package/src/base-node.test.ts +0 -511
  84. package/src/base-node.ts +0 -605
  85. package/src/behavior-tree.test.ts +0 -431
  86. package/src/behavior-tree.ts +0 -283
  87. package/src/blackboard.test.ts +0 -222
  88. package/src/blackboard.ts +0 -192
  89. package/src/composites/conditional.schema.ts +0 -19
  90. package/src/composites/conditional.test.ts +0 -309
  91. package/src/composites/conditional.ts +0 -129
  92. package/src/composites/for-each.schema.ts +0 -23
  93. package/src/composites/for-each.test.ts +0 -254
  94. package/src/composites/for-each.ts +0 -132
  95. package/src/composites/index.ts +0 -15
  96. package/src/composites/memory-sequence.schema.ts +0 -19
  97. package/src/composites/memory-sequence.test.ts +0 -223
  98. package/src/composites/memory-sequence.ts +0 -98
  99. package/src/composites/parallel.schema.ts +0 -28
  100. package/src/composites/parallel.test.ts +0 -502
  101. package/src/composites/parallel.ts +0 -157
  102. package/src/composites/reactive-sequence.schema.ts +0 -19
  103. package/src/composites/reactive-sequence.test.ts +0 -170
  104. package/src/composites/reactive-sequence.ts +0 -85
  105. package/src/composites/recovery.schema.ts +0 -19
  106. package/src/composites/recovery.test.ts +0 -366
  107. package/src/composites/recovery.ts +0 -90
  108. package/src/composites/selector.schema.ts +0 -19
  109. package/src/composites/selector.test.ts +0 -387
  110. package/src/composites/selector.ts +0 -85
  111. package/src/composites/sequence.schema.ts +0 -19
  112. package/src/composites/sequence.test.ts +0 -337
  113. package/src/composites/sequence.ts +0 -72
  114. package/src/composites/sub-tree.schema.ts +0 -21
  115. package/src/composites/sub-tree.test.ts +0 -893
  116. package/src/composites/sub-tree.ts +0 -177
  117. package/src/composites/while.schema.ts +0 -24
  118. package/src/composites/while.test.ts +0 -381
  119. package/src/composites/while.ts +0 -149
  120. package/src/data-store/index.ts +0 -10
  121. package/src/data-store/memory-store.ts +0 -161
  122. package/src/data-store/types.ts +0 -94
  123. package/src/debug/breakpoint.test.ts +0 -47
  124. package/src/debug/breakpoint.ts +0 -30
  125. package/src/debug/index.ts +0 -17
  126. package/src/debug/resume-point.test.ts +0 -49
  127. package/src/debug/resume-point.ts +0 -29
  128. package/src/decorators/delay.schema.ts +0 -21
  129. package/src/decorators/delay.test.ts +0 -261
  130. package/src/decorators/delay.ts +0 -140
  131. package/src/decorators/force-result.schema.ts +0 -32
  132. package/src/decorators/force-result.test.ts +0 -133
  133. package/src/decorators/force-result.ts +0 -63
  134. package/src/decorators/index.ts +0 -13
  135. package/src/decorators/invert.schema.ts +0 -19
  136. package/src/decorators/invert.test.ts +0 -135
  137. package/src/decorators/invert.ts +0 -42
  138. package/src/decorators/keep-running.schema.ts +0 -20
  139. package/src/decorators/keep-running.test.ts +0 -105
  140. package/src/decorators/keep-running.ts +0 -49
  141. package/src/decorators/precondition.schema.ts +0 -19
  142. package/src/decorators/precondition.test.ts +0 -351
  143. package/src/decorators/precondition.ts +0 -139
  144. package/src/decorators/repeat.schema.ts +0 -21
  145. package/src/decorators/repeat.test.ts +0 -187
  146. package/src/decorators/repeat.ts +0 -94
  147. package/src/decorators/run-once.schema.ts +0 -19
  148. package/src/decorators/run-once.test.ts +0 -140
  149. package/src/decorators/run-once.ts +0 -61
  150. package/src/decorators/soft-assert.schema.ts +0 -19
  151. package/src/decorators/soft-assert.test.ts +0 -107
  152. package/src/decorators/soft-assert.ts +0 -68
  153. package/src/decorators/timeout.schema.ts +0 -21
  154. package/src/decorators/timeout.test.ts +0 -274
  155. package/src/decorators/timeout.ts +0 -159
  156. package/src/errors.test.ts +0 -63
  157. package/src/errors.ts +0 -34
  158. package/src/events.test.ts +0 -347
  159. package/src/events.ts +0 -183
  160. package/src/index.ts +0 -80
  161. package/src/integrations/index.ts +0 -30
  162. package/src/integrations/integration-action.test.ts +0 -571
  163. package/src/integrations/integration-action.ts +0 -233
  164. package/src/integrations/piece-executor.ts +0 -320
  165. package/src/observability/execution-tracker.ts +0 -320
  166. package/src/observability/index.ts +0 -23
  167. package/src/observability/sinks.ts +0 -138
  168. package/src/observability/types.ts +0 -130
  169. package/src/registry-utils.ts +0 -147
  170. package/src/registry.test.ts +0 -466
  171. package/src/registry.ts +0 -334
  172. package/src/schemas/base.schema.ts +0 -104
  173. package/src/schemas/index.ts +0 -223
  174. package/src/schemas/integration.test.ts +0 -238
  175. package/src/schemas/tree-definition.schema.ts +0 -170
  176. package/src/schemas/validation.test.ts +0 -146
  177. package/src/schemas/validation.ts +0 -122
  178. package/src/scripting/index.ts +0 -22
  179. package/src/templates/template-loader.test.ts +0 -281
  180. package/src/templates/template-loader.ts +0 -152
  181. package/src/temporal-integration.test.ts +0 -213
  182. package/src/test-nodes.ts +0 -259
  183. package/src/types.ts +0 -503
  184. package/src/utilities/index.ts +0 -17
  185. package/src/utilities/log-message.test.ts +0 -275
  186. package/src/utilities/log-message.ts +0 -134
  187. package/src/utilities/regex-extract.test.ts +0 -138
  188. package/src/utilities/regex-extract.ts +0 -108
  189. package/src/utilities/variable-resolver.test.ts +0 -416
  190. package/src/utilities/variable-resolver.ts +0 -318
  191. package/src/utils/error-handler.test.ts +0 -117
  192. package/src/utils/error-handler.ts +0 -48
  193. package/src/utils/signal-check.test.ts +0 -234
  194. package/src/utils/signal-check.ts +0 -140
  195. package/src/yaml/errors.ts +0 -143
  196. package/src/yaml/index.ts +0 -30
  197. package/src/yaml/loader.ts +0 -39
  198. package/src/yaml/parser.ts +0 -286
  199. package/src/yaml/validation/semantic-validator.ts +0 -196
  200. package/templates/google-sheets/insert-row.yaml +0 -76
  201. package/templates/notification-sender.yaml +0 -33
  202. package/templates/order-validation.yaml +0 -44
  203. package/tsconfig.json +0 -24
  204. package/vitest.config.ts +0 -25
  205. package/workflows/order-processor.yaml +0 -59
  206. package/workflows/process-order-workflow.yaml +0 -142
@@ -1,228 +0,0 @@
1
- /**
2
- * HttpRequest Node
3
- *
4
- * Makes HTTP requests via a Temporal activity.
5
- * This node requires the `fetchUrl` activity to be configured in the context -
6
- * it does not support standalone/inline execution because HTTP I/O requires
7
- * capabilities outside the workflow sandbox.
8
- *
9
- * Features:
10
- * - Support for GET, POST, PUT, DELETE, PATCH methods
11
- * - Variable resolution in URL, headers, and body
12
- * - Configurable response parsing (JSON/text/binary)
13
- * - Retry with exponential backoff support
14
- * - Timeout configuration
15
- * - Result stored in blackboard
16
- */
17
-
18
- import { ActionNode } from "../base-node.js";
19
- import { ConfigurationError } from "../errors.js";
20
- import {
21
- type TemporalContext,
22
- type NodeConfiguration,
23
- type HttpRequestActivity,
24
- NodeStatus,
25
- } from "../types.js";
26
- import { resolveValue, type VariableContext } from "../utilities/variable-resolver.js";
27
-
28
- /**
29
- * Configuration for HttpRequest node
30
- */
31
- export interface HttpRequestConfig extends NodeConfiguration {
32
- /** URL to request (supports ${input.x}, ${bb.x}) */
33
- url: string;
34
- /** HTTP method (default: GET) */
35
- method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
36
- /** Request headers (supports variable resolution) */
37
- headers?: Record<string, string>;
38
- /** Request body (supports variable resolution) */
39
- body?: unknown;
40
- /** Expected response type for parsing */
41
- responseType?: "json" | "text" | "binary";
42
- /** Request timeout in milliseconds */
43
- timeout?: number;
44
- /** Retry configuration */
45
- retry?: {
46
- maxAttempts: number;
47
- backoffMs: number;
48
- };
49
- /** Output key on blackboard for response */
50
- outputKey: string;
51
- }
52
-
53
- /**
54
- * HttpRequest Node
55
- *
56
- * Makes HTTP requests via a Temporal activity and stores the response in blackboard.
57
- * Requires the `fetchUrl` activity to be configured.
58
- *
59
- * @example YAML
60
- * ```yaml
61
- * type: HttpRequest
62
- * id: fetch-user
63
- * props:
64
- * url: "https://api.example.com/users/${input.userId}"
65
- * method: GET
66
- * headers:
67
- * Authorization: "Bearer ${bb.accessToken}"
68
- * timeout: 5000
69
- * responseType: json
70
- * outputKey: "userData"
71
- * ```
72
- *
73
- * @example YAML with POST
74
- * ```yaml
75
- * type: HttpRequest
76
- * id: create-order
77
- * props:
78
- * url: "https://api.example.com/orders"
79
- * method: POST
80
- * headers:
81
- * Content-Type: "application/json"
82
- * Authorization: "Bearer ${bb.token}"
83
- * body:
84
- * customerId: "${input.customerId}"
85
- * items: "${bb.cartItems}"
86
- * timeout: 10000
87
- * retry:
88
- * maxAttempts: 3
89
- * backoffMs: 1000
90
- * outputKey: "orderResponse"
91
- * ```
92
- */
93
- export class HttpRequest extends ActionNode {
94
- private url: string;
95
- private method: HttpRequestConfig["method"];
96
- private headers?: Record<string, string>;
97
- private body?: unknown;
98
- private responseType: HttpRequestConfig["responseType"];
99
- private timeout?: number;
100
- private retry?: HttpRequestConfig["retry"];
101
- private outputKey: string;
102
-
103
- constructor(config: HttpRequestConfig) {
104
- super(config);
105
-
106
- if (!config.url) {
107
- throw new ConfigurationError("HttpRequest requires url");
108
- }
109
-
110
- if (!config.outputKey) {
111
- throw new ConfigurationError("HttpRequest requires outputKey");
112
- }
113
-
114
- this.url = config.url;
115
- this.method = config.method || "GET";
116
- this.headers = config.headers;
117
- this.body = config.body;
118
- this.responseType = config.responseType || "json";
119
- this.timeout = config.timeout;
120
- this.retry = config.retry;
121
- this.outputKey = config.outputKey;
122
- }
123
-
124
- protected async executeTick(context: TemporalContext): Promise<NodeStatus> {
125
- // Validate activity is available
126
- if (!context.activities?.fetchUrl) {
127
- this._lastError =
128
- "HttpRequest requires activities.fetchUrl to be configured. " +
129
- "This activity handles HTTP I/O outside the workflow sandbox.";
130
- this.log(`Error: ${this._lastError}`);
131
- return NodeStatus.FAILURE;
132
- }
133
-
134
- try {
135
- const varCtx: VariableContext = {
136
- blackboard: context.blackboard,
137
- input: context.input,
138
- testData: context.testData,
139
- };
140
-
141
- // Resolve variables in URL, headers, and body
142
- const resolvedUrl = resolveValue(this.url, varCtx) as string;
143
- const resolvedHeaders = this.headers
144
- ? (resolveValue(this.headers, varCtx) as Record<string, string>)
145
- : undefined;
146
- const resolvedBody = this.body !== undefined
147
- ? resolveValue(this.body, varCtx)
148
- : undefined;
149
-
150
- const request: HttpRequestActivity = {
151
- url: resolvedUrl,
152
- method: this.method || "GET",
153
- headers: resolvedHeaders,
154
- body: resolvedBody,
155
- timeout: this.timeout,
156
- };
157
-
158
- this.log(`HTTP ${request.method} ${resolvedUrl}`);
159
-
160
- // Execute with retry logic if configured
161
- let response;
162
- let lastError: Error | undefined;
163
- const maxAttempts = this.retry?.maxAttempts || 1;
164
- const backoffMs = this.retry?.backoffMs || 1000;
165
-
166
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
167
- try {
168
- response = await context.activities.fetchUrl(request);
169
- lastError = undefined;
170
- break;
171
- } catch (error) {
172
- lastError = error instanceof Error ? error : new Error(String(error));
173
- if (attempt < maxAttempts) {
174
- const delay = backoffMs * Math.pow(2, attempt - 1);
175
- this.log(`Request failed, retrying in ${delay}ms (attempt ${attempt}/${maxAttempts})`);
176
- await this.sleep(delay);
177
- }
178
- }
179
- }
180
-
181
- if (lastError) {
182
- throw lastError;
183
- }
184
-
185
- if (!response) {
186
- throw new Error("No response received");
187
- }
188
-
189
- // Parse response based on responseType
190
- let parsedData = response.data;
191
- if (this.responseType === "json" && typeof response.data === "string") {
192
- try {
193
- parsedData = JSON.parse(response.data);
194
- } catch {
195
- // Keep as string if parsing fails
196
- this.log("Response is not valid JSON, keeping as string");
197
- }
198
- }
199
-
200
- // Store response in blackboard
201
- context.blackboard.set(this.outputKey, {
202
- status: response.status,
203
- headers: response.headers,
204
- data: parsedData,
205
- });
206
-
207
- this.log(
208
- `HTTP ${request.method} ${resolvedUrl} -> ${response.status}`
209
- );
210
-
211
- // Consider 2xx status codes as success
212
- return response.status >= 200 && response.status < 300
213
- ? NodeStatus.SUCCESS
214
- : NodeStatus.FAILURE;
215
- } catch (error) {
216
- this._lastError = error instanceof Error ? error.message : String(error);
217
- this.log(`HTTP request failed: ${this._lastError}`);
218
- return NodeStatus.FAILURE;
219
- }
220
- }
221
-
222
- /**
223
- * Sleep for the specified duration
224
- */
225
- private sleep(ms: number): Promise<void> {
226
- return new Promise((resolve) => setTimeout(resolve, ms));
227
- }
228
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * Activity-based Action Nodes
3
- *
4
- * These nodes require Temporal activities to be provided via context.activities.
5
- * They do not support standalone/inline execution because they need capabilities
6
- * outside the workflow sandbox (Python execution, file I/O, HTTP requests).
7
- */
8
-
9
- // PythonScript node
10
- export { PythonScript, type PythonScriptConfig } from "./python-script.js";
11
-
12
- // File parsing and generation nodes
13
- export { ParseFile, type ParseFileConfig } from "./parse-file.js";
14
- export { GenerateFile, type GenerateFileConfig } from "./generate-file.js";
15
-
16
- // HTTP request node
17
- export { HttpRequest, type HttpRequestConfig } from "./http-request.js";
18
-
19
- // CodeExecution node (Microsandbox-based)
20
- export { CodeExecution, type CodeExecutionConfig } from "./code-execution.js";
@@ -1,448 +0,0 @@
1
- /**
2
- * ParseFile Node Tests
3
- */
4
-
5
- import { beforeEach, describe, expect, it, vi } from "vitest";
6
- import { ScopedBlackboard } from "../blackboard.js";
7
- import { Registry } from "../registry.js";
8
- import { type TemporalContext, type BtreeActivities, NodeStatus } from "../types.js";
9
- import { ParseFile, type ParseFileConfig } from "./parse-file.js";
10
-
11
- describe("ParseFile Node", () => {
12
- let blackboard: ScopedBlackboard;
13
- let registry: Registry;
14
-
15
- beforeEach(() => {
16
- blackboard = new ScopedBlackboard();
17
- registry = new Registry();
18
- vi.clearAllMocks();
19
- });
20
-
21
- describe("Construction and validation", () => {
22
- it("should create node with valid config", () => {
23
- const node = new ParseFile({
24
- id: "test",
25
- file: "/path/to/file.csv",
26
- outputKey: "parsedData",
27
- });
28
-
29
- expect(node).toBeDefined();
30
- expect(node.id).toBe("test");
31
- });
32
-
33
- it("should require file", () => {
34
- expect(() => {
35
- new ParseFile({
36
- id: "test",
37
- outputKey: "data",
38
- } as ParseFileConfig);
39
- }).toThrow(/requires file/i);
40
- });
41
-
42
- it("should require outputKey", () => {
43
- expect(() => {
44
- new ParseFile({
45
- id: "test",
46
- file: "/path/to/file.csv",
47
- } as ParseFileConfig);
48
- }).toThrow(/requires outputKey/i);
49
- });
50
-
51
- it("should accept optional format, sheetName, and options", () => {
52
- const node = new ParseFile({
53
- id: "test",
54
- file: "/path/to/file.xlsx",
55
- format: "xlsx",
56
- sheetName: "Orders",
57
- outputKey: "data",
58
- options: {
59
- skipRows: 1,
60
- trim: true,
61
- },
62
- });
63
-
64
- expect(node).toBeDefined();
65
- });
66
- });
67
-
68
- describe("Activity requirement", () => {
69
- it("should fail without parseFile activity", async () => {
70
- const context: TemporalContext = {
71
- blackboard,
72
- treeRegistry: registry,
73
- timestamp: Date.now(),
74
- deltaTime: 0,
75
- activities: undefined,
76
- };
77
-
78
- const node = new ParseFile({
79
- id: "test",
80
- file: "/data/orders.csv",
81
- outputKey: "orders",
82
- });
83
-
84
- const status = await node.tick(context);
85
-
86
- expect(status).toBe(NodeStatus.FAILURE);
87
- expect(node.lastError).toContain("requires activities.parseFile");
88
- });
89
-
90
- it("should fail when activities object exists but parseFile is missing", async () => {
91
- const context: TemporalContext = {
92
- blackboard,
93
- treeRegistry: registry,
94
- timestamp: Date.now(),
95
- deltaTime: 0,
96
- activities: {
97
- executePieceAction: vi.fn(),
98
- // parseFile is not provided
99
- } as BtreeActivities,
100
- };
101
-
102
- const node = new ParseFile({
103
- id: "test",
104
- file: "/data/orders.csv",
105
- outputKey: "orders",
106
- });
107
-
108
- const status = await node.tick(context);
109
-
110
- expect(status).toBe(NodeStatus.FAILURE);
111
- expect(node.lastError).toContain("requires activities.parseFile");
112
- });
113
- });
114
-
115
- describe("Execution with activity", () => {
116
- it("should parse CSV file via activity", async () => {
117
- const mockParseActivity = vi.fn().mockResolvedValue({
118
- data: [
119
- { orderId: "1", product: "Widget", quantity: 5 },
120
- { orderId: "2", product: "Gadget", quantity: 3 },
121
- ],
122
- rowCount: 2,
123
- columns: ["orderId", "product", "quantity"],
124
- });
125
-
126
- const context: TemporalContext = {
127
- blackboard,
128
- treeRegistry: registry,
129
- timestamp: Date.now(),
130
- deltaTime: 0,
131
- activities: {
132
- executePieceAction: vi.fn(),
133
- parseFile: mockParseActivity,
134
- },
135
- };
136
-
137
- const node = new ParseFile({
138
- id: "test",
139
- file: "/data/orders.csv",
140
- format: "csv",
141
- outputKey: "orders",
142
- });
143
-
144
- const status = await node.tick(context);
145
-
146
- expect(status).toBe(NodeStatus.SUCCESS);
147
- expect(mockParseActivity).toHaveBeenCalledWith(
148
- expect.objectContaining({
149
- file: "/data/orders.csv",
150
- format: "csv",
151
- })
152
- );
153
- });
154
-
155
- it("should store parsed data in blackboard", async () => {
156
- const parsedData = [
157
- { id: "1", name: "Alice" },
158
- { id: "2", name: "Bob" },
159
- ];
160
-
161
- const mockParseActivity = vi.fn().mockResolvedValue({
162
- data: parsedData,
163
- rowCount: 2,
164
- columns: ["id", "name"],
165
- });
166
-
167
- const context: TemporalContext = {
168
- blackboard,
169
- treeRegistry: registry,
170
- timestamp: Date.now(),
171
- deltaTime: 0,
172
- activities: {
173
- executePieceAction: vi.fn(),
174
- parseFile: mockParseActivity,
175
- },
176
- };
177
-
178
- const node = new ParseFile({
179
- id: "test",
180
- file: "/data/users.csv",
181
- outputKey: "users",
182
- });
183
-
184
- await node.tick(context);
185
-
186
- expect(blackboard.get("users")).toEqual(parsedData);
187
- });
188
-
189
- it("should resolve file path from input", async () => {
190
- const mockParseActivity = vi.fn().mockResolvedValue({
191
- data: [],
192
- rowCount: 0,
193
- columns: [],
194
- });
195
-
196
- const context: TemporalContext = {
197
- blackboard,
198
- treeRegistry: registry,
199
- timestamp: Date.now(),
200
- deltaTime: 0,
201
- input: { dataFile: "/uploads/report-2024.csv" },
202
- activities: {
203
- executePieceAction: vi.fn(),
204
- parseFile: mockParseActivity,
205
- },
206
- };
207
-
208
- const node = new ParseFile({
209
- id: "test",
210
- file: "${input.dataFile}",
211
- outputKey: "report",
212
- });
213
-
214
- await node.tick(context);
215
-
216
- expect(mockParseActivity).toHaveBeenCalledWith(
217
- expect.objectContaining({
218
- file: "/uploads/report-2024.csv",
219
- })
220
- );
221
- });
222
-
223
- it("should resolve file path from blackboard", async () => {
224
- blackboard.set("uploadedFile", "/tmp/data.xlsx");
225
-
226
- const mockParseActivity = vi.fn().mockResolvedValue({
227
- data: [],
228
- rowCount: 0,
229
- columns: [],
230
- });
231
-
232
- const context: TemporalContext = {
233
- blackboard,
234
- treeRegistry: registry,
235
- timestamp: Date.now(),
236
- deltaTime: 0,
237
- activities: {
238
- executePieceAction: vi.fn(),
239
- parseFile: mockParseActivity,
240
- },
241
- };
242
-
243
- const node = new ParseFile({
244
- id: "test",
245
- file: "${bb.uploadedFile}",
246
- outputKey: "data",
247
- });
248
-
249
- await node.tick(context);
250
-
251
- expect(mockParseActivity).toHaveBeenCalledWith(
252
- expect.objectContaining({
253
- file: "/tmp/data.xlsx",
254
- })
255
- );
256
- });
257
-
258
- it("should pass column mapping to activity", async () => {
259
- const mockParseActivity = vi.fn().mockResolvedValue({
260
- data: [],
261
- rowCount: 0,
262
- columns: [],
263
- });
264
-
265
- const context: TemporalContext = {
266
- blackboard,
267
- treeRegistry: registry,
268
- timestamp: Date.now(),
269
- deltaTime: 0,
270
- activities: {
271
- executePieceAction: vi.fn(),
272
- parseFile: mockParseActivity,
273
- },
274
- };
275
-
276
- const node = new ParseFile({
277
- id: "test",
278
- file: "/data/orders.csv",
279
- outputKey: "orders",
280
- columnMapping: {
281
- "Order ID": "orderId",
282
- "Customer Name": "customerName",
283
- "Total Amount": "amount",
284
- },
285
- });
286
-
287
- await node.tick(context);
288
-
289
- expect(mockParseActivity).toHaveBeenCalledWith(
290
- expect.objectContaining({
291
- columnMapping: {
292
- "Order ID": "orderId",
293
- "Customer Name": "customerName",
294
- "Total Amount": "amount",
295
- },
296
- })
297
- );
298
- });
299
-
300
- it("should pass parse options to activity", async () => {
301
- const mockParseActivity = vi.fn().mockResolvedValue({
302
- data: [],
303
- rowCount: 0,
304
- columns: [],
305
- });
306
-
307
- const context: TemporalContext = {
308
- blackboard,
309
- treeRegistry: registry,
310
- timestamp: Date.now(),
311
- deltaTime: 0,
312
- activities: {
313
- executePieceAction: vi.fn(),
314
- parseFile: mockParseActivity,
315
- },
316
- };
317
-
318
- const node = new ParseFile({
319
- id: "test",
320
- file: "/data/orders.csv",
321
- outputKey: "orders",
322
- options: {
323
- skipRows: 2,
324
- trim: true,
325
- emptyAsNull: true,
326
- dateColumns: ["orderDate", "shipDate"],
327
- dateFormat: "YYYY-MM-DD",
328
- },
329
- });
330
-
331
- await node.tick(context);
332
-
333
- expect(mockParseActivity).toHaveBeenCalledWith(
334
- expect.objectContaining({
335
- options: {
336
- skipRows: 2,
337
- trim: true,
338
- emptyAsNull: true,
339
- dateColumns: ["orderDate", "shipDate"],
340
- dateFormat: "YYYY-MM-DD",
341
- },
342
- })
343
- );
344
- });
345
-
346
- it("should pass sheet name for Excel files", async () => {
347
- const mockParseActivity = vi.fn().mockResolvedValue({
348
- data: [],
349
- rowCount: 0,
350
- columns: [],
351
- });
352
-
353
- const context: TemporalContext = {
354
- blackboard,
355
- treeRegistry: registry,
356
- timestamp: Date.now(),
357
- deltaTime: 0,
358
- activities: {
359
- executePieceAction: vi.fn(),
360
- parseFile: mockParseActivity,
361
- },
362
- };
363
-
364
- const node = new ParseFile({
365
- id: "test",
366
- file: "/data/report.xlsx",
367
- format: "xlsx",
368
- sheetName: "Q4 Sales",
369
- outputKey: "sales",
370
- });
371
-
372
- await node.tick(context);
373
-
374
- expect(mockParseActivity).toHaveBeenCalledWith(
375
- expect.objectContaining({
376
- format: "xlsx",
377
- sheetName: "Q4 Sales",
378
- })
379
- );
380
- });
381
-
382
- it("should handle activity errors", async () => {
383
- const mockParseActivity = vi.fn().mockRejectedValue(
384
- new Error("File not found: /data/missing.csv")
385
- );
386
-
387
- const context: TemporalContext = {
388
- blackboard,
389
- treeRegistry: registry,
390
- timestamp: Date.now(),
391
- deltaTime: 0,
392
- activities: {
393
- executePieceAction: vi.fn(),
394
- parseFile: mockParseActivity,
395
- },
396
- };
397
-
398
- const node = new ParseFile({
399
- id: "test",
400
- file: "/data/missing.csv",
401
- outputKey: "data",
402
- });
403
-
404
- const status = await node.tick(context);
405
-
406
- expect(status).toBe(NodeStatus.FAILURE);
407
- expect(node.lastError).toContain("File not found");
408
- });
409
- });
410
-
411
- describe("Node lifecycle", () => {
412
- it("should clone correctly", () => {
413
- const node = new ParseFile({
414
- id: "original",
415
- file: "/data/file.csv",
416
- format: "csv",
417
- outputKey: "data",
418
- });
419
-
420
- const cloned = node.clone() as ParseFile;
421
-
422
- expect(cloned.id).toBe("original");
423
- });
424
-
425
- it("should reset status correctly", async () => {
426
- const context: TemporalContext = {
427
- blackboard,
428
- treeRegistry: registry,
429
- timestamp: Date.now(),
430
- deltaTime: 0,
431
- // No activities - will fail
432
- };
433
-
434
- const node = new ParseFile({
435
- id: "test",
436
- file: "/data/file.csv",
437
- outputKey: "data",
438
- });
439
-
440
- await node.tick(context);
441
- expect(node.status()).toBe(NodeStatus.FAILURE);
442
-
443
- node.reset();
444
- expect(node.status()).toBe(NodeStatus.IDLE);
445
- expect(node.lastError).toBeUndefined();
446
- });
447
- });
448
- });