@nocobase/flow-engine 2.1.0-beta.42 → 2.1.0-beta.44

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 (45) hide show
  1. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -31
  2. package/lib/components/subModel/LazyDropdown.js +17 -9
  3. package/lib/executor/FlowExecutor.js +0 -3
  4. package/lib/flowContext.d.ts +6 -1
  5. package/lib/flowContext.js +35 -6
  6. package/lib/flowEngine.d.ts +4 -3
  7. package/lib/flowEngine.js +69 -37
  8. package/lib/models/flowModel.js +45 -13
  9. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
  10. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
  11. package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
  12. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
  13. package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
  14. package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
  15. package/lib/runjs-context/contexts/base.js +464 -29
  16. package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
  17. package/lib/runjs-context/contexts/elementDoc.js +152 -0
  18. package/lib/utils/loadedPageCache.d.ts +24 -0
  19. package/lib/utils/loadedPageCache.js +139 -0
  20. package/package.json +4 -4
  21. package/src/__tests__/flowContext.test.ts +23 -0
  22. package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
  23. package/src/__tests__/runjsContext.test.ts +18 -0
  24. package/src/__tests__/runjsContextImplementations.test.ts +9 -2
  25. package/src/__tests__/runjsLocales.test.ts +6 -5
  26. package/src/__tests__/viewScopedFlowEngine.test.ts +133 -0
  27. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +79 -37
  28. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +150 -3
  29. package/src/components/subModel/LazyDropdown.tsx +16 -7
  30. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +51 -0
  31. package/src/executor/FlowExecutor.ts +0 -3
  32. package/src/executor/__tests__/flowExecutor.test.ts +2 -4
  33. package/src/flowContext.ts +40 -6
  34. package/src/flowEngine.ts +71 -35
  35. package/src/models/__tests__/flowModel.test.ts +13 -28
  36. package/src/models/flowModel.tsx +62 -29
  37. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +4 -3
  38. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
  39. package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
  40. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
  41. package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
  42. package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
  43. package/src/runjs-context/contexts/base.ts +467 -31
  44. package/src/runjs-context/contexts/elementDoc.ts +130 -0
  45. package/src/utils/loadedPageCache.ts +147 -0
@@ -207,8 +207,17 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
207
207
  const [visible, setVisible] = (0, import_react.useState)(false);
208
208
  const [refreshTick, setRefreshTick] = (0, import_react.useState)(0);
209
209
  const [extraMenuItems, setExtraMenuItems] = (0, import_react.useState)([]);
210
+ const [extraMenuItemsLoaded, setExtraMenuItemsLoaded] = (0, import_react.useState)(false);
210
211
  const [configurableFlowsAndSteps, setConfigurableFlowsAndSteps] = (0, import_react.useState)([]);
