@gadmin2n/schematics 0.0.152 → 0.0.154

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 (110) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.git-blame-ignore-revs +5 -0
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/.nvmrc +1 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.local.md +8 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.md +4 -4
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.codegen +16 -9
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +21 -13
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.web +11 -11
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +1 -1
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/PRODUCT.md +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +1 -0
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +4 -4
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +22 -22
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.oxfmtrc.json +13 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.oxlintrc.json +17 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.seed-timestamp +1 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +14 -13
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/pnpm-workspace.yaml +23 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +2 -1
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +23 -44
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +25 -4
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +74 -64
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/auth.guard.ts +4 -1
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.ts +2 -2
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/tracing.ts +4 -6
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +2 -2
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.ts +1 -1
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.ts +6 -6
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.ts +1 -1
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.controller.ts +6 -6
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.util.ts +4 -4
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +1 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.ts +4 -4
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.ts +1 -1
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +2 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.ts +1 -1
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/page-transfer.service.ts +1 -1
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +1 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.ts +1 -1
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.ts +1 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.spec.ts +1 -1
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/subject-provider.errors.ts +4 -1
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/subject-provider.registry.ts +4 -4
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/subject-provider.types.ts +4 -4
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +1 -1
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +2 -2
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/parameter-binding.ts +4 -4
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +0 -7
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +17 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +58 -3
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +66 -6
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-data-edge.util.ts +5 -5
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +61 -149
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +1 -1
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-handle-validator.ts +2 -2
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.spec.ts +1 -1
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.ts +1 -1
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.spec.ts +2 -2
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +38 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +18 -7
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +1 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +142 -253
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +3 -1
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.ts +1 -1
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.ts +1 -1
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +4 -4
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/.oxfmtrc.json +12 -0
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/.oxlintrc.json +24 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/README.MD +2 -2
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/nginx.conf +0 -4
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +17 -17
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/pnpm-workspace.yaml +13 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/VanillaJSONEditor/index.jsx +4 -4
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/propPatcher.ts +36 -8
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +7 -5
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +6 -36
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/style.css +24 -7
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +0 -4
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +29 -7
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +29 -7
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +2 -2
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.css +1 -1
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +20 -17
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +38 -35
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +6 -1
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +24 -22
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +0 -11
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/StatusCards.tsx +24 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +20 -18
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +7 -0
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +28 -79
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +5 -0
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +0 -1
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +227 -97
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +62 -0
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/settings/index.tsx +73 -0
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +21 -46
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +2 -0
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +1 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +1 -1
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/test/setup.ts +1 -1
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/vite.config.ts +53 -41
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/vitest.config.ts +8 -2
  104. package/package.json +1 -1
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.eslintrc.js +0 -28
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierignore +0 -7
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierrc +0 -4
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/.eslintrc.json +0 -38
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/.prettierignore +0 -2
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/.prettierrc +0 -4
@@ -19,11 +19,7 @@ import {
19
19
  scopedIdempotencyKey,
20
20
  workflowDataHash,
21
21
  } from './manual-workflow-execution';
22
- import {
23
- TemporalService,
24
- WorkflowRetryCheckpoint,
25
- workflowTemporalId,
26
- } from './temporal.service';
22
+ import { TemporalService, workflowTemporalId } from './temporal.service';
27
23
  import { verifyWebhookSignature } from './webhook-signature.util';
28
24
  import { describeCallableWorkflow } from './workflow-callable';
29
25
  import { WorkflowCompilerService } from './workflow-compiler.service';
@@ -97,19 +93,22 @@ type CronSchedulePlan =
97
93
  version: { id: bigint; dsl: unknown };
98
94
  };
99
95
  const WORKFLOW_PUBLISH_LOCK_KEY = BigInt(734_221_907);
100
- const RETRYABLE_MAIN_FLOW_NODE_TYPES = new Set([
101
- 'http_request',
102
- 'db_execute',
103
- 'code',
104
- 'delay',
105
- 'sub_workflow',
106
- ]);
107
-
108
96
  interface WorkflowExecutionInternalOptions {
109
97
  operationScopeId?: string;
110
- retryCheckpoint?: WorkflowRetryCheckpoint;
111
98
  retryOfInstanceId?: bigint;
112
- retryFromNodeId?: string;
99
+ }
100
+
101
+ export type WorkflowServiceOutcome<T> =
102
+ | { kind: 'ok'; value: T }
103
+ | { kind: 'not_found'; message: string }
104
+ | { kind: 'conflict'; message: string }
105
+ | { kind: 'invalid_input'; message: string };
106
+
107
+ export interface WorkflowExecutionResult {
108
+ instanceId: string;
109
+ temporalRunId: string | null;
110
+ workflowVersionId: string;
111
+ status: string;
113
112
  }
