@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,184 +0,0 @@
1
- import { DslNode, DslEdge, WorkflowDSL } from './types';
2
-
3
- /**
4
- * DAG representation of a workflow DSL.
5
- * Provides topological ordering and graph traversal utilities.
6
- */
7
- export interface GraphNode {
8
- node: DslNode;
9
- inEdges: DslEdge[];
10
- outEdges: DslEdge[];
11
- }
12
-
13
- export interface ExecutionLevel {
14
- nodes: DslNode[];
15
- }
16
-
17
- /**
18
- * Build a graph (adjacency list) from DSL nodes + edges.
19
- */
20
- export function buildGraph(dsl: WorkflowDSL): Map<string, GraphNode> {
21
- const graph = new Map<string, GraphNode>();
22
-
23
- for (const node of dsl.nodes) {
24
- graph.set(node.id, { node, inEdges: [], outEdges: [] });
25
- }
26
-
27
- for (const edge of dsl.edges) {
28
- const sourceNode = graph.get(edge.source);
29
- const targetNode = graph.get(edge.target);
30
- if (sourceNode) sourceNode.outEdges.push(edge);
31
- if (targetNode) targetNode.inEdges.push(edge);
32
- }
33
-
34
- return graph;
35
- }
36
-
37
- /**
38
- * Find trigger nodes (nodes with category-type starting with *_trigger or no incoming edges).
39
- */
40
- export function findTriggerNodes(dsl: WorkflowDSL): DslNode[] {
41
- const graph = buildGraph(dsl);
42
- const triggers: DslNode[] = [];
43
-
44
- for (const [, gn] of graph) {
45
- if (
46
- gn.node.type.endsWith('_trigger') ||
47
- gn.inEdges.length === 0
48
- ) {
49
- triggers.push(gn.node);
50
- }
51
- }
52
-
53
- return triggers;
54
- }
55
-
56
- /**
57
- * Get successor node IDs from a given node, optionally filtering by sourceHandle (for branches).
58
- */
59
- export function getSuccessors(
60
- dsl: WorkflowDSL,
61
- nodeId: string,
62
- branchHandle?: string,
63
- ): string[] {
64
- return dsl.edges
65
- .filter((e) => {
66
- if (e.source !== nodeId) return false;
67
- if (branchHandle !== undefined) return e.sourceHandle === branchHandle;
68
- return true;
69
- })
70
- .map((e) => e.target);
71
- }
72
-
73
- /**
74
- * Topological sort using Kahn's algorithm.
75
- * Returns nodes in execution order. Nodes at the same "level" can be executed in parallel.
76
- */
77
- export function topologicalSort(dsl: WorkflowDSL, startNodeIds: string[]): ExecutionLevel[] {
78
- const graph = buildGraph(dsl);
79
- const levels: ExecutionLevel[] = [];
80
-
81
- // Calculate in-degree (only count edges from nodes that are reachable from start)
82
- const reachable = getReachableNodes(dsl, startNodeIds);
83
- const inDegree = new Map<string, number>();
84
-
85
- for (const id of reachable) {
86
- inDegree.set(id, 0);
87
- }
88
-
89
- for (const edge of dsl.edges) {
90
- if (reachable.has(edge.source) && reachable.has(edge.target)) {
91
- inDegree.set(edge.target, (inDegree.get(edge.target) || 0) + 1);
92
- }
93
- }
94
-
95
- // Start with provided start nodes (triggers already fired)
96
- let currentLevel = startNodeIds.filter((id) => reachable.has(id));
97
-
98
- while (currentLevel.length > 0) {
99
- const levelNodes: DslNode[] = [];
100
- const nextLevel: string[] = [];
101
-
102
- for (const nodeId of currentLevel) {
103
- const gn = graph.get(nodeId);
104
- if (gn) levelNodes.push(gn.node);
105
-
106
- // Decrease in-degree of successors
107
- const successors = dsl.edges
108
- .filter((e) => e.source === nodeId && reachable.has(e.target))
109
- .map((e) => e.target);
110
-
111
- for (const succ of successors) {
112
- const deg = (inDegree.get(succ) || 0) - 1;
113
- inDegree.set(succ, deg);
114
- if (deg === 0) {
115
- nextLevel.push(succ);
116
- }
117
- }
118
- }
119
-
120
- if (levelNodes.length > 0) {
121
- levels.push({ nodes: levelNodes });
122
- }
123
-
124
- currentLevel = [...new Set(nextLevel)];
125
- }
126
-
127
- return levels;
128
- }
129
-
130
- /**
131
- * Get all node IDs reachable from a set of start nodes via BFS.
132
- */
133
- export function getReachableNodes(dsl: WorkflowDSL, startNodeIds: string[]): Set<string> {
134
- const visited = new Set<string>();
135
- const queue = [...startNodeIds];
136
-
137
- while (queue.length > 0) {
138
- const current = queue.shift()!;
139
- if (visited.has(current)) continue;
140
- visited.add(current);
141
-
142
- const successors = dsl.edges
143
- .filter((e) => e.source === current)
144
- .map((e) => e.target);
145
-
146
- for (const succ of successors) {
147
- if (!visited.has(succ)) queue.push(succ);
148
- }
149
- }
150
-
151
- return visited;
152
- }
153
-
154
- /**
155
- * Get the sub-graph (nodes + edges) reachable from given start nodes,
156
- * stopping at a set of boundary nodes.
157
- */
158
- export function getSubGraph(
159
- dsl: WorkflowDSL,
160
- startNodeIds: string[],
161
- stopNodeIds: Set<string> = new Set(),
162
- ): WorkflowDSL {
163
- const visited = new Set<string>();
164
- const queue = [...startNodeIds];
165
-
166
- while (queue.length > 0) {
167
- const current = queue.shift()!;
168
- if (visited.has(current) || stopNodeIds.has(current)) continue;
169
- visited.add(current);
170
-
171
- const successors = dsl.edges
172
- .filter((e) => e.source === current)
173
- .map((e) => e.target);
174
-
175
- for (const succ of successors) {
176
- if (!visited.has(succ) && !stopNodeIds.has(succ)) queue.push(succ);
177
- }
178
- }
179
-
180
- return {
181
- nodes: dsl.nodes.filter((n) => visited.has(n.id)),
182
- edges: dsl.edges.filter((e) => visited.has(e.source) && visited.has(e.target)),
183
- };
184
- }
@@ -1,133 +0,0 @@
1
- /**
2
- * Pure helper functions for the DSL workflow interpreter.
3
- *
4
- * These functions are deterministic and have no side effects,
5
- * making them safe to import in Temporal workflow code and easy to unit test.
6
- */
7
- import type { WorkflowDSL, DslNode } from './types';
8
-
9
- // ─── Template Resolution ─────────────────────────────────────────────────────
10
-
11
- /**
12
- * Recursively resolve {{path}} template variables in an object.
13
- */
14
- export function resolveTemplates(obj: any, ctx: Record<string, any>): any {
15
- if (typeof obj === 'string') {
16
- return obj.replace(/\{\{(.+?)\}\}/g, (_, path) => {
17
- const val = getNestedValue(ctx, path.trim());
18
- return val !== undefined ? String(val) : '';
19
- });
20
- }
21
- if (Array.isArray(obj)) return obj.map((v) => resolveTemplates(v, ctx));
22
- if (obj && typeof obj === 'object') {
23
- const result: Record<string, any> = {};
24
- for (const [k, v] of Object.entries(obj)) {
25
- result[k] = resolveTemplates(v, ctx);
26
- }
27
- return result;
28
- }
29
- return obj;
30
- }
31
-
32
- /**
33
- * Resolve a simple path expression against the context.
34
- * If the path resolves to a value, return it; otherwise return the raw string.
35
- */
36
- export function resolveExpression(expr: string, ctx: Record<string, any>): any {
37
- const value = getNestedValue(ctx, expr);
38
- if (value !== undefined) return value;
39
- return expr;
40
- }
41
-
42
- // ─── Object Path Access ──────────────────────────────────────────────────────
43
-
44
- /**
45
- * Get a nested value from an object using dot notation.
46
- * e.g. getNestedValue({ a: { b: 1 } }, "a.b") → 1
47
- */
48
- export function getNestedValue(obj: Record<string, any>, path: string): any {
49
- const parts = path.split('.');
50
- let current: any = obj;
51
- for (const part of parts) {
52
- if (current == null) return undefined;
53
- current = current[part];
54
- }
55
- return current;
56
- }
57
-
58
- // ─── Condition Evaluation ────────────────────────────────────────────────────
59
-
60
- /**
61
- * Evaluate multiple condition rules with AND/OR combining.
62
- */
63
- export function evaluateConditions(
64
- conditions: Array<{ field: string; operator: string; value: string }>,
65
- ctx: Record<string, any>,
66
- combineOp: 'AND' | 'OR',
67
- ): boolean {
68
- if (conditions.length === 0) return true;
69
- const results = conditions.map((rule) => {
70
- const fieldValue = getNestedValue(ctx, rule.field);
71
- return evaluateRule(fieldValue, rule.operator, rule.value);
72
- });
73
- return combineOp === 'AND' ? results.every(Boolean) : results.some(Boolean);
74
- }
75
-
76
- /**
77
- * Evaluate a single condition rule.
78
- */
79
- export function evaluateRule(fieldValue: any, operator: string, compareValue: string): boolean {
80
- switch (operator) {
81
- case 'equals':
82
- case '==':
83
- return String(fieldValue) === compareValue;
84
- case 'notEquals':
85
- case '!=':
86
- return String(fieldValue) !== compareValue;
87
- case 'contains':
88
- return String(fieldValue).includes(compareValue);
89
- case 'notContains':
90
- return !String(fieldValue).includes(compareValue);
91
- case 'greaterThan':
92
- case '>':
93
- return Number(fieldValue) > Number(compareValue);
94
- case 'lessThan':
95
- case '<':
96
- return Number(fieldValue) < Number(compareValue);
97
- case 'greaterThanOrEqual':
98
- case '>=':
99
- return Number(fieldValue) >= Number(compareValue);
100
- case 'lessThanOrEqual':
101
- case '<=':
102
- return Number(fieldValue) <= Number(compareValue);
103
- case 'isEmpty':
104
- return fieldValue == null || fieldValue === '';
105
- case 'isNotEmpty':
106
- return fieldValue != null && fieldValue !== '';
107
- default:
108
- return false;
109
- }
110
- }
111
-
112
- // ─── Graph Navigation ────────────────────────────────────────────────────────
113
-
114
- /**
115
- * Get successor node IDs from a list of source node IDs.
116
- */
117
- export function getSuccessorsFromNodes(dsl: WorkflowDSL, nodeIds: string[]): string[] {
118
- return dsl.edges.filter((e) => nodeIds.includes(e.source)).map((e) => e.target);
119
- }
120
-
121
- /**
122
- * Determine next nodes to execute based on the current node and optional branch.
123
- * For condition nodes, follows only the matching branch edge.
124
- */
125
- export function getNextNodes(dsl: WorkflowDSL, node: DslNode, branch?: string): string[] {
126
- if (branch !== undefined) {
127
- const branchEdges = dsl.edges.filter(
128
- (e) => e.source === node.id && e.sourceHandle === branch,
129
- );
130
- if (branchEdges.length > 0) return branchEdges.map((e) => e.target);
131
- }
132
- return dsl.edges.filter((e) => e.source === node.id).map((e) => e.target);
133
- }
@@ -1,96 +0,0 @@
1
- /**
2
- * Node type metadata. Source of truth for which node types exist and
3
- * what handle ids each one declares.
4
- *
5
- * outputs semantics:
6
- * null = dynamic (handles derived from config at render/validate time, e.g. switch)
7
- * [] = single anonymous source (no named sourceHandle on outgoing edges)
8
- * ['a','b'] = enumerated named handles (each outgoing edge MUST have sourceHandle ∈ outputs)
9
- *
10
- * MUST stay in sync with server/seed/workflow-node-types.ts and the
11
- * t_workflow_node_type.outputs column. Validators read this map.
12
- */
13
- export interface NodeTypeMeta {
14
- outputs: string[] | null;
15
- }
16
-
17
- /**
18
- * Canonical tuple of all workflow node types.
19
- *
20
- * Preserved as an `as const` tuple (in addition to NODE_TYPE_META below)
21
- * so that consumers like DslNode.type and the executeNode switch keep
22
- * compile-time literal-union types and exhaustiveness checks.
23
- *
24
- * The order and contents MUST match the keys of NODE_TYPE_META.
25
- */
26
- export const ALL_NODE_TYPES = [
27
- // TRIGGER
28
- 'cron_trigger',
29
- 'webhook_trigger',
30
- 'event_trigger',
31
- 'manual_trigger',
32
- // ACTION (calls an activity)
33
- 'http_request',
34
- 'db_query',
35
- 'db_execute',
36
- 'send_notification',
37
- 'code',
38
- // DATA PROCESSING (inline logic)
39
- 'set_variable',
40
- // CONDITION (inline logic)
41
- 'if_else',
42
- 'switch',
43
- // FLOW CONTROL
44
- 'for_each',
45
- 'delay',
46
- 'parallel',
47
- 'error_handler',
48
- // APPROVAL (Temporal signal)
49
- 'approval',
50
- // SUB WORKFLOW
51
- 'sub_workflow',
52
- ] as const;
53
-
54
- /** Union of all supported node type strings. */
55
- export type NodeType = (typeof ALL_NODE_TYPES)[number];
56
-
57
- export const NODE_TYPE_META: Record<NodeType, NodeTypeMeta> = {
58
- cron_trigger: { outputs: [] },
59
- webhook_trigger: { outputs: [] },
60
- event_trigger: { outputs: [] },
61
- manual_trigger: { outputs: [] },
62
- http_request: { outputs: [] },
63
- db_query: { outputs: [] },
64
- db_execute: { outputs: [] },
65
- send_notification: { outputs: [] },
66
- code: { outputs: [] },
67
- set_variable: { outputs: [] },
68
- if_else: { outputs: ['true', 'false'] },
69
- switch: { outputs: null },
70
- for_each: { outputs: [] },
71
- delay: { outputs: [] },
72
- parallel: { outputs: [] },
73
- error_handler: { outputs: [] },
74
- approval: { outputs: [] },
75
- sub_workflow: { outputs: [] },
76
- };
77
-
78
- /** Node types that require a Temporal activity call. */
79
- export const ACTIVITY_NODE_TYPES = [
80
- 'http_request',
81
- 'db_query',
82
- 'db_execute',
83
- 'send_notification',
84
- 'code',
85
- ] as const satisfies readonly NodeType[];
86
-
87
- export type ActivityNodeType = (typeof ACTIVITY_NODE_TYPES)[number];
88
-
89
- /**
90
- * Compile-time exhaustiveness check.
91
- * Place in the `default` of a switch over NodeType — if a case is missing,
92
- * TypeScript emits: "Argument of type 'X' is not assignable to parameter of type 'never'."
93
- */
94
- export function assertNever(x: never): never {
95
- throw new Error(`Unhandled node type: ${x}`);
96
- }
@@ -1,77 +0,0 @@
1
- // ─── DSL Types (mirrors frontend WorkflowDSL) ─────────────────────────────────
2
-
3
- import type { NodeType } from './node-types';
4
-
5
- export interface DslNode {
6
- id: string;
7
- type: NodeType;
8
- label: string;
9
- position: { x: number; y: number };
10
- config: Record<string, any>;
11
- }
12
-
13
- export interface DslEdge {
14
- id: string;
15
- source: string;
16
- target: string;
17
- sourceHandle?: string; // for condition branches: "true"/"false" or case labels
18
- label?: string;
19
- }
20
-
21
- export interface WorkflowDSL {
22
- nodes: DslNode[];
23
- edges: DslEdge[];
24
- }
25
-
26
- // ─── Workflow Execution Types ──────────────────────────────────────────────────
27
-
28
- export interface WorkflowInput {
29
- instanceId: number;
30
- workflowId: number;
31
- versionId: number;
32
- dsl: WorkflowDSL;
33
- context: Record<string, any>;
34
- }
35
-
36
- export interface WorkflowResult {
37
- status: 'COMPLETED' | 'FAILED' | 'CANCELLED';
38
- context: Record<string, any>;
39
- error?: string;
40
- }
41
-
42
- // ─── Node Execution Context ───────────────────────────────────────────────────
43
-
44
- export interface NodeExecutionResult {
45
- output: Record<string, any>;
46
- branch?: string; // For condition nodes: which branch to take
47
- }
48
-
49
- export type NodeStatus = 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
50
-
51
- // ─── Activity Input Types ─────────────────────────────────────────────────────
52
-
53
- export interface ReportNodeStatusInput {
54
- instanceId: number;
55
- nodeId: string;
56
- nodeType: string;
57
- nodeLabel?: string;
58
- status: NodeStatus;
59
- input?: Record<string, any>;
60
- output?: Record<string, any>;
61
- error?: Record<string, any>;
62
- }
63
-
64
- export interface ReportWorkflowStatusInput {
65
- instanceId: number;
66
- status: 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
67
- result?: Record<string, any>;
68
- }
69
-
70
- // ─── Signals ──────────────────────────────────────────────────────────────────
71
-
72
- export interface ApprovalSignalPayload {
73
- nodeId: string;
74
- approved: boolean;
75
- comment?: string;
76
- approvedBy: string;
77
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * DSL handle validator. Confirms each edge's sourceHandle matches the
3
- * source node's declared outputs (or, for dynamic nodes like switch,
4
- * matches a value derived from config).
5
- *
6
- * Used at workflow publish time (server-side via a duplicated copy)
7
- * and optionally during DSL parsing on the worker side.
8
- */
9
-
10
- import type { WorkflowDSL, DslNode } from './types';
11
- import { NODE_TYPE_META } from './node-types';
12
-
13
- export type DslValidationCode =
14
- | 'EXTRA_HANDLE' // node has [] outputs, but edge set sourceHandle
15
- | 'MISSING_HANDLE' // node has named outputs, but edge has no sourceHandle (or empty string)
16
- | 'UNKNOWN_HANDLE'; // edge sourceHandle is set but ∉ declared outputs
17
-
18
- export interface DslValidationError {
19
- edgeId: string;
20
- source: string;
21
- target: string;
22
- code: DslValidationCode;
23
- reason: string;
24
- }
25
-
26
- /**
27
- * Validate every edge's sourceHandle against the source node's outputs metadata.
28
- *
29
- * Rules per source node's outputs:
30
- * - outputs === null: dynamic — handles must come from deriveDynamicOutputs(node).
31
- * Edge sourceHandle MUST be present and ∈ derived list, OR if the
32
- * derived list is empty (config not yet filled), no constraint.
33
- * - outputs === []: edge sourceHandle MUST be undefined (no named handles allowed).
34
- * - outputs.length>0: edge sourceHandle MUST be present and ∈ outputs.
35
- *
36
- * Returns an array of errors (empty if DSL is valid).
37
- */
38
- export function validateDslHandles(dsl: WorkflowDSL): DslValidationError[] {
39
- const errors: DslValidationError[] = [];
40
- const nodeMap = new Map<string, DslNode>(dsl.nodes.map((n) => [n.id, n]));
41
-
42
- for (const edge of dsl.edges) {
43
- const src = nodeMap.get(edge.source);
44
- if (!src) continue; // separate concern (orphan edge); not this validator's job
45
-
46
- const meta = NODE_TYPE_META[src.type];
47
- if (!meta) continue; // guards against retired/unknown node types from older DSLs
48
-
49
- const handles = meta.outputs === null
50
- ? deriveDynamicOutputs(src)
51
- : meta.outputs;
52
-
53
- if (meta.outputs !== null && handles.length === 0) {
54
- // Single anonymous output: sourceHandle must be absent
55
- if (edge.sourceHandle != null && edge.sourceHandle !== '') {
56
- errors.push({
57
- edgeId: edge.id,
58
- source: edge.source,
59
- target: edge.target,
60
- code: 'EXTRA_HANDLE',
61
- reason: `Node "${src.type}" has no named output handles; edge must not set sourceHandle (got "${edge.sourceHandle}").`,
62
- });
63
- }
64
- continue;
65
- }
66
-
67
- // Enumerated or dynamic non-empty: sourceHandle required and must match
68
- if (handles.length === 0) {
69
- // Dynamic + config not yet filled: skip rather than block edits in progress
70
- continue;
71
- }
72
- if (edge.sourceHandle == null || edge.sourceHandle === '') {
73
- errors.push({
74
- edgeId: edge.id,
75
- source: edge.source,
76
- target: edge.target,
77
- code: 'MISSING_HANDLE',
78
- reason: `Node "${src.type}" requires a sourceHandle; choose one of: ${handles.join(', ')}.`,
79
- });
80
- continue;
81
- }
82
- if (!handles.includes(edge.sourceHandle)) {
83
- errors.push({
84
- edgeId: edge.id,
85
- source: edge.source,
86
- target: edge.target,
87
- code: 'UNKNOWN_HANDLE',
88
- reason: `Node "${src.type}" sourceHandle "${edge.sourceHandle}" is not in declared outputs: ${handles.join(', ')}.`,
89
- });
90
- }
91
- }
92
-
93
- return errors;
94
- }
95
-
96
- /**
97
- * For dynamic-output nodes (currently just `switch`), derive the list of
98
- * handle ids from the node's config.
99
- */
100
- function deriveDynamicOutputs(node: DslNode): string[] {
101
- if (node.type === 'switch') {
102
- const cases = (node.config as any)?.cases as Array<{ value?: string }> | undefined;
103
- if (!Array.isArray(cases)) return [];
104
- return cases
105
- .map((c) => c?.value)
106
- .filter((v): v is string => typeof v === 'string' && v.length > 0);
107
- }
108
- return [];
109
- }
@@ -1,101 +0,0 @@
1
- import * as http from 'http';
2
- import { NativeConnection, Worker } from '@temporalio/worker';
3
- import * as activities from './activities';
4
- import { config } from './config';
5
- import { startOutboxPoller } from './outbox-poller';
6
-
7
- // 顶层状态:health server 与 shutdown handler 共享
8
- let isReady = false;
9
- let isShuttingDown = false;
10
-
11
- async function run() {
12
- console.log(`[Worker] Connecting to Temporal at ${config.temporal.address}...`);
13
-
14
- const connection = await NativeConnection.connect({
15
- address: config.temporal.address,
16
- });
17
-
18
- const worker = await Worker.create({
19
- connection,
20
- namespace: config.temporal.namespace,
21
- taskQueue: config.temporal.taskQueue,
22
- workflowsPath: require.resolve('./workflows/dsl-workflow'),
23
- activities,
24
- });
25
-
26
- console.log(`[Worker] Started. Polling task queue: ${config.temporal.taskQueue}`);
27
- console.log(`[Worker] Namespace: ${config.temporal.namespace}`);
28
-
29
- // event_trigger workflow 的 outbox poller
30
- const outboxPoller = await startOutboxPoller();
31
-
32
- // K8s probe 用的 HTTP health server
33
- const healthServer = startHealthServer();
34
-
35
- // 优雅关闭协调器
36
- const shutdown = async (signal: string) => {
37
- if (isShuttingDown) return;
38
- isShuttingDown = true;
39
- console.log(`[Worker] ${signal} received, beginning graceful shutdown...`);
40
-
41
- // 1. worker.shutdown() 是同步调用,触发 worker.run() 在当前 activity/workflow 完成后 resolve
42
- worker.shutdown();
43
- console.log('[Worker] worker.shutdown() called, draining in-flight tasks');
44
-
45
- // 2. 停止 outbox poller,释放其 PG / Temporal client 连接
46
- try {
47
- await outboxPoller.stop();
48
- } catch (err) {
49
- console.error('[Worker] OutboxPoller stop error:', err);
50
- }
51
-
52
- // 3. 关闭 health server(停止 K8s probe 请求)
53
- await new Promise<void>((resolve) => healthServer.close(() => resolve()));
54
- console.log('[Worker] Health server closed');
55
- };
56
-
57
- process.on('SIGTERM', () => void shutdown('SIGTERM'));
58
- process.on('SIGINT', () => void shutdown('SIGINT'));
59
-
60
- isReady = true;
61
-
62
- // worker.run() 会一直阻塞到 worker.shutdown() 被调用并且 in-flight 任务全部 drain
63
- await worker.run();
64
-
65
- // worker.run() resolve 之后再关闭 NativeConnection(worker 仍在用它的时候不能关)
66
- await connection.close();
67
- console.log('[Worker] Temporal connection closed. Bye.');
68
- }
69
-
70
- /**
71
- * 最简 HTTP health 端点,用于 K8s liveness/readiness probe。
72
- * - /health/live — 进程存活就返回 200
73
- * - /health/ready — 处于运行中且未进入 shutdown 才返回 200,否则 503
74
- */
75
- function startHealthServer(): http.Server {
76
- const port = Number(process.env.WORKER_HEALTH_PORT) || 8081;
77
- const server = http.createServer((req, res) => {
78
- if (req.url === '/health/live') {
79
- res.writeHead(200, { 'Content-Type': 'application/json' });
80
- res.end(JSON.stringify({ status: 'ok' }));
81
- return;
82
- }
83
- if (req.url === '/health/ready') {
84
- const ok = isReady && !isShuttingDown;
85
- res.writeHead(ok ? 200 : 503, { 'Content-Type': 'application/json' });
86
- res.end(JSON.stringify({ status: ok ? 'ok' : 'shutting_down' }));
87
- return;
88
- }
89
- res.writeHead(404);
90
- res.end();
91
- });
92
- server.listen(port, () => {
93
- console.log(`[Worker] Health server listening on :${port}`);
94
- });
95
- return server;
96
- }
97
-
98
- run().catch((err) => {
99
- console.error('[Worker] Fatal error:', err);
100
- process.exit(1);
101
- });