@nocobase/flow-engine 2.2.0-beta.15 → 2.2.0-beta.17

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/acl/Acl.d.ts +2 -1
  2. package/lib/acl/Acl.js +28 -0
  3. package/lib/components/FlowContextSelector.js +7 -1
  4. package/lib/components/MobilePopup.js +14 -3
  5. package/lib/components/subModel/LazyDropdown.js +41 -26
  6. package/lib/flowContext.d.ts +5 -1
  7. package/lib/flowContext.js +18 -6
  8. package/lib/locale/en-US.json +2 -0
  9. package/lib/locale/index.d.ts +4 -0
  10. package/lib/locale/zh-CN.json +2 -0
  11. package/lib/utils/associationObjectVariable.d.ts +10 -0
  12. package/lib/utils/associationObjectVariable.js +10 -7
  13. package/lib/utils/dateVariable.d.ts +22 -0
  14. package/lib/utils/dateVariable.js +123 -16
  15. package/lib/utils/index.d.ts +3 -3
  16. package/lib/utils/index.js +8 -0
  17. package/lib/utils/params-resolvers.d.ts +3 -0
  18. package/lib/utils/params-resolvers.js +10 -0
  19. package/lib/utils/variablesParams.js +5 -0
  20. package/lib/views/createViewMeta.d.ts +1 -0
  21. package/lib/views/createViewMeta.js +53 -22
  22. package/package.json +4 -4
  23. package/src/__tests__/createViewMeta.popup.test.ts +84 -1
  24. package/src/__tests__/flowContext.test.ts +8 -0
  25. package/src/__tests__/objectVariable.test.ts +6 -1
  26. package/src/acl/Acl.tsx +36 -1
  27. package/src/acl/__tests__/Acl.test.tsx +70 -0
  28. package/src/components/FlowContextSelector.tsx +7 -1
  29. package/src/components/MobilePopup.tsx +16 -4
  30. package/src/components/__tests__/MobilePopup.test.tsx +42 -1
  31. package/src/components/subModel/LazyDropdown.tsx +44 -26
  32. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +85 -2
  33. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +35 -0
  34. package/src/flowContext.ts +31 -5
  35. package/src/locale/__tests__/index.test.ts +21 -0
  36. package/src/locale/en-US.json +2 -0
  37. package/src/locale/zh-CN.json +2 -0
  38. package/src/utils/__tests__/dateVariable.test.ts +57 -4
  39. package/src/utils/__tests__/variablesParams.test.ts +28 -1
  40. package/src/utils/associationObjectVariable.ts +9 -6
  41. package/src/utils/dateVariable.ts +145 -18
  42. package/src/utils/index.ts +17 -2
  43. package/src/utils/params-resolvers.ts +12 -0
  44. package/src/utils/variablesParams.ts +10 -0
  45. package/src/views/createViewMeta.ts +52 -18
@@ -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
  });
@@ -11,17 +11,17 @@ export { escapeT, getT, tExpr } from './translation';
11
11
  export { FlowCancelSaveException, FlowExitAllException, FlowExitException } from './exceptions';
12
12
  export { defineAction } from './flow-definitions';
13
13
  export { isInheritedFrom } from './inheritance';
14
- export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
14
+ export { buildFlowModelResolveDescriptor, resolveCreateModelOptions, resolveDefaultParams, resolveExpressions, } from './params-resolvers';
15
15
  export { compileUiSchema, resolveStepUiSchema, resolveStepDisabledInSettings, resolveUiMode, shouldHideEventInSettings, shouldHideStepInSettings, } from './schema-utils';
16
16
  export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
17
17
  export { createCollectionContextMeta } from './createCollectionContextMeta';
18
- export { createAssociationAwareObjectMetaFactory, createAssociationSubpathResolver } from './associationObjectVariable';
18
+ export { createAssociationAwareObjectMetaFactory, createAssociationSubpathResolver, getAssociationFilterByTk, } from './associationObjectVariable';
19
19
  export { buildRecordMeta, collectContextParamsForTemplate, createCurrentRecordMetaFactory, createRecordResolveOnServerWithLocal, createRecordMetaFactory, extractUsedVariableNames, extractUsedVariablePaths, inferRecordRef, type RecordParamsBuilder, } from './variablesParams';
