@nocobase/flow-engine 2.1.0-alpha.1 → 2.1.0-alpha.2

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 (252) hide show
  1. package/lib/BlockScopedFlowEngine.js +0 -1
  2. package/lib/FlowDefinition.d.ts +2 -0
  3. package/lib/JSRunner.d.ts +6 -0
  4. package/lib/JSRunner.js +32 -2
  5. package/lib/ViewScopedFlowEngine.js +3 -0
  6. package/lib/acl/Acl.js +13 -3
  7. package/lib/components/FlowContextSelector.js +155 -10
  8. package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
  9. package/lib/components/dnd/gridDragPlanner.js +53 -1
  10. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
  11. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
  12. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
  13. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
  14. package/lib/components/variables/VariableInput.js +9 -4
  15. package/lib/components/variables/VariableTag.js +46 -39
  16. package/lib/components/variables/utils.d.ts +7 -0
  17. package/lib/components/variables/utils.js +42 -2
  18. package/lib/data-source/index.d.ts +7 -27
  19. package/lib/data-source/index.js +84 -51
  20. package/lib/executor/FlowExecutor.d.ts +2 -1
  21. package/lib/executor/FlowExecutor.js +163 -22
  22. package/lib/flowContext.d.ts +230 -7
  23. package/lib/flowContext.js +2267 -148
  24. package/lib/flowEngine.d.ts +21 -0
  25. package/lib/flowEngine.js +55 -13
  26. package/lib/flowI18n.js +6 -4
  27. package/lib/flowSettings.js +17 -11
  28. package/lib/index.d.ts +7 -1
  29. package/lib/index.js +21 -0
  30. package/lib/locale/en-US.json +9 -2
  31. package/lib/locale/index.d.ts +14 -0
  32. package/lib/locale/zh-CN.json +8 -1
  33. package/lib/models/CollectionFieldModel.d.ts +1 -0
  34. package/lib/models/CollectionFieldModel.js +3 -2
  35. package/lib/models/flowModel.d.ts +7 -0
  36. package/lib/models/flowModel.js +66 -1
  37. package/lib/provider.js +7 -6
  38. package/lib/resources/baseRecordResource.d.ts +5 -0
  39. package/lib/resources/baseRecordResource.js +24 -0
  40. package/lib/resources/multiRecordResource.d.ts +1 -0
  41. package/lib/resources/multiRecordResource.js +11 -4
  42. package/lib/resources/singleRecordResource.js +2 -0
  43. package/lib/resources/sqlResource.d.ts +4 -3
  44. package/lib/resources/sqlResource.js +8 -3
  45. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
  46. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
  47. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
  48. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
  49. package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
  50. package/lib/runjs-context/contexts/base.js +706 -41
  51. package/lib/runjs-context/contributions.d.ts +33 -0
  52. package/lib/runjs-context/contributions.js +88 -0
  53. package/lib/runjs-context/helpers.js +12 -1
  54. package/lib/runjs-context/setup.js +6 -0
  55. package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
  56. package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
  57. package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
  58. package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
  59. package/lib/runjs-context/snippets/index.d.ts +11 -1
  60. package/lib/runjs-context/snippets/index.js +61 -40
  61. package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
  62. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
  63. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
  64. package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
  65. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
  66. package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
  67. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
  68. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
  69. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
  70. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
  71. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
  72. package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
  73. package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
  74. package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
  75. package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
  76. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
  77. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
  78. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
  79. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
  80. package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
  81. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
  82. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
  83. package/lib/runjsLibs.d.ts +28 -0
  84. package/lib/runjsLibs.js +532 -0
  85. package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
  86. package/lib/scheduler/ModelOperationScheduler.js +25 -21
  87. package/lib/types.d.ts +27 -0
  88. package/lib/utils/associationObjectVariable.d.ts +2 -2
  89. package/lib/utils/createCollectionContextMeta.js +1 -0
  90. package/lib/utils/createEphemeralContext.js +2 -2
  91. package/lib/utils/dateVariable.d.ts +16 -0
  92. package/lib/utils/dateVariable.js +380 -0
  93. package/lib/utils/exceptions.d.ts +7 -0
  94. package/lib/utils/exceptions.js +10 -0
  95. package/lib/utils/index.d.ts +8 -3
  96. package/lib/utils/index.js +45 -0
  97. package/lib/utils/params-resolvers.js +16 -9
  98. package/lib/utils/resolveModuleUrl.d.ts +58 -0
  99. package/lib/utils/resolveModuleUrl.js +65 -0
  100. package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
  101. package/lib/utils/resolveRunJSObjectValues.js +61 -0
  102. package/lib/utils/runjsModuleLoader.d.ts +58 -0
  103. package/lib/utils/runjsModuleLoader.js +422 -0
  104. package/lib/utils/runjsTemplateCompat.d.ts +35 -0
  105. package/lib/utils/runjsTemplateCompat.js +743 -0
  106. package/lib/utils/runjsValue.d.ts +29 -0
  107. package/lib/utils/runjsValue.js +275 -0
  108. package/lib/utils/safeGlobals.d.ts +18 -8
  109. package/lib/utils/safeGlobals.js +164 -17
  110. package/lib/utils/schema-utils.d.ts +10 -0
  111. package/lib/utils/schema-utils.js +61 -0
  112. package/lib/views/createViewMeta.d.ts +0 -7
  113. package/lib/views/createViewMeta.js +19 -70
  114. package/lib/views/index.d.ts +1 -2
  115. package/lib/views/index.js +4 -3
  116. package/lib/views/useDialog.js +8 -3
  117. package/lib/views/useDrawer.js +7 -2
  118. package/lib/views/usePage.d.ts +4 -0
  119. package/lib/views/usePage.js +43 -6
  120. package/lib/views/usePopover.js +4 -1
  121. package/lib/views/viewEvents.d.ts +17 -0
  122. package/lib/views/viewEvents.js +90 -0
  123. package/package.json +4 -4
  124. package/src/BlockScopedFlowEngine.ts +2 -5
  125. package/src/JSRunner.ts +44 -2
  126. package/src/ViewScopedFlowEngine.ts +4 -0
  127. package/src/__tests__/JSRunner.test.ts +64 -0
  128. package/src/__tests__/createViewMeta.popup.test.ts +62 -1
  129. package/src/__tests__/flowContext.test.ts +693 -1
  130. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
  131. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  132. package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
  133. package/src/__tests__/flowRuntimeContext.test.ts +2 -1
  134. package/src/__tests__/flowSettings.open.test.tsx +123 -19
  135. package/src/__tests__/provider.test.tsx +0 -5
  136. package/src/__tests__/runjsContext.test.ts +10 -7
  137. package/src/__tests__/runjsContextImplementations.test.ts +34 -3
  138. package/src/__tests__/runjsContextRuntime.test.ts +3 -3
  139. package/src/__tests__/runjsContributions.test.ts +89 -0
  140. package/src/__tests__/runjsExternalLibs.test.ts +242 -0
  141. package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
  142. package/src/__tests__/runjsLocales.test.ts +4 -1
  143. package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
  144. package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
  145. package/src/__tests__/runjsSnippets.test.ts +40 -3
  146. package/src/acl/Acl.tsx +3 -3
  147. package/src/components/FlowContextSelector.tsx +208 -12
  148. package/src/components/__tests__/gridDragPlanner.test.ts +141 -1
  149. package/src/components/dnd/gridDragPlanner.ts +60 -0
  150. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
  151. package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
  152. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
  153. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
  154. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
  155. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
  156. package/src/components/variables/VariableInput.tsx +12 -4
  157. package/src/components/variables/VariableTag.tsx +54 -45
  158. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
  159. package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
  160. package/src/components/variables/__tests__/utils.test.ts +81 -3
  161. package/src/components/variables/utils.ts +67 -6
  162. package/src/data-source/index.ts +88 -110
  163. package/src/executor/FlowExecutor.ts +200 -23
  164. package/src/executor/__tests__/flowExecutor.test.ts +66 -0
  165. package/src/flowContext.ts +2986 -211
  166. package/src/flowEngine.ts +58 -13
  167. package/src/flowI18n.ts +7 -5
  168. package/src/flowSettings.ts +18 -12
  169. package/src/index.ts +14 -1
  170. package/src/locale/en-US.json +9 -2
  171. package/src/locale/zh-CN.json +8 -1
  172. package/src/models/CollectionFieldModel.tsx +3 -1
  173. package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
  174. package/src/models/__tests__/flowModel.clone.test.ts +416 -0
  175. package/src/models/__tests__/flowModel.test.ts +20 -4
  176. package/src/models/flowModel.tsx +94 -1
  177. package/src/provider.tsx +9 -7
  178. package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
  179. package/src/resources/__tests__/sqlResource.test.ts +60 -0
  180. package/src/resources/baseRecordResource.ts +31 -0
  181. package/src/resources/multiRecordResource.ts +11 -4
  182. package/src/resources/singleRecordResource.ts +3 -0
  183. package/src/resources/sqlResource.ts +11 -6
  184. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
  185. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
  186. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
  187. package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
  188. package/src/runjs-context/contexts/base.ts +715 -44
  189. package/src/runjs-context/contributions.ts +88 -0
  190. package/src/runjs-context/helpers.ts +11 -1
  191. package/src/runjs-context/setup.ts +6 -0
  192. package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
  193. package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
  194. package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
  195. package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
  196. package/src/runjs-context/snippets/index.ts +75 -41
  197. package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
  198. package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
  199. package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
  200. package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
  201. package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
  202. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
  203. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
  204. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
  205. package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
  206. package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
  207. package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
  208. package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
  209. package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
  210. package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
  211. package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
  212. package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
  213. package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
  214. package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
  215. package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
  216. package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
  217. package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
  218. package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
  219. package/src/runjsLibs.ts +622 -0
  220. package/src/scheduler/ModelOperationScheduler.ts +27 -21
  221. package/src/types.ts +38 -1
  222. package/src/utils/__tests__/dateVariable.test.ts +101 -0
  223. package/src/utils/__tests__/params-resolvers.test.ts +40 -0
  224. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
  225. package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
  226. package/src/utils/__tests__/runjsValue.test.ts +44 -0
  227. package/src/utils/__tests__/safeGlobals.test.ts +57 -2
  228. package/src/utils/__tests__/utils.test.ts +95 -0
  229. package/src/utils/associationObjectVariable.ts +2 -2
  230. package/src/utils/createCollectionContextMeta.ts +1 -0
  231. package/src/utils/createEphemeralContext.ts +5 -4
  232. package/src/utils/dateVariable.ts +397 -0
  233. package/src/utils/exceptions.ts +11 -0
  234. package/src/utils/index.ts +37 -3
  235. package/src/utils/params-resolvers.ts +23 -9
  236. package/src/utils/resolveModuleUrl.ts +91 -0
  237. package/src/utils/resolveRunJSObjectValues.ts +46 -0
  238. package/src/utils/runjsModuleLoader.ts +553 -0
  239. package/src/utils/runjsTemplateCompat.ts +828 -0
  240. package/src/utils/runjsValue.ts +287 -0
  241. package/src/utils/safeGlobals.ts +188 -17
  242. package/src/utils/schema-utils.ts +79 -0
  243. package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
  244. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
  245. package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
  246. package/src/views/createViewMeta.ts +22 -75
  247. package/src/views/index.tsx +1 -2
  248. package/src/views/useDialog.tsx +9 -2
  249. package/src/views/useDrawer.tsx +8 -1
  250. package/src/views/usePage.tsx +51 -5
  251. package/src/views/usePopover.tsx +4 -1
  252. package/src/views/viewEvents.ts +55 -0