211
212
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
213
+ const commonExtras = (0, import_react.useMemo)(
214
+ () => extraMenuItems.filter((it) => it.group === "common-actions").sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0)),
215
+ [extraMenuItems]
216
+ );
217
+ const hasCommonActions = showCopyUidButton || showDeleteButton || commonExtras.length > 0;
218
+ const shouldDeferConfigLoading = flattenSubMenus && menuLevels > 1 && hasCommonActions;
219
+ const shouldWaitForCommonActionProbe = flattenSubMenus && menuLevels > 1 && !showCopyUidButton && !showDeleteButton && !extraMenuItemsLoaded;
220
+ const canRenderIcon = hasCommonActions || !isLoading && configurableFlowsAndSteps.length > 0;
212
221
  const closeDropdown = (0, import_react.useCallback)(() => {
213
222
  setVisible(false);
214
223
  onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(false);
@@ -240,24 +249,28 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
240
249
  let mounted = true;
241
250
  const loadExtras = /* @__PURE__ */ __name(async () => {
242
251
  var _a;
243
- const allExtras = [];
244
- const modelsToProcess = [];
245
- walkSubModels(model, { maxDepth: menuLevels, arrayLimit: 50, mode: "stack" }, (targetModel, { modelKey }) => {
246
- modelsToProcess.push({ model: targetModel, modelKey });
247
- });
248
- for (const { model: targetModel, modelKey } of modelsToProcess) {
249
- const Cls = targetModel.constructor;
250
- const extras = await ((_a = Cls.getExtraMenuItems) == null ? void 0 : _a.call(Cls, targetModel, t));
251
- if (extras == null ? void 0 : extras.length) {
252
- allExtras.push(
253
- ...extras.map((item) => ({
254
- ...item,
255
- key: modelKey ? `${modelKey}:${item.key}` : item.key
256
- }))
257
- );
252
+ setExtraMenuItemsLoaded(false);
253
+ try {
254
+ const allExtras = [];
255
+ const modelsToProcess = [];
256
+ walkSubModels(model, { maxDepth: menuLevels, arrayLimit: 50, mode: "stack" }, (targetModel, { modelKey }) => {
257
+ modelsToProcess.push({ model: targetModel, modelKey });
258
+ });
259
+ for (const { model: targetModel, modelKey } of modelsToProcess) {
260
+ const Cls = targetModel.constructor;
261
+ const extras = await ((_a = Cls.getExtraMenuItems) == null ? void 0 : _a.call(Cls, targetModel, t));
262
+ if (extras == null ? void 0 : extras.length) {
263
+ allExtras.push(
264
+ ...extras.map((item) => ({
265
+ ...item,
266
+ key: modelKey ? `${modelKey}:${item.key}` : item.key
267
+ }))
268
+ );
269
+ }
270
+ }
271
+ if (!mounted) {
272
+ return;
258
273
  }
259
- }
260
- if (mounted) {
261
274
  const seen = /* @__PURE__ */ new Set();
262
275
  const dedupedExtras = allExtras.filter((item) => {
263
276
  if (seen.has(`${item.key}`)) {
@@ -267,15 +280,22 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
267
280
  return true;
268
281
  });
269
282
  setExtraMenuItems(dedupedExtras);
283
+ } catch (error) {
284
+ console.error("Failed to load extra menu items:", error);
285
+ if (mounted) {
286
+ setExtraMenuItems([]);
287
+ }
288
+ } finally {
289
+ if (mounted) {
290
+ setExtraMenuItemsLoaded(true);
291
+ }
270
292
  }
271
293
  }, "loadExtras");
272
- if (visible) {
273
- loadExtras();
274
- }
294
+ loadExtras();
275
295
  return () => {
276
296
  mounted = false;
277
297
  };
278
- }, [model, menuLevels, t, refreshTick, visible]);
298
+ }, [model, menuLevels, t, refreshTick]);
279
299
  const copyUidToClipboard = (0, import_react.useCallback)(
280
300
  async (uid) => {
281
301
  var _a;
@@ -520,7 +540,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
520
540
  return [];
521
541
  }
522
542
  },
523
- []
543
+ [t]
524
544
  );
525
545
  const getConfigurableFlowsAndSteps = (0, import_react.useCallback)(async () => {
526
546
  const result = [];
@@ -558,20 +578,47 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
558
578
  };
559
579
  }, [model, menuLevels, refreshTick]);
560
580
  (0, import_react.useEffect)(() => {
581
+ let mounted = true;
561
582
  const loadConfigurableFlowsAndSteps = /* @__PURE__ */ __name(async () => {
562
583
  setIsLoading(true);
584
+ if (shouldDeferConfigLoading) {
585
+ setConfigurableFlowsAndSteps([]);
586
+ }
563
587
  try {
564
588
  const flows = await getConfigurableFlowsAndSteps();
565
- setConfigurableFlowsAndSteps(flows);
589
+ if (mounted) {
590
+ setConfigurableFlowsAndSteps(flows);
591
+ }
566
592
  } catch (error) {
567
593
  console.error("Failed to load configurable flows and steps:", error);
568
- setConfigurableFlowsAndSteps([]);
594
+ if (mounted) {
595
+ setConfigurableFlowsAndSteps([]);
596
+ }
569
597
  } finally {
570
- setIsLoading(false);
598
+ if (mounted) {
599
+ setIsLoading(false);
600
+ }
571
601
  }
572
602
  }, "loadConfigurableFlowsAndSteps");
603
+ if (shouldWaitForCommonActionProbe) {
604
+ setConfigurableFlowsAndSteps([]);
605
+ setIsLoading(false);
606
+ return () => {
607
+ mounted = false;
608
+ };
609
+ }
610
+ if (!visible && shouldDeferConfigLoading) {
611
+ setConfigurableFlowsAndSteps([]);
612
+ setIsLoading(false);
613
+ return () => {
614
+ mounted = false;
615
+ };
616
+ }
573
617
  loadConfigurableFlowsAndSteps();
574
- }, [getConfigurableFlowsAndSteps, refreshTick]);
618
+ return () => {
619
+ mounted = false;
620
+ };
621
+ }, [getConfigurableFlowsAndSteps, refreshTick, shouldDeferConfigLoading, shouldWaitForCommonActionProbe, visible]);
575
622
  const menuItems = (0, import_react.useMemo)(() => {
576
623
  const items = [];
577
624
  const keyCounter = /* @__PURE__ */ new Map();
@@ -696,10 +743,9 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
696
743
  }
697
744
  }
