@gadmin2n/schematics 0.0.136 → 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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
- package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
- 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
|
-
}
|