@nocobase/plugin-flow-engine 2.0.0-alpha.3 → 2.0.0-alpha.31

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.
@@ -8,14 +8,14 @@
8
8
  */
9
9
 
10
10
  module.exports = {
11
- "@nocobase/client": "2.0.0-alpha.3",
11
+ "@nocobase/client": "2.0.0-alpha.31",
12
12
  "lodash": "4.17.21",
13
- "@nocobase/database": "2.0.0-alpha.3",
14
- "@nocobase/data-source-manager": "2.0.0-alpha.3",
15
- "@nocobase/resourcer": "2.0.0-alpha.3",
16
- "@nocobase/server": "2.0.0-alpha.3",
17
- "@nocobase/cache": "2.0.0-alpha.3",
18
- "@nocobase/utils": "2.0.0-alpha.3",
19
- "@nocobase/plugin-localization": "2.0.0-alpha.3",
20
- "@nocobase/actions": "2.0.0-alpha.3"
13
+ "@nocobase/database": "2.0.0-alpha.31",
14
+ "@nocobase/data-source-manager": "2.0.0-alpha.31",
15
+ "@nocobase/resourcer": "2.0.0-alpha.31",
16
+ "@nocobase/server": "2.0.0-alpha.31",
17
+ "@nocobase/utils": "2.0.0-alpha.31",
18
+ "@nocobase/cache": "2.0.0-alpha.31",
19
+ "@nocobase/plugin-localization": "2.0.0-alpha.31",
20
+ "@nocobase/actions": "2.0.0-alpha.31"
21
21
  };
@@ -23,6 +23,7 @@
23
23
  "UID copied to clipboard": "UID copied to clipboard",
24
24
  "Copy failed": "Copy failed",
25
25
  "Copy failed, please copy [{{uid}}] manually.": "Copy failed, please copy [{{uid}}] manually.",
26
+ "Copy failed under HTTP. Clipboard API is unavailable on non-HTTPS pages. Please copy [{{uid}}] manually.": "Copy failed under HTTP. Clipboard API is unavailable on non-HTTPS pages. Please copy [{{uid}}] manually.",
26
27
  "Confirm delete": "Confirm delete",
27
28
  "Are you sure you want to delete this item? This action cannot be undone.": "Are you sure you want to delete this item? This action cannot be undone.",
28
29
  "Delete operation failed": "Delete operation failed",
@@ -57,5 +58,6 @@
57
58
  "Name": "Name",
58
59
  "Model with ID {{uid}} not found": "Model with ID {{uid}} not found",
59
60
  "Common actions": "Common actions",
