@gadmin2n/schematics 0.0.135 → 0.0.137

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 (95) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +7 -6
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/gen-jwt.ts +85 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +1 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
  55. package/package.json +1 -1
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
@@ -1,499 +0,0 @@
1
- /**
2
- * DSL Workflow — The dynamic interpreter that executes any workflow DSL.
3
- *
4
- * This is the ONLY Temporal workflow in the system. It receives a DSL definition
5
- * and interprets it at runtime, calling the appropriate activities for each node.
6
- *
7
- * Temporal Workflow constraints apply:
8
- * - Must be deterministic (no Math.random, Date.now, etc.)
9
- * - Side effects only through activities
10
- * - Use workflow.sleep() instead of setTimeout
11
- */
12
- import {
13
- proxyActivities,
14
- defineSignal,
15
- defineQuery,
16
- setHandler,
17
- condition,
18
- sleep,
19
- CancellationScope,
20
- isCancellation,
21
- executeChild,
22
- } from '@temporalio/workflow';
23
- import type {
24
- WorkflowInput,
25
- WorkflowResult,
26
- DslNode,
27
- WorkflowDSL,
28
- NodeStatus,
29
- ApprovalSignalPayload,
30
- ReportNodeStatusInput,
31
- ReportWorkflowStatusInput,
32
- } from '../dsl/types';
33
- import { assertNever } from '../dsl/node-types';
34
- import {
35
- resolveTemplates,
36
- resolveExpression,
37
- getNestedValue,
38
- evaluateConditions,
39
- getSuccessorsFromNodes,
40
- getNextNodes,
41
- } from '../dsl/helpers';
42
-
43
- // ─── Activity Proxies ─────────────────────────────────────────────────────────
44
- //
45
- // Activity 按"职责类别"分组配置 timeout / retry,避免一刀切。
46
- // startToCloseTimeout 必须覆盖:业务上限(含 axios/vm 内部 timeout)+ Worker SIGTERM 后 cancel 响应窗口。
47
- // 业务可在 dsl.config 里通过 `timeoutSec` 覆盖某次调用,这里的值是默认值/上限。
48
- //
49
- // 注意:单条 Activity 的最坏耗时 ≈ startToCloseTimeout × maximumAttempts。
50
- // 若长于 K8s Pod 的 grace period(300s)会被 SIGKILL,把后续重试推给其它 worker。
51
-
52
- // HTTP — axios 默认 timeout 30s,给 Activity 预留 cancel 响应余量
53
- const httpActs = proxyActivities<
54
- Pick<typeof import('../activities'), 'httpRequest'>
55
- >({
56
- startToCloseTimeout: '60s',
57
- retry: { maximumAttempts: 3, initialInterval: '1s', maximumInterval: '30s' },
58
- });
59
-
60
- // DB — 慢查询 / 大写入可能比较久;不重试写入(dbExecute 可能非幂等)
61
- const dbActs = proxyActivities<
62
- Pick<typeof import('../activities'), 'dbQuery' | 'dbExecute'>
63
- >({
64
- startToCloseTimeout: '120s',
65
- retry: { maximumAttempts: 1 }, // 写入默认不重试,避免重复扣款类问题
66
- });
67
-
68
- // 通知 —— 邮件/IM 网关一般快;失败重试要积极
69
- const notifyActs = proxyActivities<
70
- Pick<typeof import('../activities'), 'sendNotification'>
71
- >({
72
- startToCloseTimeout: '30s',
73
- retry: { maximumAttempts: 5, initialInterval: '2s', maximumInterval: '60s' },
74
- });
75
-
76
- // 自定义 JS —— vm 内部默认 5s timeout,外层留足边界 + 不重试(脚本可能非幂等)
77
- const codeActs = proxyActivities<
78
- Pick<typeof import('../activities'), 'codeExecute'>
79
- >({
80
- startToCloseTimeout: '15s',
81
- retry: { maximumAttempts: 1 },
82
- });
83
-
84
- const reportActs = proxyActivities<typeof import('../activities')>({
85
- startToCloseTimeout: '10s',
86
- retry: { maximumAttempts: 5 },
87
- });
88
-
89
- // ─── Signals & Queries ────────────────────────────────────────────────────────
90
-
91
- export const approvalSignal = defineSignal<[ApprovalSignalPayload]>('approval');
92
- export const cancelSignal = defineSignal('cancel');
93
-
94
- export const getProgressQuery = defineQuery<Record<string, NodeStatus>>('getProgress');
95
-
96
- // ─── Main Workflow ────────────────────────────────────────────────────────────
97
-
98
- export async function dslWorkflow(input: WorkflowInput): Promise<WorkflowResult> {
99
- const { instanceId, dsl, context: initialContext } = input;
100
-
101
- // Node status tracking (for queries)
102
- const nodeStatuses: Record<string, NodeStatus> = {};
103
- // Execution context — flows between nodes
104
- const ctx: Record<string, any> = { ...initialContext };
105
- // Approval results collected from signals
106
- const approvalResults: Record<string, ApprovalSignalPayload> = {};
107
- // Cancellation flag
108
- let cancelled = false;
109
-
110
- // Set up signal handlers
111
- setHandler(approvalSignal, (payload: ApprovalSignalPayload) => {
112
- approvalResults[payload.nodeId] = payload;
113
- });
114
-
115
- setHandler(cancelSignal, () => {
116
- cancelled = true;
117
- });
118
-
119
- setHandler(getProgressQuery, () => nodeStatuses);
120
-
121
- // Report workflow as RUNNING
122
- await reportActs.reportWorkflowStatus({ instanceId, status: 'RUNNING' });
123
-
124
- try {
125
- // Find trigger nodes and start from their successors
126
- const triggerNodes = dsl.nodes.filter((n) => n.type.endsWith('_trigger'));
127
- const startNodeIds = getSuccessorsFromNodes(dsl, triggerNodes.map((n) => n.id));
128
-
129
- // Execute the graph starting from trigger successors
130
- await executeGraph(dsl, startNodeIds, ctx, nodeStatuses, instanceId, approvalResults, () => cancelled);
131
-
132
- // Report success
133
- await reportActs.reportWorkflowStatus({ instanceId, status: 'COMPLETED', result: ctx });
134
- return { status: 'COMPLETED', context: ctx };
135
- } catch (err: any) {
136
- if (isCancellation(err) || cancelled) {
137
- await reportActs.reportWorkflowStatus({ instanceId, status: 'CANCELLED' });
138
- return { status: 'CANCELLED', context: ctx };
139
- }
140
- await reportActs.reportWorkflowStatus({ instanceId, status: 'FAILED', result: { error: err.message } });
141
- return { status: 'FAILED', context: ctx, error: err.message };
142
- }
143
- }
144
-
145
- // ─── Graph Execution Engine ───────────────────────────────────────────────────
146
-
147
- async function executeGraph(
148
- dsl: WorkflowDSL,
149
- startNodeIds: string[],
150
- ctx: Record<string, any>,
151
- nodeStatuses: Record<string, NodeStatus>,
152
- instanceId: number,
153
- approvalResults: Record<string, ApprovalSignalPayload>,
154
- isCancelled: () => boolean,
155
- ): Promise<void> {
156
- // BFS-style execution following edges
157
- const executed = new Set<string>();
158
- const queue = [...startNodeIds];
159
-
160
- // 防御:DSL 是循环图(A→B→A)或不可达 predecessor 时,
161
- // 当前 BFS 会把 nodeId 不停 push 回 queue 形成死循环。
162
- // 给一个上限:每个节点最多被处理 nodeCount * 2 次(包含一次重排队)。
163
- const MAX_ITERATIONS = Math.max(dsl.nodes.length * 10, 100);
164
- let iterations = 0;
165
-
166
- while (queue.length > 0) {
167
- if (isCancelled()) throw new CancellationScope().cancel;
168
-
169
- if (++iterations > MAX_ITERATIONS) {
170
- throw new Error(
171
- `executeGraph exceeded ${MAX_ITERATIONS} iterations — DSL likely contains a cycle ` +
172
- `or unreachable predecessors. Pending queue: [${queue.join(', ')}], executed: [${[...executed].join(', ')}]`,
173
- );
174
- }
175
-
176
- const nodeId = queue.shift()!;
177
- if (executed.has(nodeId)) continue;
178
-
179
- // Check if all predecessors have been executed
180
- const predecessors = dsl.edges.filter((e) => e.target === nodeId).map((e) => e.source);
181
- const allPredsDone = predecessors.every((p) => executed.has(p) || dsl.nodes.find((n) => n.id === p)?.type.endsWith('_trigger'));
182
- if (!allPredsDone) {
183
- // Re-queue for later
184
- queue.push(nodeId);
185
- continue;
186
- }
187
-
188
- const node = dsl.nodes.find((n) => n.id === nodeId);
189
- if (!node) continue;
190
-
191
- executed.add(nodeId);
192
- nodeStatuses[nodeId] = 'RUNNING';
193
-
194
- // Report node as RUNNING
195
- await reportActs.reportNodeStatus({
196
- instanceId,
197
- nodeId: node.id,
198
- nodeType: node.type,
199
- nodeLabel: node.label,
200
- status: 'RUNNING',
201
- input: ctx,
202
- });
203
-
204
- try {
205
- // Execute the node and get result
206
- const result = await executeNode(node, dsl, ctx, instanceId, approvalResults, nodeStatuses, isCancelled);
207
- nodeStatuses[nodeId] = 'COMPLETED';
208
-
209
- // Report node completion
210
- await reportActs.reportNodeStatus({
211
- instanceId,
212
- nodeId: node.id,
213
- nodeType: node.type,
214
- nodeLabel: node.label,
215
- status: 'COMPLETED',
216
- output: result?.output,
217
- });
218
-
219
- // Merge output into context
220
- if (result?.output) {
221
- ctx[nodeId] = result.output;
222
- }
223
-
224
- // Determine next nodes based on node type
225
- const nextNodeIds = getNextNodes(dsl, node, result?.branch);
226
- for (const nextId of nextNodeIds) {
227
- if (!executed.has(nextId)) queue.push(nextId);
228
- }
229
- } catch (err: any) {
230
- nodeStatuses[nodeId] = 'FAILED';
231
- await reportActs.reportNodeStatus({
232
- instanceId,
233
- nodeId: node.id,
234
- nodeType: node.type,
235
- nodeLabel: node.label,
236
- status: 'FAILED',
237
- error: { message: err.message, stack: err.stack },
238
- });
239
- throw err; // Propagate to workflow level
240
- }
241
- }
242
- }
243
-
244
- // ─── Node Execution Dispatcher ────────────────────────────────────────────────
245
-
246
- async function executeNode(
247
- node: DslNode,
248
- dsl: WorkflowDSL,
249
- ctx: Record<string, any>,
250
- instanceId: number,
251
- approvalResults: Record<string, ApprovalSignalPayload>,
252
- nodeStatuses: Record<string, NodeStatus>,
253
- isCancelled: () => boolean,
254
- ): Promise<{ output?: Record<string, any>; branch?: string } | undefined> {
255
- const config = resolveTemplates(node.config, ctx);
256
-
257
- switch (node.type) {
258
- // ─── Triggers (never reach executeNode; handled at graph entry) ──
259
- case 'cron_trigger':
260
- case 'webhook_trigger':
261
- case 'event_trigger':
262
- case 'manual_trigger':
263
- return { output: { triggeredAt: new Date().toISOString() } };
264
-
265
- // ─── Actions ────────────────────────────────────────────────────
266
- case 'http_request': {
267
- const result = await httpActs.httpRequest(config as any);
268
- return { output: result };
269
- }
270
-
271
- case 'db_query': {
272
- const result = await dbActs.dbQuery(config as any);
273
- return { output: result };
274
- }
275
-
276
- case 'db_execute': {
277
- const result = await dbActs.dbExecute(config as any);
278
- return { output: result };
279
- }
280
-
281
- case 'send_notification': {
282
- const result = await notifyActs.sendNotification(config as any);
283
- return { output: result };
284
- }
285
-
286
- case 'code': {
287
- const result = await codeActs.codeExecute({
288
- ...config,
289
- data: ctx,
290
- } as any);
291
- return { output: result };
292
- }
293
-
294
- // ─── Data Processing ────────────────────────────────────────────
295
- case 'set_variable': {
296
- const assignments = config.assignments as Array<{ name: string; value: string }>;
297
- const output: Record<string, any> = {};
298
- for (const { name, value } of assignments || []) {
299
- const resolved = resolveExpression(value, ctx);
300
- ctx[name] = resolved;
301
- output[name] = resolved;
302
- }
303
- return { output };
304
- }
305
-
306
- // ─── Conditions ─────────────────────────────────────────────────
307
- case 'if_else': {
308
- const conditions = config.conditions as Array<{ field: string; operator: string; value: string }> || [];
309
- const combineOp = (config.combineOperation as 'AND' | 'OR') || 'AND';
310
- const result = evaluateConditions(conditions, ctx, combineOp);
311
- return { output: { branch: result ? 'true' : 'false' }, branch: result ? 'true' : 'false' };
312
- }
313
-
314
- case 'switch': {
315
- const field = config.field as string;
316
- const cases = config.cases as Array<{ value: string; label: string }> || [];
317
- const fieldValue = String(getNestedValue(ctx, field) ?? '');
318
- const matched = cases.find((c) => c.value === fieldValue);
319
- const branch = matched ? matched.value : (config.defaultCase as string || 'default');
320
- return { output: { matchedCase: branch }, branch };
321
- }
322
-
323
- // ─── Flow Control ───────────────────────────────────────────────
324
- case 'delay': {
325
- const delayType = config.delayType as string;
326
- if (delayType === 'fixedDuration') {
327
- const duration = (config.duration as number) || 1;
328
- await sleep(duration * 1000);
329
- } else if (delayType === 'untilTime') {
330
- const targetTime = new Date(config.untilTime as string).getTime();
331
- const now = Date.now();
332
- if (targetTime > now) {
333
- await sleep(targetTime - now);
334
- }
335
- } else if (delayType === 'untilSignal') {
336
- // Wait for a specific signal (reuse approval mechanism)
337
- await condition(() => approvalResults[node.id] !== undefined, '24h');
338
- }
339
- return { output: { resumedAt: new Date().toISOString(), resumeReason: delayType } };
340
- }
341
-
342
- case 'parallel': {
343
- // Execute all outgoing branches in parallel
344
- const branches = getSuccessorsFromNodes(dsl, [node.id]);
345
- const branchPromises = branches.map(async (branchNodeId) => {
346
- await executeGraph(
347
- dsl,
348
- [branchNodeId],
349
- ctx,
350
- nodeStatuses,
351
- instanceId,
352
- approvalResults,
353
- isCancelled,
354
- );
355
- });
356
-
357
- const waitMode = config.waitMode as string || 'all';
358
- if (waitMode === 'all') {
359
- await Promise.all(branchPromises);
360
- } else if (waitMode === 'any') {
361
- await Promise.race(branchPromises);
362
- }
363
- return { output: { completedBranches: branches.length } };
364
- }
365
-
366
- case 'for_each': {
367
- const iteratorField = config.iteratorField as string;
368
- const items = getNestedValue(ctx, iteratorField) as any[];
369
- if (!Array.isArray(items)) {
370
- return { output: { results: [], error: 'iteratorField is not an array' } };
371
- }
372
-
373
- const concurrency = (config.concurrency as number) || 1;
374
- const errorPolicy = (config.errorPolicy as string) || 'stop';
375
- const results: any[] = [];
376
- const errors: any[] = [];
377
-
378
- // Process items with concurrency control
379
- for (let i = 0; i < items.length; i += concurrency) {
380
- const batch = items.slice(i, i + concurrency);
381
- const batchPromises = batch.map(async (item, batchIdx) => {
382
- const idx = i + batchIdx;
383
- // Set iteration context
384
- ctx['$currentItem'] = item;
385
- ctx['$index'] = idx;
386
- try {
387
- // Execute successor nodes for each item
388
- const successors = getSuccessorsFromNodes(dsl, [node.id]);
389
- if (successors.length > 0) {
390
- await executeGraph(dsl, successors, { ...ctx }, nodeStatuses, instanceId, approvalResults, isCancelled);
391
- }
392
- results.push({ index: idx, success: true });
393
- } catch (err: any) {
394
- if (errorPolicy === 'stop') throw err;
395
- errors.push({ index: idx, error: err.message });
396
- }
397
- });
398
- await Promise.all(batchPromises);
399
- }
400
-
401
- return { output: { results, errors, totalProcessed: items.length } };
402
- }
403
-
404
- case 'error_handler': {
405
- // Error handler is special — it wraps the execution of upstream nodes
406
- // In practice, the error handling is done at the graph execution level
407
- return { output: { handled: true } };
408
- }
409
-
410
- // ─── Approval ───────────────────────────────────────────────────
411
- case 'approval': {
412
- const timeoutSec = (config.timeout as number) || 86400; // Default 24h
413
- const timeoutAction = (config.timeoutAction as string) || 'auto_reject';
414
-
415
- // Wait for approval signal
416
- const gotApproval = await condition(
417
- () => approvalResults[node.id] !== undefined,
418
- `${timeoutSec}s`,
419
- );
420
-
421
- if (!gotApproval) {
422
- // Timeout
423
- if (timeoutAction === 'auto_approve') {
424
- return { output: { approved: true, approvedBy: 'system', comment: 'Auto-approved (timeout)' } };
425
- } else {
426
- return { output: { approved: false, approvedBy: 'system', comment: 'Auto-rejected (timeout)' } };
427
- }
428
- }
429
-
430
- const approval = approvalResults[node.id];
431
- return {
432
- output: { approved: approval.approved, approvedBy: approval.approvedBy, comment: approval.comment },
433
- branch: approval.approved ? 'true' : 'false',
434
- };
435
- }
436
-
437
- // ─── Sub Workflow ────────────────────────────────────────────────
438
- case 'sub_workflow': {
439
- const childWorkflowId = config.workflowId as string;
440
- const inputMapping = config.inputMapping as Record<string, string> || {};
441
- const childContext: Record<string, any> = {};
442
-
443
- for (const [key, path] of Object.entries(inputMapping)) {
444
- childContext[key] = getNestedValue(ctx, path);
445
- }
446
-
447
- // TODO: Load child workflow DSL from DB via activity, then execute as child workflow
448
- // For now, placeholder
449
- return { output: { status: 'not_implemented', childWorkflowId } };
450
- }
451
-
452
- default:
453
- return assertNever(node.type);
454
- }
455
- }
456
-
457
- // ─── Helper Functions ─────────────────────────────────────────────────────────
458
- // Pure functions are defined in ../dsl/helpers.ts and imported above.
459
-
460
- // ─── Cron Trigger Workflow ────────────────────────────────────────────────────
461
- //
462
- // Started by Temporal Schedules (registered via NestJS TemporalService.upsertCronSchedule).
463
- // Creates a t_workflow_instance row with creator='cron', then delegates to dslWorkflow
464
- // as a child workflow so it shares all the per-node logic.
465
-
466
- const cronActs = proxyActivities<typeof import('../activities')>({
467
- startToCloseTimeout: '15s',
468
- retry: { maximumAttempts: 5 },
469
- });
470
-
471
- export interface CronTriggerInput {
472
- workflowId: number;
473
- versionId: number;
474
- dsl: WorkflowDSL;
475
- scheduleId: string;
476
- }
477
-
478
- export async function cronTriggerWorkflow(
479
- input: CronTriggerInput,
480
- ): Promise<WorkflowResult> {
481
- const { instanceId } = await cronActs.createCronInstance({
482
- workflowId: input.workflowId,
483
- versionId: input.versionId,
484
- scheduleId: input.scheduleId,
485
- });
486
-
487
- return await executeChild(dslWorkflow, {
488
- workflowId: `wf-${input.workflowId}-${instanceId}`,
489
- args: [
490
- {
491
- instanceId,
492
- workflowId: input.workflowId,
493
- versionId: input.versionId,
494
- dsl: input.dsl,
495
- context: { scheduledAt: new Date().toISOString(), scheduleId: input.scheduleId },
496
- },
497
- ],
498
- });
499
- }
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2021",
4
- "module": "commonjs",
5
- "lib": ["ES2021"],
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "declaration": true,
14
- "declarationMap": true,
15
- "sourceMap": true
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }
@@ -1,8 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- root: './src',
6
- include: ['**/tests/**/*.test.ts'],
7
- },
8
- });