@nocobase/flow-engine 3.0.0-alpha.7 → 3.0.0-alpha.9

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.
@@ -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;
@@ -27,6 +27,9 @@ import type { RecordRef } from './utils/serverContextParams';
27
27
  import { FlowView, FlowViewer } from './views/FlowView';
28
28
  import { type RunJSVersion } from './runjs-context/registry';
29
29
  type Getter<T = any> = (ctx: FlowContext) => T | Promise<T>;
30
+ export type ResolveJsonTemplateOptions = {
31
+ contractModelUid?: string | number | null;
32
+ };
30
33
  export type FlowContextDocRef = string | {
31
34
  url: string;
32
35
  title?: string;
@@ -77,6 +80,7 @@ export interface MetaTreeNode {
77
80
  hidden?: boolean | (() => boolean);
78
81
  disabled?: boolean | (() => boolean);
79
82
  disabledReason?: string | (() => string | undefined);
83
+ selectable?: boolean;
80
84
  children?: MetaTreeNode[] | (() => Promise<MetaTreeNode[]>);
81
85
  }
82
86
  export interface PropertyMeta {
@@ -343,7 +347,7 @@ declare class BaseFlowEngineContext extends FlowContext {
343
347
  * @deprecated use `resolveJsonTemplate` instead
344
348
  */
345
349
  renderJson: (template: JSONValue) => Promise<any>;
346
- resolveJsonTemplate: (template: JSONValue) => Promise<any>;
350
+ resolveJsonTemplate: (template: JSONValue, options?: ResolveJsonTemplateOptions) => Promise<any>;
347
351
  getVar: (path: string) => Promise<any>;
348
352
  request: (options: RequestOptions) => Promise<any>;
349
353
  runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
@@ -2383,7 +2383,7 @@ 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) {
2386
+ const resolveJsonTemplate = /* @__PURE__ */ __name(async function(template, options) {
2387
2387
  var _a, _b, _c;
2388
2388
  const used = (0, import_utils.extractUsedVariablePaths)(template);
2389
2389
  const usedVarNames = Object.keys(used || {});
@@ -2516,7 +2516,12 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2516
2516
  }
2517
2517
  if (this.api) {
2518
2518
  try {
2519
+ const contractRd = (0, import_params_resolvers.buildFlowModelResolveDescriptor)(
2520
+ this,
2521
+ options == null ? void 0 : options.contractModelUid
2522
+ );
2519
2523
  serverResolved = await (0, import_params_resolvers.enqueueVariablesResolve)(this, {
2524
+ ...contractRd ? { contractRd } : {},
2520
2525
  rd: (0, import_params_resolvers.buildFlowModelResolveDescriptor)(this, (_a = this.model) == null ? void 0 : _a.uid),
2521
2526
  template,
2522
2527
  contextParams: autoContextParams || {}
@@ -2528,7 +2533,8 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2528
2533
  }
2529
2534
  }
2530
2535
  return (0, import_utils.resolveExpressions)(serverResolved, this);
2531
- });
2536
+ }, "resolveJsonTemplate");
2537
+ this.defineMethod("resolveJsonTemplate", resolveJsonTemplate);
2532
2538
  this.defineMethod(
2533
2539
  "getVar",
2534
2540
  async function(varPath) {
@@ -3505,8 +3511,13 @@ function __mergeRunJSDocMeta(base, patch) {
3505
3511
  __name(__mergeRunJSDocMeta, "__mergeRunJSDocMeta");
3506
3512
  const _FlowRunJSContext = class _FlowRunJSContext extends FlowContext {
3507
3513
  constructor(delegate) {
3514
+ var _a, _b;
3508
3515
  super();
3509
3516
  this.addDelegate(delegate);
3517
+ const submit = (_b = (_a = delegate.blockModel) == null ? void 0 : _a.submitFromRunJs) == null ? void 0 : _b.bind(delegate.blockModel);
3518
+ if (delegate.form && submit) {
3519
+ this.defineProperty("form", { value: { ...delegate.form, submit } });
3520
+ }
3510
3521
  this.defineProperty("React", { value: import_react.default });
3511
3522
  this.defineProperty("antd", { value: antd });
3512
3523
  this.defineProperty("dayjs", {
@@ -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
  });
@@ -21,7 +21,7 @@ export { extractPropertyPath, formatPathToVariable, isVariableExpression } from
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';
@@ -73,6 +73,7 @@ __export(utils_exports, {
73
73
  isVariableExpression: () => import_context.isVariableExpression,
74
74
  normalizeRunJSValue: () => import_runjsValue.normalizeRunJSValue,
75
75
  parseCtxDateExpression: () => import_dateVariable.parseCtxDateExpression,
76
+ parseCtxDateExpressionConfig: () => import_dateVariable.parseCtxDateExpressionConfig,
76
77
  parsePathnameToViewParams: () => import_parsePathnameToViewParams.parsePathnameToViewParams,
77
78
  prepareRunJsCode: () => import_runjsTemplateCompat.prepareRunJsCode,
78
79
  preprocessRunJsTemplates: () => import_runjsTemplateCompat.preprocessRunJsTemplates,
@@ -87,6 +88,7 @@ __export(utils_exports, {
87
88
  resolveStepDisabledInSettings: () => import_schema_utils.resolveStepDisabledInSettings,
88
89
  resolveStepUiSchema: () => import_schema_utils.resolveStepUiSchema,
89
90
  resolveUiMode: () => import_schema_utils.resolveUiMode,
91
+ serializeCtxDateExpressionConfig: () => import_dateVariable.serializeCtxDateExpressionConfig,
90
92
  serializeCtxDateValue: () => import_dateVariable.serializeCtxDateValue,
91
93
  setAutoFlowError: () => import_autoFlowError.setAutoFlowError,
92
94
  setupRuntimeContextSteps: () => import_setupRuntimeContextSteps.setupRuntimeContextSteps,
@@ -168,6 +170,7 @@ var import_randomId = require("./randomId");
168
170
  isVariableExpression,
169
171
  normalizeRunJSValue,
170
172
  parseCtxDateExpression,
173
+ parseCtxDateExpressionConfig,
171
174
  parsePathnameToViewParams,
172
175
  prepareRunJsCode,
173
176
  preprocessRunJsTemplates,
@@ -182,6 +185,7 @@ var import_randomId = require("./randomId");
182
185
  resolveStepDisabledInSettings,
183
186
  resolveStepUiSchema,
184
187
  resolveUiMode,
188
+ serializeCtxDateExpressionConfig,
185
189
  serializeCtxDateValue,
186
190
  setAutoFlowError,
187
191
  setupRuntimeContextSteps,
@@ -27,6 +27,7 @@ export type JSONValue = string | {
27
27
  [key: string]: JSONValue;
28
28
  } | JSONValue[];
29
29
  type BatchPayload = {
30
+ contractRd?: string;
30
31
  rd?: string;
31
32
  template: JSONValue;
32
33
  contextParams?: ServerContextParams | undefined;
@@ -146,6 +146,7 @@ function enqueueVariablesResolve(ctx, payload) {
146
146
  try {
147
147
  const batch = items.map((it) => ({
148
148
  id: it.id,
149
+ contractRd: it.payload.contractRd,
149
150
  rd: it.payload.rd,
150
151
  template: it.payload.template,
151
152
  contextParams: it.payload.contextParams || {}
@@ -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>;
@@ -428,10 +428,11 @@ function createPopupMeta(ctx, anchorView) {
428
428
  }
429
429
  __name(createPopupMeta, "createPopupMeta");
430
430
  async function buildPopupRuntime(ctx, view) {
431
- var _a;
431
+ var _a, _b, _c;
432
432
  const stack = getViewStack(view);
433
433
  const currentIndex = getAnchoredViewStackIndex(view, stack);
434
- 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;
435
436
  const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
436
437
  const hasStackPopup = currentIndex >= 1;
437
438
  const isPopup = hasStackPopup || hasOpener;
@@ -448,16 +449,17 @@ async function buildPopupRuntime(ctx, view) {
448
449
  associationName: args.associationName,
449
450
  filterByTk: args.filterByTk,
450
451
  sourceId: args.sourceId
451
- }
452
+ },
453
+ ...typeof sourceRecord !== "undefined" ? { sourceRecord } : {}
452
454
  };
453
455
  }
454
456
  const buildNode = /* @__PURE__ */ __name(async (idx) => {
455
- var _a2, _b, _c, _d, _e, _f;
457
+ var _a2, _b2, _c2, _d, _e, _f;
456
458
  if (idx < 0 || !((_a2 = stack[idx]) == null ? void 0 : _a2.viewUid)) return void 0;
457
459
  const viewUid = stack[idx].viewUid;
458
- 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);
459
461
  if (!model) {
460
- 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 }));
461
463
  }
462
464
  const p = ((_d = model == null ? void 0 : model.getStepParams) == null ? void 0 : _d.call(model, "popupSettings", "openView")) || {};
463
465
  const collectionName = p == null ? void 0 : p.collectionName;
@@ -477,16 +479,43 @@ async function buildPopupRuntime(ctx, view) {
477
479
  return node;
478
480
  }, "buildNode");
479
481
  const currentNode = await buildNode(currentIndex);
482
+ if (currentNode && typeof sourceRecord !== "undefined") {
483
+ currentNode.sourceRecord = sourceRecord;
484
+ }
480
485
  return currentNode;
481
486
  }
482
487
  __name(buildPopupRuntime, "buildPopupRuntime");
483
488
  function registerPopupVariable(ctx, view) {
484
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");
485
511
  ctx.defineProperty("popup", {
486
512
  get: /* @__PURE__ */ __name(async () => buildPopupRuntime(ctx, view), "get"),
487
513
  meta: createPopupMeta(ctx, view),
488
514
  resolveOnServer: /* @__PURE__ */ __name((p) => {
489
515
  try {
516
+ if (p === "sourceRecord" || p.startsWith("sourceRecord.")) {
517
+ return shouldResolveSourceRecordOnServer(p);
518
+ }
490
519
  return !!p && POPUP_SERVER_PATH_RE.test(p);
491
520
  } catch (_) {
492
521
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "3.0.0-alpha.7",
3
+ "version": "3.0.0-alpha.9",
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": "3.0.0-alpha.7",
12
- "@nocobase/shared": "3.0.0-alpha.7",
11
+ "@nocobase/sdk": "3.0.0-alpha.9",
12
+ "@nocobase/shared": "3.0.0-alpha.9",
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": "19cd28c710f3b3c614ebe2fd7d5a415d527ee559"
40
+ "gitHead": "82aaad40c5be1120839a11301a48e7fe690f48c3"
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
  });