60
- "This variable is not available": "This variable is not available"
61
- }
61
+ "This variable is not available": "This variable is not available",
62
+ "Copy popup UID": "Copy popup UID"
63
+ }
@@ -32,6 +32,7 @@ export declare const locales: {
32
32
  "UID copied to clipboard": string;
33
33
  "Copy failed": string;
34
34
  "Copy failed, please copy [{{uid}}] manually.": string;
35
+ "Copy failed under HTTP. Clipboard API is unavailable on non-HTTPS pages. Please copy [{{uid}}] manually.": string;
35
36
  "Confirm delete": string;
36
37
  "Are you sure you want to delete this item? This action cannot be undone.": string;
37
38
  "Delete operation failed": string;
@@ -67,6 +68,7 @@ export declare const locales: {
67
68
  "Model with ID {{uid}} not found": string;
68
69
  "Common actions": string;
69
70
  "This variable is not available": string;
71
+ "Copy popup UID": string;
70
72
  };
71
73
  'zh-CN': {
72
74
  "Invalid model provided": string;
@@ -93,6 +95,7 @@ export declare const locales: {
93
95
  "UID copied to clipboard": string;
94
96
  "Copy failed": string;
95
97
  "Copy failed, please copy [{{uid}}] manually.": string;
98
+ "Copy failed under HTTP. Clipboard API is unavailable on non-HTTPS pages. Please copy [{{uid}}] manually.": string;
96
99
  "Confirm delete": string;
97
100
  "Are you sure you want to delete this item? This action cannot be undone.": string;
98
101
  "Delete operation failed": string;
@@ -128,6 +131,7 @@ export declare const locales: {
128
131
  "Model with ID {{uid}} not found": string;
129
132
  "Common actions": string;
130
133
  "This variable is not available": string;
134
+ "Copy popup UID": string;
131
135
  };
132
136
  };
133
137
  /**
@@ -23,6 +23,7 @@
23
23
  "UID copied to clipboard": "UID 已复制到剪贴板",
24
24
  "Copy failed": "复制失败",
25
25
  "Copy failed, please copy [{{uid}}] manually.": "复制失败,请手动复制 [{{uid}}]。",
26
+ "Copy failed under HTTP. Clipboard API is unavailable on non-HTTPS pages. Please copy [{{uid}}] manually.": "HTTP 环境下复制失败。非 HTTPS 页面不支持剪贴板 API,请手动复制 [{{uid}}]。",
26
27
  "Confirm delete": "确认删除",
27
28
  "Are you sure you want to delete this item? This action cannot be undone.": "确定要删除此项吗?此操作不可撤销。",
28
29
  "Delete operation failed": "删除操作失败",
@@ -57,5 +58,6 @@
57
58
  "Name": "名称",
58
59
  "Model with ID {{uid}} not found": "未找到 ID 为 {{uid}} 的模型",
59
60
  "Common actions": "通用操作",
60
- "This variable is not available": "此变量不可用"
61
- }
61
+ "This variable is not available": "此变量不可用",
62
+ "Copy popup UID": "复制弹窗 UID"
63
+ }
@@ -1 +1 @@
1
- {"name":"ses","version":"1.14.0","description":"Hardened JavaScript for Fearless Cooperation","keywords":["lockdown","harden","Compartment","assert","security","confinement","isolation","object capabilities","ocaps","secure execution","third-party code","prototype pollution","supply-chain attack","plugin"],"author":"Agoric","license":"Apache-2.0","homepage":"https://github.com/Agoric/SES-shim/tree/master/packages/ses#readme","repository":{"type":"git","url":"git+https://github.com/endojs/endo.git","directory":"packages/ses"},"bugs":{"url":"https://github.com/endojs/endo/issues"},"type":"module","main":"./dist/ses.cjs","module":"./index.js","unpkg":"./dist/ses.umd.js","types":"./types.d.ts","exports":{".":{"import":{"types":"./types.d.ts","xs":"./src-xs/index.js","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./lockdown":{"import":{"types":"./types.d.ts","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./hermes":{"require":{"types":"./dist/types.d.cts","default":"./dist/ses-hermes.cjs"}},"./tools.js":"./tools.js","./assert-shim.js":"./assert-shim.js","./lockdown-shim.js":{"xs":"./src-xs/lockdown-shim.js","default":"./lockdown-shim.js"},"./compartment-shim.js":{"xs":"./src-xs/compartment-shim.js","default":"./compartment-shim.js"},"./console-shim.js":"./console-shim.js","./package.json":"./package.json"},"scripts":{"build:vanilla":"node scripts/bundle.js","build:hermes":"node scripts/bundle.js hermes","build":"yarn build:vanilla && yarn build:hermes","clean":"rm -rf dist","cover":"c8 ava","demo":"python3 -m http.server","lint":"yarn lint:types && yarn lint:eslint","lint-fix":"eslint --fix .","lint:eslint":"eslint .","lint:types":"tsc","prepare":"npm run clean && npm run build","qt":"ava","test":"tsd && ava","test:hermes":"./scripts/hermes-test.sh","test:xs":"xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp","postpack":"git clean -fX \"*.d.ts*\" \"*.d.cts*\" \"*.d.mts*\" \"*.tsbuildinfo\""},"dependencies":{"@endo/cache-map":"^1.1.0","@endo/env-options":"^1.1.11","@endo/immutable-arraybuffer":"^1.1.2"},"devDependencies":{"@babel/generator":"^7.26.3","@babel/parser":"~7.26.2","@babel/traverse":"~7.25.9","@babel/types":"~7.26.0","@endo/compartment-mapper":"^1.6.3","@endo/module-source":"^1.3.3","@endo/test262-runner":"^0.1.48","@types/babel__traverse":"^7.20.5","ava":"^6.1.3","babel-eslint":"^10.1.0","c8":"^7.14.0","core-js":"^3.31.0","eslint":"^8.57.1","eslint-config-airbnb-base":"^15.0.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.31.0","hermes-engine-cli":"^0.12.0","prettier":"^3.5.3","terser":"^5.16.6","tsd":"^0.31.2","typescript":"~5.8.3"},"files":["./*.d.ts","./*.js","./*.map","LICENSE*","SECURITY*","dist","lib","src","tools"],"publishConfig":{"access":"public"},"eslintConfig":{"extends":["plugin:@endo/ses"]},"ava":{"files":["test/**/*.test.*"],"timeout":"2m"},"typeCoverage":{"atLeast":81.17},"gitHead":"9815aea9541f241389d2135c6097a7442bdffa17","_lastModified":"2025-09-30T15:48:13.247Z"}
1
+ {"name":"ses","version":"1.14.0","description":"Hardened JavaScript for Fearless Cooperation","keywords":["lockdown","harden","Compartment","assert","security","confinement","isolation","object capabilities","ocaps","secure execution","third-party code","prototype pollution","supply-chain attack","plugin"],"author":"Agoric","license":"Apache-2.0","homepage":"https://github.com/Agoric/SES-shim/tree/master/packages/ses#readme","repository":{"type":"git","url":"git+https://github.com/endojs/endo.git","directory":"packages/ses"},"bugs":{"url":"https://github.com/endojs/endo/issues"},"type":"module","main":"./dist/ses.cjs","module":"./index.js","unpkg":"./dist/ses.umd.js","types":"./types.d.ts","exports":{".":{"import":{"types":"./types.d.ts","xs":"./src-xs/index.js","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./lockdown":{"import":{"types":"./types.d.ts","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./hermes":{"require":{"types":"./dist/types.d.cts","default":"./dist/ses-hermes.cjs"}},"./tools.js":"./tools.js","./assert-shim.js":"./assert-shim.js","./lockdown-shim.js":{"xs":"./src-xs/lockdown-shim.js","default":"./lockdown-shim.js"},"./compartment-shim.js":{"xs":"./src-xs/compartment-shim.js","default":"./compartment-shim.js"},"./console-shim.js":"./console-shim.js","./package.json":"./package.json"},"scripts":{"build:vanilla":"node scripts/bundle.js","build:hermes":"node scripts/bundle.js hermes","build":"yarn build:vanilla && yarn build:hermes","clean":"rm -rf dist","cover":"c8 ava","demo":"python3 -m http.server","lint":"yarn lint:types && yarn lint:eslint","lint-fix":"eslint --fix .","lint:eslint":"eslint .","lint:types":"tsc","prepare":"npm run clean && npm run build","qt":"ava","test":"tsd && ava","test:hermes":"./scripts/hermes-test.sh","test:xs":"xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp","postpack":"git clean -fX \"*.d.ts*\" \"*.d.cts*\" \"*.d.mts*\" \"*.tsbuildinfo\""},"dependencies":{"@endo/cache-map":"^1.1.0","@endo/env-options":"^1.1.11","@endo/immutable-arraybuffer":"^1.1.2"},"devDependencies":{"@babel/generator":"^7.26.3","@babel/parser":"~7.26.2","@babel/traverse":"~7.25.9","@babel/types":"~7.26.0","@endo/compartment-mapper":"^1.6.3","@endo/module-source":"^1.3.3","@endo/test262-runner":"^0.1.48","@types/babel__traverse":"^7.20.5","ava":"^6.1.3","babel-eslint":"^10.1.0","c8":"^7.14.0","core-js":"^3.31.0","eslint":"^8.57.1","eslint-config-airbnb-base":"^15.0.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.31.0","hermes-engine-cli":"^0.12.0","prettier":"^3.5.3","terser":"^5.16.6","tsd":"^0.31.2","typescript":"~5.8.3"},"files":["./*.d.ts","./*.js","./*.map","LICENSE*","SECURITY*","dist","lib","src","tools"],"publishConfig":{"access":"public"},"eslintConfig":{"extends":["plugin:@endo/ses"]},"ava":{"files":["test/**/*.test.*"],"timeout":"2m"},"typeCoverage":{"atLeast":81.17},"gitHead":"9815aea9541f241389d2135c6097a7442bdffa17","_lastModified":"2025-11-03T01:52:15.357Z"}
@@ -12,11 +12,6 @@ export declare class PluginFlowEngineServer extends PluginUISchemaStorageServer
12
12
  afterAdd(): Promise<void>;
13
13
  beforeLoad(): Promise<void>;
14
14
  getDatabaseByDataSourceKey(dataSourceKey?: string): import("@nocobase/database").default;
15
- private prefetchRecordsForResolve;
16
- transformSQL(template: string): {
17
- sql: string;
18
- bind: {};
19
- };
20
15
  load(): Promise<void>;
21
16
  install(): Promise<void>;
22
17
  afterEnable(): Promise<void>;
@@ -40,10 +40,12 @@ __export(plugin_exports, {
40
40
  default: () => plugin_default
41
41
  });
42
42
  module.exports = __toCommonJS(plugin_exports);
43
+ var import_utils = require("@nocobase/utils");
43
44
  var import_server2 = __toESM(require("./server"));
44
45
  var import_contexts = require("./template/contexts");
45
46
  var import_resolver = require("./template/resolver");
46
47
  var import_registry = require("./variables/registry");
48
+ var import_utils2 = require("./variables/utils");
47
49
  class PluginFlowEngineServer extends import_server2.default {
48
50
  globalContext;
49
51
  async afterAdd() {
@@ -59,102 +61,12 @@ class PluginFlowEngineServer extends import_server2.default {
59
61
  }
60
62
  return cm.db;
61
63
  }
62
- // 预取:构建“同记录”的字段/关联并集,一次查询写入 ctx.state.__varResolveBatchCache,供后续解析复用
63
- async prefetchRecordsForResolve(koaCtx, items) {
64
- var _a;
65
- try {
66
- const groupMap = /* @__PURE__ */ new Map();
67
- const ensureGroup = (dataSourceKey, collection, filterByTk) => {
68
- const groupKey = JSON.stringify({ ds: dataSourceKey, collection, tk: filterByTk });
69
- let group = groupMap.get(groupKey);
70
- if (!group) {
71
- group = { dataSourceKey, collection, filterByTk, fields: /* @__PURE__ */ new Set(), appends: /* @__PURE__ */ new Set() };
72
- groupMap.set(groupKey, group);
73
- }
74
- return group;
75
- };
76
- const normalizeNestedSeg = (segment) => /^\d+$/.test(segment) ? `[${segment}]` : segment;
77
- const toFirstSeg = (path) => {
78
- const m = path.match(/^([^.[]+|\[[^\]]+\])([\s\S]*)$/);
79
- const segment = m ? m[1] : "";
80
- const rest = m ? m[2] : "";
81
- return { segment, hasDeep: rest.includes(".") || rest.includes("[") || rest.length > 0 };
82
- };
83
- for (const it of items) {
84
- const template = (it == null ? void 0 : it.template) ?? {};
85
- const contextParams = (it == null ? void 0 : it.contextParams) || {};
86
- const usage = import_registry.variables.extractUsage(template);
87
- for (const [cpKey, recordParams] of Object.entries(contextParams)) {
88
- const parts = String(cpKey).split(".");
89
- const varName = parts[0];
90
- const nestedSeg = parts.slice(1).join(".");
91
- const paths = (usage == null ? void 0 : usage[varName]) || [];
92
- if (!paths.length) continue;
93
- const segNorm = nestedSeg ? normalizeNestedSeg(nestedSeg) : "";
94
- const remainders = [];
95
- for (const p of paths) {
96
- if (!segNorm) remainders.push(p);
97
- else if (p === segNorm) remainders.push("");
98
- else if (p.startsWith(`${segNorm}.`) || p.startsWith(`${segNorm}[`))
99
- remainders.push(p.slice(segNorm.length + 1));
100
- }
101
- if (!remainders.length) continue;
102
- const dataSourceKey = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
103
- const collection = recordParams == null ? void 0 : recordParams.collection;
104
- const filterByTk = recordParams == null ? void 0 : recordParams.filterByTk;
105
- if (!collection || typeof filterByTk === "undefined") continue;
106
- const group = ensureGroup(dataSourceKey, collection, filterByTk);
107
- for (const r of remainders) {
108
- const { segment, hasDeep } = toFirstSeg(r);
109
- if (!segment) continue;
110
- const key = segment.replace(/^\[(.+)\]$/, "$1");
111
- if (hasDeep) group.appends.add(key);
112
- else group.fields.add(key);
113
- }
114
- }
115
- }
116
- if (!groupMap.size) return;
117
- const stateObj = koaCtx.state;
118
- if (stateObj && !stateObj["__varResolveBatchCache"]) {
119
- stateObj["__varResolveBatchCache"] = /* @__PURE__ */ new Map();
120
- }
121
- const cache = (_a = koaCtx.state) == null ? void 0 : _a["__varResolveBatchCache"];
122
- for (const { dataSourceKey, collection, filterByTk, fields, appends } of groupMap.values()) {
123
- try {
124
- const dataSource = this.app.dataSourceManager.get(dataSourceKey);
125
- const cm = dataSource.collectionManager;
126
- if (!(cm == null ? void 0 : cm.db)) continue;
127
- const repo = cm.db.getRepository(collection);
128
- const fld = fields.size ? Array.from(fields) : void 0;
129
- const app = appends.size ? Array.from(appends) : void 0;
130
- const rec = await repo.findOne({ filterByTk, fields: fld, appends: app });
131
- const json = rec ? rec.toJSON() : void 0;
132
- if (cache) {
133
- const key = JSON.stringify({ ds: dataSourceKey, c: collection, tk: filterByTk, f: fld, a: app });
134
- cache.set(key, json);
135
- }
136
- } catch {
137
- }
138
- }
139
- } catch {
140
- }
141
- }
142
- transformSQL(template) {
143
- let index = 1;
144
- const bind = {};
145
- const sql = template.replace(/{{\s*([^}]+)\s*}}/g, (_, expr) => {
146
- const key = `__var${index}`;
147
- bind[key] = `{{${expr.trim()}}}`;
148
- index++;
149
- return `$${key}`;
150
- });
151
- return { sql, bind };
152
- }
153
64
  async load() {
154
65
  var _a, _b;
155
66
  await super.load();
156
67
  this.globalContext = new import_contexts.GlobalContext((_b = (_a = this.app.environment) == null ? void 0 : _a.getVariables) == null ? void 0 : _b.call(_a));
157
68
  this.app.acl.allow("flowSql", "runById", "loggedIn");
69
+ this.app.acl.allow("flowSql", "getBind", "loggedIn");
158
70
  this.app.acl.allow("variables", "resolve", "loggedIn");
159
71
  this.app.acl.allow("fieldAssignments", "apply", "loggedIn");
160
72
  this.app.resourceManager.define({
@@ -167,7 +79,7 @@ class PluginFlowEngineServer extends import_server2.default {
167
79
  const values = typeof (raw == null ? void 0 : raw.values) !== "undefined" ? raw.values : raw;
168
80
  if (Array.isArray(values == null ? void 0 : values.batch)) {
169
81
  const batchItems = values.batch;
170
- await this.prefetchRecordsForResolve(
82
+ await (0, import_utils2.prefetchRecordsForResolve)(
171
83
  ctx,
172
84
  batchItems.map((it) => ({
173
85
  template: it.template,
@@ -196,7 +108,7 @@ class PluginFlowEngineServer extends import_server2.default {
196
108
  }
197
109
  const template = values.template;
198
110
  const contextParams = (values == null ? void 0 : values.contextParams) || {};
199
- await this.prefetchRecordsForResolve(ctx, [{ template, contextParams }]);
111
+ await (0, import_utils2.prefetchRecordsForResolve)(ctx, [{ template, contextParams }]);
200
112
  const requestCtx = new import_contexts.HttpRequestContext(ctx);
201
113
  requestCtx.delegate(this.globalContext);
202
114
  await import_registry.variables.attachUsedVariables(requestCtx, ctx, template, contextParams);
@@ -212,14 +124,15 @@ class PluginFlowEngineServer extends import_server2.default {
212
124
  });
213
125
  this.app.resourceManager.registerActionHandlers({
214
126
  "flowSql:runById": async (ctx, next) => {
215
- const { uid, type, filter, bind, dataSourceKey = "main" } = ctx.action.params.values;
127
+ const { uid, type, filter, bind, liquidContext, dataSourceKey = "main" } = ctx.action.params.values;
216
128
  const r = this.db.getRepository("flowSql");
217
129
  const record = await r.findOne({
218
130
  filter: { uid }
219
131
  });
220
132
  const db = this.getDatabaseByDataSourceKey(record.dataSourceKey || dataSourceKey);
221
- const result = this.transformSQL(record.sql);
222
- ctx.body = await db.runSQL(result.sql, {
133
+ const result = await (0, import_utils.transformSQL)(record.sql);
134
+ const sql = await (0, import_utils.parseLiquidContext)(result.sql, liquidContext);
135
+ ctx.body = await db.runSQL(sql, {
223
136
  type,
224
137
  filter,
225
138
  bind
@@ -232,8 +145,11 @@ class PluginFlowEngineServer extends import_server2.default {
232
145
  const record = await r.findOne({
233
146
  filter: { uid }
234
147
  });
235
- const { bind } = this.transformSQL(record.sql);
236
- ctx.body = bind;
148
+ const { bind, liquidContext } = await (0, import_utils.transformSQL)(record.sql);
149
+ ctx.body = {
150
+ bind,
151
+ liquidContext
152
+ };
237
153
  await next();
238
154
  },
239
155
  "flowSql:save": async (ctx, next) => {
@@ -70,7 +70,8 @@ export declare class FlowModelRepository extends Repository {
70
70
  }): Promise<void>;
71
71
  remove(uid: string, options?: Transactionable & removeParentOptions): Promise<void>;
72
72
  insertAdjacent(position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd', target: string, schema: any, options?: InsertAdjacentOptions): Promise<any>;
73
- duplicate(uid: string, options?: Transactionable): Promise<any>;
73
+ duplicate(modelUid: string, options?: Transactionable): Promise<any>;
74
+ private replaceStepParamsModelUids;
74
75
  insert(schema: any, options?: Transactionable): Promise<any>;
75
76
  insertNewSchema(schema: any, options?: Transactionable & {
76
77
  returnNode?: boolean;
@@ -426,13 +426,76 @@ const _FlowModelRepository = class _FlowModelRepository extends import_database.
426
426
  await this.clearXUidPathCache(result["uid"], transaction2);
427
427
  return result;
428
428
  }
429
- async duplicate(uid2, options) {
430
- const s = await this.getJsonSchema(uid2, { ...options, includeAsyncNode: true });
431
- if (!(s == null ? void 0 : s["uid"])) {
429
+ async duplicate(modelUid, options) {
430
+ const nodes = await this.findNodesById(modelUid, { ...options, includeAsyncNode: true });
431
+ if (!(nodes == null ? void 0 : nodes.length)) {
432
432
  return null;
433
433
  }
434
- this.regenerateUid(s);
435
- return this.insert(s, options);
434
+ const uidMap = {};
435
+ for (const n of nodes) {
436
+ uidMap[n["uid"]] = (0, import_utils.uid)();
437
+ }
438
+ const sorted = [...nodes].sort((a, b) => {
439
+ if (a.depth !== b.depth) return a.depth - b.depth;
440
+ const ap = a.parent || "";
441
+ const bp = b.parent || "";
442
+ if (ap !== bp) return ap < bp ? -1 : 1;
443
+ const at = a.type || "";
444
+ const bt = b.type || "";
445
+ if (at !== bt) return at < bt ? -1 : 1;
446
+ const as = a.sort ?? 0;
447
+ const bs = b.sort ?? 0;
448
+ return as - bs;
449
+ });
450
+ for (const n of sorted) {
451
+ const oldUid = n["uid"];
452
+ const newUid = uidMap[oldUid];
453
+ const oldParentUid = n["parent"];
454
+ const newParentUid = uidMap[oldParentUid] ?? null;
455
+ const optionsObj = this.replaceStepParamsModelUids(
456
+ import_lodash.default.isPlainObject(n.options) ? n.options : JSON.parse(n.options),
457
+ uidMap
458
+ );
459
+ if (newParentUid) {
460
+ optionsObj.parent = newParentUid;
461
+ optionsObj.parentId = newParentUid;
462
+ }
463
+ const schemaNode = {
464
+ uid: newUid,
465
+ ["x-async"]: !!n.async,
466
+ ...optionsObj
467
+ };
468
+ if (newParentUid) {
469
+ schemaNode.childOptions = {
470
+ parentUid: newParentUid,
471
+ type: n.type,
472
+ position: "last"
473
+ };
474
+ }
475
+ await this.insertSingleNode(schemaNode, { transaction: options == null ? void 0 : options.transaction });
476
+ }
477
+ return this.findModelById(uidMap[modelUid], { ...options });
478
+ }
479
+ replaceStepParamsModelUids(options, uidMap) {
480
+ const opts = options && typeof options === "object" ? options : {};
481
+ const replaceUidString = (v) => typeof v === "string" && uidMap[v] ? uidMap[v] : v;
482
+ const replaceInPlace = (val) => {
483
+ if (Array.isArray(val)) {
484
+ for (let i = 0; i < val.length; i++) {
485
+ val[i] = replaceInPlace(val[i]);
486
+ }
487
+ return val;
488
+ }
489
+ if (val && typeof val === "object") {
490
+ for (const k of Object.keys(val)) {
491
+ val[k] = replaceInPlace(val[k]);
492
+ }
493
+ return val;
494
+ }
495
+ return replaceUidString(val);
496
+ };
497
+ if (opts.stepParams) opts.stepParams = replaceInPlace(opts.stepParams);
498
+ return opts;
436
499
  }
437
500
  async insert(schema, options) {
438
501
  const nodes = _FlowModelRepository.schemaToSingleNodes(schema);
@@ -136,6 +136,13 @@ class PluginUISchemaStorageServer extends import_server.Plugin {
136
136
  }
137
137
  await next();
138
138
  },
139
+ duplicate: async (ctx, next) => {
140
+ const { uid: uid2 } = ctx.action.params;
141
+ const repository = ctx.db.getRepository("flowModels");
142
+ const duplicated = await repository.duplicate(uid2);
143
+ ctx.body = duplicated;
144
+ await next();
145
+ },
139
146
  move: async (ctx, next) => {
140
147
  const { sourceId, targetId, position } = ctx.action.params;
141
148
  const repository = ctx.db.getRepository("flowModels");
@@ -39,4 +39,14 @@ declare class VariableRegistry {
39
39
  attachUsedVariables(ctx: HttpRequestContext, koaCtx: ResourcerContext, template: JSONValue, contextParams: any): Promise<void>;
40
40
  }
41
41
  export declare const variables: VariableRegistry;
42
+ /** 仅测试使用:重置变量注册表为内置默认集 */
43
+ /**
44
+ * 从使用路径推断查询所需的 fields 与 appends。
45
+ * @param paths 使用到的子路径数组
46
+ * @param params 显式参数(仅用于兼容签名)
47
+ */
48
+ export declare function inferSelectsFromUsage(paths?: string[], _params?: unknown): {
49
+ generatedAppends?: string[];
50
+ generatedFields?: string[];
51
+ };
42
52
  export {};
@@ -36,12 +36,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
  var registry_exports = {};
38
38
  __export(registry_exports, {
39
+ inferSelectsFromUsage: () => inferSelectsFromUsage,
39
40
  variables: () => variables
40
41
  });
41
42
  module.exports = __toCommonJS(registry_exports);
42
43
  var import_lodash = __toESM(require("lodash"));
43
44
  var import_contexts = require("../template/contexts");
44
45
  var import_utils = require("@nocobase/utils");
46
+ var import_selects = require("./selects");
45
47
  class VariableRegistry {
46
48
  vars = /* @__PURE__ */ new Map();
47
49
  register(def) {
@@ -90,40 +92,51 @@ if (!g[GLOBAL_KEY]) {
90
92
  g[GLOBAL_KEY] = new VariableRegistry();
91
93
  }
92
94
  const variables = g[GLOBAL_KEY];
93
- function inferSelectsFromUsage(paths = [], params) {
95
+ function inferSelectsFromUsage(paths = [], _params) {
94
96
  if (!Array.isArray(paths) || paths.length === 0) {
95
97
  return { generatedAppends: void 0, generatedFields: void 0 };
96
98
  }
97
99
  const appendSet = /* @__PURE__ */ new Set();
98
100
  const fieldSet = /* @__PURE__ */ new Set();
101
+ const normalizePath = (raw) => {
102
+ if (!raw) return "";
103
+ let s = String(raw);
104
+ s = s.replace(/\[(?:\d+)\]/g, "");
105
+ s = s.replace(/\[(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')\]/g, (_m, g1, g2) => `.${g1 || g2}`);
106
+ s = s.replace(/\.\.+/g, ".");
107
+ s = s.replace(/^\./, "").replace(/\.$/, "");
108
+ return s;
109
+ };
99
110
  for (let path of paths) {
100
111
  if (!path) continue;
101
112
  while (/^\[(\d+)\](\.|$)/.test(path)) {
102
113
  path = path.replace(/^\[(\d+)\]\.?/, "");
103
114
  }
104
- if (!path) continue;
105
- let first = "";
106
- let rest = "";
107
- const mStr = path.match(/^\[(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')\](.*)$/);
108
- if (mStr) {
109
- first = (mStr[1] ?? mStr[2]) || "";
110
- rest = mStr[3] || "";
111
- } else {
112
- const m = path.match(/^([^.[]+)([\s\S]*)$/);
113
- first = (m == null ? void 0 : m[1]) ?? "";
114
- rest = (m == null ? void 0 : m[2]) ?? "";
115
+ const norm = normalizePath(path);
116
+ if (!norm) continue;
117
+ const segments = norm.split(".").filter(Boolean);
118
+ if (segments.length === 0) continue;
119
+ if (segments.length === 1) {
120
+ fieldSet.add(segments[0]);
121
+ continue;
122
+ }
123
+ for (let i = 0; i < segments.length - 1; i++) {
124
+ appendSet.add(segments.slice(0, i + 1).join("."));
115
125
  }
116
- if (!first) continue;
117
- const hasDeep = rest.includes(".") || rest.includes("[");
118
- if (hasDeep) appendSet.add(first);
119
- else fieldSet.add(first);
126
+ fieldSet.add(segments.join("."));
120
127
  }
121
128
  const generatedAppends = appendSet.size ? Array.from(appendSet) : void 0;
122
129
  const generatedFields = fieldSet.size ? Array.from(fieldSet) : void 0;
123
130
  return { generatedAppends, generatedFields };
124
131
  }
125
- async function fetchRecordWithRequestCache(koaCtx, dataSourceKey, collection, filterByTk, fields, appends) {
132
+ async function fetchRecordWithRequestCache(koaCtx, dataSourceKey, collection, filterByTk, fields, appends, preferFullRecord) {
133
+ var _a, _b, _c, _d, _e;
126
134
  try {
135
+ const log = (_b = (_a = koaCtx.app) == null ? void 0 : _a.logger) == null ? void 0 : _b.child({
136
+ module: "plugin-flow-engine",
137
+ submodule: "variables.resolve",
138
+ method: "fetchRecordWithRequestCache"
139
+ });
127
140
  const kctx = koaCtx;
128
141
  if (!kctx.state) kctx.state = {};
129
142
  if (!kctx.state["__varResolveBatchCache"]) {
@@ -134,41 +147,79 @@ async function fetchRecordWithRequestCache(koaCtx, dataSourceKey, collection, fi
134
147
  const cm = ds.collectionManager;
135
148
  if (!(cm == null ? void 0 : cm.db)) return void 0;
136
149
  const repo = cm.db.getRepository(collection);
150
+ const modelInfo = (_c = repo.collection) == null ? void 0 : _c.model;
151
+ const pkAttr = modelInfo == null ? void 0 : modelInfo.primaryKeyAttribute;
152
+ const pkIsValid = pkAttr && (modelInfo == null ? void 0 : modelInfo.rawAttributes) && Object.prototype.hasOwnProperty.call(modelInfo.rawAttributes, pkAttr);
153
+ const fieldsWithPk = Array.isArray(fields) && fields.length > 0 && pkIsValid ? Array.from(/* @__PURE__ */ new Set([...fields, pkAttr])) : fields;
137
154
  const keyObj = {
138
155
  ds: dataSourceKey || "main",
139
156
  c: collection,
140
157
  tk: filterByTk,
141
- f: Array.isArray(fields) ? [...fields].sort() : void 0,
142
- a: Array.isArray(appends) ? [...appends].sort() : void 0
158
+ f: Array.isArray(fieldsWithPk) ? [...fieldsWithPk].sort() : void 0,
159
+ a: Array.isArray(appends) ? [...appends].sort() : void 0,
160
+ full: preferFullRecord ? true : void 0
143
161
  };
144
162
  const key = JSON.stringify(keyObj);
145
163
  if (cache) {
146
- if (cache.has(key)) return cache.get(key);
147
- const needFields = Array.isArray(fields) ? new Set(fields) : void 0;
164
+ if (cache.has(key)) {
165
+ return cache.get(key);
166
+ }
167
+ const needFields = Array.isArray(fieldsWithPk) ? [...new Set(fieldsWithPk)] : void 0;
148
168
  const needAppends = Array.isArray(appends) ? new Set(appends) : void 0;
149
- let fallbackAny = void 0;
150
169
  for (const [cacheKey, cacheVal] of cache.entries()) {
151
170
  const parsed = JSON.parse(cacheKey);
152
171
  if (!parsed || parsed.ds !== keyObj.ds || parsed.c !== keyObj.c || parsed.tk !== keyObj.tk) continue;
153
- const cachedFields = Array.isArray(parsed.f) ? new Set(parsed.f) : void 0;
154
- const cachedAppends = Array.isArray(parsed.a) ? new Set(parsed.a) : void 0;
155
- const fieldsOk = !needFields || cachedFields && [...needFields].every((x) => cachedFields.has(x));
156
- const appendsOk = !needAppends || cachedAppends && [...needAppends].every((x) => cachedAppends.has(x));
157
- if (fieldsOk && appendsOk) return cacheVal;
158
- if (typeof fallbackAny === "undefined") fallbackAny = cacheVal;
172
+ const cachedFields = new Set(parsed.f || []);
173
+ const cachedAppends = new Set(parsed.a || []);
174
+ const fieldCoveredByAppends = (fieldPath) => {
175
+ const p = String(fieldPath || "");
176
+ for (const a of cachedAppends) {
177
+ if (!a) continue;
178
+ if (p === a || p.startsWith(a + ".")) return true;
179
+ }
180
+ return false;
181
+ };
182
+ const fieldsOk = needFields ? needFields.every((f) => cachedFields.has(f) || fieldCoveredByAppends(f)) : parsed.f === void 0;
183
+ const appendsOk = !needAppends || [...needAppends].every((a) => cachedAppends.has(a));
184
+ const fullOk = preferFullRecord ? parsed.full === true : true;
185
+ if (fieldsOk && appendsOk && fullOk) {
186
+ return cacheVal;
187
+ }
159
188
  }
160
- if (typeof fallbackAny !== "undefined") return fallbackAny;
161
189
  }
162
- const tk = filterByTk;
163
190
  const rec = await repo.findOne({
164
- filterByTk: tk,
165
- fields,
191
+ filterByTk,
192
+ fields: fieldsWithPk,
166
193
  appends
167
194
  });
168
- const json = rec ? rec.toJSON() : void 0;
195
+ let json = rec ? rec.toJSON() : void 0;
196
+ if (preferFullRecord && json && typeof json === "object" && pkIsValid) {
197
+ const keys = Object.keys(json);
198
+ const pkOnly = keys.length === 1 && keys[0] === pkAttr;
199
+ const rawAttrs = modelInfo == null ? void 0 : modelInfo.rawAttributes;
200
+ const hasMoreAttrs = rawAttrs && Object.keys(rawAttrs).some((k) => k !== pkAttr);
201
+ if (pkOnly && hasMoreAttrs) {
202
+ const rec2 = await repo.findOne({ filterByTk });
203
+ json = rec2 ? rec2.toJSON() : json;
204
+ }
205
+ }
169
206
  if (cache) cache.set(key, json);
170
207
  return json;
171
- } catch (_2) {
208
+ } catch (e) {
209
+ const log = (_e = (_d = koaCtx.app) == null ? void 0 : _d.logger) == null ? void 0 : _e.child({
210
+ module: "plugin-flow-engine",
211
+ submodule: "variables.resolve",
212
+ method: "fetchRecordWithRequestCache"
213
+ });
214
+ const errMsg = e instanceof Error ? e.message : String(e);
215
+ log == null ? void 0 : log.warn("[variables.resolve] fetchRecordWithRequestCache error", {
216
+ ds: dataSourceKey,
217
+ collection,
218
+ tk: filterByTk,
219
+ fields,
220
+ appends,
221
+ error: errMsg
222
+ });
172
223
  return void 0;
173
224
  }
174
225
  }
@@ -185,17 +236,26 @@ function attachGenericRecordVariables(flowCtx, koaCtx, usage, contextParams) {
185
236
  const topParams = import_lodash.default.get(contextParams, varName);
186
237
  if (isRecordParams(topParams)) {
187
238
  const { generatedAppends, generatedFields } = inferSelectsFromUsage(usedPaths, topParams);
239
+ const hasDirectRefTop = (usedPaths || []).some((p) => p === "");
188
240
  flowCtx.defineProperty(varName, {
189
241
  get: async () => {
190
242
  const dataSourceKey = (topParams == null ? void 0 : topParams.dataSourceKey) || "main";
191
- return await fetchRecordWithRequestCache(
243
+ const fixed = (0, import_selects.adjustSelectsForCollection)(
192
244
  koaCtx,
193
245
  dataSourceKey,
194
246
  topParams.collection,
195
- topParams.filterByTk,
196
247
  generatedFields,
197
248
  generatedAppends
198
249
  );
250
+ return await fetchRecordWithRequestCache(
251
+ koaCtx,
252
+ dataSourceKey,
253
+ topParams.collection,
254
+ topParams.filterByTk,
255
+ fixed.fields,
256
+ fixed.appends,
257
+ hasDirectRefTop
258
+ );
199
259
  },
200
260
  cache: true
201
261
  });
@@ -228,44 +288,105 @@ function attachGenericRecordVariables(flowCtx, koaCtx, usage, contextParams) {
228
288
  segmentMap.set(seg, arr);
229
289
  }
230
290
  const segEntries = Array.from(segmentMap.entries());
231
- const recordChildren = segEntries.filter(([seg]) => {
291
+ const oneLevelRecordChildren = segEntries.filter(([seg]) => {
232
292
  const idx = parseIndexSegment(seg);
233
293
  const nestedObj = import_lodash.default.get(contextParams, [varName, seg]) ?? (idx ? import_lodash.default.get(contextParams, [varName, idx]) : void 0);
234
294
  const dotted = (contextParams || {})[`${varName}.${seg}`] ?? (idx ? (contextParams || {})[`${varName}.${idx}`] : void 0);
235
295
  return isRecordParams(nestedObj) || isRecordParams(dotted);
236
296
  });
237
- if (!recordChildren.length) continue;
297
+ const deepRecordMap = /* @__PURE__ */ new Map();
298
+ const cp = contextParams;
299
+ if (cp && typeof cp === "object") {
300
+ const cpRec = cp;
301
+ for (const key of Object.keys(cpRec)) {
302
+ if (!key || key !== varName && !key.startsWith(`${varName}.`)) continue;
303
+ if (key === varName) continue;
304
+ const val = cpRec[key];
305
+ if (!isRecordParams(val)) continue;
306
+ const relative = key.slice(varName.length + 1);
307
+ if (!relative) continue;
308
+ deepRecordMap.set(relative, val);
309
+ }
310
+ }
311
+ if (!oneLevelRecordChildren.length && deepRecordMap.size === 0) continue;
238
312
  flowCtx.defineProperty(varName, {
239
313
  get: () => {
240
- const subContext = new import_contexts.ServerBaseContext();
241
- for (const [seg, remainders] of recordChildren) {
242
- const idx = parseIndexSegment(seg);
243
- const recordParams = import_lodash.default.get(contextParams, [varName, seg]) ?? (idx ? import_lodash.default.get(contextParams, [varName, idx]) : void 0) ?? (contextParams || {})[`${varName}.${seg}`] ?? (idx ? (contextParams || {})[`${varName}.${idx}`] : void 0);
244
- let effRemainders = remainders.filter((r) => !!r);
245
- if (!effRemainders.length) {
246
- const all = usedPaths.map(
247
- (p) => p.startsWith(`${seg}.`) ? p.slice(seg.length + 1) : p.startsWith(`${seg}[`) ? p.slice(seg.length) : ""
248
- ).filter((x) => !!x);
249
- if (all.length) effRemainders = all;
250
- }
251
- const { generatedAppends, generatedFields } = inferSelectsFromUsage(effRemainders, recordParams);
252
- const definitionKey = idx ?? seg;
253
- subContext.defineProperty(definitionKey, {
314
+ const root = new import_contexts.ServerBaseContext();
315
+ const definedFirstLevel = /* @__PURE__ */ new Set();
316
+ const defineRecordGetter = (container, key, recordParams, subPaths = [], preferFull) => {
317
+ const { generatedAppends, generatedFields } = inferSelectsFromUsage(subPaths, recordParams);
318
+ container.defineProperty(key, {
254
319
  get: async () => {
255
320
  const dataSourceKey = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
256
- return await fetchRecordWithRequestCache(
321
+ const fixed = (0, import_selects.adjustSelectsForCollection)(
257
322
  koaCtx,
258
323
  dataSourceKey,
259
324
  recordParams.collection,
260
- recordParams.filterByTk,
261
325
  generatedFields,
262
326
  generatedAppends
263
327
  );
328
+ return await fetchRecordWithRequestCache(
329
+ koaCtx,
330
+ dataSourceKey,
331
+ recordParams.collection,
332
+ recordParams.filterByTk,
333
+ fixed.fields,
334
+ fixed.appends,
335
+ preferFull || ((subPaths == null ? void 0 : subPaths.length) ?? 0) === 0
336
+ );
264
337
  },
265
338
  cache: true
266
339
  });
340
+ };
341
+ const subContainers = /* @__PURE__ */ new Map();
342
+ const ensureSubContainer = (parent, key) => {
343
+ let map = subContainers.get(parent);
344
+ if (!map) {
345
+ map = /* @__PURE__ */ new Map();
346
+ subContainers.set(parent, map);
347
+ }
348
+ let child = map.get(key);
349
+ if (!child) {
350
+ const inst = new import_contexts.ServerBaseContext();
351
+ parent.defineProperty(key, { get: () => inst.createProxy(), cache: true });
352
+ map.set(key, inst);
353
+ child = inst;
354
+ }
355
+ return child;
356
+ };
357
+ for (const [seg, remainders] of oneLevelRecordChildren) {
358
+ const idx = parseIndexSegment(seg);
359
+ const recordParams = import_lodash.default.get(contextParams, [varName, seg]) ?? (idx ? import_lodash.default.get(contextParams, [varName, idx]) : void 0) ?? (contextParams || {})[`${varName}.${seg}`] ?? (idx ? (contextParams || {})[`${varName}.${idx}`] : void 0);
360
+ let effRemainders = (remainders || []).filter((r) => !!r);
361
+ if (!effRemainders.length) {
362
+ const all = usedPaths.map(
363
+ (p) => p.startsWith(`${seg}.`) ? p.slice(seg.length + 1) : p.startsWith(`${seg}[`) ? p.slice(seg.length) : ""
364
+ ).filter((x) => !!x);
365
+ if (all.length) effRemainders = all;
366
+ }
367
+ const hasDirectRefOne = (usedPaths || []).some((p) => p === seg || !!idx && p === `[${idx}]`);
368
+ defineRecordGetter(root, idx ?? seg, recordParams, effRemainders, hasDirectRefOne);
369
+ definedFirstLevel.add(idx ?? seg);
370
+ }
371
+ for (const [relative, recordParams] of deepRecordMap.entries()) {
372
+ const segs = String(relative).split(".").filter(Boolean);
373
+ if (segs.length === 0) continue;
374
+ const first = segs[0];
375
+ let container;
376
+ if (definedFirstLevel.has(first)) {
377
+ continue;
378
+ } else {
379
+ container = root;
380
+ for (let i = 0; i < segs.length - 1; i++) {
381
+ container = ensureSubContainer(container, segs[i]);
382
+ }
383
+ }
384
+ const leaf = segs[segs.length - 1];
385
+ const subPaths = (usedPaths || []).map((p) => p === relative ? "" : p.startsWith(relative + ".") ? p.slice(relative.length + 1) : "").filter((x) => x !== "");
386
+ const hasDirectRef = (usedPaths || []).some((p) => p === relative);
387
+ defineRecordGetter(container, leaf, recordParams, subPaths, hasDirectRef);
267
388
  }
268
- return subContext.createProxy();
389
+ return root.createProxy();
269
390
  },
270
391
  cache: true
271
392
  });
@@ -295,5 +416,6 @@ function registerBuiltInVariables(reg) {
295
416
  registerBuiltInVariables(variables);
296
417
  // Annotate the CommonJS export names for ESM import in node:
297
418
  0 && (module.exports = {
419
+ inferSelectsFromUsage,
298
420
  variables
299
421
  });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { ResourcerContext } from '@nocobase/resourcer';
10
+ /**
11
+ * 针对给定集合,修正 selects:
12
+ * - 若 fields 中包含单段且为关联名(如 'roles'),则将其从 fields 移到 appends。
13
+ * - 若 fields 中包含多段且首段为关联名(如 'roles.name'),确保 appends 包含该关联名,并将首段替换为模型真实关联名。
14
+ * - 非关联字段:仅当模型存在该属性(或其 snake/camel 变体)时才保留,否则丢弃以避免数据库错误。
15
+ */
16
+ export declare function adjustSelectsForCollection(koaCtx: ResourcerContext, dataSourceKey: string, collection: string, fields?: string[], appends?: string[]): {
17
+ fields?: string[];
18
+ appends?: string[];
19
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var selects_exports = {};
28
+ __export(selects_exports, {
29
+ adjustSelectsForCollection: () => adjustSelectsForCollection
30
+ });
31
+ module.exports = __toCommonJS(selects_exports);
32
+ function adjustSelectsForCollection(koaCtx, dataSourceKey, collection, fields, appends) {
33
+ var _a, _b, _c, _d;
34
+ const ds = koaCtx.app.dataSourceManager.get(dataSourceKey || "main");
35
+ const cm = ds.collectionManager;
36
+ const coll = (_b = (_a = cm == null ? void 0 : cm.db) == null ? void 0 : _a.getCollection) == null ? void 0 : _b.call(_a, collection);
37
+ const assocKeys = Object.keys(((_c = coll == null ? void 0 : coll.model) == null ? void 0 : _c.associations) || {});
38
+ const rawAttrs = ((_d = coll == null ? void 0 : coll.model) == null ? void 0 : _d.rawAttributes) || {};
39
+ const toCamel = (s) => s.replace(/_([a-zA-Z0-9])/g, (_m, c) => String(c).toUpperCase());
40
+ const toSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
41
+ const assocMap = /* @__PURE__ */ new Map();
42
+ for (const k of assocKeys) {
43
+ assocMap.set(k, k);
44
+ assocMap.set(toSnake(k), k);
45
+ assocMap.set(toCamel(k), k);
46
+ }
47
+ const outFields = [];
48
+ const outAppends = new Set(appends || []);
49
+ for (const f of fields || []) {
50
+ const segs = String(f).split(".").filter(Boolean);
51
+ if (!segs.length) continue;
52
+ const first = segs[0];
53
+ const assocCanonical = assocMap.get(first) || assocMap.get(toCamel(first)) || assocMap.get(toSnake(first));
54
+ if (assocCanonical) {
55
+ outAppends.add(assocCanonical);
56
+ if (segs.length === 1) {
57
+ continue;
58
+ }
59
+ outFields.push([assocCanonical, ...segs.slice(1)].join("."));
60
+ continue;
61
+ }
62
+ if (rawAttrs[first]) {
63
+ outFields.push(f);
64
+ } else if (rawAttrs[toSnake(first)]) {
65
+ outFields.push([toSnake(first), ...segs.slice(1)].join("."));
66
+ } else if (rawAttrs[toCamel(first)]) {
67
+ outFields.push([toCamel(first), ...segs.slice(1)].join("."));
68
+ } else {
69
+ continue;
70
+ }
71
+ }
72
+ return {
73
+ fields: outFields.length ? outFields : void 0,
74
+ appends: outAppends.size ? Array.from(outAppends) : void 0
75
+ };
76
+ }
77
+ // Annotate the CommonJS export names for ESM import in node:
78
+ 0 && (module.exports = {
79
+ adjustSelectsForCollection
80
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import type { ResourcerContext } from '@nocobase/resourcer';
10
+ import type { JSONValue } from '../template/resolver';
11
+ /**
12
+ * 预取:构建“同记录”的字段/关联并集,一次查询写入 ctx.state.__varResolveBatchCache,供后续解析复用
13
+ */
14
+ export declare function prefetchRecordsForResolve(koaCtx: ResourcerContext, items: Array<{
15
+ template: JSONValue;
16
+ contextParams?: Record<string, unknown>;
17
+ }>): Promise<void>;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var utils_exports = {};
28
+ __export(utils_exports, {
29
+ prefetchRecordsForResolve: () => prefetchRecordsForResolve
30
+ });
31
+ module.exports = __toCommonJS(utils_exports);
32
+ var import_registry = require("./registry");
33
+ var import_selects = require("./selects");
34
+ async function prefetchRecordsForResolve(koaCtx, items) {
35
+ var _a, _b, _c, _d, _e, _f, _g;
36
+ try {
37
+ const log = (_b = (_a = koaCtx.app) == null ? void 0 : _a.logger) == null ? void 0 : _b.child({ module: "plugin-flow-engine", submodule: "variables.prefetch" });
38
+ const groupMap = /* @__PURE__ */ new Map();
39
+ const ensureGroup = (dataSourceKey, collection, filterByTk) => {
40
+ const groupKey = JSON.stringify({ ds: dataSourceKey, collection, tk: filterByTk });
41
+ let group = groupMap.get(groupKey);
42
+ if (!group) {
43
+ group = { dataSourceKey, collection, filterByTk, fields: /* @__PURE__ */ new Set(), appends: /* @__PURE__ */ new Set() };
44
+ groupMap.set(groupKey, group);
45
+ }
46
+ return group;
47
+ };
48
+ const normalizeNestedSeg = (segment) => /^\d+$/.test(segment) ? `[${segment}]` : segment;
49
+ for (const it of items) {
50
+ const template = (it == null ? void 0 : it.template) ?? {};
51
+ const contextParams = (it == null ? void 0 : it.contextParams) || {};
52
+ const usage = import_registry.variables.extractUsage(template);
53
+ for (const [cpKey, recordParams] of Object.entries(contextParams)) {
54
+ const parts = String(cpKey).split(".");
55
+ const varName = parts[0];
56
+ const nestedSeg = parts.slice(1).join(".");
57
+ const paths = (usage == null ? void 0 : usage[varName]) || [];
58
+ if (!paths.length) continue;
59
+ const segNorm = nestedSeg ? normalizeNestedSeg(nestedSeg) : "";
60
+ const remainders = [];
61
+ for (const p of paths) {
62
+ if (!segNorm) remainders.push(p);
63
+ else if (p === segNorm) remainders.push("");
64
+ else if (p.startsWith(`${segNorm}.`) || p.startsWith(`${segNorm}[`))
65
+ remainders.push(p.slice(segNorm.length + 1));
66
+ }
67
+ if (!remainders.length) continue;
68
+ const dataSourceKey = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
69
+ const collection = recordParams == null ? void 0 : recordParams.collection;
70
+ const filterByTk = recordParams == null ? void 0 : recordParams.filterByTk;
71
+ if (!collection || typeof filterByTk === "undefined") continue;
72
+ const group = ensureGroup(dataSourceKey, collection, filterByTk);
73
+ let { generatedAppends, generatedFields } = (0, import_registry.inferSelectsFromUsage)(remainders);
74
+ const fixed = (0, import_selects.adjustSelectsForCollection)(koaCtx, dataSourceKey, collection, generatedFields, generatedAppends);
75
+ generatedFields = fixed.fields;
76
+ generatedAppends = fixed.appends;
77
+ if (generatedFields == null ? void 0 : generatedFields.length) generatedFields.forEach((f) => group.fields.add(f));
78
+ if (generatedAppends == null ? void 0 : generatedAppends.length) generatedAppends.forEach((a) => group.appends.add(a));
79
+ }
80
+ }
81
+ if (!groupMap.size) return;
82
+ const stateObj = koaCtx.state;
83
+ if (stateObj && !stateObj["__varResolveBatchCache"]) {
84
+ stateObj["__varResolveBatchCache"] = /* @__PURE__ */ new Map();
85
+ }
86
+ const cache = (_c = koaCtx.state) == null ? void 0 : _c["__varResolveBatchCache"];
87
+ for (const { dataSourceKey, collection, filterByTk, fields, appends } of groupMap.values()) {
88
+ try {
89
+ const ds = koaCtx.app.dataSourceManager.get(dataSourceKey);
90
+ const cm = ds.collectionManager;
91
+ if (!(cm == null ? void 0 : cm.db)) continue;
92
+ const repo = cm.db.getRepository(collection);
93
+ const modelInfo = (_d = repo.collection) == null ? void 0 : _d.model;
94
+ const pkAttr = modelInfo == null ? void 0 : modelInfo.primaryKeyAttribute;
95
+ const pkIsValid = pkAttr && (modelInfo == null ? void 0 : modelInfo.rawAttributes) && Object.prototype.hasOwnProperty.call(modelInfo.rawAttributes, pkAttr);
96
+ if (fields.size && pkIsValid) {
97
+ fields.add(pkAttr);
98
+ }
99
+ const fld = fields.size ? Array.from(fields).sort() : void 0;
100
+ const app = appends.size ? Array.from(appends).sort() : void 0;
101
+ const rec = await repo.findOne({ filterByTk, fields: fld, appends: app });
102
+ const json = rec ? rec.toJSON() : void 0;
103
+ if (cache) {
104
+ const key = JSON.stringify({ ds: dataSourceKey, c: collection, tk: filterByTk, f: fld, a: app });
105
+ cache.set(key, json);
106
+ }
107
+ } catch (e) {
108
+ log == null ? void 0 : log.debug("[variables.resolve] prefetch query error", {
109
+ ds: dataSourceKey,
110
+ collection,
111
+ tk: filterByTk,
112
+ error: (e == null ? void 0 : e.message) || String(e)
113
+ });
114
+ }
115
+ }
116
+ } catch (e) {
117
+ (_g = (_f = (_e = koaCtx.app) == null ? void 0 : _e.logger) == null ? void 0 : _f.child({ module: "plugin-flow-engine", submodule: "variables.prefetch" })) == null ? void 0 : _g.debug("[variables.resolve] prefetch fatal error", { error: (e == null ? void 0 : e.message) || String(e) });
118
+ }
119
+ }
120
+ // Annotate the CommonJS export names for ESM import in node:
121
+ 0 && (module.exports = {
122
+ prefetchRecordsForResolve
123
+ });
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "displayName.zh-CN": "前端流引擎",
5
5
  "description": "",
6
6
  "description.zh-CN": "",
7
- "version": "2.0.0-alpha.3",
7
+ "version": "2.0.0-alpha.31",
8
8
  "main": "./dist/server/index.js",
9
9
  "license": "AGPL-3.0",
10
10
  "devDependencies": {
@@ -24,5 +24,5 @@
24
24
  "@nocobase/test": "2.x",
25
25
  "@nocobase/utils": "2.x"
26
26
  },
27
- "gitHead": "8efc23aa78058d871e98a91419f3c4a61762cc15"
27
+ "gitHead": "1ccdc0a8af73f6c2e3e83df75741947a5a8c1984"
28
28
  }