698
745
  return items;
699
- }, [configurableFlowsAndSteps, disabledIconColor, flattenSubMenus, t]);
746
+ }, [configurableFlowsAndSteps, disabledIconColor, flattenSubMenus, message, model, t]);
700
747
  const finalMenuItems = (0, import_react.useMemo)(() => {
701
748
  const items = [...menuItems];
702
- const commonExtras = extraMenuItems.filter((it) => it.group === "common-actions").sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
703
749
  if (showCopyUidButton || showDeleteButton || commonExtras.length > 0) {
704
750
  items.push({
705
751
  type: "divider"
@@ -721,9 +767,8 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
721
767
  }
722
768
  }
723
769
  return items;
724
- }, [menuItems, showCopyUidButton, showDeleteButton, model.uid, model.destroy, t, extraMenuItems]);
725
- const hasExtras = extraMenuItems.some((it) => it.group === "common-actions");
726
- if (isLoading || configurableFlowsAndSteps.length === 0 && !showDeleteButton && !showCopyUidButton && !hasExtras) {
770
+ }, [menuItems, showCopyUidButton, showDeleteButton, commonExtras, model.uid, model.destroy, t]);
771
+ if (!canRenderIcon) {
727
772
  return null;
728
773
  }
729
774
  if (!model || !model.uid) {
@@ -332,7 +332,7 @@ const getLabelSearchText = /* @__PURE__ */ __name((label) => {
332
332
  }
333
333
  return "";
334
334
  }, "getLabelSearchText");
