@gadmin2n/schematics 0.0.141 → 0.0.143

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 (140) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +5 -23
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +4 -2
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/page-manage.ts +36 -1
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +14 -14
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +18 -13
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +14 -14
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +1 -1
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +67 -404
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +16 -40
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +12 -124
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +3 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +0 -21
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.ts +3 -10
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/codeRuntime.module.ts +1 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.spec.ts +48 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.ts +96 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.controller.spec.ts +1 -59
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.controller.ts +8 -43
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.dto.ts +0 -23
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction.service.ts +2 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-wait.handler.ts +2 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/externalInteraction.module.ts +7 -3
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.controller.ts +2 -2
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.service.spec.ts +18 -123
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.service.ts +4 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.controller.ts +3 -10
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/httpRequest.module.ts +1 -2
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.controller.ts +0 -10
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +12 -4
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.errors.ts +1 -1
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.external-decision.spec.ts +42 -209
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.ts +108 -225
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/humanApproval.module.ts +0 -2
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.ts +27 -22
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.dto.ts +9 -1
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/runtime-token.guard.spec.ts +30 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/runtime-token.guard.ts +8 -15
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +0 -2
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/runtime-connectivity-probe.service.spec.ts +14 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/runtime-connectivity-probe.service.ts +37 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +41 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +53 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +7 -2
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/workflow-runtime-health.controller.ts +21 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +2 -2
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-callable.spec.ts +16 -88
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-callable.ts +23 -59
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +125 -39
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +95 -28
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-handle-validator.ts +0 -20
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.spec.ts +23 -26
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.ts +6 -52
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +2 -2
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +24 -0
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +26 -21
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/widget-catalog.service.spec.ts +0 -1
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/widget-catalog.service.ts +0 -1
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowRuntimeConfig/workflow-runtime-config.service.spec.ts +32 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowRuntimeConfig/workflow-runtime-config.service.ts +41 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowRuntimeConfig/workflowRuntimeConfig.module.ts +10 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.build.json +10 -1
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +16 -12
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/PageHeader.tsx +1 -1
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/inspectorActions.ts +19 -19
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +1 -1
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +15 -11
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +0 -6
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/AddDataModal.tsx +3 -3
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/AddPageModal.tsx +1 -1
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/DeleteDataConfirm.tsx +1 -1
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/EditDataModal.tsx +1 -1
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +1 -0
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +85 -95
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +85 -95
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +2 -2
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +21 -2
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/game/list.tsx +21 -2
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/inbox.tsx +14 -66
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +4 -4
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +3 -3
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +3 -3
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.test.tsx +29 -0
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +4 -2
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +4 -6
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.test.tsx +3 -0
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.tsx +3 -1
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +4 -6
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NewNodeDefaults.test.tsx +23 -92
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.tsx +200 -0
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +61 -136
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +96 -195
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeTypeBadge.tsx +36 -0
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/CreateNodeInstanceModal.tsx +5 -10
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +47 -22
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/create.tsx +7 -5
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/edit.tsx +8 -6
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +35 -0
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/default-widgets.tsx +1 -6
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApproverSourceEditor.tsx +1 -4
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.tsx +1 -1
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.tsx +1 -1
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalWaitConfigEditor.tsx +82 -259
  104. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HttpRequestConfigEditor.tsx +1 -2
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +7 -6
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +77 -71
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.test.tsx +10 -57
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.tsx +8 -273
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +54 -0
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +110 -20
  111. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +4 -34
  112. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.test.ts +3 -10
  113. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.ts +2 -37
  114. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/list.tsx +26 -2
  115. package/package.json +1 -1
  116. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.controller.ts +0 -89
  117. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.dto.ts +0 -68
  118. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.service.spec.ts +0 -177
  119. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.service.ts +0 -283
  120. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approvalChannel.module.ts +0 -10
  121. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/ApprovalChannelModal.test.tsx +0 -84
  122. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/ApprovalChannelModal.tsx +0 -141
  123. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/approval-channel.types.ts +0 -16
  124. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/index.tsx +0 -210
  125. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApprovalRequestTriggerConfigEditor.tsx +0 -39
  126. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubmitApprovalResultConfigEditor.tsx +0 -104
  127. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubmitExternalResultConfigEditor.tsx +0 -98
  128. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/AssignRolesModal.tsx +0 -0
  129. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/CreatePageModal.tsx +0 -0
  130. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/EditPageModal.tsx +0 -0
  131. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/PageDetailDrawer.tsx +0 -0
  132. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/PageFormModal.tsx +0 -0
  133. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/CreateModal.tsx +0 -0
  134. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/EditModal.tsx +0 -0
  135. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/ResourceDetailDrawer.tsx +0 -0
  136. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/modal.tsx +0 -0
  137. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/CreateModal.tsx +0 -0
  138. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/EditModal.tsx +0 -0
  139. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/RoleDetailDrawer.tsx +0 -0
  140. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/modal.tsx +0 -0