20
20
  export { extractPropertyPath, formatPathToVariable, isVariableExpression } from './context';
21
21
  export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
22
22
  export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
23
23
  export { createOpenViewRouteState, decodeOpenViewRouteState, encodeOpenViewRouteState, isOpenViewRouteStateToken, RUNJS_OPEN_VIEW_ROUTE_STATE, type OpenViewRouteMode, type OpenViewRouteSize, type OpenViewRouteState, } from './openViewRouteState';
24
- export { decodeBase64Url, encodeBase64Url, isCompleteCtxDatePath, isCtxDatePathPrefix, isCtxDateExpression, parseCtxDateExpression, resolveCtxDatePath, serializeCtxDateValue, } from './dateVariable';
24
+ export { decodeBase64Url, encodeBase64Url, isCompleteCtxDatePath, isCtxDatePathPrefix, isCtxDateExpression, parseCtxDateExpression, parseCtxDateExpressionConfig, resolveCtxDatePath, serializeCtxDateExpressionConfig, serializeCtxDateValue, type CtxDateExpressionConfig, type CtxDatePreset, type CtxDateRelativeDirection, type CtxDateRelativeUnit, } from './dateVariable';
25
25
  export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
26
26
  export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
27
27
  export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
@@ -34,6 +34,7 @@ __export(utils_exports, {
34
34
  FlowExitException: () => import_exceptions.FlowExitException,
35
35
  MENU_KEYS: () => import_constants.MENU_KEYS,
36
36
  RUNJS_OPEN_VIEW_ROUTE_STATE: () => import_openViewRouteState.RUNJS_OPEN_VIEW_ROUTE_STATE,
37
+ buildFlowModelResolveDescriptor: () => import_params_resolvers.buildFlowModelResolveDescriptor,
37
38
  buildRecordMeta: () => import_variablesParams.buildRecordMeta,
38
39
  clearAutoFlowError: () => import_autoFlowError.clearAutoFlowError,
39
40
  collectContextParamsForTemplate: () => import_variablesParams.collectContextParamsForTemplate,
@@ -57,6 +58,7 @@ __export(utils_exports, {
57
58
  extractUsedVariablePaths: () => import_variablesParams.extractUsedVariablePaths,
58
59
  extractUsedVariablePathsFromRunJS: () => import_runjsValue.extractUsedVariablePathsFromRunJS,
59
60
  formatPathToVariable: () => import_context.formatPathToVariable,
61
+ getAssociationFilterByTk: () => import_associationObjectVariable.getAssociationFilterByTk,
60
62
  getAutoFlowError: () => import_autoFlowError.getAutoFlowError,
61
63
  getT: () => import_translation.getT,
62
64
  inferRecordRef: () => import_variablesParams.inferRecordRef,
@@ -71,6 +73,7 @@ __export(utils_exports, {
71
73
  isVariableExpression: () => import_context.isVariableExpression,
72
74
  normalizeRunJSValue: () => import_runjsValue.normalizeRunJSValue,
73
75
  parseCtxDateExpression: () => import_dateVariable.parseCtxDateExpression,
76
+ parseCtxDateExpressionConfig: () => import_dateVariable.parseCtxDateExpressionConfig,
74
77
  parsePathnameToViewParams: () => import_parsePathnameToViewParams.parsePathnameToViewParams,
75
78
  prepareRunJsCode: () => import_runjsTemplateCompat.prepareRunJsCode,
76
79
  preprocessRunJsTemplates: () => import_runjsTemplateCompat.preprocessRunJsTemplates,
@@ -85,6 +88,7 @@ __export(utils_exports, {
85
88
  resolveStepDisabledInSettings: () => import_schema_utils.resolveStepDisabledInSettings,
86
89
  resolveStepUiSchema: () => import_schema_utils.resolveStepUiSchema,
87
90
  resolveUiMode: () => import_schema_utils.resolveUiMode,
91
+ serializeCtxDateExpressionConfig: () => import_dateVariable.serializeCtxDateExpressionConfig,
88
92
  serializeCtxDateValue: () => import_dateVariable.serializeCtxDateValue,
89
93
  setAutoFlowError: () => import_autoFlowError.setAutoFlowError,
90
94
  setupRuntimeContextSteps: () => import_setupRuntimeContextSteps.setupRuntimeContextSteps,
@@ -127,6 +131,7 @@ var import_randomId = require("./randomId");
127
131
  FlowExitException,
128
132
  MENU_KEYS,
129
133
  RUNJS_OPEN_VIEW_ROUTE_STATE,
134
+ buildFlowModelResolveDescriptor,
130
135
  buildRecordMeta,
131
136
  clearAutoFlowError,
132
137
  collectContextParamsForTemplate,
@@ -150,6 +155,7 @@ var import_randomId = require("./randomId");
150
155
  extractUsedVariablePaths,
151
156
  extractUsedVariablePathsFromRunJS,
152
157
  formatPathToVariable,
158
+ getAssociationFilterByTk,
153
159
  getAutoFlowError,
154
160
  getT,
155
161
  inferRecordRef,
@@ -164,6 +170,7 @@ var import_randomId = require("./randomId");
164
170
  isVariableExpression,
165
171
  normalizeRunJSValue,
166
172
  parseCtxDateExpression,
173
+ parseCtxDateExpressionConfig,
167
174
  parsePathnameToViewParams,
168
175
  prepareRunJsCode,
169
176
  preprocessRunJsTemplates,
@@ -178,6 +185,7 @@ var import_randomId = require("./randomId");
178
185
  resolveStepDisabledInSettings,
179
186
  resolveStepUiSchema,
180
187
  resolveUiMode,
188
+ serializeCtxDateExpressionConfig,
181
189
  serializeCtxDateValue,
182
190
  setAutoFlowError,
183
191
  setupRuntimeContextSteps,
@@ -27,10 +27,13 @@ export type JSONValue = string | {
27
27
  [key: string]: JSONValue;
28
28
  } | JSONValue[];
29
29
  type BatchPayload = {
30
+ contractRd?: string;
31
+ rd?: string;
30
32
  template: JSONValue;
31
33
  contextParams?: ServerContextParams | undefined;
32
34
  };
33
35
  export declare function enqueueVariablesResolve(ctx: FlowRuntimeContext, payload: BatchPayload): Promise<unknown>;
36
+ export declare function buildFlowModelResolveDescriptor(ctx: Pick<FlowModelContext, 'api'>, flowModelUid?: string | number | null): any;
34
37
  /**
35
38
  * 解析参数中的 {{xxx}} 表达式,自动处理异步属性访问
36
39
  */
@@ -37,6 +37,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
37
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
  var params_resolvers_exports = {};
39
39
  __export(params_resolvers_exports, {
40
+ buildFlowModelResolveDescriptor: () => buildFlowModelResolveDescriptor,
40
41
  enqueueVariablesResolve: () => enqueueVariablesResolve,
41
42
  preprocessExpression: () => preprocessExpression,
42
43
  resolveCreateModelOptions: () => resolveCreateModelOptions,
@@ -45,6 +46,7 @@ __export(params_resolvers_exports, {
45
46
  });
46
47
  module.exports = __toCommonJS(params_resolvers_exports);
47
48
  var import_shared = require("@nocobase/shared");
49
+ var import_client = require("@nocobase/utils/client");
48
50
  var import_lodash = __toESM(require("lodash"));
49
51
  async function resolveDefaultParams(defaultParams, ctx) {
50
52
  if (!defaultParams) {
@@ -144,6 +146,8 @@ function enqueueVariablesResolve(ctx, payload) {
144
146
  try {
145
147
  const batch = items.map((it) => ({
146
148
  id: it.id,
149
+ contractRd: it.payload.contractRd,
150
+ rd: it.payload.rd,
147
151
  template: it.payload.template,
148
152
  contextParams: it.payload.contextParams || {}
149
153
  }));
@@ -187,6 +191,11 @@ function enqueueVariablesResolve(ctx, payload) {
187
191
  return p;
188
192
  }
189
193
  __name(enqueueVariablesResolve, "enqueueVariablesResolve");
194
+ function buildFlowModelResolveDescriptor(ctx, flowModelUid) {
195
+ var _a, _b;
196
+ return (0, import_client.generateFlowModelRdFromToken)(flowModelUid, (_b = (_a = ctx == null ? void 0 : ctx.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.token);
197
+ }
198
+ __name(buildFlowModelResolveDescriptor, "buildFlowModelResolveDescriptor");
190
199
  async function resolveExpressions(params, ctx) {
191
200
  const compile = /* @__PURE__ */ __name(async (source) => {
192
201
  if (typeof source === "string" && /\{\{.*?\}\}/.test(source)) {
@@ -308,6 +317,7 @@ async function compileExpression(expression, ctx) {
308
317
  __name(compileExpression, "compileExpression");
309
318
  // Annotate the CommonJS export names for ESM import in node:
310
319
  0 && (module.exports = {
320
+ buildFlowModelResolveDescriptor,
311
321
  enqueueVariablesResolve,
312
322
  preprocessExpression,
313
323
  resolveCreateModelOptions,
@@ -210,6 +210,11 @@ async function collectContextParamsForTemplate(ctx, template) {
210
210
  input[key] = built;
211
211
  }
212
212
  }
213
+ const viewPaths = usage.view || [];
214
+ if (!input.view && viewPaths.some((path) => path === "record" || path.startsWith("record.") || path.startsWith("record["))) {
215
+ const recordRef = inferViewRecordRef(ctx);
216
+ if (recordRef) input.view = { record: recordRef };
217
+ }
213
218
  return (0, import_serverContextParams.buildServerContextParams)(ctx, input);
214
219
  }
215
220
  __name(collectContextParamsForTemplate, "collectContextParamsForTemplate");
@@ -28,6 +28,7 @@ interface PopupNodeResource {
28
28
  interface PopupNode {
29
29
  uid?: string;
30
30
  resource: PopupNodeResource;
31
+ sourceRecord?: unknown;
31
32
  parent?: PopupNode;
32
33
  }
33
34
  export declare function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode | undefined>;
@@ -33,6 +33,13 @@ __export(createViewMeta_exports, {
33
33
  });
34
34
  module.exports = __toCommonJS(createViewMeta_exports);
35
35
  var import_variablesParams = require("../utils/variablesParams");
36
+ function buildPopupSourceRecordRef(ref) {
37
+ if ((ref == null ? void 0 : ref.sourceId) == null || ref.sourceId === "" || typeof ref.associationName !== "string") return void 0;
38
+ const collection = ref.associationName.split(".")[0];
39
+ if (!collection) return void 0;
40
+ return { collection, dataSourceKey: ref.dataSourceKey || "main", filterByTk: ref.sourceId };
41
+ }
42
+ __name(buildPopupSourceRecordRef, "buildPopupSourceRecordRef");
36
43
  function isDefined(value) {
37
44
  return value !== void 0 && value !== null;
38
45
  }
@@ -306,9 +313,11 @@ function createPopupMeta(ctx, anchorView) {
306
313
  let level = 1;
307
314
  let parentRef = await getParentRecordRef(level, c);
308
315
  while (parentRef) {
309
- if (!cur.parent) cur.parent = {};
310
- cur.parent.record = parentRef;
311
- cur = cur.parent;
316
+ const parent = { record: parentRef };
317
+ const sourceRecord = buildPopupSourceRecordRef(parentRef);
318
+ if (sourceRecord) parent.sourceRecord = sourceRecord;
319
+ cur.parent = parent;
320
+ cur = parent;
312
321
  level += 1;
313
322
  parentRef = await getParentRecordRef(level, c);
314
323
  }
@@ -317,19 +326,12 @@ function createPopupMeta(ctx, anchorView) {
317
326
  (_b = (_a = c.logger) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(_a, { err }, "[FlowEngine] buildVariablesParams: build parent-chain failed");
318
327
  }
319
328
  try {
320
- const srcId = inputArgs == null ? void 0 : inputArgs.sourceId;
321
- const assoc = inputArgs == null ? void 0 : inputArgs.associationName;
322
- const dsKey = (inputArgs == null ? void 0 : inputArgs.dataSourceKey) || "main";
323
- if (srcId != null && srcId !== "" && assoc && typeof assoc === "string") {
324
- const parentCollectionName = String(assoc).split(".")[0];
325
- if (parentCollectionName) {
326
- params.sourceRecord = {
327
- collection: parentCollectionName,
328
- dataSourceKey: dsKey,
329
- filterByTk: srcId
330
- };
331
- }
332
- }
329
+ const sourceRecord = buildPopupSourceRecordRef({
330
+ sourceId: inputArgs == null ? void 0 : inputArgs.sourceId,
331
+ associationName: inputArgs == null ? void 0 : inputArgs.associationName,
332
+ dataSourceKey: inputArgs == null ? void 0 : inputArgs.dataSourceKey
333
+ });
334
+ if (sourceRecord) params.sourceRecord = sourceRecord;
333
335
  } catch (err) {
334
336
  (_d = (_c = c.logger) == null ? void 0 : _c.debug) == null ? void 0 : _d.call(_c, { err }, "[FlowEngine] buildVariablesParams: infer sourceRecord failed");
335
337
  }
@@ -426,10 +428,11 @@ function createPopupMeta(ctx, anchorView) {
426
428
  }
427
429
  __name(createPopupMeta, "createPopupMeta");
428
430
  async function buildPopupRuntime(ctx, view) {
429
- var _a;
431
+ var _a, _b, _c;
430
432
  const stack = getViewStack(view);
431
433
  const currentIndex = getAnchoredViewStackIndex(view, stack);
432
- const openerUids = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.openerUids;
434
+ const sourceRecord = (_b = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.parentItem) == null ? void 0 : _b.value;
435
+ const openerUids = (_c = view == null ? void 0 : view.inputArgs) == null ? void 0 : _c.openerUids;
433
436
  const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
434
437
  const hasStackPopup = currentIndex >= 1;
435
438
  const isPopup = hasStackPopup || hasOpener;
@@ -446,16 +449,17 @@ async function buildPopupRuntime(ctx, view) {
446
449
  associationName: args.associationName,
447
450
  filterByTk: args.filterByTk,
448
451
  sourceId: args.sourceId
449
- }
452
+ },
453
+ ...typeof sourceRecord !== "undefined" ? { sourceRecord } : {}
450
454
  };
451
455
  }
452
456
  const buildNode = /* @__PURE__ */ __name(async (idx) => {
453
- var _a2, _b, _c, _d, _e, _f;
457
+ var _a2, _b2, _c2, _d, _e, _f;
454
458
  if (idx < 0 || !((_a2 = stack[idx]) == null ? void 0 : _a2.viewUid)) return void 0;
455
459
  const viewUid = stack[idx].viewUid;
456
- let model = (_b = ctx.engine) == null ? void 0 : _b.getModel(viewUid, true);
460
+ let model = (_b2 = ctx.engine) == null ? void 0 : _b2.getModel(viewUid, true);
457
461
  if (!model) {
458
- model = await ((_c = ctx.engine) == null ? void 0 : _c.loadModel({ uid: viewUid }));
462
+ model = await ((_c2 = ctx.engine) == null ? void 0 : _c2.loadModel({ uid: viewUid }));
459
463
  }
460
464
  const p = ((_d = model == null ? void 0 : model.getStepParams) == null ? void 0 : _d.call(model, "popupSettings", "openView")) || {};
461
465
  const collectionName = p == null ? void 0 : p.collectionName;
@@ -475,16 +479,43 @@ async function buildPopupRuntime(ctx, view) {
475
479
  return node;
476
480
  }, "buildNode");
477
481
  const currentNode = await buildNode(currentIndex);
482
+ if (currentNode && typeof sourceRecord !== "undefined") {
483
+ currentNode.sourceRecord = sourceRecord;
484
+ }
478
485
  return currentNode;
479
486
  }
480
487
  __name(buildPopupRuntime, "buildPopupRuntime");
481
488
  function registerPopupVariable(ctx, view) {
482
489
  const POPUP_SERVER_PATH_RE = /^(?:record|sourceRecord)(?:\.|$)|^parent(?:\.parent)*(?:\.(?:record|sourceRecord))(?:\.|$)/;
490
+ const shouldResolveSourceRecordOnServer = /* @__PURE__ */ __name((path) => {
491
+ var _a, _b;
492
+ if (path !== "sourceRecord" && !path.startsWith("sourceRecord.")) return false;
493
+ const parentItem = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.parentItem;
494
+ if (typeof (parentItem == null ? void 0 : parentItem.value) === "undefined") return true;
495
+ const sourcePath = path === "sourceRecord" ? "" : path.slice("sourceRecord.".length);
496
+ if (!sourcePath) return false;
497
+ const parentItemResolver = (_b = view == null ? void 0 : view.inputArgs) == null ? void 0 : _b.parentItemResolver;
498
+ if (typeof parentItemResolver === "function") {
499
+ return parentItemResolver(`value.${sourcePath}`);
500
+ }
501
+ const segments = sourcePath.split(".").filter(Boolean);
502
+ let current = parentItem.value;
503
+ for (const segment of segments) {
504
+ if (current === null || typeof current !== "object" || !(segment in current)) {
505
+ return true;
506
+ }
507
+ current = current[segment];
508
+ }
509
+ return false;
510
+ }, "shouldResolveSourceRecordOnServer");
483
511
  ctx.defineProperty("popup", {
484
512
  get: /* @__PURE__ */ __name(async () => buildPopupRuntime(ctx, view), "get"),
485
513
  meta: createPopupMeta(ctx, view),
486
514
  resolveOnServer: /* @__PURE__ */ __name((p) => {
487
515
  try {
516
+ if (p === "sourceRecord" || p.startsWith("sourceRecord.")) {
517
+ return shouldResolveSourceRecordOnServer(p);
518
+ }
488
519
  return !!p && POPUP_SERVER_PATH_RE.test(p);
489
520
  } catch (_) {
490
521
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.2.0-beta.15",
3
+ "version": "2.2.0-beta.17",
4
4
  "private": false,
5
5
  "description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
6
6
  "main": "lib/index.js",
@@ -8,8 +8,8 @@
8
8
  "dependencies": {
9
9
  "@formily/antd-v5": "1.x",
10
10
  "@formily/reactive": "2.x",
11
- "@nocobase/sdk": "2.2.0-beta.15",
12
- "@nocobase/shared": "2.2.0-beta.15",
11
+ "@nocobase/sdk": "2.2.0-beta.17",
12
+ "@nocobase/shared": "2.2.0-beta.17",
13
13
  "ahooks": "^3.7.2",
14
14
  "axios": "^1.7.0",
15
15
  "dayjs": "^1.11.9",
@@ -37,5 +37,5 @@
37
37
  ],
38
38
  "author": "NocoBase Team",
39
39
  "license": "Apache-2.0",
40
- "gitHead": "4c471f6b7d26adc412b2cc1d91264572296ca873"
40
+ "gitHead": "d7a69c4f47fe38b17a2ce1408258902cf64f6c83"
41
41
  }
@@ -11,7 +11,7 @@ import { describe, it, expect, vi } from 'vitest';
11
11
  import { FlowContext } from '../flowContext';
12
12
  import { FlowEngine } from '../flowEngine';
13
13
  import type { FlowView } from '../views/FlowView';
14
- import { buildPopupRuntime, createPopupMeta } from '../views/createViewMeta';
14
+ import { buildPopupRuntime, createPopupMeta, registerPopupVariable } from '../views/createViewMeta';
15
15
 
16
16
  describe('createPopupMeta - popup variables', () => {
17
17
  function makeCtx() {
@@ -167,6 +167,7 @@ describe('createPopupMeta - popup variables', () => {
167
167
  it('buildPopupRuntime anchors current popup even when the navigation stack already has a child popup', async () => {
168
168
  const { engine, ctx } = makeCtx();
169
169
  const parentView = makeNestedPopupView('parent-popup-uid', 13);
170
+ parentView.inputArgs.parentItem = { value: { id: 13, phone: '9999' } };
170
171
  mockNestedPopupModels(engine);
171
172
 
172
173
  const popup = await buildPopupRuntime(ctx, parentView);
@@ -179,6 +180,7 @@ describe('createPopupMeta - popup variables', () => {
179
180
  filterByTk: 13,
180
181
  sourceId: 13,
181
182
  });
183
+ expect(popup?.sourceRecord).toEqual({ id: 13, phone: '9999' });
182
184
  });
183
185
 
184
186
  it('buildVariablesParams(record) keeps the parent view record when a child popup is open', async () => {
@@ -314,4 +316,85 @@ describe('createPopupMeta - popup variables', () => {
314
316
  const props = typeof meta.properties === 'function' ? await (meta.properties as any)() : meta.properties || {};
315
317
  expect(props.record).toBeUndefined();
316
318
  });
319
+
320
+ it('uses the current parent item value for popup sourceRecord fields', async () => {
321
+ const { ctx } = makeCtx();
322
+ const parentItemResolver = vi.fn(() => false);
323
+ const anchorView: FlowView = {
324
+ type: 'dialog',
325
+ inputArgs: {
326
+ openerUids: ['opener-uid-1'],
327
+ viewUid: 'popup-uid',
328
+ dataSourceKey: 'main',
329
+ collectionName: 'roles',
330
+ associationName: 'users.roles',
331
+ sourceId: 1,
332
+ parentItem: { value: { id: 1, phone: '9999' } },
333
+ parentItemResolver,
334
+ },
335
+ Header: null,
336
+ Footer: null,
337
+ close: () => void 0,
338
+ update: () => void 0,
339
+ } as any;
340
+
341
+ registerPopupVariable(ctx, anchorView);
342
+
343
+ await expect(ctx.popup).resolves.toMatchObject({
344
+ sourceRecord: { id: 1, phone: '9999' },
345
+ });
346
+ expect(ctx.getPropertyOptions('popup')?.resolveOnServer?.('sourceRecord.phone')).toBe(false);
347
+ expect(parentItemResolver).toHaveBeenCalledWith('value.phone');
348
+ });
349
+
350
+ it('resolves a popup sourceRecord field from the current parent item without a server request', async () => {
351
+ const engine = new FlowEngine();
352
+ const ctx = engine.context;
353
+ const anchorView: FlowView = {
354
+ type: 'dialog',
355
+ inputArgs: {
356
+ openerUids: ['opener-uid-1'],
357
+ viewUid: 'popup-uid',
358
+ dataSourceKey: 'main',
359
+ collectionName: 'roles',
360
+ associationName: 'users.roles',
361
+ sourceId: 1,
362
+ parentItem: { value: { id: 1, phone: '9999' } },
363
+ parentItemResolver: () => false,
364
+ },
365
+ Header: null,
366
+ Footer: null,
367
+ close: () => void 0,
368
+ update: () => void 0,
369
+ } as any;
370
+
371
+ registerPopupVariable(ctx, anchorView);
372
+
373
+ await expect(ctx.resolveJsonTemplate('{{ ctx.popup.sourceRecord.phone }}')).resolves.toBe('9999');
374
+ });
375
+
376
+ it('keeps server resolution for popup sourceRecord association subpaths', async () => {
377
+ const { ctx } = makeCtx();
378
+ const anchorView: FlowView = {
379
+ type: 'dialog',
380
+ inputArgs: {
381
+ openerUids: ['opener-uid-1'],
382
+ viewUid: 'popup-uid',
383
+ dataSourceKey: 'main',
384
+ collectionName: 'roles',
385
+ associationName: 'users.roles',
386
+ sourceId: 1,
387
+ parentItem: { value: { id: 1, departmentId: 2 } },
388
+ parentItemResolver: (path: string) => path === 'value.department.title',
389
+ },
390
+ Header: null,
391
+ Footer: null,
392
+ close: () => void 0,
393
+ update: () => void 0,
394
+ } as any;
395
+
396
+ registerPopupVariable(ctx, anchorView);
397
+
398
+ expect(ctx.getPropertyOptions('popup')?.resolveOnServer?.('sourceRecord.department.title')).toBe(true);
399
+ });
317
400
  });