335
- const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, searchHandlers, activateSearchSubmenu, deactivateSearchSubmenu) => ({
335
+ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, searchHandlers, activateSearchSubmenu, deactivateSearchSubmenu, shouldActivateSearchSubmenu) => ({
336
336
  key: `${searchKey}-search`,
337
337
  type: "group",
338
338
  label: /* @__PURE__ */ import_react.default.createElement("div", { onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement(
@@ -350,28 +350,34 @@ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchV
350
350
  var _a;
351
351
  e.stopPropagation();
352
352
  const value = e.target.value;
353
- activateSearchSubmenu(searchKey);
353
+ if (shouldActivateSearchSubmenu) {
354
+ activateSearchSubmenu(searchKey);
355
+ }
354
356
  if (((_a = e.nativeEvent) == null ? void 0 : _a.isComposing) || searchHandlers.isComposing(searchKey)) {
355
357
  searchHandlers.updateInputValue(searchKey, value);
356
358
  return;
357
359
  }
358
- if (!value) {
360
+ if (!value && shouldActivateSearchSubmenu) {
359
361
  deactivateSearchSubmenu(searchKey);
360
362
  }
361
363
  searchHandlers.updateSearchValue(searchKey, value);
362
364
  },
363
365
  onCompositionStart: (e) => {
364
366
  e.stopPropagation();
365
- activateSearchSubmenu(searchKey);
367
+ if (shouldActivateSearchSubmenu) {
368
+ activateSearchSubmenu(searchKey);
369
+ }
366
370
  searchHandlers.startComposition(searchKey);
367
371
  },
368
372
  onCompositionEnd: (e) => {
369
373
  e.stopPropagation();
370
374
  const value = e.currentTarget.value;
371
- if (value) {
372
- activateSearchSubmenu(searchKey);
373
- } else {
374
- deactivateSearchSubmenu(searchKey);
375
+ if (shouldActivateSearchSubmenu) {
376
+ if (value) {
377
+ activateSearchSubmenu(searchKey);
378
+ } else {
379
+ deactivateSearchSubmenu(searchKey);
380
+ }
375
381
  }
376
382
  searchHandlers.endComposition(searchKey, value);
377
383
  },
@@ -557,6 +563,7 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
557
563
  const searchKey = keyPath;
558
564
  const currentSearchValue = searchValues[searchKey] || "";
559
565
  const currentInputValue = inputValues[searchKey] ?? currentSearchValue;
566
+ const shouldActivateSearchSubmenu = !(item.type === "group" && path.length === 0);
560
567
  const filteredChildren = currentSearchValue ? (/* @__PURE__ */ __name(function deepFilter(items2) {
561
568
  const searchText = currentSearchValue.toLowerCase();
562
569
  return items2.map((child) => {
@@ -581,7 +588,8 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
581
588
  t,
582
589
  searchHandlers,
583
590
  activateSearchSubmenu,
584
- deactivateSearchSubmenu
591
+ deactivateSearchSubmenu,
592
+ shouldActivateSearchSubmenu
585
593
  );
586
594
  const dividerItem = { key: `${keyPath}-search-divider`, type: "divider" };
587
595
  if (currentSearchValue && resolvedFiltered.length === 0) {
@@ -153,9 +153,6 @@ const _FlowExecutor = class _FlowExecutor {
153
153
  const stepDefaultParams = await (0, import_utils.resolveDefaultParams)(step.defaultParams, runtimeCtx);
154
154
  combinedParams = { ...stepDefaultParams };
155
155
  } else {
156
- flowContext.logger.warn(
157
- `BaseModel.applyFlow: Step '${stepKey}' in flow '${flowKey}' has neither 'use' nor 'handler'. Skipping.`
158
- );
159
156
  continue;
160
157
  }
161
158
  const modelStepParams = model.getStepParams(flowKey, stepKey);
@@ -222,6 +222,10 @@ export type FlowContextGetApiInfosOptions = {
222
222
  * RunJS 文档版本(默认 v1)。
223
223
  */
224
224
  version?: RunJSVersion;
225
+ /**
226
+ * Include editor completion metadata. Defaults to false so API-doc callers keep the compact public shape.
227
+ */
228
+ includeCompletion?: boolean;
225
229
  };
226
230
  export type FlowContextGetVarInfosOptions = {
227
231
  /**
@@ -293,7 +297,7 @@ export declare class FlowContext {
293
297
  * - 输出仅来自 RunJS doc 与 defineProperty/defineMethod 的 info
294
298
  * - 不读取/展开 PropertyMeta(变量结构)
295
299
  * - 不自动展开深层 properties
296
- * - 不返回自动补全字段(例如 completion
300
+ * - 默认不返回自动补全字段(例如 completion),传入 includeCompletion=true 时返回
297
301
  */
298
302
  getApiInfos(options?: FlowContextGetApiInfosOptions): Promise<Record<string, FlowContextApiInfo>>;
299
303
  /**
@@ -423,6 +427,7 @@ export declare class FlowRuntimeContext<TModel extends FlowModel = FlowModel, TM
423
427
  export type FlowSettingsContext<TModel extends FlowModel = FlowModel> = FlowRuntimeContext<TModel, 'settings'>;
424
428
  export type RunJSDocCompletionDoc = {
425
429
  insertText?: string;
430
+ requires?: Array<'element'>;
426
431
  };
427
432
  export type RunJSDocHiddenDoc = boolean | ((ctx: any) => boolean | Promise<boolean>);
428
433
  export type RunJSDocHiddenOrPathsDoc = boolean | string[] | ((ctx: any) => boolean | string[] | Promise<boolean | string[]>);
@@ -399,10 +399,11 @@ const _FlowContext = class _FlowContext {
399
399
  * - 输出仅来自 RunJS doc 与 defineProperty/defineMethod 的 info
400
400
  * - 不读取/展开 PropertyMeta(变量结构)
401
401
  * - 不自动展开深层 properties
402
- * - 不返回自动补全字段(例如 completion
402
+ * - 默认不返回自动补全字段(例如 completion),传入 includeCompletion=true 时返回
403
403
  */
404
404
  async getApiInfos(options = {}) {
405
405
  const version = options.version || "v1";
406
+ const includeCompletion = !!options.includeCompletion;
406
407
  const evalCtx = this.createProxy();
407
408
  const isPrivateKey = /* @__PURE__ */ __name((key) => typeof key === "string" && key.startsWith("_"), "isPrivateKey");
408
409
  const isVarRootKey = /* @__PURE__ */ __name((key) => key === "record" || key === "formValues" || key === "popup", "isVarRootKey");
@@ -439,7 +440,14 @@ const _FlowContext = class _FlowContext {
439
440
  const src = toDocObject(obj);
440
441
  if (!src) return {};
441
442
  const out2 = {};
442
- for (const k of ["description", "examples", "ref", "params", "returns"]) {
443
+ for (const k of [
444
+ "description",
445
+ "examples",
446
+ ...includeCompletion ? ["completion"] : [],
447
+ "ref",
448
+ "params",
449
+ "returns"
450
+ ]) {
443
451
  const v = src[k];
444
452
  if (typeof v !== "undefined") out2[k] = v;
445
453
  }
@@ -452,7 +460,17 @@ const _FlowContext = class _FlowContext {
452
460
  const src = toDocObject(obj);
453
461
  if (!src) return {};
454
462
  const out2 = {};
455
- for (const k of ["title", "type", "interface", "description", "examples", "ref", "params", "returns"]) {
463
+ for (const k of [
464
+ "title",
465
+ "type",
466
+ "interface",
467
+ "description",
468
+ "examples",
469
+ ...includeCompletion ? ["completion"] : [],
470
+ "ref",
471
+ "params",
472
+ "returns"
473
+ ]) {
456
474
  const v = src[k];
457
475
  if (typeof v !== "undefined") out2[k] = v;
458
476
  }
@@ -539,7 +557,7 @@ const _FlowContext = class _FlowContext {
539
557
  node = { ...node, ...pickPropertyInfo(docObj) };
540
558
  node = { ...node, ...pickPropertyInfo(infoObj) };
541
559
  delete node.properties;
542
- delete node.completion;
560
+ if (!includeCompletion) delete node.completion;
543
561
  if (!Object.keys(node).length) continue;
544
562
  const outKey = mapDocKeyToApiKey(key, docNode);
545
563
  out[outKey] = out[outKey] ? { ...out[outKey] || {}, ...node || {} } : node;
@@ -554,7 +572,7 @@ const _FlowContext = class _FlowContext {
554
572
  node = { ...node, ...pickMethodInfo(docObj) };
555
573
  node = { ...node, ...pickMethodInfo(info) };
556
574
  delete node.properties;
557
- delete node.completion;
575
+ if (!includeCompletion) delete node.completion;
558
576
  if (!Object.keys(node).length) continue;
559
577
  node.type = "function";
560
578
  if (!out[key]) out[key] = node;
@@ -571,7 +589,7 @@ const _FlowContext = class _FlowContext {
571
589
  let node = {};
572
590
  node = { ...node, ...pickPropertyInfo(childObj) };
573
591
  delete node.properties;
574
- delete node.completion;
592
+ if (!includeCompletion) delete node.completion;
575
593
  if (!node.description || !String(node.description).trim()) continue;
576
594
  out[outKey] = node;
577
595
  }
@@ -2255,6 +2273,17 @@ const _BaseFlowEngineContext = class _BaseFlowEngineContext extends FlowContext
2255
2273
  });
2256
2274
  const jsCode = await (0, import_utils.prepareRunJsCode)(String(code ?? ""), { preprocessTemplates: shouldPreprocessTemplates });
2257
2275
  return runner.run(jsCode);
2276
+ },
2277
+ {
2278
+ description: "Execute a RunJS code string in the current Flow context.",
2279
+ detail: "(code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<RunJSResult>",
2280
+ params: [
2281
+ { name: "code", type: "string", description: "RunJS code to execute." },
2282
+ { name: "variables", type: "Record<string, any>", optional: true, description: "Additional globals." },
2283
+ { name: "options", type: "JSRunnerOptions", optional: true, description: "Runner options." }
2284
+ ],
2285
+ returns: { type: "Promise<{ success: boolean; value?: any; error?: any; timeout?: boolean }>" },
2286
+ completion: { insertText: `await ctx.runjs('return 1')` }
2258
2287
  }
2259
2288
  );
2260
2289
  }
@@ -93,6 +93,7 @@ export declare class FlowEngine {
93
93
  * @private
94
94
  */
95
95
  private _savingModels;
96
+ private _loadedPageCache;
96
97
  /**
97
98
  * Flow engine context object.
98
99
  * @private
@@ -537,11 +538,11 @@ export declare class FlowEngine {
537
538
  replaceModel<T extends FlowModel = FlowModel>(uid: string, optionsOrFn?: Partial<FlowModelOptions> | ((currentOptions: FlowModelOptions) => Partial<FlowModelOptions>)): Promise<T | null>;
538
539
  /**
539
540
  * Move a model instance within its parent model.
540
- * @param {any} sourceId Source model UID
541
- * @param {any} targetId Target model UID
541
+ * @param {string | number} sourceId Source model UID
542
+ * @param {string | number} targetId Target model UID
542
543
  * @returns {Promise<void>} No return value
543
544
  */
544
- moveModel(sourceId: any, targetId: any, options?: PersistOptions): Promise<void>;
545
+ moveModel(sourceId: string | number, targetId: string | number, options?: PersistOptions): Promise<void>;
545
546
  /**
546
547
  * Filter model classes by parent class (supports multi-level inheritance).
547
548
  * @param {string | ModelConstructor} parentClass Parent class name or constructor
package/lib/flowEngine.js CHANGED
@@ -61,6 +61,7 @@ var import_ReactView = require("./ReactView");
61
61
  var import_resources = require("./resources");
62
62
  var import_emitter = require("./emitter");
63
63
  var import_ModelOperationScheduler = __toESM(require("./scheduler/ModelOperationScheduler"));
64
+ var import_loadedPageCache = require("./utils/loadedPageCache");
64
65
  var import_utils = require("./utils");
65
66
  var _FlowEngine_instances, registerModel_fn;
66
67
  const getFlowEngineLoggerLevel = /* @__PURE__ */ __name(() => process.env.NODE_ENV === "production" ? "warn" : "trace", "getFlowEngineLoggerLevel");
@@ -130,6 +131,7 @@ const _FlowEngine = class _FlowEngine {
130
131
  * @private
131
132
  */
132
133
  __publicField(this, "_savingModels", /* @__PURE__ */ new Map());
134
+ __publicField(this, "_loadedPageCache", (0, import_loadedPageCache.createLoadedPageCache)());
133
135
  /**
134
136
  * Flow engine context object.
135
137
  * @private
@@ -1138,10 +1140,11 @@ const _FlowEngine = class _FlowEngine {
1138
1140
  async loadModel(options) {
1139
1141
  if (!this.ensureModelRepository()) return;
1140
1142
  const refresh = !!(options == null ? void 0 : options.refresh);
1141
- if (!refresh) {
1142
- const model = this.findModelByParentId(options.parentId, options.subKey);
1143
- if (model) {
1144
- return model;
1143
+ const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled);
1144
+ if (!refresh && !bypassLoadedPageCache) {
1145
+ const model2 = this.findModelByParentId(options.parentId, options.subKey);
1146
+ if (model2) {
1147
+ return model2;
1145
1148
  }
1146
1149
  const hydrated = await this.hydrateModelFromPreviousEngines(options);
1147
1150
  if (hydrated) {
@@ -1149,15 +1152,24 @@ const _FlowEngine = class _FlowEngine {
1149
1152
  }
1150
1153
  }
1151
1154
  const data = await this._modelRepository.findOne(options);
1152
- if (!(data == null ? void 0 : data.uid)) return null;
1153
- await this.resolveModelTree(data);
1154
- if (refresh) {
1155
+ if (!(data == null ? void 0 : data.uid)) {
1156
+ if (bypassLoadedPageCache) {
1157
+ this._loadedPageCache.clear(options);
1158
+ }
1159
+ return null;
1160
+ }
1161
+ if (refresh || bypassLoadedPageCache) {
1155
1162
  const existing = this.getModel(data.uid);
1156
1163
  if (existing) {
1157
1164
  this.removeModelWithSubModels(existing.uid);
1158
1165
  }
1159
1166
  }
1160
- return this.createModelAsync(data);
1167
+ const model = await this.createModelAsync(data);
1168
+ if (bypassLoadedPageCache) {
1169
+ this._loadedPageCache.mountModelToParent(model, true);
1170
+ this._loadedPageCache.clear(options);
1171
+ }
1172
+ return model;
1161
1173
  }
1162
1174
  /**
1163
1175
  * Find a sub-model by parent model ID and subKey.
@@ -1188,20 +1200,29 @@ const _FlowEngine = class _FlowEngine {
1188
1200
  async loadOrCreateModel(options, extra) {
1189
1201
  if (!this.ensureModelRepository()) return;
1190
1202
  const { uid, parentId, subKey } = options;
1191
- if (uid && this._modelInstances.has(uid)) {
1203
+ const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled);
1204
+ if (uid && !bypassLoadedPageCache && this._modelInstances.has(uid)) {
1192
1205
  return this._modelInstances.get(uid);
1193
1206
  }
1194
- const m = this.findModelByParentId(parentId, subKey);
1195
- if (m) {
1196
- return m;
1197
- }
1198
- const hydrated = await this.hydrateModelFromPreviousEngines(options, extra);
1199
- if (hydrated) {
1200
- return hydrated;
1207
+ if (!bypassLoadedPageCache) {
1208
+ const m = this.findModelByParentId(parentId, subKey);
1209
+ if (m) {
1210
+ return m;
1211
+ }
1212
+ const hydrated = await this.hydrateModelFromPreviousEngines(options, extra);
1213
+ if (hydrated) {
1214
+ return hydrated;
1215
+ }
1201
1216
  }
1202
1217
  const data = await this._modelRepository.findOne(options);
1203
1218
  let model = null;
1204
1219
  if (data == null ? void 0 : data.uid) {
1220
+ if (bypassLoadedPageCache) {
1221
+ const existing = this.getModel(data.uid);
1222
+ if (existing) {
1223
+ this.removeModelWithSubModels(existing.uid);
1224
+ }
1225
+ }
1205
1226
  model = await this.createModelAsync(data, extra);
1206
1227
  } else {
1207
1228
  model = await this.createModelAsync(options, extra);
@@ -1209,18 +1230,9 @@ const _FlowEngine = class _FlowEngine {
1209
1230
  await model.save();
1210
1231
  }
1211
1232
  }
1212
- if (model.parent) {
1213
- const subModel = model.parent.findSubModel(model.subKey, (m2) => {
1214
- return m2.uid === model.uid;
1215
- });
1216
- if (subModel) {
1217
- return model;
1218
- }
1219
- if (model.subType === "array") {
1220
- model.parent.addSubModel(model.subKey, model);
1221
- } else {
1222
- model.parent.setSubModel(model.subKey, model);
1223
- }
1233
+ this._loadedPageCache.mountModelToParent(model, bypassLoadedPageCache);
1234
+ if (bypassLoadedPageCache) {
1235
+ this._loadedPageCache.clear(options);
1224
1236
  }
1225
1237
  return model;
1226
1238
  }
@@ -1238,6 +1250,9 @@ const _FlowEngine = class _FlowEngine {
1238
1250
  async saveModel(model, options) {
1239
1251
  if (!this.ensureModelRepository()) return;
1240
1252
  const modelUid = model.uid;
1253
+ const dirtyLoadedPageKey = this._loadedPageCache.getDirtyKeyForModel(model, {
1254
+ force: !!(options == null ? void 0 : options.onlyStepParams)
1255
+ });
1241
1256
  if (this._savingModels.has(modelUid)) {
1242
1257
  this.logger.debug(`Model ${modelUid} is already being saved, waiting for existing save operation`);
1243
1258
  return await this._savingModels.get(modelUid);
@@ -1246,6 +1261,7 @@ const _FlowEngine = class _FlowEngine {
1246
1261
  this._savingModels.set(modelUid, savePromise);
1247
1262
  try {
1248
1263
  const result = await savePromise;
1264
+ this._loadedPageCache.markDirty(dirtyLoadedPageKey);
1249
1265
  return result;
1250
1266
  } finally {
1251
1267
  this._savingModels.delete(modelUid);
@@ -1276,10 +1292,15 @@ const _FlowEngine = class _FlowEngine {
1276
1292
  * @returns {Promise<boolean>} Whether destroyed successfully
1277
1293
  */
1278
1294
  async destroyModel(uid) {
1279
- if (this.ensureModelRepository()) {
1295
+ const modelInstance = this._modelInstances.get(uid);
1296
+ const dirtyLoadedPageKey = this._loadedPageCache.getDirtyKeyForModel(modelInstance);
1297
+ const hasModelRepository = this.ensureModelRepository();
1298
+ if (hasModelRepository) {
1280
1299
  await this._modelRepository.destroy(uid);
1281
1300
  }
1282
- const modelInstance = this._modelInstances.get(uid);
1301
+ if (hasModelRepository) {
1302
+ this._loadedPageCache.markDirty(dirtyLoadedPageKey);
1303
+ }
1283
1304
  const parent = modelInstance == null ? void 0 : modelInstance.parent;
1284
1305
  const result = this.removeModel(uid);
1285
1306
  parent && parent.emitter.emit("onSubModelDestroyed", modelInstance);
@@ -1355,18 +1376,25 @@ const _FlowEngine = class _FlowEngine {
1355
1376
  }
1356
1377
  /**
1357
1378
  * Move a model instance within its parent model.
1358
- * @param {any} sourceId Source model UID
1359
- * @param {any} targetId Target model UID
1379
+ * @param {string | number} sourceId Source model UID
1380
+ * @param {string | number} targetId Target model UID
1360
1381
  * @returns {Promise<void>} No return value
1361
1382
  */
1362
1383
  async moveModel(sourceId, targetId, options) {
1363
1384
  var _a, _b;
1364
- const sourceModel = this.getModel(sourceId);
1365
- const targetModel = this.getModel(targetId);
1385
+ const sourceUid = String(sourceId);
1386
+ const targetUid = String(targetId);
1387
+ if (!sourceUid || !targetUid || sourceUid === targetUid) {
1388
+ return;
1389
+ }
1390
+ const sourceModel = this.getModel(sourceUid);
1391
+ const targetModel = this.getModel(targetUid);
1366
1392
  if (!sourceModel || !targetModel) {
1367
1393
  console.warn(`FlowEngine: Cannot move model. Source or target model not found.`);
1368
1394
  return;
1369
1395
  }
1396
+ let position = "after";
1397
+ const dirtyLoadedPageKey = this._loadedPageCache.getDirtyKeyForModel(sourceModel);
1370
1398
  const move = /* @__PURE__ */ __name((sourceModel2, targetModel2) => {
1371
1399
  if (!sourceModel2.parent || !targetModel2.parent || sourceModel2.parent !== targetModel2.parent) {
1372
1400
  console.error("FlowModel.moveTo: Both models must have the same parent to perform move operation.");
@@ -1389,6 +1417,7 @@ const _FlowEngine = class _FlowEngine {
1389
1417
  console.warn("FlowModel.moveTo: Current model is already at the target position. No action taken.");
1390
1418
  return false;
1391
1419
  }
1420
+ position = currentIndex < targetIndex ? "after" : "before";
1392
1421
  const [movedModel] = subModelsCopy.splice(currentIndex, 1);
1393
1422
  subModelsCopy.splice(targetIndex, 0, movedModel);
1394
1423
  subModelsCopy.forEach((model, index) => {
@@ -1397,10 +1426,13 @@ const _FlowEngine = class _FlowEngine {
1397
1426
  subModels.splice(0, subModels.length, ...subModelsCopy);
1398
1427
  return true;
1399
1428
  }, "move");
1400
- move(sourceModel, targetModel);
1429
+ const moved = move(sourceModel, targetModel);
1430
+ if (!moved) {
1431
+ return;
1432
+ }
1401
1433
  if ((options == null ? void 0 : options.persist) !== false && this.ensureModelRepository()) {
1402
- const position = sourceModel.sortIndex - targetModel.sortIndex > 0 ? "after" : "before";
1403
- await this._modelRepository.move(sourceId, targetId, position);
1434
+ await this._modelRepository.move(sourceUid, targetUid, position);
1435
+ this._loadedPageCache.markDirty(dirtyLoadedPageKey);
1404
1436
  }
1405
1437
  sourceModel.parent.emitter.emit("onSubModelMoved", { source: sourceModel, target: targetModel });
1406
1438
  (_b = this.emitter) == null ? void 0 : _b.emit("model:subModel:moved", {