@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
@@ -37,7 +37,6 @@ function createBlockScopedEngine(parent) {
37
37
  local.setModelRepository(parent.modelRepository);
38
38
  }
39
39
  local.context.addDelegate(parent.context);
40
- const originalUnlink = local.unlinkFromStack.bind(local);
41
40
  local.unlinkFromStack = function() {
42
41
  const prev = local._previousEngine;
43
42
  const next = local._nextEngine;
package/lib/JSRunner.d.ts CHANGED
@@ -11,6 +11,12 @@ export interface JSRunnerOptions {
11
11
  timeoutMs?: number;
12
12
  globals?: Record<string, any>;
13
13
  version?: string;
14
+ /**
15
+ * Enable RunJS template compatibility preprocessing for `{{ ... }}`.
16
+ * When enabled via `ctx.runjs(code, vars, { preprocessTemplates: true })` (default),
17
+ * the code will be rewritten to call `ctx.resolveJsonTemplate(...)` at runtime.
18
+ */
19
+ preprocessTemplates?: boolean;
14
20
  }
15
21
  export declare class JSRunner {
16
22
  private globals;
package/lib/JSRunner.js CHANGED
@@ -62,7 +62,8 @@ const _JSRunner = class _JSRunner {
62
62
  * 异步运行代码,带错误处理和超时机制
63
63
  */
64
64
  async run(code) {
65
- if (location == null ? void 0 : location.search.includes("skipRunJs=true")) {
65
+ const search = typeof location !== "undefined" ? location.search : void 0;
66
+ if (typeof search === "string" && search.includes("skipRunJs=true")) {
66
67
  return { success: true, value: null };
67
68
  }
68
69
  const wrapped = `(async () => {
@@ -31,8 +31,10 @@ __export(ViewScopedFlowEngine_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(ViewScopedFlowEngine_exports);
33
33
  var import_flowEngine = require("./flowEngine");
34
+ var import_viewEvents = require("./views/viewEvents");
34
35
  function createViewScopedEngine(parent) {
35
36
  const local = new import_flowEngine.FlowEngine();
37
+ Object.defineProperty(local, import_viewEvents.ENGINE_SCOPE_KEY, { value: import_viewEvents.VIEW_ENGINE_SCOPE, configurable: true });
36
38
  if (parent.modelRepository) {
37
39
  local.setModelRepository(parent.modelRepository);
38
40
  }
@@ -48,6 +50,7 @@ function createViewScopedEngine(parent) {
48
50
  "_applyFlowCache",
49
51
  "executor",
50
52
  "context",
53
+ import_viewEvents.ENGINE_SCOPE_KEY,
51
54
  "previousEngine",
52
55
  "nextEngine",
53
56
  // 调度器与事件总线局部化
package/lib/acl/Acl.js CHANGED
@@ -7,9 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
+ var __create = Object.create;
10
11
  var __defProp = Object.defineProperty;
11
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
13
  var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
13
15
  var __hasOwnProp = Object.prototype.hasOwnProperty;
14
16
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
17
  var __export = (target, all) => {
@@ -24,13 +26,21 @@ var __copyProps = (to, from, except, desc) => {
24
26
  }
25
27
  return to;
26
28
  };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
27
37
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
38
  var Acl_exports = {};
29
39
  __export(Acl_exports, {
30
40
  ACL: () => ACL
31
41
  });
32
42
  module.exports = __toCommonJS(Acl_exports);
33
- var import_lodash = require("lodash");
43
+ var import_lodash = __toESM(require("lodash"));
34
44
  const _ACL = class _ACL {
35
45
  constructor(flowEngine) {
36
46
  this.flowEngine = flowEngine;
@@ -43,10 +53,10 @@ const _ACL = class _ACL {
43
53
  // 记录上一次用于鉴权的 token,用于识别登录态变更
44
54
  lastToken = null;
45
55
  setData(data) {
46
- this.data = data;
56
+ this.data = import_lodash.default.cloneDeep(data);
47
57
  }
48
58
  setMeta(data) {
49
- this.meta = data;
59
+ this.meta = import_lodash.default.cloneDeep(data);
50
60
  }
51
61
  async load() {
52
62
  var _a, _b, _c, _d;
@@ -31,6 +31,7 @@ export interface Point {
31
31
  export interface GridLayoutData {
32
32
  rows: Record<string, string[][]>;
33
33
  sizes: Record<string, number[]>;
34
+ rowOrder?: string[];
34
35
  }
35
36
  export interface ColumnSlot {
36
37
  type: 'column';
@@ -59,6 +59,43 @@ const COLUMN_EDGE_MAX_WIDTH = 28;
59
59
  const COLUMN_EDGE_WIDTH_RATIO = 0.2;
60
60
  const COLUMN_INSERT_THICKNESS_RATIO = 0.5;
61
61
  const ROW_GAP_HEIGHT_RATIO = 0.33;
62
+ const deriveRowOrder = /* @__PURE__ */ __name((rows, provided) => {
63
+ const order = [];
64
+ const used = /* @__PURE__ */ new Set();
65
+ (provided || Object.keys(rows)).forEach((rowId) => {
66
+ if (rows[rowId] && !used.has(rowId)) {
67
+ order.push(rowId);
68
+ used.add(rowId);
69
+ }
70
+ });
71
+ Object.keys(rows).forEach((rowId) => {
72
+ if (!used.has(rowId)) {
73
+ order.push(rowId);
74
+ used.add(rowId);
75
+ }
76
+ });
77
+ return order;
78
+ }, "deriveRowOrder");
79
+ const normalizeRowsWithOrder = /* @__PURE__ */ __name((rows, order) => {
80
+ const next = {};
81
+ order.forEach((rowId) => {
82
+ if (rows[rowId]) {
83
+ next[rowId] = rows[rowId];
84
+ }
85
+ });
86
+ Object.keys(rows).forEach((rowId) => {
87
+ if (!next[rowId]) {
88
+ next[rowId] = rows[rowId];
89
+ }
90
+ });
91
+ return next;
92
+ }, "normalizeRowsWithOrder");
93
+ const ensureRowOrder = /* @__PURE__ */ __name((layout) => {
94
+ const order = deriveRowOrder(layout.rows, layout.rowOrder);
95
+ layout.rowOrder = order;
96
+ layout.rows = normalizeRowsWithOrder(layout.rows, order);
97
+ return order;
98
+ }, "ensureRowOrder");
62
99
  const toRect = /* @__PURE__ */ __name((domRect) => ({
63
100
  top: domRect.top,
64
101
  left: domRect.left,
@@ -330,9 +367,11 @@ const removeItemFromLayout = /* @__PURE__ */ __name((layout, uidValue) => {
330
367
  if (columns.length === 0) {
331
368
  delete layout.rows[rowId];
332
369
  delete layout.sizes[rowId];
370
+ ensureRowOrder(layout);
333
371
  return;
334
372
  }
335
373
  normalizeRowSizes(rowId, layout);
374
+ ensureRowOrder(layout);
336
375
  }, "removeItemFromLayout");
337
376
  const toIntSizes = /* @__PURE__ */ __name((weights, count) => {
338
377
  if (count === 0) {
@@ -420,8 +459,10 @@ const simulateLayoutForSlot = /* @__PURE__ */ __name(({
420
459
  }) => {
421
460
  const cloned = {
422
461
  rows: import_lodash.default.cloneDeep(layout.rows),
423
- sizes: import_lodash.default.cloneDeep(layout.sizes)
462
+ sizes: import_lodash.default.cloneDeep(layout.sizes),
463
+ rowOrder: layout.rowOrder ? [...layout.rowOrder] : void 0
424
464
  };
465
+ ensureRowOrder(cloned);
425
466
  removeItemFromLayout(cloned, sourceUid);
426
467
  const createRowId = generateRowId ?? import_shared.uid;
427
468
  switch (slot.type) {
@@ -464,8 +505,15 @@ const simulateLayoutForSlot = /* @__PURE__ */ __name(({
464
505
  case "row-gap": {
465
506
  const newRowId = createRowId();
466
507
  const rowPosition = slot.position === "above" ? "before" : "after";
508
+ const currentOrder = deriveRowOrder(cloned.rows, cloned.rowOrder);
467
509
  cloned.rows = insertRow(cloned.rows, slot.targetRowId, newRowId, rowPosition, [[sourceUid]]);
468
510
  cloned.sizes[newRowId] = [DEFAULT_GRID_COLUMNS];
511
+ const targetIndex = currentOrder.indexOf(slot.targetRowId);
512
+ const insertIndex = targetIndex === -1 ? currentOrder.length : rowPosition === "before" ? targetIndex : targetIndex + 1;
513
+ const nextOrder = [...currentOrder];
514
+ nextOrder.splice(insertIndex, 0, newRowId);
515
+ cloned.rowOrder = nextOrder;
516
+ cloned.rows = normalizeRowsWithOrder(cloned.rows, nextOrder);
469
517
  break;
470
518
  }
471
519
  case "empty-row": {
@@ -475,11 +523,15 @@ const simulateLayoutForSlot = /* @__PURE__ */ __name(({
475
523
  [newRowId]: [[sourceUid]]
476
524
  };
477
525
  cloned.sizes[newRowId] = [DEFAULT_GRID_COLUMNS];
526
+ const currentOrder = deriveRowOrder(cloned.rows, cloned.rowOrder);
527
+ cloned.rowOrder = [...currentOrder.filter((id) => id !== newRowId), newRowId];
528
+ cloned.rows = normalizeRowsWithOrder(cloned.rows, cloned.rowOrder);
478
529
  break;
479
530
  }
480
531
  default:
481
532
  break;
482
533
  }
534
+ ensureRowOrder(cloned);
483
535
  return cloned;
484
536
  }, "simulateLayoutForSlot");
485
537
  // Annotate the CommonJS export names for ESM import in node:
@@ -73,7 +73,8 @@ const SwitchWithTitle = (0, import_reactive.observer)(
73
73
  setChecked(val);
74
74
  onChange == null ? void 0 : onChange({ [itemKey]: val });
75
75
  }, "handleChange");
76
- const handleWrapperClick = /* @__PURE__ */ __name(() => {
76
+ const handleWrapperClick = /* @__PURE__ */ __name((e) => {
77
+ e.stopPropagation();
77
78
  if (disabled) return;
78
79
  handleChange(!checked);
79
80
  }, "handleWrapperClick");
@@ -153,6 +153,9 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
153
153
  const [visible, setVisible] = (0, import_react.useState)(false);
154
154
  const [refreshTick, setRefreshTick] = (0, import_react.useState)(0);
155
155
  const [extraMenuItems, setExtraMenuItems] = (0, import_react.useState)([]);
156
+ const closeDropdown = (0, import_react.useCallback)(() => {
157
+ setVisible(false);
158
+ }, []);
156
159
  const handleOpenChange = (0, import_react.useCallback)((nextOpen, info) => {
157
160
  if (info.source === "trigger" || nextOpen) {
158
161
  (0, import_react.startTransition)(() => {
@@ -249,6 +252,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
249
252
  [model, copyUidToClipboard]
250
253
  );
251
254
  const handleDelete = (0, import_react.useCallback)(() => {
255
+ closeDropdown();
252
256
  import_antd.Modal.confirm({
253
257
  title: t("Confirm delete"),
254
258
  icon: /* @__PURE__ */ import_react.default.createElement(import_icons.ExclamationCircleOutlined, null),
@@ -269,7 +273,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
269
273
  }
270
274
  }
271
275
  });
272
- }, [model]);
276
+ }, [closeDropdown, model, t]);
273
277
  const handleStepConfiguration = (0, import_react.useCallback)(
274
278
  (key) => {
275
279
  const keyParts = key.split(":");
@@ -294,6 +298,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
294
298
  [flowKey, stepKey] = keyParts;
295
299
  }
296
300
  try {
301
+ closeDropdown();
297
302
  targetModel.openFlowSettings({
298
303
  flowKey,
299
304
  stepKey
@@ -302,23 +307,26 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
302
307
  console.log(t("Configuration popup cancelled or error"), ":", error);
303
308
  }
304
309
  },
305
- [model]
310
+ [closeDropdown, model, t]
306
311
  );
307
312
  const handleMenuClick = (0, import_react.useCallback)(
308
313
  ({ key }) => {
309
314
  const originalKey = key;
310
315
  const cleanKey = key.includes("-") && /^(.+)-\d+$/.test(key) ? key.replace(/-\d+$/, "") : key;
311
316
  if (cleanKey.startsWith("copy-pop-uid:")) {
317
+ closeDropdown();
312
318
  handleCopyPopupUid(cleanKey);
313
319
  return;
314
320
  }
315
321
  const extra = extraMenuItems.find((it) => (it == null ? void 0 : it.key) === originalKey || (it == null ? void 0 : it.key) === cleanKey);
316
322
  if (extra == null ? void 0 : extra.onClick) {
323
+ closeDropdown();
317
324
  extra.onClick();
318
325
  return;
319
326
  }
320
327
  switch (cleanKey) {
321
328
  case "copy-uid":
329
+ closeDropdown();
322
330
  handleCopyUid();
323
331
  break;
324
332
  case "delete":
@@ -329,7 +337,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
329
337
  break;
330
338
  }
331
339
  },
332
- [handleCopyUid, handleDelete, handleStepConfiguration, handleCopyPopupUid, extraMenuItems]
340
+ [closeDropdown, handleCopyUid, handleDelete, handleStepConfiguration, handleCopyPopupUid, extraMenuItems]
333
341
  );
334
342
  const getModelConfigurableFlowsAndSteps = (0, import_react.useCallback)(
335
343
  async (targetModel, modelKey) => {
@@ -230,12 +230,18 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
230
230
  );
231
231
  const handleVariableSelect = (0, import_react.useCallback)(
232
232
  (variableValue, metaTreeNode) => {
233
+ if (!metaTreeNode && variableValue === "") {
234
+ const cleared = clearValue !== void 0 ? clearValue : null;
235
+ setInnerValue(cleared);
236
+ emitChange(cleared);
237
+ return;
238
+ }
233
239
  setCurrentMetaTreeNode(metaTreeNode);
234
240
  const finalValue = (resolveValueFromPath == null ? void 0 : resolveValueFromPath(metaTreeNode)) || variableValue;
235
241
  setInnerValue(finalValue);
236
242
  emitChange(finalValue, metaTreeNode);
237
243
  },
238
- [emitChange, resolveValueFromPath]
244
+ [emitChange, resolveValueFromPath, clearValue]
239
245
  );
240
246
  const { disabled } = restProps;
241
247
  const handleClear = (0, import_react.useCallback)(() => {
@@ -265,7 +271,7 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
265
271
  }, [restProps]);
266
272
  const inputProps = (0, import_react.useMemo)(() => {
267
273
  const baseProps = {
268
- value: innerValue ?? "",
274
+ value: ValueComponent === import_antd.Input ? innerValue ?? "" : innerValue,
269
275
  onChange: handleInputChange,
270
276
  disabled
271
277
  };
@@ -400,6 +400,9 @@ const _Collection = class _Collection {
400
400
  if (typeof this.filterTargetKey === "string") {
401
401
  return record[this.filterTargetKey];
402
402
  }
403
+ if (Array.isArray(this.filterTargetKey) && this.filterTargetKey.length === 1) {
404
+ return record[this.filterTargetKey[0]];
405
+ }
403
406
  return import_lodash.default.pick(record, this.filterTargetKey);
404
407
  }
405
408
  get titleableFields() {
@@ -682,6 +685,9 @@ const _CollectionField = class _CollectionField {
682
685
  if (typeof v !== "object") {
683
686
  return v;
684
687
  }
688
+ if (v.value === null || v.value === void 0) {
689
+ return v;
690
+ }
685
691
  return {
686
692
  ...v,
687
693
  value: Number(v.value)
@@ -12,12 +12,13 @@ import type { DispatchEventOptions } from '../types';
12
12
  export declare class FlowExecutor {
13
13
  private readonly engine;
14
14
  constructor(engine: FlowEngine);
15
+ private emitModelEventIf;
15
16
  /** Cache wrapper for applyFlow cache lifecycle */
16
17
  private withApplyFlowCache;
17
18
  /**
18
19
  * Execute a single flow on model.
19
20
  */
20
- runFlow(model: FlowModel, flowKey: string, inputArgs?: Record<string, any>, runId?: string): Promise<any>;
21
+ runFlow(model: FlowModel, flowKey: string, inputArgs?: Record<string, any>, runId?: string, eventName?: string): Promise<any>;
21
22
  /**
22
23
  * Dispatch an event to flows bound via flow.on and execute them.
23
24
  */
@@ -51,6 +51,10 @@ const _FlowExecutor = class _FlowExecutor {
51
51
  constructor(engine) {
52
52
  this.engine = engine;
53
53
  }
54
+ async emitModelEventIf(eventName, topic, payload) {
55
+ if (!eventName) return;
56
+ await this.engine.emitter.emitAsync(`model:event:${eventName}:${topic}`, payload);
57
+ }
54
58
  /** Cache wrapper for applyFlow cache lifecycle */
55
59
  async withApplyFlowCache(cacheKey, executor) {
56
60
  if (!cacheKey || !this.engine) return await executor();
@@ -81,7 +85,7 @@ const _FlowExecutor = class _FlowExecutor {
81
85
  /**
82
86
  * Execute a single flow on model.
83
87
  */
84
- async runFlow(model, flowKey, inputArgs, runId) {
88
+ async runFlow(model, flowKey, inputArgs, runId, eventName) {
85
89
  var _a;
86
90
  const flow = model.getFlow(flowKey);
87
91
  if (!flow) {
@@ -113,6 +117,14 @@ const _FlowExecutor = class _FlowExecutor {
113
117
  const stepDefs = eventStep ? { eventStep, ...flow.steps } : flow.steps;
114
118
  (0, import_setupRuntimeContextSteps.setupRuntimeContextSteps)(flowContext, stepDefs, model, flowKey);
115
119
  const stepsRuntime = flowContext.steps;
120
+ const flowEventBasePayload = {
121
+ uid: model.uid,
122
+ model,
123
+ runId: flowContext.runId,
124
+ inputArgs,
125
+ flowKey
126
+ };
127
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:start`, flowEventBasePayload);
116
128
  for (const [stepKey, step] of Object.entries(stepDefs)) {
117
129
  let handler;
118
130
  let combinedParams = {};
@@ -163,20 +175,56 @@ const _FlowExecutor = class _FlowExecutor {
163
175
  );
164
176
  continue;
165
177
  }
178
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:step:${stepKey}:start`, {
179
+ ...flowEventBasePayload,
180
+ stepKey
181
+ });
166
182
  const currentStepResult = handler(runtimeCtx, combinedParams);
167
183
  const isAwait = step.isAwait !== false;
168
184
  lastResult = isAwait ? await currentStepResult : currentStepResult;
169
185
  stepResults[stepKey] = lastResult;
170
186
  stepsRuntime[stepKey].result = stepResults[stepKey];
187
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:step:${stepKey}:end`, {
188
+ ...flowEventBasePayload,
189
+ result: lastResult,
190
+ stepKey
191
+ });
171
192
  } catch (error) {
193
+ if (!(error instanceof import_utils.FlowExitException) && !(error instanceof import_exceptions.FlowExitAllException)) {
194
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:step:${stepKey}:error`, {
195
+ ...flowEventBasePayload,
196
+ error,
197
+ stepKey
198
+ });
199
+ }
172
200
  if (error instanceof import_utils.FlowExitException) {
173
201
  flowContext.logger.info(`[FlowEngine] ${error.message}`);
202
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:step:${stepKey}:end`, {
203
+ ...flowEventBasePayload,
204
+ stepKey
205
+ });
206
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:end`, {
207
+ ...flowEventBasePayload,
208
+ result: stepResults
209
+ });
174
210
  return Promise.resolve(stepResults);
175
211
  }
176
212
  if (error instanceof import_exceptions.FlowExitAllException) {
177
213
  flowContext.logger.info(`[FlowEngine] ${error.message}`);
214
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:step:${stepKey}:end`, {
215
+ ...flowEventBasePayload,
216
+ stepKey
217
+ });
218
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:end`, {
219
+ ...flowEventBasePayload,
220
+ result: error
221
+ });
178
222
  return Promise.resolve(error);
179
223
  }
224
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:error`, {
225
+ ...flowEventBasePayload,
226
+ error
227
+ });
180
228
  flowContext.logger.error(
181
229
  { err: error },
182
230
  `BaseModel.applyFlow: Error executing step '${stepKey}' in flow '${flowKey}':`
@@ -184,9 +232,12 @@ const _FlowExecutor = class _FlowExecutor {
184
232
  return Promise.reject(error);
185
233
  }
186
234
  }
235
+ await this.emitModelEventIf(eventName, `flow:${flowKey}:end`, {
236
+ ...flowEventBasePayload,
237
+ result: stepResults
238
+ });
187
239
  return Promise.resolve(stepResults);
188
240
  }
