@nocobase/flow-engine 3.0.0-alpha.1 → 3.0.0-alpha.11

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 (39) hide show
  1. package/lib/components/FlowContextSelector.js +7 -1
  2. package/lib/components/subModel/LazyDropdown.js +21 -7
  3. package/lib/flowContext.d.ts +12 -1
  4. package/lib/flowContext.js +47 -6
  5. package/lib/resources/flowResource.js +1 -0
  6. package/lib/utils/associationObjectVariable.d.ts +10 -0
  7. package/lib/utils/associationObjectVariable.js +10 -7
  8. package/lib/utils/dateVariable.d.ts +22 -0
  9. package/lib/utils/dateVariable.js +123 -16
  10. package/lib/utils/dirtyAwareApiClient.d.ts +1 -0
  11. package/lib/utils/dirtyAwareApiClient.js +15 -2
  12. package/lib/utils/index.d.ts +3 -3
  13. package/lib/utils/index.js +8 -0
  14. package/lib/utils/params-resolvers.d.ts +3 -0
  15. package/lib/utils/params-resolvers.js +10 -0
  16. package/lib/utils/variablesParams.js +5 -0
  17. package/lib/views/createViewMeta.d.ts +1 -0
  18. package/lib/views/createViewMeta.js +53 -22
  19. package/package.json +4 -4
  20. package/src/__tests__/createViewMeta.popup.test.ts +84 -1
  21. package/src/__tests__/flowContext.test.ts +8 -0
  22. package/src/__tests__/objectVariable.test.ts +6 -1
  23. package/src/__tests__/runjsFormSubmit.test.ts +138 -0
  24. package/src/components/FlowContextSelector.tsx +7 -1
  25. package/src/components/subModel/LazyDropdown.tsx +28 -13
  26. package/src/components/subModel/__tests__/LazyDropdown.test.tsx +202 -0
  27. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +35 -0
  28. package/src/flowContext.ts +79 -6
  29. package/src/resources/__tests__/flowResource.test.ts +3 -0
  30. package/src/resources/flowResource.ts +1 -0
  31. package/src/utils/__tests__/dateVariable.test.ts +57 -4
  32. package/src/utils/__tests__/variablesParams.test.ts +28 -1
  33. package/src/utils/associationObjectVariable.ts +9 -6
  34. package/src/utils/dateVariable.ts +145 -18
  35. package/src/utils/dirtyAwareApiClient.ts +25 -2
  36. package/src/utils/index.ts +17 -2
  37. package/src/utils/params-resolvers.ts +12 -0
  38. package/src/utils/variablesParams.ts +10 -0
  39. package/src/views/createViewMeta.ts +52 -18
@@ -247,6 +247,7 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
247
247
  }, [active, cascaderProps.disabled, currentPath]);
