@nocobase/flow-engine 2.0.0-beta.2 → 2.0.0-beta.20

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 (124) hide show
  1. package/lib/BlockScopedFlowEngine.js +0 -1
  2. package/lib/JSRunner.d.ts +6 -0
  3. package/lib/JSRunner.js +2 -1
  4. package/lib/ViewScopedFlowEngine.js +3 -0
  5. package/lib/acl/Acl.js +13 -3
  6. package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
  7. package/lib/components/dnd/gridDragPlanner.js +53 -1
  8. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
  9. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +11 -3
  10. package/lib/components/variables/VariableInput.js +8 -2
  11. package/lib/data-source/index.js +6 -0
  12. package/lib/executor/FlowExecutor.d.ts +2 -1
  13. package/lib/executor/FlowExecutor.js +156 -22
  14. package/lib/flowContext.d.ts +4 -1
  15. package/lib/flowContext.js +176 -107
  16. package/lib/flowEngine.d.ts +21 -0
  17. package/lib/flowEngine.js +38 -0
  18. package/lib/flowSettings.js +12 -10
  19. package/lib/index.d.ts +3 -0
  20. package/lib/index.js +16 -0
  21. package/lib/models/CollectionFieldModel.d.ts +1 -0
  22. package/lib/models/CollectionFieldModel.js +3 -2
  23. package/lib/models/flowModel.d.ts +7 -0
  24. package/lib/models/flowModel.js +66 -1
  25. package/lib/provider.js +7 -6
  26. package/lib/resources/baseRecordResource.d.ts +5 -0
  27. package/lib/resources/baseRecordResource.js +24 -0
  28. package/lib/resources/multiRecordResource.d.ts +1 -0
  29. package/lib/resources/multiRecordResource.js +11 -4
  30. package/lib/resources/singleRecordResource.js +2 -0
  31. package/lib/resources/sqlResource.d.ts +1 -0
  32. package/lib/resources/sqlResource.js +8 -3
  33. package/lib/runjs-context/contexts/base.js +10 -4
  34. package/lib/runjsLibs.d.ts +28 -0
  35. package/lib/runjsLibs.js +532 -0
  36. package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
  37. package/lib/scheduler/ModelOperationScheduler.js +21 -21
  38. package/lib/types.d.ts +15 -0
  39. package/lib/utils/createCollectionContextMeta.js +1 -0
  40. package/lib/utils/index.d.ts +2 -0
  41. package/lib/utils/index.js +10 -0
  42. package/lib/utils/params-resolvers.js +16 -9
  43. package/lib/utils/resolveModuleUrl.d.ts +58 -0
  44. package/lib/utils/resolveModuleUrl.js +65 -0
  45. package/lib/utils/runjsModuleLoader.d.ts +58 -0
  46. package/lib/utils/runjsModuleLoader.js +422 -0
  47. package/lib/utils/runjsTemplateCompat.d.ts +35 -0
  48. package/lib/utils/runjsTemplateCompat.js +743 -0
  49. package/lib/utils/safeGlobals.d.ts +5 -9
  50. package/lib/utils/safeGlobals.js +129 -17
  51. package/lib/views/createViewMeta.d.ts +0 -7
  52. package/lib/views/createViewMeta.js +19 -70
  53. package/lib/views/index.d.ts +1 -2
  54. package/lib/views/index.js +4 -3
  55. package/lib/views/useDialog.js +8 -3
  56. package/lib/views/useDrawer.js +7 -2
  57. package/lib/views/usePage.d.ts +4 -0
  58. package/lib/views/usePage.js +43 -6
  59. package/lib/views/usePopover.js +4 -1
  60. package/lib/views/viewEvents.d.ts +17 -0
  61. package/lib/views/viewEvents.js +90 -0
  62. package/package.json +4 -4
  63. package/src/BlockScopedFlowEngine.ts +2 -5
  64. package/src/JSRunner.ts +8 -1
  65. package/src/ViewScopedFlowEngine.ts +4 -0
  66. package/src/__tests__/createViewMeta.popup.test.ts +62 -1
  67. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
  68. package/src/__tests__/flowSettings.open.test.tsx +69 -15
  69. package/src/__tests__/provider.test.tsx +0 -5
  70. package/src/__tests__/runjsExternalLibs.test.ts +242 -0
  71. package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
  72. package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
  73. package/src/acl/Acl.tsx +3 -3
  74. package/src/components/__tests__/gridDragPlanner.test.ts +141 -1
  75. package/src/components/dnd/gridDragPlanner.ts +60 -0
  76. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
  77. package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
  78. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +11 -3
  79. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +63 -4
  80. package/src/components/variables/VariableInput.tsx +8 -2
  81. package/src/data-source/index.ts +6 -0
  82. package/src/executor/FlowExecutor.ts +193 -23
  83. package/src/executor/__tests__/flowExecutor.test.ts +66 -0
  84. package/src/flowContext.ts +234 -118
  85. package/src/flowEngine.ts +41 -0
  86. package/src/flowSettings.ts +12 -11
  87. package/src/index.ts +10 -0
  88. package/src/models/CollectionFieldModel.tsx +3 -1
  89. package/src/models/__tests__/dispatchEvent.when.test.ts +356 -0
  90. package/src/models/__tests__/flowModel.clone.test.ts +416 -0
  91. package/src/models/__tests__/flowModel.test.ts +16 -0
  92. package/src/models/flowModel.tsx +94 -1
  93. package/src/provider.tsx +9 -7
  94. package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
  95. package/src/resources/__tests__/sqlResource.test.ts +60 -0
  96. package/src/resources/baseRecordResource.ts +31 -0
  97. package/src/resources/multiRecordResource.ts +11 -4
  98. package/src/resources/singleRecordResource.ts +3 -0
  99. package/src/resources/sqlResource.ts +8 -3
  100. package/src/runjs-context/contexts/base.ts +9 -2
  101. package/src/runjsLibs.ts +622 -0
  102. package/src/scheduler/ModelOperationScheduler.ts +23 -21
  103. package/src/types.ts +26 -1
  104. package/src/utils/__tests__/params-resolvers.test.ts +40 -0
  105. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
  106. package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
  107. package/src/utils/__tests__/safeGlobals.test.ts +49 -2
  108. package/src/utils/createCollectionContextMeta.ts +1 -0
  109. package/src/utils/index.ts +6 -0
  110. package/src/utils/params-resolvers.ts +23 -9
  111. package/src/utils/resolveModuleUrl.ts +91 -0
  112. package/src/utils/runjsModuleLoader.ts +553 -0
  113. package/src/utils/runjsTemplateCompat.ts +828 -0
  114. package/src/utils/safeGlobals.ts +133 -16
  115. package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
  116. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
  117. package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
  118. package/src/views/createViewMeta.ts +22 -75
  119. package/src/views/index.tsx +1 -2
  120. package/src/views/useDialog.tsx +9 -2
  121. package/src/views/useDrawer.tsx +8 -1
  122. package/src/views/usePage.tsx +51 -5
  123. package/src/views/usePopover.tsx +4 -1
  124. package/src/views/viewEvents.ts +55 -0