@@ -0,0 +1,554 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { describe, test, expect } from 'vitest';
11
+ import { FlowEngine } from '../../flowEngine';
12
+ import { FlowModel } from '../flowModel';
13
+
14
+ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integration)', () => {
15
+ test('default (phase undefined): instance flows run before static flows', async () => {
16
+ const engine = new FlowEngine();
17
+ class M extends FlowModel {}
18
+ engine.registerModels({ M });
19
+
20
+ const calls: string[] = [];
21
+
22
+ M.registerFlow({
23
+ key: 'S',
24
+ on: { eventName: 'go' },
25
+ steps: {
26
+ a: { handler: async () => void calls.push('static-a') } as any,
27
+ },
28
+ });
29
+
30
+ const model = engine.createModel({ use: 'M' });
31
+ model.registerFlow('D', {
32
+ on: { eventName: 'go' },
33
+ steps: {
34
+ d: { handler: async () => void calls.push('dynamic') } as any,
35
+ },
36
+ });
37
+
38
+ await model.dispatchEvent('go');
39
+ expect(calls).toEqual(['dynamic', 'static-a']);
40
+ });
41
+
42
+ test('default (phase undefined): ctx.exitAll() stops static flows (beforeAllFlows regression)', async () => {
43
+ const engine = new FlowEngine();
44
+ class M extends FlowModel {}
45
+ engine.registerModels({ M });
46
+
47
+ const calls: string[] = [];
48
+
49
+ M.registerFlow({
50
+ key: 'S',
51
+ on: { eventName: 'go' },
52
+ steps: {
53
+ a: { handler: async () => void calls.push('static-a') } as any,
54
+ },
55
+ });
56
+ M.registerFlow({
57
+ key: 'T',
58
+ on: { eventName: 'go' },
59
+ steps: {
60
+ t: { handler: async () => void calls.push('static-t') } as any,
61
+ },
62
+ });
63
+
64
+ const model = engine.createModel({ use: 'M' });
65
+ model.registerFlow('D', {
66
+ on: { eventName: 'go' },
67
+ steps: {
68
+ d: {
69
+ handler: async (ctx: any) => {
70
+ calls.push('dynamic');
71
+ ctx.exitAll();
72
+ },
73
+ } as any,
74
+ },
75
+ });
76
+
77
+ await model.dispatchEvent('go');
78
+ expect(calls).toEqual(['dynamic']);
79
+ });
80
+
81
+ test("phase='afterAllFlows': instance flow runs after static flows", async () => {
82
+ const engine = new FlowEngine();
83
+ class M extends FlowModel {}
84
+ engine.registerModels({ M });
85
+
86
+ const calls: string[] = [];
87
+
88
+ M.registerFlow({
89
+ key: 'S',
90
+ on: { eventName: 'go' },
91
+ steps: {
92
+ a: { handler: async () => void calls.push('static-a') } as any,
93
+ },
94
+ });
95
+
96
+ const model = engine.createModel({ use: 'M' });
97
+ model.registerFlow('D', {
98
+ on: { eventName: 'go', phase: 'afterAllFlows' },
99
+ steps: {
100
+ d: { handler: async () => void calls.push('dynamic') } as any,
101
+ },
102
+ });
103
+
104
+ await model.dispatchEvent('go');
105
+ expect(calls).toEqual(['static-a', 'dynamic']);
106
+ });
107
+
108
+ test("phase='beforeFlow': instance flow runs before the target static flow", async () => {
109
+ const engine = new FlowEngine();
110
+ class M extends FlowModel {}
111
+ engine.registerModels({ M });
112
+
113
+ const calls: string[] = [];
114
+
115
+ M.registerFlow({
116
+ key: 'S',
117
+ on: { eventName: 'go' },
118
+ steps: {
119
+ a: { handler: async () => void calls.push('static-a') } as any,
120
+ b: { handler: async () => void calls.push('static-b') } as any,
121
+ },
122
+ });
123
+
124
+ const model = engine.createModel({ use: 'M' });
125
+ model.registerFlow('D', {
126
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'S' },
127
+ steps: {
128
+ d: { handler: async () => void calls.push('dynamic') } as any,
129
+ },
130
+ });
131
+
132
+ await model.dispatchEvent('go');
133
+ expect(calls).toEqual(['dynamic', 'static-a', 'static-b']);
134
+ });
135
+
136
+ test("phase='afterFlow': instance flow runs after the target static flow", async () => {
137
+ const engine = new FlowEngine();
138
+ class M extends FlowModel {}
139
+ engine.registerModels({ M });
140
+
141
+ const calls: string[] = [];
142
+
143
+ M.registerFlow({
144
+ key: 'S',
145
+ on: { eventName: 'go' },
146
+ steps: {
147
+ a: { handler: async () => void calls.push('static-a') } as any,
148
+ b: { handler: async () => void calls.push('static-b') } as any,
149
+ },
150
+ });
151
+
152
+ const model = engine.createModel({ use: 'M' });
153
+ model.registerFlow('D', {
154
+ on: { eventName: 'go', phase: 'afterFlow', flowKey: 'S' },
155
+ steps: {
156
+ d: { handler: async () => void calls.push('dynamic') } as any,
157
+ },
158
+ });
159
+
160
+ await model.dispatchEvent('go');
161
+ expect(calls).toEqual(['static-a', 'static-b', 'dynamic']);
162
+ });
163
+
164
+ test("phase='beforeStep': instance flow runs before the target static step", async () => {
165
+ const engine = new FlowEngine();
166
+ class M extends FlowModel {}
167
+ engine.registerModels({ M });
168
+
169
+ const calls: string[] = [];
170
+
171
+ M.registerFlow({
172
+ key: 'S',
173
+ on: { eventName: 'go' },
174
+ steps: {
175
+ a: { handler: async () => void calls.push('static-a') } as any,
176
+ b: { handler: async () => void calls.push('static-b') } as any,
177
+ },
178
+ });
179
+
180
+ const model = engine.createModel({ use: 'M' });
181
+ model.registerFlow('D', {
182
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
183
+ steps: {
184
+ d: { handler: async () => void calls.push('dynamic') } as any,
185
+ },
186
+ });
187
+
188
+ await model.dispatchEvent('go');
189
+ expect(calls).toEqual(['dynamic', 'static-a', 'static-b']);
190
+ });
191
+
192
+ test("phase='afterStep': instance flow runs after the target static step", async () => {
193
+ const engine = new FlowEngine();
194
+ class M extends FlowModel {}
195
+ engine.registerModels({ M });
196
+
197
+ const calls: string[] = [];
198
+
199
+ M.registerFlow({
200
+ key: 'S',
201
+ on: { eventName: 'go' },
202
+ steps: {
203
+ a: { handler: async () => void calls.push('static-a') } as any,
204
+ b: { handler: async () => void calls.push('static-b') } as any,
205
+ },
206
+ });
207
+
208
+ const model = engine.createModel({ use: 'M' });
209
+ model.registerFlow('D', {
210
+ on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
211
+ steps: {
212
+ d: { handler: async () => void calls.push('dynamic') } as any,
213
+ },
214
+ });
215
+
216
+ await model.dispatchEvent('go');
217
+ expect(calls).toEqual(['static-a', 'dynamic', 'static-b']);
218
+ });
219
+
220
+ test("phase='beforeFlow': ctx.exitAll() stops anchor flow and subsequent flows", async () => {
221
+ const engine = new FlowEngine();
222
+ class M extends FlowModel {}
223
+ engine.registerModels({ M });
224
+
225
+ const calls: string[] = [];
226
+
227
+ M.registerFlow({
228
+ key: 'S',
229
+ on: { eventName: 'go' },
230
+ steps: {
231
+ a: { handler: async () => void calls.push('static-a') } as any,
232
+ },
233
+ });
234
+ M.registerFlow({
235
+ key: 'T',
236
+ on: { eventName: 'go' },
237
+ steps: {
238
+ t: { handler: async () => void calls.push('static-t') } as any,
239
+ },
240
+ });
241
+
242
+ const model = engine.createModel({ use: 'M' });
243
+ model.registerFlow('D', {
244
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'S' },
245
+ steps: {
246
+ d: {
247
+ handler: async (ctx: any) => {
248
+ calls.push('dynamic');
249
+ ctx.exitAll();
250
+ },
251
+ } as any,
252
+ },
253
+ });
254
+
255
+ await model.dispatchEvent('go');
256
+ expect(calls).toEqual(['dynamic']);
257
+ });
258
+
259
+ test("phase='beforeStep': ctx.exitAll() stops anchor step and subsequent flows", async () => {
260
+ const engine = new FlowEngine();
261
+ class M extends FlowModel {}
262
+ engine.registerModels({ M });
263
+
264
+ const calls: string[] = [];
265
+
266
+ M.registerFlow({
267
+ key: 'S',
268
+ on: { eventName: 'go' },
269
+ steps: {
270
+ a: { handler: async () => void calls.push('static-a') } as any,
271
+ b: { handler: async () => void calls.push('static-b') } as any,
272
+ },
273
+ });
274
+ M.registerFlow({
275
+ key: 'T',
276
+ on: { eventName: 'go' },
277
+ steps: {
278
+ t: { handler: async () => void calls.push('static-t') } as any,
279
+ },
280
+ });
281
+
282
+ const model = engine.createModel({ use: 'M' });
283
+ model.registerFlow('D', {
284
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
285
+ steps: {
286
+ d: {
287
+ handler: async (ctx: any) => {
288
+ calls.push('dynamic');
289
+ ctx.exitAll();
290
+ },
291
+ } as any,
292
+ },
293
+ });
294
+
295
+ await model.dispatchEvent('go');
296
+ expect(calls).toEqual(['dynamic']);
297
+ });
298
+
299
+ test("phase='afterStep': ctx.exitAll() stops subsequent steps and subsequent flows", async () => {
300
+ const engine = new FlowEngine();
301
+ class M extends FlowModel {}
302
+ engine.registerModels({ M });
303
+
304
+ const calls: string[] = [];
305
+
306
+ M.registerFlow({
307
+ key: 'S',
308
+ on: { eventName: 'go' },
309
+ steps: {
310
+ a: { handler: async () => void calls.push('static-a') } as any,
311
+ b: { handler: async () => void calls.push('static-b') } as any,
312
+ },
313
+ });
314
+ M.registerFlow({
315
+ key: 'T',
316
+ on: { eventName: 'go' },
317
+ steps: {
318
+ t: { handler: async () => void calls.push('static-t') } as any,
319
+ },
320
+ });
321
+
322
+ const model = engine.createModel({ use: 'M' });
323
+ model.registerFlow('D', {
324
+ on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
325
+ steps: {
326
+ d: {
327
+ handler: async (ctx: any) => {
328
+ calls.push('dynamic');
329
+ ctx.exitAll();
330
+ },
331
+ } as any,
332
+ },
333
+ });
334
+
335
+ await model.dispatchEvent('go');
336
+ expect(calls).toEqual(['static-a', 'dynamic']);
337
+ });
338
+
339
+ test("phase='afterFlow': ctx.exitAll() stops subsequent flows", async () => {
340
+ const engine = new FlowEngine();
341
+ class M extends FlowModel {}
342
+ engine.registerModels({ M });
343
+
344
+ const calls: string[] = [];
345
+
346
+ M.registerFlow({
347
+ key: 'S',
348
+ on: { eventName: 'go' },
349
+ steps: {
350
+ a: { handler: async () => void calls.push('static-a') } as any,
351
+ b: { handler: async () => void calls.push('static-b') } as any,
352
+ },
353
+ });
354
+ M.registerFlow({
355
+ key: 'T',
356
+ on: { eventName: 'go' },
357
+ steps: {
358
+ t: { handler: async () => void calls.push('static-t') } as any,
359
+ },
360
+ });
361
+
362
+ const model = engine.createModel({ use: 'M' });
363
+ model.registerFlow('D', {
364
+ on: { eventName: 'go', phase: 'afterFlow', flowKey: 'S' },
365
+ steps: {
366
+ d: {
367
+ handler: async (ctx: any) => {
368
+ calls.push('dynamic');
369
+ ctx.exitAll();
370
+ },
371
+ } as any,
372
+ },
373
+ });
374
+
375
+ await model.dispatchEvent('go');
376
+ expect(calls).toEqual(['static-a', 'static-b', 'dynamic']);
377
+ });
378
+
379
+ test("phase='beforeFlow' missing flow: falls back to afterAllFlows", async () => {
380
+ const engine = new FlowEngine();
381
+ class M extends FlowModel {}
382
+ engine.registerModels({ M });
383
+
384
+ const calls: string[] = [];
385
+
386
+ M.registerFlow({
387
+ key: 'S',
388
+ on: { eventName: 'go' },
389
+ steps: {
390
+ a: { handler: async () => void calls.push('static-a') } as any,
391
+ },
392
+ });
393
+
394
+ const model = engine.createModel({ use: 'M' });
395
+ model.registerFlow('D', {
396
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'missing' },
397
+ steps: {
398
+ d: { handler: async () => void calls.push('dynamic') } as any,
399
+ },
400
+ });
401
+
402
+ await model.dispatchEvent('go');
403
+ expect(calls).toEqual(['static-a', 'dynamic']);
404
+ });
405
+
406
+ test("phase='beforeStep' missing step: falls back to afterAllFlows", async () => {
407
+ const engine = new FlowEngine();
408
+ class M extends FlowModel {}
409
+ engine.registerModels({ M });
410
+
411
+ const calls: string[] = [];
412
+
413
+ M.registerFlow({
414
+ key: 'S',
415
+ on: { eventName: 'go' },
416
+ steps: {
417
+ a: { handler: async () => void calls.push('static-a') } as any,
418
+ },
419
+ });
420
+
421
+ const model = engine.createModel({ use: 'M' });
422
+ model.registerFlow('D', {
423
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'missing' },
424
+ steps: {
425
+ d: { handler: async () => void calls.push('dynamic') } as any,
426
+ },
427
+ });
428
+
429
+ await model.dispatchEvent('go');
430
+ expect(calls).toEqual(['static-a', 'dynamic']);
431
+ });
432
+
433
+ test('multiple flows on same anchor: executes by flow.sort asc (stable)', async () => {
434
+ const engine = new FlowEngine();
435
+ class M extends FlowModel {}
436
+ engine.registerModels({ M });
437
+
438
+ const calls: string[] = [];
439
+
440
+ M.registerFlow({
441
+ key: 'S',
442
+ on: { eventName: 'go' },
443
+ steps: {
444
+ a: { handler: async () => void calls.push('static-a') } as any,
445
+ },
446
+ });
447
+
448
+ const model = engine.createModel({ use: 'M' });
449
+ model.registerFlow('D5', {
450
+ sort: 5,
451
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
452
+ steps: {
453
+ d: { handler: async () => void calls.push('dynamic-5') } as any,
454
+ },
455
+ });
456
+ model.registerFlow('D0', {
457
+ sort: 0,
458
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
459
+ steps: {
460
+ d: { handler: async () => void calls.push('dynamic-0') } as any,
461
+ },
462
+ });
463
+
464
+ await model.dispatchEvent('go');
465
+ expect(calls).toEqual(['dynamic-0', 'dynamic-5', 'static-a']);
466
+ });
467
+ });
468
+
469
+ describe('dispatchEvent static flow phase (scheduleModelOperation integration)', () => {
470
+ test("phase='beforeFlow': static flow runs before the target static flow", async () => {
471
+ const engine = new FlowEngine();
472
+ class M extends FlowModel {}
473
+ engine.registerModels({ M });
474
+
475
+ const calls: string[] = [];
476
+
477
+ M.registerFlow({
478
+ key: 'S',
479
+ on: { eventName: 'go' },
480
+ steps: {
481
+ a: { handler: async () => void calls.push('static-a') } as any,
482
+ },
483
+ });
484
+
485
+ M.registerFlow({
486
+ key: 'P',
487
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'S' },
488
+ steps: {
489
+ p: { handler: async () => void calls.push('phase') } as any,
490
+ },
491
+ });
492
+
493
+ const model = engine.createModel({ use: 'M' });
494
+ await model.dispatchEvent('go');
495
+ expect(calls).toEqual(['phase', 'static-a']);
496
+ });
497
+
498
+ test("phase='afterStep': static flow runs after the target static step", async () => {
499
+ const engine = new FlowEngine();
500
+ class M extends FlowModel {}
501
+ engine.registerModels({ M });
502
+
503
+ const calls: string[] = [];
504
+
505
+ M.registerFlow({
506
+ key: 'S',
507
+ on: { eventName: 'go' },
508
+ steps: {
509
+ a: { handler: async () => void calls.push('static-a') } as any,
510
+ b: { handler: async () => void calls.push('static-b') } as any,
511
+ },
512
+ });
513
+
514
+ M.registerFlow({
515
+ key: 'P',
516
+ on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
517
+ steps: {
518
+ p: { handler: async () => void calls.push('phase') } as any,
519
+ },
520
+ });
521
+
522
+ const model = engine.createModel({ use: 'M' });
523
+ await model.dispatchEvent('go');
524
+ expect(calls).toEqual(['static-a', 'phase', 'static-b']);
525
+ });
526
+
527
+ test("phase='afterAllFlows': static flow runs after static flows", async () => {
528
+ const engine = new FlowEngine();
529
+ class M extends FlowModel {}
530
+ engine.registerModels({ M });
531
+
532
+ const calls: string[] = [];
533
+
534
+ M.registerFlow({
535
+ key: 'S',
536
+ on: { eventName: 'go' },
537
+ steps: {
538
+ a: { handler: async () => void calls.push('static-a') } as any,
539
+ },
540
+ });
541
+
542
+ M.registerFlow({
543
+ key: 'P',
544
+ on: { eventName: 'go', phase: 'afterAllFlows' },
545
+ steps: {
546
+ p: { handler: async () => void calls.push('phase') } as any,
547
+ },
548
+ });
549
+
550
+ const model = engine.createModel({ use: 'M' });
551
+ await model.dispatchEvent('go');
552
+ expect(calls).toEqual(['static-a', 'phase']);
553
+ });
554
+ });