248
248
  const handleChange = (0, import_react.useCallback)(
249
249
  (selectedValues, selectedOptions) => {
250
+ var _a;
250
251
  const lastOption = selectedOptions == null ? void 0 : selectedOptions[selectedOptions.length - 1];
251
252
  if (!selectedValues || selectedValues.length === 0) {
252
253
  onChange == null ? void 0 : onChange("", lastOption == null ? void 0 : lastOption.meta);
@@ -256,6 +257,7 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
256
257
  const path = selectedValues.map(String);
257
258
  const pathString = path.join(".");
258
259
  const isLeaf = lastOption == null ? void 0 : lastOption.isLeaf;
260
+ const isSelectable = ((_a = lastOption == null ? void 0 : lastOption.meta) == null ? void 0 : _a.selectable) !== false;
259
261
  const now = Date.now();
260
262
  let formattedValue;
261
263
  if (customFormatPathToValue) {
@@ -267,12 +269,16 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
267
269
  formattedValue = (0, import_utils.formatPathToValue)(lastOption == null ? void 0 : lastOption.meta);
268
270
  }
269
271
  if (isLeaf) {
272
+ if (!isSelectable) {
273
+ setTempSelectedPath(path);
274
+ return;
275
+ }
270
276
  onChange == null ? void 0 : onChange(formattedValue, lastOption == null ? void 0 : lastOption.meta);
271
277
  setTempSelectedPath([]);
272
278
  return;
273
279
  }
274
280
  const lastSelected = lastSelectedRef.current;
275
- const isDoubleClick = !onlyLeafSelectable && (lastSelected == null ? void 0 : lastSelected.path) === pathString && now - lastSelected.time < 300;
281
+ const isDoubleClick = isSelectable && !onlyLeafSelectable && (lastSelected == null ? void 0 : lastSelected.path) === pathString && now - lastSelected.time < 300;
276
282
  if (isDoubleClick) {
277
283
  onChange == null ? void 0 : onChange(formattedValue, lastOption == null ? void 0 : lastOption.meta);
278
284
  lastSelectedRef.current = null;
@@ -44,12 +44,6 @@ var import_css = require("@emotion/css");
44
44
  var import_antd = require("antd");
45
45
  var import_react = __toESM(require("react"));
46
46
  var import_provider = require("../../provider");
47
- const useNiceDropdownMaxHeight = /* @__PURE__ */ __name(() => {
48
- return (0, import_react.useMemo)(() => {
49
- const maxHeight = Math.min(window.innerHeight * 0.6, 400);
50
- return maxHeight;
51
- }, []);
52
- }, "useNiceDropdownMaxHeight");
53
47
  const useAsyncMenuItems = /* @__PURE__ */ __name((menuVisible, rootItems, resetKey, openKeySet, refreshKeys) => {
54
48
  const [loadedChildren, setLoadedChildren] = (0, import_react.useState)({});
55
49
  const [loadingKeys, setLoadingKeys] = (0, import_react.useState)(/* @__PURE__ */ new Set());
@@ -411,6 +405,7 @@ const KEEP_OPEN_LABEL_STYLE = {
411
405
  width: "100%"
412
406
  };
413
407
  const DROPDOWN_PERSIST_TTL_MS = 350;
408
+ const DEFAULT_DROPDOWN_MAX_HEIGHT = 400;
414
409
  const MENU_CLOSE_DELAY = 0.3;
415
410
  const SUBMENU_MOTION_DISABLED = {
416
411
  motionEnter: false,
@@ -420,8 +415,11 @@ const dropdownPersistRegistry = /* @__PURE__ */ new Map();
420
415
  const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
421
416
  const engine = (0, import_provider.useFlowEngine)();
422
417
  const { getPrefixCls } = import_react.default.useContext(import_antd.ConfigProvider.ConfigContext);
418
+ const { token } = import_antd.theme.useToken();
423
419
  const triggerId = import_react.default.useId();
420
+ const showArrow = Boolean(props.arrow);
424
421
  const [menuVisible, setMenuVisible] = (0, import_react.useState)(false);
422
+ const [dropdownMaxHeight, setDropdownMaxHeight] = (0, import_react.useState)(DEFAULT_DROPDOWN_MAX_HEIGHT);
425
423
  const [openKeys, setOpenKeys] = (0, import_react.useState)(/* @__PURE__ */ new Set());
426
424
  const [rootItems, setRootItems] = (0, import_react.useState)([]);
427
425
  const [rootLoading, setRootLoading] = (0, import_react.useState)(false);
@@ -431,7 +429,6 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
431
429
  const triggerOpenClassName = `nb-lazy-dropdown-trigger-${triggerId.replace(/[^a-zA-Z0-9_-]/g, "")}`;
432
430
  const defaultOpenClassName = `${getPrefixCls("dropdown", props.prefixCls)}-open`;
433
431
  const mergedOpenClassName = [props.openClassName ?? defaultOpenClassName, triggerOpenClassName].filter(Boolean).join(" ");
434
- const dropdownMaxHeight = useNiceDropdownMaxHeight();
435
432
  const t = engine.translate.bind(engine);
436
433
  const { items: menuItems, keepDropdownOpen, persistKey, stateVersion, refreshKeys, ...dropdownMenuProps } = menu;
437
434
  const { loadedChildren, loadingKeys, handleLoadChildren } = useAsyncMenuItems(
@@ -445,6 +442,23 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
445
442
  const { searchValues, inputValues, clearSearchValue, clearAllSearchValues } = searchHandlers;
446
443
  const { requestKeepOpen, shouldPreventClose } = useKeepDropdownOpen();
447
444
  useSubmenuStyles(menuVisible, dropdownMaxHeight);
445
+ (0, import_react.useLayoutEffect)(() => {
446
+ if (!menuVisible) return;
447
+ const updateDropdownMaxHeight = /* @__PURE__ */ __name(() => {
448
+ const trigger = document.querySelector(`.${triggerOpenClassName}`);
449
+ if (!trigger) return;
450
+ const triggerRect = trigger.getBoundingClientRect();
451
+ const placementOffset = token.marginXXS + (showArrow ? token.sizePopupArrow / 2 : 0);
452
+ const reservedSpace = placementOffset + token.marginXXS;
453
+ const availableAbove = triggerRect.top - reservedSpace;
454
+ const availableBelow = window.innerHeight - triggerRect.bottom - reservedSpace;
455
+ const nextMaxHeight = Math.min(DEFAULT_DROPDOWN_MAX_HEIGHT, Math.max(0, availableAbove, availableBelow));
456
+ setDropdownMaxHeight(nextMaxHeight);
457
+ }, "updateDropdownMaxHeight");
458
+ updateDropdownMaxHeight();
459
+ window.addEventListener("resize", updateDropdownMaxHeight);
460
+ return () => window.removeEventListener("resize", updateDropdownMaxHeight);
461
+ }, [menuVisible, showArrow, token.marginXXS, token.sizePopupArrow, triggerOpenClassName]);
448
462
  const closeMenu = (0, import_react.useCallback)(() => {
449
463
  setMenuVisible(false);
450
464
  activeSearchKeyRef.current = null;
@@ -24,9 +24,13 @@ import { FlowResource, FlowSQLRepository } from './resources';
24
24
  import type { ActionDefinition, EventDefinition, ResourceType } from './types';
25
25
  import { JSONValue } from './utils/params-resolvers';
26
26
  import type { RecordRef } from './utils/serverContextParams';
27
+ import { PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS } from './utils/dirtyAwareApiClient';
27
28
  import { FlowView, FlowViewer } from './views/FlowView';
28
29
  import { type RunJSVersion } from './runjs-context/registry';
29
30
  type Getter<T = any> = (ctx: FlowContext) => T | Promise<T>;
31
+ export type ResolveJsonTemplateOptions = {
32
+ contractModelUid?: string | number | null;
33
+ };
30
34
  export type FlowContextDocRef = string | {
31
35
  url: string;
32
36
  title?: string;
@@ -77,6 +81,7 @@ export interface MetaTreeNode {
77
81
  hidden?: boolean | (() => boolean);
78
82
  disabled?: boolean | (() => boolean);
79
83
  disabledReason?: string | (() => string | undefined);
84
+ selectable?: boolean;
80
85
  children?: MetaTreeNode[] | (() => Promise<MetaTreeNode[]>);
81
86
  }
82
87
  export interface PropertyMeta {
@@ -343,7 +348,7 @@ declare class BaseFlowEngineContext extends FlowContext {
343
348
  * @deprecated use `resolveJsonTemplate` instead
344
349
  */
345
350
  renderJson: (template: JSONValue) => Promise<any>;
346
- resolveJsonTemplate: (template: JSONValue) => Promise<any>;
351
+ resolveJsonTemplate: (template: JSONValue, options?: ResolveJsonTemplateOptions) => Promise<any>;
347
352
  getVar: (path: string) => Promise<any>;
348
353
  request: (options: RequestOptions) => Promise<any>;
349
354
  runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
@@ -469,6 +474,12 @@ export declare function createRunJSDeprecationProxy(ctx: any, options?: {
469
474
  doc?: RunJSDocMeta;
470
475
  }): any;
471
476
  export declare class FlowRunJSContext extends FlowContext {
477
+ [PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS](action: {
478
+ actionName: string;
479
+ dataSourceKey?: string;
480
+ resourceName: string;
481
+ resourceOf?: unknown;
482
+ }, params: Record<string, unknown> | undefined): Record<string, unknown>;
472
483
  constructor(delegate: FlowContext);
473
484
  exit(): void;
474
485
  exitAll(): void;
@@ -2383,8 +2383,8 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2383
2383
  this.defineMethod("renderJson", function(template) {
2384
2384
  return this.resolveJsonTemplate(template);
2385
2385
  });
2386
- this.defineMethod("resolveJsonTemplate", async function(template) {
2387
- var _a, _b;
2386
+ const resolveJsonTemplate = /* @__PURE__ */ __name(async function(template, options) {
2387
+ var _a, _b, _c;
2388
2388
  const used = (0, import_utils.extractUsedVariablePaths)(template);
2389
2389
  const usedVarNames = Object.keys(used || {});
2390
2390
  if (!usedVarNames.length) {
@@ -2412,13 +2412,13 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2412
2412
  let serverResolved = template;
2413
2413
  if (needServer) {
2414
2414
  const inferRecordRefWithMeta = /* @__PURE__ */ __name((ctx) => {
2415
- var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2415
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2416
2416
  const ref = (0, import_variablesParams.inferRecordRef)(ctx);
2417
2417
  if (ref) return ref;
2418
2418
  try {
2419
2419
  const tk = (_b2 = (_a2 = ctx == null ? void 0 : ctx.resource) == null ? void 0 : _a2.getMeta) == null ? void 0 : _b2.call(_a2, "currentFilterByTk");
2420
2420
  if (typeof tk === "undefined" || tk === null) return void 0;
2421
- const collection = ((_c = ctx == null ? void 0 : ctx.collection) == null ? void 0 : _c.name) || ((_j = (_i = (_h = (_g = (_f = (_e = (_d = ctx == null ? void 0 : ctx.resource) == null ? void 0 : _d.getResourceName) == null ? void 0 : _e.call(_d)) == null ? void 0 : _f.split) == null ? void 0 : _g.call(_f, ".")) == null ? void 0 : _h.slice) == null ? void 0 : _i.call(_h, -1)) == null ? void 0 : _j[0]);
2421
+ const collection = ((_c2 = ctx == null ? void 0 : ctx.collection) == null ? void 0 : _c2.name) || ((_j = (_i = (_h = (_g = (_f = (_e = (_d = ctx == null ? void 0 : ctx.resource) == null ? void 0 : _d.getResourceName) == null ? void 0 : _e.call(_d)) == null ? void 0 : _f.split) == null ? void 0 : _g.call(_f, ".")) == null ? void 0 : _h.slice) == null ? void 0 : _i.call(_h, -1)) == null ? void 0 : _j[0]);
2422
2422
  if (!collection) return void 0;
2423
2423
  const dataSourceKey = ((_k = ctx == null ? void 0 : ctx.collection) == null ? void 0 : _k.dataSourceKey) || ((_m = (_l = ctx == null ? void 0 : ctx.resource) == null ? void 0 : _l.getDataSourceKey) == null ? void 0 : _m.call(_l));
2424
2424
  return { collection, dataSourceKey, filterByTk: tk };
@@ -2461,6 +2461,11 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2461
2461
  }, "collectFromMeta");
2462
2462
  const inputFromMeta = await collectFromMeta();
2463
2463
  const autoInput = { ...inputFromMeta };
2464
+ const viewPaths = serverVarPaths.view || [];
2465
+ if (!autoInput.view && viewPaths.some((path) => path === "record" || path.startsWith("record.") || path.startsWith("record["))) {
2466
+ const recordRef = (0, import_variablesParams.inferViewRecordRef)(this);
2467
+ if (recordRef) autoInput.view = { record: recordRef };
2468
+ }
2464
2469
  try {
2465
2470
  const varName = "formValues";
2466
2471
  const neededPaths = serverVarPaths[varName] || [];
@@ -2511,18 +2516,25 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2511
2516
  }
2512
2517
  if (this.api) {
2513
2518
  try {
2519
+ const contractRd = (0, import_params_resolvers.buildFlowModelResolveDescriptor)(
2520
+ this,
2521
+ options == null ? void 0 : options.contractModelUid
2522
+ );
2514
2523
  serverResolved = await (0, import_params_resolvers.enqueueVariablesResolve)(this, {
2524
+ ...contractRd ? { contractRd } : {},
2525
+ rd: (0, import_params_resolvers.buildFlowModelResolveDescriptor)(this, (_a = this.model) == null ? void 0 : _a.uid),
2515
2526
  template,
2516
2527
  contextParams: autoContextParams || {}
2517
2528
  });
2518
2529
  } catch (e) {
2519
- (_b = (_a = this.logger) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, { err: e }, "variables:resolve failed, fallback to client-only");
2530
+ (_c = (_b = this.logger) == null ? void 0 : _b.warn) == null ? void 0 : _c.call(_b, { err: e }, "variables:resolve failed, fallback to client-only");
2520
2531
  serverResolved = template;
2521
2532
  }
2522
2533
  }
2523
2534
  }
2524
2535
  return (0, import_utils.resolveExpressions)(serverResolved, this);
2525
- });
2536
+ }, "resolveJsonTemplate");
2537
+ this.defineMethod("resolveJsonTemplate", resolveJsonTemplate);
2526
2538
  this.defineMethod(
2527
2539
  "getVar",
2528
2540
  async function(varPath) {
@@ -3498,9 +3510,38 @@ function __mergeRunJSDocMeta(base, patch) {
3498
3510
  }
3499
3511
  __name(__mergeRunJSDocMeta, "__mergeRunJSDocMeta");
3500
3512
  const _FlowRunJSContext = class _FlowRunJSContext extends FlowContext {
3513
+ [import_dirtyAwareApiClient.PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS](action, params) {
3514
+ var _a, _b, _c, _d, _e;
3515
+ if (action.actionName.toLowerCase() !== "create" || !params || Array.isArray(params) || Object.prototype.hasOwnProperty.call(params, "updateAssociationValues") || !this.form || typeof ((_a = this.blockModel) == null ? void 0 : _a.submitFromRunJs) !== "function") {
3516
+ return params;
3517
+ }
3518
+ const resource = this.resource;
3519
+ const currentResourceName = (_b = resource == null ? void 0 : resource.getResourceName) == null ? void 0 : _b.call(resource);
3520
+ const currentDataSourceKey = ((_c = resource == null ? void 0 : resource.getDataSourceKey) == null ? void 0 : _c.call(resource)) || "main";
3521
+ if (action.resourceName !== currentResourceName || (action.dataSourceKey || "main") !== currentDataSourceKey) {
3522
+ return params;
3523
+ }
3524
+ const currentSourceId = (_d = resource == null ? void 0 : resource.getSourceId) == null ? void 0 : _d.call(resource);
3525
+ if ((currentResourceName == null ? void 0 : currentResourceName.includes(".")) && currentSourceId !== null && typeof currentSourceId !== "undefined" && String(action.resourceOf ?? "") !== String(currentSourceId)) {
3526
+ return params;
3527
+ }
3528
+ const updateAssociationValues = (_e = resource == null ? void 0 : resource.getUpdateAssociationValues) == null ? void 0 : _e.call(resource);
3529
+ if (!Array.isArray(updateAssociationValues) || updateAssociationValues.length === 0) {
3530
+ return params;
3531
+ }
3532
+ return {
3533
+ ...params,
3534
+ updateAssociationValues: [...updateAssociationValues]
3535
+ };
3536
+ }
3501
3537
  constructor(delegate) {
3538
+ var _a, _b;
3502
3539
  super();
3503
3540
  this.addDelegate(delegate);
3541
+ const submit = (_b = (_a = delegate.blockModel) == null ? void 0 : _a.submitFromRunJs) == null ? void 0 : _b.bind(delegate.blockModel);
3542
+ if (delegate.form && submit) {
3543
+ this.defineProperty("form", { value: { ...delegate.form, submit } });
3544
+ }
3504
3545
  this.defineProperty("React", { value: import_react.default });
3505
3546
  this.defineProperty("antd", { value: antd });
3506
3547
  this.defineProperty("dayjs", {
@@ -60,6 +60,7 @@ const _ResourceError = class _ResourceError extends Error {
60
60
  constructor(error) {
61
61
  const data = toErrMessages(error).shift();
62
62
  super(data.message);
63
+ this.data = data;
63
64
  this.name = "ResponseError";
64
65
  }
65
66
  get code() {
@@ -8,6 +8,16 @@
8
8
  */
9
9
  import type { Collection } from '../data-source';
10
10
  import type { FlowContext, PropertyMetaFactory } from '../flowContext';
11
+ /**
12
+ * 从值中提取主键:
13
+ * - 支持主键原始值(string/number)
14
+ * - 支持对象(按主键名取值)
15
+ *
16
+ * @param value 字段当前值
17
+ * @param primaryKey 主键字段名
18
+ * @returns 解析出的主键值,无法解析时返回 undefined
19
+ */
20
+ export declare function getAssociationFilterByTk(value: unknown, primaryKey: string | string[]): unknown;
11
21
  /**
12
22
  * 创建一个用于“对象类变量”(如 formValues / item)的 `resolveOnServer` 判定函数。
13
23
  * 仅当访问路径以“关联字段名”开头(且继续访问其子属性)时,返回 true 交由服务端解析;
@@ -38,7 +38,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
38
38
  var associationObjectVariable_exports = {};
39
39
  __export(associationObjectVariable_exports, {
40
40
  createAssociationAwareObjectMetaFactory: () => createAssociationAwareObjectMetaFactory,
41
- createAssociationSubpathResolver: () => createAssociationSubpathResolver
41
+ createAssociationSubpathResolver: () => createAssociationSubpathResolver,
42
+ getAssociationFilterByTk: () => getAssociationFilterByTk
42
43
  });
43
44
  module.exports = __toCommonJS(associationObjectVariable_exports);
44
45
  var import_lodash = __toESM(require("lodash"));
@@ -58,13 +59,14 @@ function findFieldByName(collection, name) {
58
59
  return fields.find((f) => f.name === name);
59
60
  }
60
61
  __name(findFieldByName, "findFieldByName");
61
- function toFilterByTk(value, primaryKey) {
62
+ function getAssociationFilterByTk(value, primaryKey) {
62
63
  if (value == null) return void 0;
63
64
  if (Array.isArray(primaryKey)) {
64
65
  if (typeof value !== "object" || !value) return void 0;
66
+ const record = value;
65
67
  const out = {};
66
68
  for (const k of primaryKey) {
67
- const v = value[k];
69
+ const v = record[k];
68
70
  if (typeof v === "undefined" || v === null) return void 0;
69
71
  out[k] = v;
70
72
  }
@@ -76,7 +78,7 @@ function toFilterByTk(value, primaryKey) {
76
78
  }
77
79
  return void 0;
78
80
  }
79
- __name(toFilterByTk, "toFilterByTk");
81
+ __name(getAssociationFilterByTk, "getAssociationFilterByTk");
80
82
  function createAssociationSubpathResolver(collectionAccessor, valueAccessor) {
81
83
  return (p) => {
82
84
  if (!p || !p.includes(".")) return false;
@@ -122,7 +124,7 @@ function createAssociationAwareObjectMetaFactory(collectionAccessor, title, valu
122
124
  const associationValue = obj[name];
123
125
  if (associationValue == null) continue;
124
126
  if (Array.isArray(associationValue)) {
125
- const ids = associationValue.map((item) => toFilterByTk(item, primaryKey)).filter((v) => v != null);
127
+ const ids = associationValue.map((item) => getAssociationFilterByTk(item, primaryKey)).filter((v) => v != null);
126
128
  if (ids.length) {
127
129
  params[name] = {
128
130
  collection: target,
@@ -131,7 +133,7 @@ function createAssociationAwareObjectMetaFactory(collectionAccessor, title, valu
131
133
  };
132
134
  }
133
135
  } else {
134
- const id = toFilterByTk(associationValue, primaryKey);
136
+ const id = getAssociationFilterByTk(associationValue, primaryKey);
135
137
  if (id != null) {
136
138
  params[name] = {
137
139
  collection: target,
@@ -153,5 +155,6 @@ __name(createAssociationAwareObjectMetaFactory, "createAssociationAwareObjectMet
153
155
  // Annotate the CommonJS export names for ESM import in node:
154
156
  0 && (module.exports = {
155
157
  createAssociationAwareObjectMetaFactory,
156
- createAssociationSubpathResolver
158
+ createAssociationSubpathResolver,
159
+ getAssociationFilterByTk
157
160
  });
@@ -6,11 +6,33 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ declare const PRESET_KEY_LIST: readonly ["today", "now", "yesterday", "tomorrow", "thisWeek", "lastWeek", "nextWeek", "thisMonth", "lastMonth", "nextMonth", "thisQuarter", "lastQuarter", "nextQuarter", "thisYear", "lastYear", "nextYear"];
10
+ export type CtxDatePreset = (typeof PRESET_KEY_LIST)[number];
11
+ export type CtxDateRelativeDirection = 'next' | 'past';
12
+ export type CtxDateRelativeUnit = 'day' | 'week' | 'month' | 'year';
13
+ export type CtxDateExpressionConfig = {
14
+ kind: 'exact';
15
+ value: string | [string, string];
16
+ format?: string;
17
+ } | {
18
+ kind: 'relative';
19
+ direction: CtxDateRelativeDirection;
20
+ amount: number;
21
+ unit: CtxDateRelativeUnit;
22
+ format?: string;
23
+ } | {
24
+ kind: 'preset';
25
+ preset: CtxDatePreset;
26
+ format?: string;
27
+ };
9
28
  export declare function isCtxDatePathPrefix(pathSegments: string[]): boolean;
10
29
  export declare function encodeBase64Url(input: string): string;
11
30
  export declare function decodeBase64Url(input: string): string | undefined;
12
31
  export declare function isCtxDateExpression(value: unknown): value is string;
13
32
  export declare function isCompleteCtxDatePath(pathSegments: string[]): boolean;
14
33
  export declare function parseCtxDateExpression(value: unknown): any;
34
+ export declare function parseCtxDateExpressionConfig(value: unknown): CtxDateExpressionConfig | undefined;
35
+ export declare function serializeCtxDateExpressionConfig(config: CtxDateExpressionConfig): string | undefined;
15
36
  export declare function serializeCtxDateValue(value: unknown): string | undefined;
16
37
  export declare function resolveCtxDatePath(pathSegments: string[]): any;
38
+ export {};
@@ -43,13 +43,15 @@ __export(dateVariable_exports, {
43
43
  isCtxDateExpression: () => isCtxDateExpression,
44
44
  isCtxDatePathPrefix: () => isCtxDatePathPrefix,
45
45
  parseCtxDateExpression: () => parseCtxDateExpression,
46
+ parseCtxDateExpressionConfig: () => parseCtxDateExpressionConfig,
46
47
  resolveCtxDatePath: () => resolveCtxDatePath,
48
+ serializeCtxDateExpressionConfig: () => serializeCtxDateExpressionConfig,
47
49
  serializeCtxDateValue: () => serializeCtxDateValue
48
50
  });
49
51
  module.exports = __toCommonJS(dateVariable_exports);
50
52
  var import_dayjs = __toESM(require("dayjs"));
51
53
  const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
52
- const PRESET_KEYS = /* @__PURE__ */ new Set([
54
+ const PRESET_KEY_LIST = [
53
55
  "today",
54
56
  "now",
55
57
  "yesterday",
@@ -66,9 +68,11 @@ const PRESET_KEYS = /* @__PURE__ */ new Set([
66
68
  "thisYear",
67
69
  "lastYear",
68
70
  "nextYear"
69
- ]);
71
+ ];
72
+ const PRESET_KEYS = new Set(PRESET_KEY_LIST);
70
73
  const RELATIVE_DIRECTIONS = /* @__PURE__ */ new Set(["next", "past"]);
71
74
  const RELATIVE_UNITS = /* @__PURE__ */ new Set(["day", "week", "month", "year"]);
75
+ const MAX_DATE_FORMAT_LENGTH = 128;
72
76
  function parseCtxDateSegments(value) {
73
77
  if (typeof value !== "string") return null;
74
78
  const trimmed = value.trim();
@@ -79,8 +83,7 @@ function parseCtxDateSegments(value) {
79
83
  return rawPath.split(".").map((seg) => seg.trim()).filter(Boolean);
80
84
  }
81
85
  __name(parseCtxDateSegments, "parseCtxDateSegments");
82
- function isCtxDatePathPrefix(pathSegments) {
83
- const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
86
+ function isBaseCtxDatePathPrefix(segments) {
84
87
  if (segments[0] !== "date") return false;
85
88
  if (segments.length === 1) return true;
86
89
  if (segments[1] === "preset") {
@@ -117,6 +120,34 @@ function isCtxDatePathPrefix(pathSegments) {
117
120
  }
118
121
  return false;
119
122
  }
123
+ __name(isBaseCtxDatePathPrefix, "isBaseCtxDatePathPrefix");
124
+ function decodeFormatToken(token) {
125
+ const raw = String(token || "");
126
+ if (!raw.startsWith("v")) return void 0;
127
+ const decoded = decodeBase64Url(raw.slice(1));
128
+ if (!decoded || decoded.length > MAX_DATE_FORMAT_LENGTH) return void 0;
129
+ return decoded;
130
+ }
131
+ __name(decodeFormatToken, "decodeFormatToken");
132
+ function splitFormattedDateSegments(segments) {
133
+ if (segments[0] !== "date") return null;
134
+ if (segments[1] !== "format") return { baseSegments: segments };
135
+ if (segments.length < 4) return null;
136
+ const format = decodeFormatToken(segments[2]);
137
+ if (!format) return null;
138
+ return { baseSegments: ["date", ...segments.slice(3)], format };
139
+ }
140
+ __name(splitFormattedDateSegments, "splitFormattedDateSegments");
141
+ function isCtxDatePathPrefix(pathSegments) {
142
+ const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
143
+ if (segments[0] !== "date") return false;
144
+ if (segments.length === 1) return true;
145
+ if (segments[1] !== "format") return isBaseCtxDatePathPrefix(segments);
146
+ if (segments.length === 2) return true;
147
+ if (segments.length === 3) return typeof decodeFormatToken(segments[2]) === "string";
148
+ const formatted = splitFormattedDateSegments(segments);
149
+ return formatted ? isBaseCtxDatePathPrefix(formatted.baseSegments) : false;
150
+ }
120
151
  __name(isCtxDatePathPrefix, "isCtxDatePathPrefix");
121
152
  function withDatePrefix(pathSegments) {
122
153
  if (pathSegments[0] === "date") {
@@ -225,26 +256,31 @@ __name(isCtxDateExpression, "isCtxDateExpression");
225
256
  function isCompleteCtxDatePath(pathSegments) {
226
257
  if (!isCtxDatePathPrefix(pathSegments)) return false;
227
258
  const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
228
- if (segments[0] !== "date") return false;
229
- if (segments[1] === "preset") {
230
- return segments.length === 3 && PRESET_KEYS.has(segments[2]);
259
+ const formatted = splitFormattedDateSegments(segments);
260
+ if (!formatted) return false;
261
+ const baseSegments = formatted.baseSegments;
262
+ if (baseSegments[1] === "preset") {
263
+ return baseSegments.length === 3 && PRESET_KEYS.has(baseSegments[2]);
231
264
  }
232
- if (segments[1] === "relative") {
233
- if (segments.length !== 5) return false;
234
- return RELATIVE_DIRECTIONS.has(segments[2]) && RELATIVE_UNITS.has(segments[3]) && typeof parseNumberToken(segments[4]) === "number";
265
+ if (baseSegments[1] === "relative") {
266
+ if (baseSegments.length !== 5) return false;
267
+ return RELATIVE_DIRECTIONS.has(baseSegments[2]) && RELATIVE_UNITS.has(baseSegments[3]) && typeof parseNumberToken(baseSegments[4]) === "number";
235
268
  }
236
- if (segments[1] === "exact" && segments[2] === "single" && segments[3] === "date") {
237
- return segments.length === 5 && /^v.+/.test(segments[4]);
269
+ if (baseSegments[1] === "exact" && baseSegments[2] === "single" && baseSegments[3] === "date") {
270
+ return baseSegments.length === 5 && /^v.+/.test(baseSegments[4]);
238
271
  }
239
- if (segments[1] === "exact" && segments[2] === "range" && segments[3] === "date") {
240
- return segments.length === 6 && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
272
+ if (baseSegments[1] === "exact" && baseSegments[2] === "range" && baseSegments[3] === "date") {
273
+ return baseSegments.length === 6 && /^v.+/.test(baseSegments[4]) && /^v.+/.test(baseSegments[5]);
241
274
  }
242
275
  return false;
243
276
  }
244
277
  __name(isCompleteCtxDatePath, "isCompleteCtxDatePath");
245
278
  function parseCtxDateExpression(value) {
246
279
  if (!isCtxDateExpression(value)) return void 0;
247
- const segments = withDatePrefix(parseCtxDateSegments(value) || []);
280
+ const rawSegments = withDatePrefix(parseCtxDateSegments(value) || []);
281
+ const formatted = splitFormattedDateSegments(rawSegments);
282
+ if (!formatted) return void 0;
283
+ const segments = formatted.baseSegments;
248
284
  if (segments[1] === "preset" && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
249
285
  return { type: segments[2] };
250
286
  }
@@ -272,6 +308,60 @@ function parseCtxDateExpression(value) {
272
308
  return void 0;
273
309
  }
274
310
  __name(parseCtxDateExpression, "parseCtxDateExpression");
311
+ function parseCtxDateExpressionConfig(value) {
312
+ if (!isCtxDateExpression(value)) return void 0;
313
+ const segments = withDatePrefix(parseCtxDateSegments(value) || []);
314
+ const formatted = splitFormattedDateSegments(segments);
315
+ if (!formatted) return void 0;
316
+ const parsed = parseCtxDateExpression(value);
317
+ const formatConfig = formatted.format ? { format: formatted.format } : {};
318
+ if (typeof parsed === "string") {
319
+ return { kind: "exact", value: parsed, ...formatConfig };
320
+ }
321
+ if (Array.isArray(parsed) && parsed.length === 2 && typeof parsed[0] === "string" && typeof parsed[1] === "string") {
322
+ return { kind: "exact", value: [parsed[0], parsed[1]], ...formatConfig };
323
+ }
324
+ if (!parsed || typeof parsed !== "object") return void 0;
325
+ const typed = parsed;
326
+ if (typed.type === "past" || typed.type === "next") {
327
+ if (typeof typed.unit !== "string" || !RELATIVE_UNITS.has(typed.unit) || typeof typed.number !== "number") {
328
+ return void 0;
329
+ }
330
+ return {
331
+ kind: "relative",
332
+ direction: typed.type,
333
+ amount: typed.number,
334
+ unit: typed.unit,
335
+ ...formatConfig
336
+ };
337
+ }
338
+ if (typeof typed.type === "string" && PRESET_KEYS.has(typed.type)) {
339
+ return { kind: "preset", preset: typed.type, ...formatConfig };
340
+ }
341
+ return void 0;
342
+ }
343
+ __name(parseCtxDateExpressionConfig, "parseCtxDateExpressionConfig");
344
+ function serializeCtxDateExpressionConfig(config) {
345
+ let legacyValue;
346
+ if (config.kind === "preset") {
347
+ if (!PRESET_KEYS.has(config.preset)) return void 0;
348
+ legacyValue = { type: config.preset };
349
+ } else if (config.kind === "relative") {
350
+ if (!RELATIVE_DIRECTIONS.has(config.direction) || !RELATIVE_UNITS.has(config.unit)) return void 0;
351
+ const amount = Math.floor(Number(config.amount));
352
+ if (!Number.isFinite(amount) || amount <= 0) return void 0;
353
+ legacyValue = { type: config.direction, unit: config.unit, number: amount };
354
+ } else {
355
+ legacyValue = config.value;
356
+ }
357
+ const expression = serializeCtxDateValue(legacyValue);
358
+ if (!expression || !config.format) return expression;
359
+ const format = String(config.format);
360
+ if (!format.trim() || format.length > MAX_DATE_FORMAT_LENGTH) return void 0;
361
+ const segments = withDatePrefix(parseCtxDateSegments(expression) || []);
362
+ return toCtxDateExpression(["date", "format", `v${encodeBase64Url(format)}`, ...segments.slice(1)]);
363
+ }
364
+ __name(serializeCtxDateExpressionConfig, "serializeCtxDateExpressionConfig");
275
365
  function serializeCtxDateValue(value) {
276
366
  if (isCtxDateExpression(value)) {
277
367
  return String(value).trim();
@@ -317,8 +407,23 @@ function serializeCtxDateValue(value) {
317
407
  return void 0;
318
408
  }
319
409
  __name(serializeCtxDateValue, "serializeCtxDateValue");
410
+ function formatResolvedDateValue(value, format) {
411
+ const formatValue = /* @__PURE__ */ __name((item) => {
412
+ if (typeof item !== "string") return item;
413
+ const parsed = (0, import_dayjs.default)(item);
414
+ return parsed.isValid() ? parsed.format(format) : item;
415
+ }, "formatValue");
416
+ return Array.isArray(value) ? value.map(formatValue) : formatValue(value);
417
+ }
418
+ __name(formatResolvedDateValue, "formatResolvedDateValue");
320
419
  function resolveCtxDatePath(pathSegments) {
321
- const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
420
+ const rawSegments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
421
+ const formatted = splitFormattedDateSegments(rawSegments);
422
+ if (!formatted) return void 0;
423
+ if (formatted.format) {
424
+ return formatResolvedDateValue(resolveCtxDatePath(formatted.baseSegments), formatted.format);
425
+ }
426
+ const segments = formatted.baseSegments;
322
427
  if (segments[0] !== "date") return void 0;
323
428
  if (segments[1] === "preset" && segments.length === 3) {
324
429
  const key = segments[2];
@@ -375,6 +480,8 @@ __name(resolveCtxDatePath, "resolveCtxDatePath");
375
480
  isCtxDateExpression,
376
481
  isCtxDatePathPrefix,
377
482
  parseCtxDateExpression,
483
+ parseCtxDateExpressionConfig,
378
484
  resolveCtxDatePath,
485
+ serializeCtxDateExpressionConfig,
379
486
  serializeCtxDateValue
380
487
  });
@@ -8,4 +8,5 @@
8
8
  */
9
9
  import type { FlowContext } from '../flowContext';
10
10
  export declare const SKIP_DATA_SOURCE_DIRTY = "__nocobaseSkipDataSourceDirty";
11
+ export declare const PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS: unique symbol;
11
12
  export declare function getDirtyAwareApiClient(value: unknown, context: FlowContext): unknown;
@@ -27,6 +27,7 @@ var __copyProps = (to, from, except, desc) => {
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
  var dirtyAwareApiClient_exports = {};
29
29
  __export(dirtyAwareApiClient_exports, {
30
+ PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS: () => PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS,
30
31
  SKIP_DATA_SOURCE_DIRTY: () => SKIP_DATA_SOURCE_DIRTY,
31
32
  getDirtyAwareApiClient: () => getDirtyAwareApiClient
32
33
  });
@@ -35,6 +36,7 @@ var import_sdk = require("@nocobase/sdk");
35
36
  var import_dataSourceDirty = require("./dataSourceDirty");
36
37
  const SKIP_DATA_SOURCE_DIRTY = "__nocobaseSkipDataSourceDirty";
37
38
  const DIRTY_DISPATCH_TOKEN = Symbol("nocobaseDirtyDispatchToken");
39
+ const PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS = Symbol("prepareContextResourceActionParams");
38
40
  const dirtyAwareApiClientCache = /* @__PURE__ */ new WeakMap();
39
41
  const dirtyAwareApiClientProxies = /* @__PURE__ */ new WeakSet();
40
42
  const MUTATING_RESOURCE_ACTIONS = [
@@ -277,6 +279,7 @@ function resolveDirtyResourceActionFromResource(resourceName, resourceOf, action
277
279
  }
278
280
  return {
279
281
  resourceName: normalizedResourceName,
282
+ resourceOf,
280
283
  actionName: normalizedActionName
281
284
  };
282
285
  }
@@ -359,6 +362,15 @@ function createDirtyAwareResource(context, resource, resourceName, resourceOf, h
359
362
  return async (...args) => {
360
363
  const actionOptions = isObjectRecord(args[1]) ? args[1] : void 0;
361
364
  const dirtyResourceAction = resolveDirtyResourceActionFromResource(resourceName, resourceOf, prop, context);
365
+ const prepareParams = context[PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS];
366
+ const actionParams = dirtyResourceAction && typeof prepareParams === "function" ? prepareParams.call(
367
+ context,
368
+ {
369
+ ...dirtyResourceAction,
370
+ dataSourceKey: dirtyResourceAction.dataSourceKey || (0, import_dataSourceDirty.getDataSourceKeyFromHeaders)(headers)
371
+ },
372
+ args[0]
373
+ ) : args[0];
362
374
  const requestKey = getDirtyResourceActionDispatchKey(dirtyResourceAction, headers);
363
375
  const resourceKey = getResourceDispatchKey(resourceName, resourceOf, headers);
364
376
  const inheritedToken = getMatchingRequestToken(
@@ -373,12 +385,12 @@ function createDirtyAwareResource(context, resource, resourceName, resourceOf, h
373
385
  token.skip = true;
374
386
  }
375
387
  const forwardedArgs = actionOptions || args[1] == null ? [
376
- args[0],
388
+ actionParams,
377
389
  {
378
390
  ...actionOptions,
379
391
  [DIRTY_DISPATCH_TOKEN]: token
380
392
  }
381
- ] : args;
393
+ ] : [actionParams, args[1]];
382
394
  let actionResult;
383
395
  requestTokenStack.push({ key: requestKey, token });
384
396
  try {
@@ -627,6 +639,7 @@ function getDirtyAwareApiClient(value, context) {
627
639
  __name(getDirtyAwareApiClient, "getDirtyAwareApiClient");
628
640
  // Annotate the CommonJS export names for ESM import in node:
629
641
  0 && (module.exports = {
642
+ PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS,
630
643
  SKIP_DATA_SOURCE_DIRTY,
631
644
  getDirtyAwareApiClient
632
645
  });