@@ -32,6 +32,7 @@ import {
32
32
  shouldHideStepInSettings,
33
33
  } from './utils';
34
34
  import { FlowStepContext } from './hooks/useFlowStep';
35
+ import { GLOBAL_EMBED_CONTAINER_ID, EMBED_REPLACING_DATA_KEY } from './views';
35
36
 
36
37
  const Panel = Collapse.Panel;
37
38
 
@@ -682,14 +683,10 @@ export class FlowSettings {
682
683
  typeof resolvedUiMode === 'object' && resolvedUiMode ? resolvedUiMode.props || {} : {};
683
684
 
684
685
  if (modeType === 'embed') {
685
- const target = document.querySelector<HTMLDivElement>('#nocobase-embed-container');
686
+ const target = document.querySelector<HTMLDivElement>(`#${GLOBAL_EMBED_CONTAINER_ID}`);
686
687
  const onOpen = modeProps.onOpen;
687
688
  const onClose = modeProps.onClose;
688
689
 
689
- if (target) {
690
- target.innerHTML = ''; // 清空容器内原有内容
691
- }
692
-
693
690
  modeProps = {
694
691
  target,
695
692
  styles: {
@@ -699,15 +696,19 @@ export class FlowSettings {
699
696
  },
700
697
  ...modeProps,
701
698
  onOpen() {
702
- target.style.width = modeProps.width || '33.3%';
703
- target.style.maxWidth = modeProps.maxWidth || '800px';
704
- target.style.minWidth = modeProps.minWidth || '0px';
699
+ if (target) {
700
+ target.style.width = modeProps.width || '33.3%';
701
+ target.style.maxWidth = modeProps.maxWidth || '800px';
702
+ target.style.minWidth = modeProps.minWidth || '0px';
703
+ }
705
704
  onOpen?.();
706
705
  },
707
706
  onClose() {
708
- target.style.width = 'auto';
709
- target.style.maxWidth = 'none';
710
- target.style.minWidth = 'auto';
707
+ if (target && target.dataset[EMBED_REPLACING_DATA_KEY] !== '1') {
708
+ target.style.width = 'auto';
709
+ target.style.maxWidth = 'none';
710
+ target.style.minWidth = 'auto';
711
+ }
711
712
  onClose?.();
712
713
  },
713
714
  };
package/src/index.ts CHANGED
@@ -13,6 +13,8 @@ export * from './types';
13
13
  // 工具函数
14
14
  export * from './utils';
15
15
  export { compileRunJs } from './utils/jsxTransform';
16
+ export { registerRunJSLib } from './runjsLibs';
17
+ export type { RunJSLibCache, RunJSLibLoader } from './runjsLibs';
16
18
 
17
19
  // 资源类
18
20
  export * from './resources';
@@ -42,6 +44,14 @@ export { setupRunJSContexts } from './runjs-context/setup';
42
44
  export { getSnippetBody, listSnippetsForContext } from './runjs-context/snippets';
43
45
 
44
46
  export * from './views';
47
+ export {
48
+ DATA_SOURCE_DIRTY_EVENT,
49
+ ENGINE_SCOPE_KEY,
50
+ getEmitterViewActivatedVersion,
51
+ VIEW_ACTIVATED_EVENT,
52
+ VIEW_ACTIVATED_VERSION,
53
+ VIEW_ENGINE_SCOPE,
54
+ } from './views/viewEvents';
45
55
 
46
56
  export * from './FlowDefinition';
47
57
  export { createViewScopedEngine } from './ViewScopedFlowEngine';
@@ -190,7 +190,7 @@ export class CollectionFieldModel<T extends DefaultStructure = DefaultStructure>
190
190
  }
191
191
 
192
192
  // Filter the mappings based on the `when` condition
193
- const bindings = this.bindings.get(interfaceName);
193
+ const bindings = this.bindings.get(interfaceName).sort((a, b) => a.order - b.order);
194
194
  return bindings.filter(
195
195
  (binding) => ctx.engine.getModelClass(binding.modelName) && binding.when(ctx, collectionField),
196
196
  );
@@ -261,6 +261,7 @@ export class CollectionFieldModel<T extends DefaultStructure = DefaultStructure>
261
261
  interfaceName: string | string[],
262
262
  options: {
263
263
  isDefault?: boolean;
264
+ order?: number;
264
265
  defaultProps?: object | ((ctx: FlowEngineContext, fieldInstance: CollectionField) => object);
265
266
  when?: (ctx: FlowEngineContext, fieldInstance: CollectionField) => boolean;
266
267
  } = {},
@@ -281,6 +282,7 @@ export class CollectionFieldModel<T extends DefaultStructure = DefaultStructure>
281
282
  isDefault: options.isDefault || false,
282
283
  defaultProps: options.defaultProps || null,
283
284
  when: options.when || defaultWhen,
285
+ order: options.order,
284
286
  });