114
113
 
115
114
  function dslUsesBusinessEvent(dsl: unknown, registrationCode: string): boolean {
@@ -325,8 +324,8 @@ function validateBusinessEventMatch(
325
324
  field.type === 'number'
326
325
  ? 'NUMBER'
327
326
  : field.type === 'boolean'
328
- ? 'BOOLEAN'
329
- : 'STRING';
327
+ ? 'BOOLEAN'
328
+ : 'STRING';
330
329
  let values: unknown[] = [right.value];
331
330
  if (operator === 'IN') {
332
331
  try {
@@ -564,7 +563,7 @@ export class WorkflowService {
564
563
  const latest = workflow.versions?.[0]?.dsl;
565
564
  return Boolean(
566
565
  workflow.dsl &&
567
- (!latest || !areWorkflowDslSourcesEqual(workflow.dsl, latest)),
566
+ (!latest || !areWorkflowDslSourcesEqual(workflow.dsl, latest)),
568
567
  );
569
568
  }
570
569
 
@@ -644,6 +643,7 @@ export class WorkflowService {
644
643
  {
645
644
  pending: number;
646
645
  running: number;
646
+ waiting_retry: number;
647
647
  completed: number;
648
648
  failed: number;
649
649
  cancelled: number;
@@ -654,6 +654,7 @@ export class WorkflowService {
654
654
  runsMap.set(row.workflowId, {
655
655
  pending: 0,
656
656
  running: 0,
657
+ waiting_retry: 0,
657
658
  completed: 0,
658
659
  failed: 0,
659
660
  cancelled: 0,
@@ -681,6 +682,7 @@ export class WorkflowService {
681
682
  runs: runsMap.get(item.id) || {
682
683
  pending: 0,
683
684
  running: 0,
685
+ waiting_retry: 0,
684
686
  completed: 0,
685
687
  failed: 0,
686
688
  cancelled: 0,
@@ -752,7 +754,7 @@ export class WorkflowService {
752
754
  typeof dsl === 'object' &&
753
755
  !Array.isArray(dsl) &&
754
756
  Array.isArray((dsl as { nodes?: unknown }).nodes)
755
- ? (dsl as { nodes: Array<{ type?: string }> }).nodes ?? []
757
+ ? ((dsl as { nodes: Array<{ type?: string }> }).nodes ?? [])
756
758
  : [];
757
759
  if (
758
760
  nodes.filter((node) => node.type === 'webhook_trigger').length === 1
@@ -1407,7 +1409,7 @@ export class WorkflowService {
1407
1409
  const activeInstances = await tx.workflowInstance.count({
1408
1410
  where: {
1409
1411
  workflowId,
1410
- status: { in: ['PENDING', 'RUNNING'] },
1412
+ status: { in: ['PENDING', 'RUNNING', 'WAITING_RETRY'] },
1411
1413
  },
1412
1414
  });
1413
1415
  if (activeInstances > 0)
@@ -1480,7 +1482,7 @@ export class WorkflowService {
1480
1482
  const config =
1481
1483
  override && typeof override === 'object' && !Array.isArray(override)
1482
1484
  ? asRecord(override)
1483
- : instanceConfigs.get(instanceId) ?? asRecord(node.config);
1485
+ : (instanceConfigs.get(instanceId) ?? asRecord(node.config));
1484
1486
  return String(config.workflowId ?? '') === String(workflowId);
1485
1487
  });
1486
1488
  };
@@ -1589,10 +1591,10 @@ export class WorkflowService {
1589
1591
  nodeType.outputs === null
1590
1592
  ? null
1591
1593
  : Array.isArray(nodeType.outputs)
1592
- ? nodeType.outputs.filter(
1593
- (output): output is string => typeof output === 'string',
1594
- )
1595
- : [],
1594
+ ? nodeType.outputs.filter(
1595
+ (output): output is string => typeof output === 'string',
1596
+ )
1597
+ : [],
1596
1598
  ]),
1597
1599
  ) as NodeOutputsMap;
1598
1600
  const handleErrors = validateWorkflowHandles(
@@ -1727,19 +1729,24 @@ export class WorkflowService {
1727
1729
  }
1728
1730
 
1729
1731
  async getStats(workflowId: bigint) {
1730
- const [running, failed, completed, total] = await Promise.all([
1731
- this.prisma.workflowInstance.count({
1732
- where: { workflowId, status: 'RUNNING' },
1733
- }),
1734
- this.prisma.workflowInstance.count({
1735
- where: { workflowId, status: 'FAILED' },
1736
- }),
1737
- this.prisma.workflowInstance.count({
1738
- where: { workflowId, status: 'COMPLETED' },
1739
- }),
1740
- this.prisma.workflowInstance.count({ where: { workflowId } }),
1741
- ]);
1742
- return { running, failed, completed, total };
1732
+ const [running, waitingRetry, failed, completed, total] = await Promise.all(
1733
+ [
1734
+ this.prisma.workflowInstance.count({
1735
+ where: { workflowId, status: 'RUNNING' },
1736
+ }),
1737
+ this.prisma.workflowInstance.count({
1738
+ where: { workflowId, status: 'WAITING_RETRY' },
1739
+ }),
1740
+ this.prisma.workflowInstance.count({
1741
+ where: { workflowId, status: 'FAILED' },
1742
+ }),
1743
+ this.prisma.workflowInstance.count({
1744
+ where: { workflowId, status: 'COMPLETED' },
1745
+ }),
1746
+ this.prisma.workflowInstance.count({ where: { workflowId } }),
1747
+ ],
1748
+ );
1749
+ return { running, waitingRetry, failed, completed, total };
1743
1750
  }
1744
1751
 
1745
1752
  async getWorkflowStatsByCode(code: string) {
@@ -1764,6 +1771,7 @@ export class WorkflowService {
1764
1771
  failed24h,
1765
1772
  running,
1766
1773
  pending,
1774
+ waitingRetry,
1767
1775
  ] = await Promise.all([
1768
1776
  this.prisma.workflowInstance.count(),
1769
1777
  this.prisma.workflowInstance.count({
@@ -1779,6 +1787,9 @@ export class WorkflowService {
1779
1787
  }),
1780
1788
  this.prisma.workflowInstance.count({ where: { status: 'RUNNING' } }),
1781
1789
  this.prisma.workflowInstance.count({ where: { status: 'PENDING' } }),
1790
+ this.prisma.workflowInstance.count({
1791
+ where: { status: 'WAITING_RETRY' },
1792
+ }),
1782
1793
  ]);
1783
1794
 
1784
1795
  // Success rate calculation
@@ -1828,6 +1839,7 @@ export class WorkflowService {
1828
1839
  },
1829
1840
  running,
1830
1841
  pending,
1842
+ waitingRetry,
1831
1843
  };
1832
1844
  }
1833
1845
 
@@ -1902,7 +1914,6 @@ export class WorkflowService {
1902
1914
  const executionContext = createWorkflowExecutionState(
1903
1915
  dto.workflow,
1904
1916
  internal?.operationScopeId,
1905
- internal?.retryCheckpoint,
1906
1917
  );
1907
1918
  const idempotencyRequestHash = dto.idempotencyKey
1908
1919
  ? workflowDataHash(
@@ -1938,7 +1949,6 @@ export class WorkflowService {
1938
1949
  }),
1939
1950
  ...(internal?.retryOfInstanceId && {
1940
1951
  retryOfInstanceId: internal.retryOfInstanceId,
1941
- retryFromNodeId: internal.retryFromNodeId,
1942
1952
  }),
1943
1953
  creator,
1944
1954
  },
@@ -2011,7 +2021,6 @@ export class WorkflowService {
2011
2021
  versionId: Number(instanceVersion.id),
2012
2022
  dsl: instanceVersion.dsl,
2013
2023
  context: instance.context as Record<string, any>,
2014
- retryCheckpoint: readRetryCheckpoint(instance.context),
2015
2024
  });
2016
2025
  } catch (error) {
2017
2026
  await this.workflowLifecycle.markFailed({
@@ -2250,7 +2259,6 @@ export class WorkflowService {
2250
2259
  select: {
2251
2260
  id: true,
2252
2261
  status: true,
2253
- retryFromNodeId: true,
2254
2262
  createdAt: true,
2255
2263
  },
2256
2264
  }),
@@ -2271,16 +2279,10 @@ export class WorkflowService {
2271
2279
  workflow: { name: instance.workflow.name },
2272
2280
  approvableNodeIds: approvableRequests.map((request) => request.nodeId),
2273
2281
  retryableNodeIds:
2274
- instance.status === 'FAILED' && dsl && retryAttempts.length === 0
2275
- ? listRetryableNodeIds(
2276
- dsl,
2277
- instance.nodeExecutions,
2278
- readRetryCheckpoint(instance.context),
2279
- )
2282
+ instance.status === 'WAITING_RETRY' && dsl
2283
+ ? listSameRunRetryableNodeIds(dsl, instance.nodeExecutions)
2280
2284
  : [],
2281
2285
  retryAttempts,
2282
- reusedNodeIds:
2283
- readRetryCheckpoint(instance.context)?.completedNodeIds ?? [],
2284
2286
  dsl,
2285
2287
  };
2286
2288
  }
@@ -2306,15 +2308,15 @@ export class WorkflowService {
2306
2308
  ...(Object.prototype.hasOwnProperty.call(result, 'output') && {
2307
2309
  result: result.output,
2308
2310
  }),
2309
- ...(instance.status === 'FAILED' && {
2311
+ ...(['FAILED', 'WAITING_RETRY'].includes(instance.status) && {
2310
2312
  error: {
2311
2313
  ...(typeof result.code === 'string' ? { code: result.code } : {}),
2312
2314
  message:
2313
2315
  typeof result.message === 'string'
2314
2316
  ? result.message
2315
2317
  : typeof result.error === 'string'
2316
- ? result.error
2317
- : 'Workflow execution failed',
2318
+ ? result.error
2319
+ : 'Workflow execution failed',
2318
2320
  },
2319
2321
  }),
2320
2322
  };
@@ -2347,10 +2349,10 @@ export class WorkflowService {
2347
2349
  if (!instance) throw new NotFoundException('Workflow instance not found');
2348
2350
  if (
2349
2351
  instance.status !== 'CANCELLED' &&
2350
- !['RUNNING', 'PENDING'].includes(instance.status)
2352
+ !['RUNNING', 'PENDING', 'WAITING_RETRY'].includes(instance.status)
2351
2353
  )
2352
2354
  throw new ConflictException(
2353
- 'Only RUNNING or PENDING instances can be cancelled',
2355
+ 'Only RUNNING, PENDING, or WAITING_RETRY instances can be cancelled',
2354
2356
  );
2355
2357
  if (
2356
2358
  instance.status !== 'CANCELLED' &&
@@ -2510,7 +2512,7 @@ export class WorkflowService {
2510
2512
 
2511
2513
  const reservedInstances = await this.prisma.workflowInstance.findMany({
2512
2514
  where: {
2513
- status: { in: ['PENDING', 'RUNNING'] },
2515
+ status: { in: ['PENDING', 'RUNNING', 'WAITING_RETRY'] },
2514
2516
  temporalRunId: { not: null },
2515
2517
  updatedAt: { lte: cutoff },
2516
2518
  },
@@ -2529,7 +2531,10 @@ export class WorkflowService {
2529
2531
  instance.temporalRunId!,
2530
2532
  );
2531
2533
  if (temporalState.kind === 'MISSING') {
2532
- if (instance.status === 'RUNNING') {
2534
+ if (
2535
+ instance.status === 'RUNNING' ||
2536
+ instance.status === 'WAITING_RETRY'
2537
+ ) {
2533
2538
  await this.failMissingRunningInstance(
2534
2539
  instance.id,
2535
2540
  instance.temporalRunId!,
@@ -2555,7 +2560,6 @@ export class WorkflowService {
2555
2560
  versionId: Number(instance.versionId),
2556
2561
  dsl: version.dsl,
2557
2562
  context: (instance.context ?? {}) as Record<string, any>,
2558
- retryCheckpoint: readRetryCheckpoint(instance.context),
2559
2563
  });
2560
2564
  } catch (error) {
2561
2565
  await this.failUnstartedInstance(
@@ -2599,7 +2603,7 @@ export class WorkflowService {
2599
2603
  ): Promise<void> {
2600
2604
  await this.workflowLifecycle.markFailed({
2601
2605
  instanceId,
2602
- expectedStatuses: ['RUNNING'],
2606
+ expectedStatuses: ['RUNNING', 'WAITING_RETRY'],
2603
2607
  temporalRunId,
2604
2608
  result: {
2605
2609
  code: 'TEMPORAL_EXECUTION_MISSING',
@@ -2685,7 +2689,7 @@ export class WorkflowService {
2685
2689
  if (state.status !== 'COMPLETED' && state.status !== 'CANCELLED') {
2686
2690
  await this.workflowLifecycle.markFailed({
2687
2691
  instanceId,
2688
- expectedStatuses: ['PENDING', 'RUNNING'],
2692
+ expectedStatuses: ['PENDING', 'RUNNING', 'WAITING_RETRY'],
2689
2693
  temporalRunId,
2690
2694
  result:
2691
2695
  state.result === undefined
@@ -2702,7 +2706,7 @@ export class WorkflowService {
2702
2706
  await this.prisma.workflowInstance.updateMany({
2703
2707
  where: {
2704
2708
  id: instanceId,
2705
- status: { in: ['PENDING', 'RUNNING'] },
2709
+ status: { in: ['PENDING', 'RUNNING', 'WAITING_RETRY'] },
2706
2710
  temporalRunId,
2707
2711
  },
2708
2712
  data: {
@@ -2785,59 +2789,72 @@ export class WorkflowService {
2785
2789
  );
2786
2790
  }
2787
2791
 
2788
- async retryInstanceFromNode(
2792
+ async retryFailedNode(
2789
2793
  instanceId: bigint,
2790
2794
  nodeId: string,
2791
- creator: string,
2792
2795
  temporalService: TemporalService,
2793
- ) {
2796
+ ): Promise<WorkflowServiceOutcome<WorkflowExecutionResult>> {
2794
2797
  const instance = await this.prisma.workflowInstance.findUnique({
2795
2798
  where: { id: instanceId },
2796
2799
  include: { nodeExecutions: true },
2797
2800
  });
2798
- if (!instance) throw new NotFoundException('Instance not found');
2799
- if (instance.status !== 'FAILED') {
2800
- throw new BadRequestException('只有失败的 Workflow 才能从节点重试');
2801
- }
2802
-
2803
- const existingRetry = await this.prisma.workflowInstance.findFirst({
2804
- where: { retryOfInstanceId: instance.id },
2805
- orderBy: { createdAt: 'desc' },
2806
- });
2807
- if (existingRetry) return workflowExecutionResult(existingRetry);
2801
+ if (!instance) return { kind: 'not_found', message: 'Instance not found' };
2802
+ if (instance.status !== 'WAITING_RETRY')
2803
+ return {
2804
+ kind: 'conflict',
2805
+ message: '只有等待重试的 Workflow 才能重试失败节点',
2806
+ };
2808
2807
 
2809
2808
  const version = await this.prisma.workflowVersion.findUnique({
2810
2809
  where: { id: instance.versionId },
2811
2810
  select: { dsl: true },
2812
2811
  });
2813
- if (!version) throw new NotFoundException('Workflow version not found');
2812
+ if (!version)
2813
+ return { kind: 'not_found', message: 'Workflow version not found' };
2814
2814
 
2815
- const retryCheckpoint = buildRetryCheckpoint(
2815
+ const invalidTargetMessage = validateSameRunRetryTarget(
2816
2816
  version.dsl,
2817
2817
  instance.nodeExecutions,
2818
2818
  nodeId,
2819
- readRetryCheckpoint(instance.context),
2820
2819
  );
2821
- const executionState = asRecord(instance.context);
2822
- const operationScopeId =
2823
- asRecord(executionState.system).operationScopeId ?? String(instance.id);
2824
-
2825
- return this.executeWorkflow(
2826
- instance.workflowId,
2827
- {
2828
- workflow: asRecord(executionState.workflow),
2829
- workflowVersionId: instance.versionId,
2830
- idempotencyKey: `retry:${instance.id}`,
2831
- },
2832
- creator,
2833
- temporalService,
2834
- {
2835
- operationScopeId: String(operationScopeId),
2836
- retryCheckpoint,
2837
- retryOfInstanceId: instance.id,
2838
- retryFromNodeId: nodeId,
2839
- },
2820
+ if (invalidTargetMessage)
2821
+ return { kind: 'invalid_input', message: invalidTargetMessage };
2822
+ if (!instance.temporalRunId)
2823
+ return {
2824
+ kind: 'conflict',
2825
+ message: '当前 Workflow 没有可继续的 Temporal Run',
2826
+ };
2827
+ const failedExecution = instance.nodeExecutions.find(
2828
+ (execution) =>
2829
+ execution.nodeId === nodeId && execution.status === 'FAILED',
2840
2830
  );
2831
+ if (!failedExecution)
2832
+ return { kind: 'invalid_input', message: '只能重试当前失败的节点' };
2833
+ try {
2834
+ await temporalService.signalWorkflow(
2835
+ instance.temporalRunId,
2836
+ 'retryNode',
2837
+ {
2838
+ nodeId,
2839
+ requestId: `retry-node:${
2840
+ instance.id
2841
+ }:${nodeId}:${failedExecution.updatedAt.toISOString()}`,
2842
+ },
2843
+ );
2844
+ } catch (error) {
2845
+ this.logger.error(
2846
+ `workflow_node_retry_signal_failed - instanceId: ${
2847
+ instance.id
2848
+ }, nodeId: ${nodeId}, error: ${
2849
+ error instanceof Error ? error.message : String(error)
2850
+ }`,
2851
+ );
2852
+ throw error;
2853
+ }
2854
+ this.logger.log(
2855
+ `workflow_node_retry_signal_sent - instanceId: ${instance.id}, nodeId: ${nodeId}`,
2856
+ );
2857
+ return { kind: 'ok', value: workflowExecutionResult(instance) };
2841
2858
  }
2842
2859
 
2843
2860
  async approveInstance(
@@ -2868,15 +2885,13 @@ export class WorkflowService {
2868
2885
  function createWorkflowExecutionState(
2869
2886
  value: Record<string, any> | undefined,
2870
2887
  operationScopeId?: string,
2871
- retryCheckpoint?: WorkflowRetryCheckpoint,
2872
2888
  ): Record<string, any> {
2873
2889
  return {
2874
2890
  workflow: value ?? {},
2875
2891
  nodes: {},
2876
- ...((operationScopeId || retryCheckpoint) && {
2892
+ ...(operationScopeId && {
2877
2893
  system: {
2878
- ...(operationScopeId && { operationScopeId }),
2879
- ...(retryCheckpoint && { retryCheckpoint }),
2894
+ operationScopeId,
2880
2895
  },
2881
2896
  }),
2882
2897
  };
@@ -2887,7 +2902,7 @@ function workflowExecutionResult(instance: {
2887
2902
  versionId: bigint;
2888
2903
  status: string;
2889
2904
  temporalRunId: string | null;
2890
- }) {
2905
+ }): WorkflowExecutionResult {
2891
2906
  return {
2892
2907
  instanceId: instance.id.toString(),
2893
2908
  temporalRunId: instance.temporalRunId,
@@ -2910,50 +2925,11 @@ function quoteLiteral(value: string): string {
2910
2925
  return `'${value.replace(/'/g, "''")}'`;
2911
2926
  }
2912
2927
 
2913
- function readRetryCheckpoint(
2914
- value: unknown,
2915
- ): WorkflowRetryCheckpoint | undefined {
2916
- const checkpoint = asRecord(asRecord(value).system).retryCheckpoint;
2917
- if (
2918
- !checkpoint ||
2919
- typeof checkpoint !== 'object' ||
2920
- Array.isArray(checkpoint)
2921
- )
2922
- return undefined;
2923
- const record = checkpoint as Record<string, unknown>;
2924
- if (
2925
- typeof record.startNodeId !== 'string' ||
2926
- !Array.isArray(record.completedNodeIds) ||
2927
- !record.completedNodeIds.every((item) => typeof item === 'string') ||
2928
- !record.outputs ||
2929
- typeof record.outputs !== 'object' ||
2930
- Array.isArray(record.outputs)
2931
- )
2932
- return undefined;
2933
- const outputs = record.outputs as Record<string, unknown>;
2934
- if (
2935
- !record.completedNodeIds.every((nodeId) =>
2936
- Object.prototype.hasOwnProperty.call(outputs, nodeId),
2937
- )
2938
- )
2939
- return undefined;
2940
- return {
2941
- startNodeId: record.startNodeId,
2942
- completedNodeIds: record.completedNodeIds,
2943
- outputs,
2944
- };
2945
- }
2946
-
2947
- function buildRetryCheckpoint(
2928
+ function validateSameRunRetryTarget(
2948
2929
  dslValue: unknown,
2949
- nodeExecutions: Array<{
2950
- nodeId: string;
2951
- status: string;
2952
- output: unknown;
2953
- }>,
2954
- retryFromNodeId: string,
2955
- inheritedCheckpoint?: WorkflowRetryCheckpoint,
2956
- ): WorkflowRetryCheckpoint {
2930
+ nodeExecutions: Array<{ nodeId: string; status: string }>,
2931
+ nodeId: string,
2932
+ ): string | undefined {
2957
2933
  const dsl = asRecord(dslValue);
2958
2934
  const nodes = Array.isArray(dsl.nodes)
2959
2935
  ? (dsl.nodes as Array<Record<string, unknown>>)
@@ -2961,128 +2937,41 @@ function buildRetryCheckpoint(
2961
2937
  const edges = Array.isArray(dsl.edges)
2962
2938
  ? (dsl.edges as Array<Record<string, unknown>>)
2963
2939
  : [];
2964
- const target = nodes.find((node) => node.id === retryFromNodeId);
2965
- if (!target) throw new BadRequestException('失败节点不在原发布版本中');
2966
- if (!RETRYABLE_MAIN_FLOW_NODE_TYPES.has(String(target.type))) {
2967
- throw new BadRequestException('该节点暂不支持从这里重试');
2968
- }
2940
+ const target = nodes.find((node) => node.id === nodeId);
2941
+ if (!target) return '失败节点不在原发布版本中';
2942
+ const nodeType = String(target.type ?? '');
2943
+ if (nodeType.endsWith('_trigger')) return '触发节点不能重试';
2944
+ const isCompositeOwner = nodeType === 'parallel' || nodeType === 'for_each';
2969
2945
  if (
2946
+ !isCompositeOwner &&
2970
2947
  edges.some(
2971
2948
  (edge) =>
2972
2949
  typeof edge.pairId === 'string' &&
2973
2950
  edge.pairId.length > 0 &&
2974
- (edge.source === retryFromNodeId || edge.target === retryFromNodeId),
2951
+ (edge.source === nodeId || edge.target === nodeId),
2975
2952
  )
2976
- ) {
2977
- throw new BadRequestException('暂不支持从循环或并行分支内部重试');
2978
- }
2979
-
2980
- const executionMap = new Map<
2981
- string,
2982
- { nodeId: string; status: string; output: unknown }
2983
- >();
2984
- for (const completedNodeId of inheritedCheckpoint?.completedNodeIds ?? []) {
2985
- executionMap.set(completedNodeId, {
2986
- nodeId: completedNodeId,
2987
- status: 'COMPLETED',
2988
- output: inheritedCheckpoint?.outputs[completedNodeId],
2989
- });
2990
- }
2991
- for (const execution of nodeExecutions) {
2992
- executionMap.set(execution.nodeId, execution);
2993
- }
2994
- if (executionMap.get(retryFromNodeId)?.status !== 'FAILED') {
2995
- throw new BadRequestException('只能从实际失败的节点重试');
2996
- }
2997
-
2998
- const mainPredecessors = new Map<string, string[]>();
2999
- for (const edge of edges) {
3000
- if (
3001
- (edge.kind === undefined || edge.kind === 'FLOW') &&
3002
- !edge.pairId &&
3003
- typeof edge.source === 'string' &&
3004
- typeof edge.target === 'string'
3005
- ) {
3006
- const predecessors = mainPredecessors.get(edge.target) ?? [];
3007
- predecessors.push(edge.source);
3008
- mainPredecessors.set(edge.target, predecessors);
3009
- }
3010
- }
3011
-
3012
- const nodeMap = new Map(
3013
- nodes
3014
- .filter((node) => typeof node.id === 'string')
3015
- .map((node) => [String(node.id), node]),
3016
- );
3017
- const ancestors = new Set<string>();
3018
- const pending = [...(mainPredecessors.get(retryFromNodeId) ?? [])];
3019
- while (pending.length > 0) {
3020
- const current = pending.pop();
3021
- if (!current) continue;
3022
- if (ancestors.has(current)) continue;
3023
- ancestors.add(current);
3024
- pending.push(...(mainPredecessors.get(current) ?? []));
3025
- }
3026
-
3027
- const completedNodeIds: string[] = [];
3028
- const outputs: Record<string, unknown> = {};
3029
- for (const node of nodes) {
3030
- const ancestorId = typeof node.id === 'string' ? node.id : '';
3031
- if (!ancestorId || !ancestors.has(ancestorId)) continue;
3032
- if (String(node.type).endsWith('_trigger')) continue;
3033
- const execution = executionMap.get(ancestorId);
3034
- if (execution?.status !== 'COMPLETED') {
3035
- throw new BadRequestException(
3036
- `节点“${String(
3037
- node.label || node.type || ancestorId,
3038
- )}”没有可复用的成功结果`,
3039
- );
3040
- }
3041
- completedNodeIds.push(ancestorId);
3042
- outputs[ancestorId] = execution.output;
3043
- }
3044
-
3045
- const immediatePredecessors = mainPredecessors.get(retryFromNodeId) ?? [];
3046
- if (immediatePredecessors.length > 1) {
3047
- throw new BadRequestException('失败节点存在多个上游,暂不支持从这里重试');
3048
- }
2953
+ )
2954
+ return '请从外层循环或并行节点重试';
3049
2955
  if (
3050
- immediatePredecessors.length === 1 &&
3051
- !String(nodeMap.get(immediatePredecessors[0])?.type ?? '').endsWith(
3052
- '_trigger',
3053
- ) &&
3054
- !Object.prototype.hasOwnProperty.call(outputs, immediatePredecessors[0])
3055
- ) {
3056
- throw new BadRequestException('失败节点的上一步没有可复用结果');
3057
- }
3058
-
3059
- return { startNodeId: retryFromNodeId, completedNodeIds, outputs };
2956
+ !nodeExecutions.some(
2957
+ (execution) =>
2958
+ execution.nodeId === nodeId && execution.status === 'FAILED',
2959
+ )
2960
+ )
2961
+ return '只能重试当前失败的节点';
2962
+ return undefined;
3060
2963
  }
3061
2964
 
3062
- function listRetryableNodeIds(
2965
+ function listSameRunRetryableNodeIds(
3063
2966
  dsl: unknown,
3064
- nodeExecutions: Array<{
3065
- nodeId: string;
3066
- status: string;
3067
- output: unknown;
3068
- }>,
3069
- inheritedCheckpoint?: WorkflowRetryCheckpoint,
2967
+ nodeExecutions: Array<{ nodeId: string; status: string }>,
3070
2968
  ): string[] {
3071
2969
  return nodeExecutions
3072
2970
  .filter((execution) => execution.status === 'FAILED')
3073
- .filter((execution) => {
3074
- try {
3075
- buildRetryCheckpoint(
3076
- dsl,
3077
- nodeExecutions,
3078
- execution.nodeId,
3079
- inheritedCheckpoint,
3080
- );
3081
- return true;
3082
- } catch {
3083
- return false;
3084
- }
3085
- })
2971
+ .filter(
2972
+ (execution) =>
2973
+ !validateSameRunRetryTarget(dsl, nodeExecutions, execution.nodeId),
2974
+ )
3086
2975
  .map((execution) => execution.nodeId);
3087
2976
  }
3088
2977
 
@@ -139,7 +139,9 @@ export class SubjectConfigValidatorService {
139
139
 
140
140
  const source = entry.source ?? 'STATIC';
141
141
  const provider =
142
- source === 'DYNAMIC' ? 'USER' : validProvider(entry.provider) ?? 'USER';
142
+ source === 'DYNAMIC'
143
+ ? 'USER'
144
+ : (validProvider(entry.provider) ?? 'USER');
143
145
  const rawRefs = entry.refs;
144
146
  if (source === 'DYNAMIC') {
145
147
  const inputKey = validNonBlankString(entry.inputKey);
@@ -171,7 +171,7 @@ export class WorkflowNodeInstanceController {
171
171
  @ACAttrFilter() acAttrFilter,
172
172
  @Req() request: AuthenticatedActorRequest,
173
173
  ) {
174
- /*
174
+ /*
175
175
  const origin = (await this.workflowNodeInstanceService.findUnique(id, {
176
176
  id: true,
177
177
  // 这里需要根据实际情况填写