@nocobase/flow-engine 2.0.0-alpha.63 → 2.0.0-alpha.65
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.
- package/lib/acl/Acl.js +13 -3
- package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
- package/lib/components/dnd/gridDragPlanner.js +53 -1
- package/lib/data-source/index.js +3 -0
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +156 -22
- package/lib/flowContext.js +97 -0
- package/lib/provider.js +2 -1
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +21 -21
- package/lib/types.d.ts +15 -0
- package/lib/views/useDialog.js +1 -1
- package/package.json +4 -4
- package/src/__tests__/provider.test.tsx +0 -5
- package/src/acl/Acl.tsx +3 -3
- package/src/components/__tests__/gridDragPlanner.test.ts +141 -1
- package/src/components/dnd/gridDragPlanner.ts +60 -0
- package/src/data-source/index.ts +3 -0
- package/src/executor/FlowExecutor.ts +193 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +128 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +356 -0
- package/src/models/__tests__/flowModel.test.ts +16 -0
- package/src/provider.tsx +2 -1
- package/src/scheduler/ModelOperationScheduler.ts +23 -21
- package/src/types.ts +26 -1
- package/src/views/useDialog.tsx +1 -1
|
@@ -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
|
+
});
|
|
@@ -1558,6 +1558,22 @@ describe('FlowModel', () => {
|
|
|
1558
1558
|
expect(model.forks.size).toBe(1);
|
|
1559
1559
|
});
|
|
1560
1560
|
|
|
1561
|
+
test('should recreate cached fork after dispose to avoid state leakage', () => {
|
|
1562
|
+
const fork1 = model.createFork({ foo: 'bar' }, 'cacheKey');
|
|
1563
|
+
fork1.hidden = true;
|
|
1564
|
+
fork1.setProps({ disabled: true });
|
|
1565
|
+
|
|
1566
|
+
fork1.dispose();
|
|
1567
|
+
|
|
1568
|
+
expect(model.getFork('cacheKey')).toBeUndefined();
|
|
1569
|
+
|
|
1570
|
+
const fork2 = model.createFork({}, 'cacheKey');
|
|
1571
|
+
|
|
1572
|
+
expect(fork2).not.toBe(fork1);
|
|
1573
|
+
expect(fork2.hidden).toBe(false);
|
|
1574
|
+
expect(fork2.localProps).toEqual({});
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1561
1577
|
test('should create different instances for different keys', () => {
|
|
1562
1578
|
const fork1 = model.createFork({}, 'key1');
|
|
1563
1579
|
const fork2 = model.createFork({}, 'key2');
|
package/src/provider.tsx
CHANGED
|
@@ -90,9 +90,10 @@ export const useFlowEngine = ({ throwError = true } = {}): FlowEngine => {
|
|
|
90
90
|
if (!context && throwError) {
|
|
91
91
|
// This error should ideally not be hit if FlowEngineProvider is used correctly at the root
|
|
92
92
|
// and always supplied with an engine.
|
|
93
|
-
|
|
93
|
+
console.warn(
|
|
94
94
|
'useFlowEngine must be used within a FlowEngineProvider, and FlowEngineProvider must be supplied with an engine.',
|
|
95
95
|
);
|
|
96
|
+
return;
|
|
96
97
|
}
|
|
97
98
|
return context;
|
|
98
99
|
};
|
|
@@ -36,6 +36,8 @@ export interface LifecycleEvent {
|
|
|
36
36
|
error?: any;
|
|
37
37
|
inputArgs?: Record<string, any>;
|
|
38
38
|
result?: any;
|
|
39
|
+
flowKey?: string;
|
|
40
|
+
stepKey?: string;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
type ScheduledItem = {
|
|
@@ -162,37 +164,37 @@ export class ModelOperationScheduler {
|
|
|
162
164
|
const emitter = this.engine.emitter;
|
|
163
165
|
if (!emitter || typeof emitter.on !== 'function') return;
|
|
164
166
|
|
|
165
|
-
const onCreated = (e: LifecycleEvent) => {
|
|
166
|
-
this.processLifecycleEvent(e.uid, { ...e, type: 'created' });
|
|
167
|
+
const onCreated = async (e: LifecycleEvent) => {
|
|
168
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: 'created' });
|
|
167
169
|
};
|
|
168
170
|
emitter.on('model:created', onCreated);
|
|
169
171
|
this.unbindHandlers.push(() => emitter.off('model:created', onCreated));
|
|
170
172
|
|
|
171
|
-
const onMounted = (e: LifecycleEvent) => {
|
|
172
|
-
this.processLifecycleEvent(e.uid, { ...e, type: 'mounted' });
|
|
173
|
+
const onMounted = async (e: LifecycleEvent) => {
|
|
174
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: 'mounted' });
|
|
173
175
|
};
|
|
174
176
|
emitter.on('model:mounted', onMounted);
|
|
175
177
|
this.unbindHandlers.push(() => emitter.off('model:mounted', onMounted));
|
|
176
178
|
|
|
177
|
-
const onGenericBeforeStart = (e: LifecycleEvent) => {
|
|
178
|
-
this.processLifecycleEvent(e.uid, { ...e, type: 'event:beforeRender:start' });
|
|
179
|
+
const onGenericBeforeStart = async (e: LifecycleEvent) => {
|
|
180
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: 'event:beforeRender:start' });
|
|
179
181
|
};
|
|
180
182
|
emitter.on('model:event:beforeRender:start', onGenericBeforeStart);
|
|
181
183
|
this.unbindHandlers.push(() => emitter.off('model:event:beforeRender:start', onGenericBeforeStart));
|
|
182
184
|
|
|
183
|
-
const onGenericBeforeEnd = (e: LifecycleEvent) => {
|
|
184
|
-
this.processLifecycleEvent(e.uid, { ...e, type: 'event:beforeRender:end' });
|
|
185
|
+
const onGenericBeforeEnd = async (e: LifecycleEvent) => {
|
|
186
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: 'event:beforeRender:end' });
|
|
185
187
|
};
|
|
186
188
|
emitter.on('model:event:beforeRender:end', onGenericBeforeEnd);
|
|
187
189
|
this.unbindHandlers.push(() => emitter.off('model:event:beforeRender:end', onGenericBeforeEnd));
|
|
188
190
|
|
|
189
|
-
const onUnmounted = (e: LifecycleEvent) => {
|
|
190
|
-
this.processLifecycleEvent(e.uid, { ...e, type: 'unmounted' });
|
|
191
|
+
const onUnmounted = async (e: LifecycleEvent) => {
|
|
192
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: 'unmounted' });
|
|
191
193
|
};
|
|
192
194
|
emitter.on('model:unmounted', onUnmounted);
|
|
193
195
|
this.unbindHandlers.push(() => emitter.off('model:unmounted', onUnmounted));
|
|
194
196
|
|
|
195
|
-
const onDestroyed = (e: LifecycleEvent) => {
|
|
197
|
+
const onDestroyed = async (e: LifecycleEvent) => {
|
|
196
198
|
const targetBucket = this.itemsByTargetUid.get(e.uid);
|
|
197
199
|
const event = { ...e, type: 'destroyed' as const };
|
|
198
200
|
if (targetBucket && targetBucket.size) {
|
|
@@ -201,7 +203,7 @@ export class ModelOperationScheduler {
|
|
|
201
203
|
const it = this.itemsById.get(id);
|
|
202
204
|
if (!it) continue;
|
|
203
205
|
if (this.shouldTrigger(it.options.when, event)) {
|
|
204
|
-
|
|
206
|
+
await this.tryExecuteOnce(id, event);
|
|
205
207
|
} else {
|
|
206
208
|
this.internalCancel(id);
|
|
207
209
|
}
|
|
@@ -220,14 +222,14 @@ export class ModelOperationScheduler {
|
|
|
220
222
|
if (this.subscribedEventNames.has(name)) return;
|
|
221
223
|
this.subscribedEventNames.add(name);
|
|
222
224
|
const emitter = this.engine.emitter;
|
|
223
|
-
const onStart = (e: LifecycleEvent) => {
|
|
224
|
-
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:start` as
|
|
225
|
+
const onStart = async (e: LifecycleEvent) => {
|
|
226
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:start` as LifecycleType });
|
|
225
227
|
};
|
|
226
|
-
const onEnd = (e: LifecycleEvent) => {
|
|
227
|
-
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:end` as
|
|
228
|
+
const onEnd = async (e: LifecycleEvent) => {
|
|
229
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:end` as LifecycleType });
|
|
228
230
|
};
|
|
229
|
-
const onError = (e: LifecycleEvent) => {
|
|
230
|
-
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:error` as
|
|
231
|
+
const onError = async (e: LifecycleEvent) => {
|
|
232
|
+
await this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:error` as LifecycleType });
|
|
231
233
|
};
|
|
232
234
|
emitter.on(`model:event:${name}:start`, onStart);
|
|
233
235
|
emitter.on(`model:event:${name}:end`, onEnd);
|
|
@@ -239,12 +241,12 @@ export class ModelOperationScheduler {
|
|
|
239
241
|
|
|
240
242
|
private parseEventWhen(when?: ScheduleWhen): { name: string; phase: 'start' | 'end' | 'error' } | null {
|
|
241
243
|
if (!when || typeof when !== 'string') return null;
|
|
242
|
-
const m = /^event:(
|
|
244
|
+
const m = /^event:(.+):(start|end|error)$/.exec(when);
|
|
243
245
|
if (!m) return null;
|
|
244
246
|
return { name: m[1], phase: m[2] as 'start' | 'end' | 'error' };
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
private processLifecycleEvent(targetUid: string, event: LifecycleEvent) {
|
|
249
|
+
private async processLifecycleEvent(targetUid: string, event: LifecycleEvent) {
|
|
248
250
|
const targetBucket = this.itemsByTargetUid.get(targetUid);
|
|
249
251
|
if (!targetBucket || targetBucket.size === 0) return;
|
|
250
252
|
const ids = Array.from(targetBucket.keys());
|
|
@@ -253,7 +255,7 @@ export class ModelOperationScheduler {
|
|
|
253
255
|
if (!item) continue;
|
|
254
256
|
const should = this.shouldTrigger(item.options.when, event);
|
|
255
257
|
if (!should) continue;
|
|
256
|
-
|
|
258
|
+
await this.tryExecuteOnce(id, event);
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
261
|
|
package/src/types.ts
CHANGED
|
@@ -213,12 +213,37 @@ export type FlowEventName =
|
|
|
213
213
|
// fallback to any string for extensibility
|
|
214
214
|
| (string & {});
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* 事件流的执行时机(phase)。
|
|
218
|
+
*
|
|
219
|
+
* 说明:
|
|
220
|
+
* - 缺省(phase 未配置)表示保持现有行为;
|
|
221
|
+
* - 当配置了 phase 时,运行时会将其映射为 `scheduleModelOperation` 的 `when` 锚点;
|
|
222
|
+
* - phase 同时适用于动态事件流(实例级)与静态流(内置)。
|
|
223
|
+
*/
|
|
224
|
+
export type FlowEventPhase =
|
|
225
|
+
| 'beforeAllFlows'
|
|
226
|
+
| 'afterAllFlows'
|
|
227
|
+
| 'beforeFlow'
|
|
228
|
+
| 'afterFlow'
|
|
229
|
+
| 'beforeStep'
|
|
230
|
+
| 'afterStep';
|
|
231
|
+
|
|
216
232
|
/**
|
|
217
233
|
* Flow 事件类型(供 FlowDefinitionOptions.on 使用)。
|
|
218
234
|
*/
|
|
219
235
|
export type FlowEvent<TModel extends FlowModel = FlowModel> =
|
|
220
236
|
| FlowEventName
|
|
221
|
-
| {
|
|
237
|
+
| {
|
|
238
|
+
eventName: FlowEventName;
|
|
239
|
+
defaultParams?: Record<string, any>;
|
|
240
|
+
/** 动态事件流的执行时机(默认 beforeAllFlows) */
|
|
241
|
+
phase?: FlowEventPhase;
|
|
242
|
+
/** phase 为 beforeFlow/afterFlow/beforeStep/afterStep 时使用 */
|
|
243
|
+
flowKey?: string;
|
|
244
|
+
/** phase 为 beforeStep/afterStep 时使用 */
|
|
245
|
+
stepKey?: string;
|
|
246
|
+
};
|
|
222
247
|
|
|
223
248
|
/**
|
|
224
249
|
* 事件分发选项。
|
package/src/views/useDialog.tsx
CHANGED
|
@@ -164,9 +164,9 @@ export function useDialog() {
|
|
|
164
164
|
className="nb-dialog-overflow-hidden"
|
|
165
165
|
ref={dialogRef}
|
|
166
166
|
hidden={config.inputArgs?.hidden?.value}
|
|
167
|
-
{...config}
|
|
168
167
|
footer={currentFooter}
|
|
169
168
|
header={currentHeader}
|
|
169
|
+
{...config}
|
|
170
170
|
onCancel={() => {
|
|
171
171
|
currentDialog.close(config.result);
|
|
172
172
|
}}
|