285
287
 
286
288
  // Update the map
@@ -0,0 +1,356 @@
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("phase='afterAllFlows': instance flow runs after static flows", 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
+
57
+ const model = engine.createModel({ use: 'M' });
58
+ model.registerFlow('D', {
59
+ on: { eventName: 'go', phase: 'afterAllFlows' },
60
+ steps: {
61
+ d: { handler: async () => void calls.push('dynamic') } as any,
62
+ },
63
+ });
64
+
65
+ await model.dispatchEvent('go');
66
+ expect(calls).toEqual(['static-a', 'dynamic']);
67
+ });
68
+
69
+ test("phase='beforeFlow': instance flow runs before the target static flow", async () => {
70
+ const engine = new FlowEngine();
71
+ class M extends FlowModel {}
72
+ engine.registerModels({ M });
73
+
74
+ const calls: string[] = [];
75
+
76
+ M.registerFlow({
77
+ key: 'S',
78
+ on: { eventName: 'go' },
79
+ steps: {
80
+ a: { handler: async () => void calls.push('static-a') } as any,
81
+ b: { handler: async () => void calls.push('static-b') } as any,
82
+ },
83
+ });
84
+
85
+ const model = engine.createModel({ use: 'M' });
86
+ model.registerFlow('D', {
87
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'S' },
88
+ steps: {
89
+ d: { handler: async () => void calls.push('dynamic') } as any,
90
+ },
91
+ });
92
+
93
+ await model.dispatchEvent('go');
94
+ expect(calls).toEqual(['dynamic', 'static-a', 'static-b']);
95
+ });
96
+
97
+ test("phase='afterFlow': instance flow runs after the target static flow", async () => {
98
+ const engine = new FlowEngine();
99
+ class M extends FlowModel {}
100
+ engine.registerModels({ M });
101
+
102
+ const calls: string[] = [];
103
+
104
+ M.registerFlow({
105
+ key: 'S',
106
+ on: { eventName: 'go' },
107
+ steps: {
108
+ a: { handler: async () => void calls.push('static-a') } as any,
109
+ b: { handler: async () => void calls.push('static-b') } as any,
110
+ },
111
+ });
112
+
113
+ const model = engine.createModel({ use: 'M' });
114
+ model.registerFlow('D', {
115
+ on: { eventName: 'go', phase: 'afterFlow', flowKey: 'S' },
116
+ steps: {
117
+ d: { handler: async () => void calls.push('dynamic') } as any,
118
+ },
119
+ });
120
+
121
+ await model.dispatchEvent('go');
122
+ expect(calls).toEqual(['static-a', 'static-b', 'dynamic']);
123
+ });
124
+
125
+ test("phase='beforeStep': instance flow runs before the target static step", async () => {
126
+ const engine = new FlowEngine();
127
+ class M extends FlowModel {}
128
+ engine.registerModels({ M });
129
+
130
+ const calls: string[] = [];
131
+
132
+ M.registerFlow({
133
+ key: 'S',
134
+ on: { eventName: 'go' },
135
+ steps: {
136
+ a: { handler: async () => void calls.push('static-a') } as any,
137
+ b: { handler: async () => void calls.push('static-b') } as any,
138
+ },
139
+ });
140
+
141
+ const model = engine.createModel({ use: 'M' });
142
+ model.registerFlow('D', {
143
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
144
+ steps: {
145
+ d: { handler: async () => void calls.push('dynamic') } as any,
146
+ },
147
+ });
148
+
149
+ await model.dispatchEvent('go');
150
+ expect(calls).toEqual(['dynamic', 'static-a', 'static-b']);
151
+ });
152
+
153
+ test("phase='afterStep': instance flow runs after the target static step", async () => {
154
+ const engine = new FlowEngine();
155
+ class M extends FlowModel {}
156
+ engine.registerModels({ M });
157
+
158
+ const calls: string[] = [];
159
+
160
+ M.registerFlow({
161
+ key: 'S',
162
+ on: { eventName: 'go' },
163
+ steps: {
164
+ a: { handler: async () => void calls.push('static-a') } as any,
165
+ b: { handler: async () => void calls.push('static-b') } as any,
166
+ },
167
+ });
168
+
169
+ const model = engine.createModel({ use: 'M' });
170
+ model.registerFlow('D', {
171
+ on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
172
+ steps: {
173
+ d: { handler: async () => void calls.push('dynamic') } as any,
174
+ },
175
+ });
176
+
177
+ await model.dispatchEvent('go');
178
+ expect(calls).toEqual(['static-a', 'dynamic', 'static-b']);
179
+ });
180
+
181
+ test("phase='beforeFlow' missing flow: falls back to afterAllFlows", async () => {
182
+ const engine = new FlowEngine();
183
+ class M extends FlowModel {}
184
+ engine.registerModels({ M });
185
+
186
+ const calls: string[] = [];
187
+
188
+ M.registerFlow({
189
+ key: 'S',
190
+ on: { eventName: 'go' },
191
+ steps: {
192
+ a: { handler: async () => void calls.push('static-a') } as any,
193
+ },
194
+ });
195
+
196
+ const model = engine.createModel({ use: 'M' });
197
+ model.registerFlow('D', {
198
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'missing' },
199
+ steps: {
200
+ d: { handler: async () => void calls.push('dynamic') } as any,
201
+ },
202
+ });
203
+
204
+ await model.dispatchEvent('go');
205
+ expect(calls).toEqual(['static-a', 'dynamic']);
206
+ });
207
+
208
+ test("phase='beforeStep' missing step: falls back to afterAllFlows", async () => {
209
+ const engine = new FlowEngine();
210
+ class M extends FlowModel {}
211
+ engine.registerModels({ M });
212
+
213
+ const calls: string[] = [];
214
+
215
+ M.registerFlow({
216
+ key: 'S',
217
+ on: { eventName: 'go' },
218
+ steps: {
219
+ a: { handler: async () => void calls.push('static-a') } as any,
220
+ },
221
+ });
222
+
223
+ const model = engine.createModel({ use: 'M' });
224
+ model.registerFlow('D', {
225
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'missing' },
226
+ steps: {
227
+ d: { handler: async () => void calls.push('dynamic') } as any,
228
+ },
229
+ });
230
+
231
+ await model.dispatchEvent('go');
232
+ expect(calls).toEqual(['static-a', 'dynamic']);
233
+ });
234
+
235
+ test('multiple flows on same anchor: executes by flow.sort asc (stable)', async () => {
236
+ const engine = new FlowEngine();
237
+ class M extends FlowModel {}
238
+ engine.registerModels({ M });
239
+
240
+ const calls: string[] = [];
241
+
242
+ M.registerFlow({
243
+ key: 'S',
244
+ on: { eventName: 'go' },
245
+ steps: {
246
+ a: { handler: async () => void calls.push('static-a') } as any,
247
+ },
248
+ });
249
+
250
+ const model = engine.createModel({ use: 'M' });
251
+ model.registerFlow('D5', {
252
+ sort: 5,
253
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
254
+ steps: {
255
+ d: { handler: async () => void calls.push('dynamic-5') } as any,
256
+ },
257
+ });
258
+ model.registerFlow('D0', {
259
+ sort: 0,
260
+ on: { eventName: 'go', phase: 'beforeStep', flowKey: 'S', stepKey: 'a' },
261
+ steps: {
262
+ d: { handler: async () => void calls.push('dynamic-0') } as any,
263
+ },
264
+ });
265
+
266
+ await model.dispatchEvent('go');
267
+ expect(calls).toEqual(['dynamic-0', 'dynamic-5', 'static-a']);
268
+ });
269
+ });
270
+
271
+ describe('dispatchEvent static flow phase (scheduleModelOperation integration)', () => {
272
+ test("phase='beforeFlow': static flow runs before the target static flow", async () => {
273
+ const engine = new FlowEngine();
274
+ class M extends FlowModel {}
275
+ engine.registerModels({ M });
276
+
277
+ const calls: string[] = [];
278
+
279
+ M.registerFlow({
280
+ key: 'S',
281
+ on: { eventName: 'go' },
282
+ steps: {
283
+ a: { handler: async () => void calls.push('static-a') } as any,
284
+ },
285
+ });
286
+
287
+ M.registerFlow({
288
+ key: 'P',
289
+ on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'S' },
290
+ steps: {
291
+ p: { handler: async () => void calls.push('phase') } as any,
292
+ },
293
+ });
294
+
295
+ const model = engine.createModel({ use: 'M' });
296
+ await model.dispatchEvent('go');
297
+ expect(calls).toEqual(['phase', 'static-a']);
298
+ });
299
+
300
+ test("phase='afterStep': static flow runs after the target static step", async () => {
301
+ const engine = new FlowEngine();
302
+ class M extends FlowModel {}
303
+ engine.registerModels({ M });
304
+
305
+ const calls: string[] = [];
306
+
307
+ M.registerFlow({
308
+ key: 'S',
309
+ on: { eventName: 'go' },
310
+ steps: {
311
+ a: { handler: async () => void calls.push('static-a') } as any,
312
+ b: { handler: async () => void calls.push('static-b') } as any,
313
+ },
314
+ });
315
+
316
+ M.registerFlow({
317
+ key: 'P',
318
+ on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
319
+ steps: {
320
+ p: { handler: async () => void calls.push('phase') } as any,
321
+ },
322
+ });
323
+
324
+ const model = engine.createModel({ use: 'M' });
325
+ await model.dispatchEvent('go');
326
+ expect(calls).toEqual(['static-a', 'phase', 'static-b']);
327
+ });
328
+
329
+ test("phase='afterAllFlows': static flow runs after static flows", async () => {
330
+ const engine = new FlowEngine();
331
+ class M extends FlowModel {}
332
+ engine.registerModels({ M });
333
+
334
+ const calls: string[] = [];
335
+
336
+ M.registerFlow({
337
+ key: 'S',
338
+ on: { eventName: 'go' },
339
+ steps: {
340
+ a: { handler: async () => void calls.push('static-a') } as any,
341
+ },
342
+ });
343
+
344
+ M.registerFlow({
345
+ key: 'P',
346
+ on: { eventName: 'go', phase: 'afterAllFlows' },
347
+ steps: {
348
+ p: { handler: async () => void calls.push('phase') } as any,
349
+ },
350
+ });
351
+
352
+ const model = engine.createModel({ use: 'M' });
353
+ await model.dispatchEvent('go');
354
+ expect(calls).toEqual(['static-a', 'phase']);
355
+ });
356
+ });