189
- // runAutoFlows 已移除:统一通过 dispatchEvent('beforeRender') + useCache 控制
190
241
  /**
191
242
  * Dispatch an event to flows bound via flow.on and execute them.
192
243
  */
@@ -198,13 +249,14 @@ const _FlowExecutor = class _FlowExecutor {
198
249
  const throwOnError = isBeforeRender;
199
250
  const runId = `${model.uid}-${eventName}-${Date.now()}`;
200
251
  const logger = model.context.logger;
252
+ const eventBasePayload = {
253
+ uid: model.uid,
254
+ model,
255
+ runId,
256
+ inputArgs
257
+ };
201
258
  try {
202
- await this.engine.emitter.emitAsync(`model:event:${eventName}:start`, {
203
- uid: model.uid,
204
- model,
205
- runId,
206
- inputArgs
207
- });
259
+ await this.emitModelEventIf(eventName, "start", eventBasePayload);
208
260
  await ((_a = model.onDispatchEventStart) == null ? void 0 : _a.call(model, eventName, options, inputArgs));
209
261
  } catch (err) {
210
262
  if (isBeforeRender && err instanceof import_utils.FlowExitException) {
@@ -226,9 +278,17 @@ const _FlowExecutor = class _FlowExecutor {
226
278
  if (typeof on === "object") return on.eventName === eventName;
227
279
  return false;
228
280
  });
281
+ const isRouterReplayClick = eventName === "click" && (inputArgs == null ? void 0 : inputArgs.triggerByRouter) === true;
282
+ const flowsToRun = isRouterReplayClick ? flows.filter((flow) => {
283
+ var _a2;
284
+ const reg = flow["flowRegistry"];
285
+ const type = (_a2 = reg == null ? void 0 : reg.constructor) == null ? void 0 : _a2._type;
286
+ return type !== "instance";
287
+ }) : flows;
288
+ const scheduledCancels = [];
229
289
  const execute = /* @__PURE__ */ __name(async () => {
230
290
  if (sequential) {
231
- const flowsWithIndex = flows.map((f, i) => ({ f, i }));
291
+ const flowsWithIndex = flowsToRun.map((f, i) => ({ f, i }));
232
292
  const ordered = flowsWithIndex.slice().sort((a, b) => {
233
293
  var _a2, _b2;
234
294
  const regA = a.f["flowRegistry"];
@@ -244,12 +304,88 @@ const _FlowExecutor = class _FlowExecutor {
244
304
  return a.i - b.i;
245
305
  }).map((x) => x.f);
246
306
  const results2 = [];
307
+ const staticFlowsByKey = new Map(
308
+ ordered.filter((f) => {
309
+ var _a2;
310
+ const reg = f["flowRegistry"];
311
+ const type = (_a2 = reg == null ? void 0 : reg.constructor) == null ? void 0 : _a2._type;
312
+ return type !== "instance";
313
+ }).map((f) => [f.key, f])
314
+ );
315
+ const scheduled = /* @__PURE__ */ new Set();
316
+ const scheduleGroups = /* @__PURE__ */ new Map();
317
+ ordered.forEach((flow, indexInOrdered) => {
318
+ var _a2;
319
+ const on = flow.on;
320
+ const onObj = typeof on === "object" ? on : void 0;
321
+ if (!onObj) return;
322
+ const phase = onObj.phase;
323
+ const flowKey = onObj.flowKey;
324
+ const stepKey = onObj.stepKey;
325
+ if (!phase || phase === "beforeAllFlows") return;
326
+ let whenKey = null;
327
+ if (phase === "afterAllFlows") {
328
+ whenKey = `event:${eventName}:end`;
329
+ } else if (phase === "beforeFlow" || phase === "afterFlow") {
330
+ if (!flowKey) {
331
+ whenKey = `event:${eventName}:end`;
332
+ } else {
333
+ const anchorFlow = staticFlowsByKey.get(String(flowKey));
334
+ if (anchorFlow) {
335
+ const anchorPhase = phase === "beforeFlow" ? "start" : "end";
336
+ whenKey = `event:${eventName}:flow:${String(flowKey)}:${anchorPhase}`;
337
+ } else {
338
+ whenKey = `event:${eventName}:end`;
339
+ }
340
+ }
341
+ } else if (phase === "beforeStep" || phase === "afterStep") {
342
+ if (!flowKey || !stepKey) {
343
+ whenKey = `event:${eventName}:end`;
344
+ } else {
345
+ const anchorFlow = staticFlowsByKey.get(String(flowKey));
346
+ const anchorStepExists = !!((_a2 = anchorFlow == null ? void 0 : anchorFlow.hasStep) == null ? void 0 : _a2.call(anchorFlow, String(stepKey)));
347
+ if (anchorFlow && anchorStepExists) {
348
+ const anchorPhase = phase === "beforeStep" ? "start" : "end";
349
+ whenKey = `event:${eventName}:flow:${String(flowKey)}:step:${String(stepKey)}:${anchorPhase}`;
350
+ } else {
351
+ whenKey = `event:${eventName}:end`;
352
+ }
353
+ }
354
+ } else {
355
+ return;
356
+ }
357
+ if (!whenKey) return;
358
+ scheduled.add(flow.key);
359
+ const list = scheduleGroups.get(whenKey) || [];
360
+ list.push({ flow, order: indexInOrdered });
361
+ scheduleGroups.set(whenKey, list);
362
+ });
363
+ for (const [whenKey, list] of scheduleGroups.entries()) {
364
+ const sorted = list.slice().sort((a, b) => {
365
+ const sa = a.flow.sort ?? 0;
366
+ const sb = b.flow.sort ?? 0;
367
+ if (sa !== sb) return sa - sb;
368
+ return a.order - b.order;
369
+ });
370
+ for (const it of sorted) {
371
+ const cancel = model.scheduleModelOperation(
372
+ model.uid,
373
+ async (m) => {
374
+ const res = await this.runFlow(m, it.flow.key, inputArgs, runId, eventName);
375
+ results2.push(res);
376
+ },
377
+ { when: whenKey }
378
+ );
379
+ scheduledCancels.push(cancel);
380
+ }
381
+ }
247
382
  for (const flow of ordered) {
383
+ if (scheduled.has(flow.key)) continue;
248
384
  try {
249
385
  logger.debug(
250
386
  `BaseModel '${model.uid}' dispatching event '${eventName}' to flow '${flow.key}' (sequential).`
251
387
  );
252
- const result = await this.runFlow(model, flow.key, inputArgs, runId);
388
+ const result = await this.runFlow(model, flow.key, inputArgs, runId, eventName);
253
389
  if (result instanceof import_exceptions.FlowExitAllException) {
254
390
  logger.debug(`[FlowEngine.dispatchEvent] ${result.message}`);
255
391
  break;
@@ -266,10 +402,10 @@ const _FlowExecutor = class _FlowExecutor {
266
402
  return results2;
267
403
  }
268
404
  const results = await Promise.all(
269
- flows.map(async (flow) => {
405
+ flowsToRun.map(async (flow) => {
270
406
  logger.debug(`BaseModel '${model.uid}' dispatching event '${eventName}' to flow '${flow.key}'.`);
271
407
  try {
272
- return await this.runFlow(model, flow.key, inputArgs, runId);
408
+ return await this.runFlow(model, flow.key, inputArgs, runId, eventName);
273
409
  } catch (error) {
274
410
  logger.error(
275
411
  { err: error },
@@ -295,11 +431,8 @@ const _FlowExecutor = class _FlowExecutor {
295
431
  } catch (hookErr) {
296
432
  logger.error({ err: hookErr }, `BaseModel.dispatchEvent: End hook error for event '${eventName}'`);
297
433
  }
298
- await this.engine.emitter.emitAsync(`model:event:${eventName}:end`, {
299
- uid: model.uid,
300
- model,
301
- runId,
302
- inputArgs,
434
+ await this.emitModelEventIf(eventName, "end", {
435
+ ...eventBasePayload,
303
436
  result
304
437
  });
305
438
  return result;
@@ -312,14 +445,15 @@ const _FlowExecutor = class _FlowExecutor {
312
445
  { err: error },
313
446
  `BaseModel.dispatchEvent: Error executing event '${eventName}' for model '${model.uid}':`
314
447
  );
315
- await this.engine.emitter.emitAsync(`model:event:${eventName}:error`, {
316
- uid: model.uid,
317
- model,
318
- runId,
319
- inputArgs,
448
+ await this.emitModelEventIf(eventName, "error", {
449
+ ...eventBasePayload,
320
450
  error
321
451
  });
322
452
  if (throwOnError) throw error;
453
+ } finally {
454
+ for (const cancel of scheduledCancels) {
455
+ cancel();
456
+ }
323
457
  }
324
458
  }
325
459
  };
@@ -31,6 +31,7 @@ export interface MetaTreeNode {
31
31
  title: string;
32
32
  type: string;
33
33
  interface?: string;
34
+ options?: any;
34
35
  uiSchema?: ISchema;
35
36
  render?: (props: any) => JSX.Element;
36
37
  paths: string[];
@@ -44,6 +45,7 @@ export interface PropertyMeta {
44
45
  type: string;
45
46
  title: string;
46
47
  interface?: string;
48
+ options?: any;
47
49
  uiSchema?: ISchema;
48
50
  render?: (props: any) => JSX.Element;
49
51
  sort?: number;
@@ -147,7 +149,7 @@ declare class BaseFlowEngineContext extends FlowContext {
147
149
  dataSourceManager: DataSourceManager;
148
150
  requireAsync: (url: string) => Promise<any>;
149
151
  importAsync: (url: string) => Promise<any>;
150
- createJSRunner: (options?: JSRunnerOptions) => JSRunner;
152
+ createJSRunner: (options?: JSRunnerOptions) => Promise<JSRunner>;
151
153
  pageInfo: {
152
154
  version?: 'v1' | 'v2';
153
155
  };
@@ -172,6 +174,7 @@ declare class BaseFlowEngineContext extends FlowContext {
172
174
  location: Location;
173
175
  sql: FlowSQLRepository;
174
176
  logger: pino.Logger;
177
+ constructor();
175
178
  }
176
179
  declare class BaseFlowModelContext extends BaseFlowEngineContext {
177
180
  model: FlowModel;