@@ -2,7 +2,7 @@ import { hashNodeInstanceConfig } from '../workflowNodeInstance/node-instance-co
2
2
  import { WorkflowCompilerService } from './workflow-compiler.service';
3
3
 
4
4
  describe('WorkflowCompilerService', () => {
5
- it('compiles a webhook function that returns JSON to its HTTP caller', async () => {
5
+ it('compiles a webhook function that returns its previous result', async () => {
6
6
  const prisma = {
7
7
  workflowNodeType: {
8
8
  findMany: jest.fn().mockResolvedValue([
@@ -54,11 +54,7 @@ describe('WorkflowCompilerService', () => {
54
54
  {
55
55
  id: 'return',
56
56
  type: 'return_result',
57
- config: {
58
- resultType: 'JSON',
59
- sourceMode: 'WORKFLOW',
60
- path: 'body',
61
- },
57
+ config: {},
62
58
  },
63
59
  ],
64
60
  edges: [
@@ -77,7 +73,7 @@ describe('WorkflowCompilerService', () => {
77
73
  expect.objectContaining({ type: 'webhook_trigger' }),
78
74
  expect.objectContaining({
79
75
  type: 'return_result',
80
- config: expect.objectContaining({ resultType: 'JSON' }),
76
+ config: {},
81
77
  }),
82
78
  ]);
83
79
  });
@@ -145,7 +141,7 @@ describe('WorkflowCompilerService', () => {
145
141
  ),
146
142
  ).rejects.toMatchObject({
147
143
  response: {
148
- message: '等待返回结果时,每个流程分支都必须以“返回结果”结束',
144
+ message: '等待返回结果时,每个流程分支都必须以“返回调用”结束',
149
145
  nodeId: 'webhook',
150
146
  },
151
147
  });
@@ -277,7 +273,7 @@ describe('WorkflowCompilerService', () => {
277
273
  expect(instance.config).toEqual({ timeoutSeconds: 0 });
278
274
  });
279
275
 
280
- it('rejects a Human Approval node that references a disabled channel', async () => {
276
+ it('rejects a Human Approval node that references a disabled Workflow', async () => {
281
277
  const prisma = {
282
278
  workflowNodeType: {
283
279
  findMany: jest.fn().mockResolvedValue([
@@ -299,12 +295,15 @@ describe('WorkflowCompilerService', () => {
299
295
  code: 'FINANCE_APPROVAL',
300
296
  nodeTypeId: BigInt(7),
301
297
  isEnabled: true,
302
- config: { channelRef: 'external-main' },
298
+ config: { approvalWorkflowId: '11' },
303
299
  },
304
300
  ]),
305
301
  },
306
- approvalChannel: {
307
- findUnique: jest.fn().mockResolvedValue({ isEnabled: false }),
302
+ workflow: {
303
+ findUnique: jest.fn().mockResolvedValue({
304
+ isEnabled: false,
305
+ status: 'PUBLISHED',
306
+ }),
308
307
  },
309
308
  };
310
309
  const service = new WorkflowCompilerService(
@@ -333,13 +332,13 @@ describe('WorkflowCompilerService', () => {
333
332
  ),
334
333
  ).rejects.toMatchObject({
335
334
  response: {
336
- message: 'Approval Channel was not found or is disabled',
335
+ message: 'Approval Workflow was not found, published, or enabled',
337
336
  nodeId: 'approval-1',
338
337
  },
339
338
  });
340
339
  });
341
340
 
342
- it('rejects a Human Approval node missing a required channel argument', async () => {
341
+ it('rejects a Human Approval node missing a required Workflow argument', async () => {
343
342
  const prisma = {
344
343
  workflowNodeType: {
345
344
  findMany: jest.fn().mockResolvedValue([
@@ -362,40 +361,52 @@ describe('WorkflowCompilerService', () => {
362
361
  nodeTypeId: BigInt(7),
363
362
  isEnabled: true,
364
363
  config: {
365
- channelRef: 'myoa',
366
- channelInputBindings: {
364
+ approvalWorkflowId: '11',
365
+ approvalWorkflowInputBindings: {
367
366
  title: { source: 'WORKFLOW', path: 'requestTitle' },
368
367
  },
369
368
  },
370
369
  },
371
370
  ]),
372
371
  },
373
- approvalChannel: {
372
+ workflow: {
374
373
  findUnique: jest.fn().mockResolvedValue({
375
374
  isEnabled: true,
376
- dispatchWorkflow: {
377
- versions: [
378
- {
379
- dsl: {
380
- nodes: [
381
- {
382
- type: 'callable_trigger',
383
- config: {
384
- inputParameters: [
385
- { key: 'title', required: true },
386
- {
387
- key: 'content',
388
- label: '审批内容',
389
- required: true,
390
- },
391
- ],
392
- },
375
+ status: 'PUBLISHED',
376
+ versions: [
377
+ {
378
+ dsl: {
379
+ nodes: [
380
+ {
381
+ id: 'input',
382
+ type: 'callable_trigger',
383
+ config: {
384
+ inputParameters: [
385
+ { key: 'title', required: true },
386
+ {
387
+ key: 'content',
388
+ label: '审批内容',
389
+ required: true,
390
+ },
391
+ ],
393
392
  },
394
- ],
395
- },
393
+ },
394
+ {
395
+ id: 'return',
396
+ type: 'return_result',
397
+ config: {},
398
+ },
399
+ ],
400
+ edges: [
401
+ {
402
+ source: 'input',
403
+ target: 'return',
404
+ kind: 'FLOW',
405
+ },
406
+ ],
396
407
  },
397
- ],
398
- },
408
+ },
409
+ ],
399
410
  }),
400
411
  },
401
412
  };
@@ -425,7 +436,7 @@ describe('WorkflowCompilerService', () => {
425
436
  ),
426
437
  ).rejects.toMatchObject({
427
438
  response: {
428
- message: '审批通道缺少必填参数:审批内容',
439
+ message: '审批 Workflow 缺少必填参数:审批内容',
429
440
  nodeId: 'approval-1',
430
441
  },
431
442
  });
@@ -509,4 +520,79 @@ describe('WorkflowCompilerService', () => {
509
520
  },
510
521
  });
511
522
  });
523
+
524
+ it('validates required inputs when calling another Workflow', async () => {
525
+ const prisma = {
526
+ workflowNodeType: {
527
+ findMany: jest.fn().mockResolvedValue([
528
+ {
529
+ id: BigInt(8),
530
+ type: 'sub_workflow',
531
+ runtimeKey: 'sub_workflow',
532
+ instancePolicy: 'INLINE_ONLY',
533
+ schemaVersion: 2,
534
+ configSchema: { type: 'object' },
535
+ uiSchema: {},
536
+ },
537
+ ]),
538
+ },
539
+ workflowNodeInstance: { findMany: jest.fn().mockResolvedValue([]) },
540
+ workflow: {
541
+ findUnique: jest.fn().mockResolvedValue({
542
+ status: 'PUBLISHED',
543
+ isEnabled: true,
544
+ versions: [
545
+ {
546
+ dsl: {
547
+ nodes: [
548
+ {
549
+ id: 'input',
550
+ type: 'callable_trigger',
551
+ config: {
552
+ inputParameters: [
553
+ { key: 'orderId', label: '订单 ID', required: true },
554
+ ],
555
+ },
556
+ },
557
+ { id: 'work', type: 'code', config: {} },
558
+ { id: 'return', type: 'return_result', config: {} },
559
+ ],
560
+ edges: [
561
+ { source: 'input', target: 'work', kind: 'FLOW' },
562
+ { source: 'work', target: 'return', kind: 'FLOW' },
563
+ ],
564
+ },
565
+ },
566
+ ],
567
+ }),
568
+ },
569
+ };
570
+ const service = new WorkflowCompilerService(
571
+ prisma as any,
572
+ { validateOrThrow: jest.fn() } as any,
573
+ { assertUiSchemaSupported: jest.fn() } as any,
574
+ { validateSubjects: jest.fn() } as any,
575
+ );
576
+
577
+ await expect(
578
+ service.compile(
579
+ {
580
+ nodes: [
581
+ {
582
+ id: 'call-1',
583
+ type: 'sub_workflow',
584
+ config: { workflowId: '42', inputBindings: {} },
585
+ },
586
+ ],
587
+ edges: [],
588
+ },
589
+ { userId: 'author' },
590
+ ),
591
+ ).rejects.toMatchObject({
592
+ response: {
593
+ message: '调用 Workflow 的参数来源无效:订单 ID',
594
+ nodeId: 'call-1',
595
+ },
596
+ });
597
+ });
512
598
  });
@@ -9,8 +9,8 @@ import { WidgetCatalogService } from '../workflowConfig/widget-catalog.service';
9
9
  import { hashNodeInstanceConfig } from '../workflowNodeInstance/node-instance-config.util';
10
10
  import { validateParameterBinding } from './parameter-binding';
11
11
  import {
12
- describeApprovalChannelInputs,
13
12
  describeCallableWorkflow,
13
+ describeReturningWorkflowInputs,
14
14
  validateCallableWorkflow,
15
15
  } from './workflow-callable';
16
16
  import {
@@ -166,49 +166,119 @@ export class WorkflowCompilerService {
166
166
  'HTTP Request Connection was not found or is disabled',
167
167
  );
168
168
  }
169
+ if (nodeType.runtimeKey === 'sub_workflow') {
170
+ const workflowId = String(
171
+ (config as Record<string, unknown>)?.workflowId ?? '',
172
+ );
173
+ if (!/^[1-9]\d*$/.test(workflowId))
174
+ this.fail('NODE_TYPE_MISMATCH', node, '调用的 Workflow ID 无效');
175
+ const workflow = await this.prisma.workflow.findUnique({
176
+ where: { id: BigInt(workflowId) },
177
+ select: {
178
+ isEnabled: true,
179
+ status: true,
180
+ versions: {
181
+ orderBy: { version: 'desc' },
182
+ take: 1,
183
+ select: { dsl: true },
184
+ },
185
+ },
186
+ });
187
+ if (!workflow?.isEnabled || workflow.status !== 'PUBLISHED')
188
+ this.fail(
189
+ 'NODE_TYPE_MISMATCH',
190
+ node,
191
+ '调用的 Workflow 未发布或已停用',
192
+ );
193
+ let descriptor;
194
+ try {
195
+ descriptor = describeCallableWorkflow(workflow.versions[0]?.dsl);
196
+ } catch (error) {
197
+ this.fail(
198
+ 'NODE_TYPE_MISMATCH',
199
+ node,
200
+ error instanceof Error ? error.message : '调用的 Workflow 配置无效',
201
+ );
202
+ }
203
+ if (!descriptor)
204
+ this.fail(
205
+ 'NODE_TYPE_MISMATCH',
206
+ node,
207
+ '选择的 Workflow 不能被内部调用',
208
+ );
209
+ const bindings =
210
+ (config as Record<string, unknown>).inputBindings &&
211
+ typeof (config as Record<string, unknown>).inputBindings ===
212
+ 'object' &&
213
+ !Array.isArray((config as Record<string, unknown>).inputBindings)
214
+ ? ((config as Record<string, unknown>).inputBindings as Record<
215
+ string,
216
+ unknown
217
+ >)
218
+ : {};
219
+ const invalidParameter = descriptor.inputParameters.find(
220
+ (parameter) =>
221
+ (parameter.required || bindings[parameter.key] !== undefined) &&
222
+ !validateParameterBinding(bindings[parameter.key]),
223
+ );
224
+ if (invalidParameter)
225
+ this.fail(
226
+ 'NODE_TYPE_MISMATCH',
227
+ node,
228
+ `调用 Workflow 的参数来源无效:${
229
+ invalidParameter.label ?? invalidParameter.key
230
+ }`,
231
+ );
232
+ }
169
233
  if (nodeType.runtimeKey === 'human_approval') {
170
- const channelRef = (config as Record<string, unknown>)?.channelRef;
171
- if (typeof channelRef === 'string' && channelRef.trim()) {
172
- const channel = await this.prisma.approvalChannel.findUnique({
173
- where: { code: channelRef.trim() },
234
+ const approvalWorkflowId = (config as Record<string, unknown>)
235
+ ?.approvalWorkflowId;
236
+ if (
237
+ typeof approvalWorkflowId === 'string' &&
238
+ /^[1-9]\d*$/.test(approvalWorkflowId)
239
+ ) {
240
+ const approvalWorkflow = await this.prisma.workflow.findUnique({
241
+ where: { id: BigInt(approvalWorkflowId) },
174
242
  select: {
175
243
  isEnabled: true,
176
- dispatchWorkflow: {
177
- select: {
178
- versions: {
179
- orderBy: { version: 'desc' },
180
- take: 1,
181
- select: { dsl: true },
182
- },
183
- },
244
+ status: true,
245
+ versions: {
246
+ orderBy: { version: 'desc' },
247
+ take: 1,
248
+ select: { dsl: true },
184
249
  },
185
250
  },
186
251
  });
187
- if (!channel?.isEnabled)
252
+ if (
253
+ !approvalWorkflow?.isEnabled ||
254
+ approvalWorkflow.status !== 'PUBLISHED'
255
+ )
188
256
  this.fail(
189
257
  'NODE_TYPE_MISMATCH',
190
258
  node,
191
- 'Approval Channel was not found or is disabled',
259
+ 'Approval Workflow was not found, published, or enabled',
192
260
  );
193
261
  const bindings =
194
- (config as Record<string, unknown>).channelInputBindings &&
195
- typeof (config as Record<string, unknown>).channelInputBindings ===
196
- 'object' &&
262
+ (config as Record<string, unknown>).approvalWorkflowInputBindings &&
263
+ typeof (config as Record<string, unknown>)
264
+ .approvalWorkflowInputBindings === 'object' &&
197
265
  !Array.isArray(
198
- (config as Record<string, unknown>).channelInputBindings,
266
+ (config as Record<string, unknown>).approvalWorkflowInputBindings,
199
267
  )
200
268
  ? ((config as Record<string, unknown>)
201
- .channelInputBindings as Record<string, unknown>)
269
+ .approvalWorkflowInputBindings as Record<string, unknown>)
202
270
  : {};
203
- const missing = describeApprovalChannelInputs(
204
- channel.dispatchWorkflow.versions[0]?.dsl,
271
+ const missing = describeReturningWorkflowInputs(
272
+ approvalWorkflow.versions[0]?.dsl,
205
273
  ).find((parameter) => parameter.required && !bindings[parameter.key]);
206
274
  if (missing)
207
275
  this.fail(
208
276
  'NODE_TYPE_MISMATCH',
209
277
  node,
210
- `审批通道缺少必填参数:${missing.label ?? missing.key}`,
278
+ `审批 Workflow 缺少必填参数:${missing.label ?? missing.key}`,
211
279
  );
280
+ } else if (approvalWorkflowId !== undefined) {
281
+ this.fail('NODE_TYPE_MISMATCH', node, 'Approval Workflow ID 无效');
212
282
  }
213
283
  const approverSources = Array.isArray(
214
284
  (config as Record<string, unknown>)?.approverSources,
@@ -256,14 +326,11 @@ export class WorkflowCompilerService {
256
326
  : '自动找审批人的 Workflow 配置无效',
257
327
  );
258
328
  }
259
- if (
260
- descriptor?.mode !== 'RETURN' ||
261
- descriptor.resultType !== 'APPROVAL_SUBJECTS'
262
- )
329
+ if (descriptor?.mode !== 'RETURN')
263
330
  this.fail(
264
331
  'NODE_TYPE_MISMATCH',
265
332
  node,
266
- '选择的 Workflow 不能返回审批人',
333
+ '选择的 Workflow 没有返回结果',
267
334
  );
268
335
  const inputBindings =
269
336
  source.inputBindings &&
@@ -104,25 +104,5 @@ function deriveDynamicOutputs(node: DslNode): string[] {
104
104
  );
105
105
  }
106
106
 
107
- if (node.type === 'external_wait') {
108
- const outcomes = Array.isArray(node.config?.outcomes)
109
- ? node.config.outcomes
110
- : [];
111
- const handles = outcomes
112
- .map((item) => item?.code)
113
- .filter(
114
- (code): code is string => typeof code === 'string' && code.length > 0,
115
- );
116
- const timeoutOutcome = node.config?.timeout?.outcome;
117
- if (
118
- typeof timeoutOutcome === 'string' &&
119
- timeoutOutcome.length > 0 &&
120
- !handles.includes(timeoutOutcome)
121
- ) {
122
- handles.push(timeoutOutcome);
123
- }
124
- return handles;
125
- }
126
-
127
107
  return [];
128
108
  }
@@ -12,15 +12,11 @@ const trigger = {
12
12
  const returnNode = (id: string) => ({
13
13
  id,
14
14
  type: 'return_result',
15
- config: {
16
- resultType: 'JSON',
17
- sourceMode: 'INPUT',
18
- httpStatusCode: 200,
19
- },
15
+ config: {},
20
16
  });
21
17
 
22
18
  describe('webhook function response contract', () => {
23
- it('accepts conditional branches that each return their own result', () => {
19
+ it('accepts conditional branches that each return their previous result', () => {
24
20
  expect(() =>
25
21
  validateWebhookResponseWorkflow({
26
22
  nodes: [
@@ -38,6 +34,15 @@ describe('webhook function response contract', () => {
38
34
  ).not.toThrow();
39
35
  });
40
36
 
37
+ it('accepts returning the external request data directly', () => {
38
+ expect(() =>
39
+ validateWebhookResponseWorkflow({
40
+ nodes: [trigger, returnNode('return')],
41
+ edges: [{ source: 'webhook', target: 'return', kind: 'FLOW' }],
42
+ }),
43
+ ).not.toThrow();
44
+ });
45
+
41
46
  it('rejects a function-mode webhook with a terminal branch that does not return', () => {
42
47
  expect(() =>
43
48
  validateWebhookResponseWorkflow({
@@ -53,33 +58,25 @@ describe('webhook function response contract', () => {
53
58
  { source: 'branch', target: 'unfinished', kind: 'FLOW' },
54
59
  ],
55
60
  }),
56
- ).toThrow('等待返回结果时,每个流程分支都必须以“返回结果”结束');
61
+ ).toThrow('等待返回结果时,每个流程分支都必须以“返回调用”结束');
57
62
  });
58
63
 
59
- it('rejects an approval-only return value for an HTTP response', () => {
64
+ it('rejects a return node with more than one previous node', () => {
60
65
  expect(() =>
61
66
  validateWebhookResponseWorkflow({
62
67
  nodes: [
63
68
  trigger,
64
- {
65
- ...returnNode('return'),
66
- config: {
67
- resultType: 'APPROVAL_SUBJECTS',
68
- sourceMode: 'INPUT',
69
- },
70
- },
69
+ { id: 'left', type: 'code', config: {} },
70
+ { id: 'right', type: 'code', config: {} },
71
+ returnNode('return'),
72
+ ],
73
+ edges: [
74
+ { source: 'webhook', target: 'left', kind: 'FLOW' },
75
+ { source: 'webhook', target: 'right', kind: 'FLOW' },
76
+ { source: 'left', target: 'return', kind: 'FLOW' },
77
+ { source: 'right', target: 'return', kind: 'FLOW' },
71
78
  ],
72
- edges: [{ source: 'webhook', target: 'return', kind: 'FLOW' }],
73
- }),
74
- ).toThrow('HTTP 调用只能返回普通数据');
75
- });
76
-
77
- it('rejects returning a previous result when only the trigger is connected', () => {
78
- expect(() =>
79
- validateWebhookResponseWorkflow({
80
- nodes: [trigger, returnNode('return')],
81
- edges: [{ source: 'webhook', target: 'return', kind: 'FLOW' }],
82
79
  }),
83
- ).toThrow('返回上一步结果时,“返回结果”前需要连接一个处理节点');
80
+ ).toThrow('“返回调用”前需要连接一个节点');
84
81
  });
85
82
  });
@@ -69,7 +69,7 @@ function validateReturningBranches(
69
69
  const queue = [...(outgoing.get(triggerId) ?? [])];
70
70
  const visited = new Set<string>();
71
71
  if (queue.length === 0) {
72
- throw new Error('等待返回结果时,每个流程分支都必须以“返回结果”结束');
72
+ throw new Error('等待返回结果时,每个流程分支都必须以“返回调用”结束');
73
73
  }
74
74
 
75
75
  while (queue.length > 0) {
@@ -81,67 +81,21 @@ function validateReturningBranches(
81
81
  const next = outgoing.get(nodeId) ?? [];
82
82
 
83
83
  if (node.type === 'return_result') {
84
- validateReturnNode(node, next, incoming.get(nodeId) ?? [], nodeById);
84
+ validateReturnNode(next, incoming.get(nodeId) ?? []);
85
85
  continue;
86
86
  }
87
87
  if (next.length === 0) {
88
- throw new Error('等待返回结果时,每个流程分支都必须以“返回结果”结束');
88
+ throw new Error('等待返回结果时,每个流程分支都必须以“返回调用”结束');
89
89
  }
90
90
  queue.push(...next);
91
91
  }
92
92
  }
93
93
 
94
- function validateReturnNode(
95
- node: DslNode,
96
- next: string[],
97
- previous: string[],
98
- nodeById: Map<string, DslNode>,
99
- ): void {
100
- const config = asRecord(node.config);
101
- if (config.resultType !== 'JSON') {
102
- throw new Error('HTTP 调用只能返回普通数据');
103
- }
94
+ function validateReturnNode(next: string[], previous: string[]): void {
104
95
  if (next.length > 0) {
105
- throw new Error('“返回结果”必须是流程分支的最后一个节点');
106
- }
107
- if (config.sourceMode === 'INPUT') {
108
- const processingNodes = previous.filter((nodeId) => {
109
- const predecessorType = nodeById.get(nodeId)?.type;
110
- return (
111
- typeof predecessorType !== 'string' ||
112
- !predecessorType.endsWith('_trigger')
113
- );
114
- });
115
- if (processingNodes.length !== 1) {
116
- throw new Error('返回上一步结果时,“返回结果”前需要连接一个处理节点');
117
- }
118
- } else if (config.sourceMode === 'WORKFLOW') {
119
- if (typeof config.path !== 'string' || !config.path.trim()) {
120
- throw new Error('返回 Workflow 数据时必须填写字段路径');
121
- }
122
- } else {
123
- throw new Error('返回结果来源必须是上一个节点或 Workflow 数据');
124
- }
125
- if (
126
- config.httpStatusCode !== undefined &&
127
- (typeof config.httpStatusCode !== 'number' ||
128
- !Number.isInteger(config.httpStatusCode) ||
129
- config.httpStatusCode < 100 ||
130
- config.httpStatusCode > 599)
131
- ) {
132
- throw new Error('HTTP 状态码必须是 100 到 599 的整数');
133
- }
134
- if (config.httpHeaders !== undefined) {
135
- const headers = asRecord(config.httpHeaders);
136
- if (
137
- !config.httpHeaders ||
138
- typeof config.httpHeaders !== 'object' ||
139
- Array.isArray(config.httpHeaders) ||
140
- Object.values(headers).some((value) => typeof value !== 'string')
141
- ) {
142
- throw new Error('HTTP 响应头必须是字符串键值');
143
- }
96
+ throw new Error('“返回调用”必须是流程分支的最后一个节点');
144
97
  }
98
+ if (previous.length !== 1) throw new Error('“返回调用”前需要连接一个节点');
145
99
  }
146
100
 
147
101
  function asDsl(value: unknown): DslLike {
@@ -64,8 +64,8 @@ export class WorkflowController {
64
64
  }
65
65
 
66
66
  @Get('callable')
67
- findCallable(@Query('resultType') resultType?: string) {
68
- return this.workflowService.findCallableWorkflows(resultType);
67
+ findCallable() {
68
+ return this.workflowService.findCallableWorkflows();
69
69
  }
70
70
 
71
71
  @Get('external-interaction-options')
@@ -156,6 +156,30 @@ describe('WorkflowService — cron lifecycle hooks', () => {
156
156
  expect(result).toMatchObject({ dsl: changedDsl, status: 'DRAFT' });
157
157
  });
158
158
 
159
+ it('rejects saving an internally callable Workflow without a return node', async () => {
160
+ prisma.workflow.findUnique.mockResolvedValueOnce({
161
+ id: BigInt(42),
162
+ versions: [],
163
+ });
164
+ const callableDsl = {
165
+ nodes: [
166
+ { id: 'input', type: 'callable_trigger', config: {} },
167
+ { id: 'work', type: 'code', config: {} },
168
+ ],
169
+ edges: [{ source: 'input', target: 'work', kind: 'FLOW' }],
170
+ };
171
+
172
+ await expect(
173
+ service.saveDraft(BigInt(42), callableDsl),
174
+ ).rejects.toMatchObject({
175
+ response: expect.objectContaining({
176
+ code: 'CALLABLE_WORKFLOW_INVALID',
177
+ message: '可调用 Workflow 必须且只能包含一个“返回调用”节点',
178
+ }),
179
+ });
180
+ expect(prisma.workflow.update).not.toHaveBeenCalled();
181
+ });
182
+
159
183
  it('does not create a version when the saved DSL is already published', async () => {
160
184
  prisma.workflow.findUnique.mockResolvedValueOnce({
161
185
  id: BigInt(42),