@nocobase/plugin-flow-engine 3.0.0-alpha.6 → 3.0.0-alpha.8

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 (51) hide show
  1. package/dist/externalVersion.js +9 -9
  2. package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
  3. package/dist/node_modules/acorn/package.json +1 -1
  4. package/dist/node_modules/acorn-jsx/package.json +1 -1
  5. package/dist/node_modules/acorn-walk/package.json +1 -1
  6. package/dist/node_modules/ses/package.json +1 -1
  7. package/dist/node_modules/zod/package.json +1 -1
  8. package/dist/server/flow-surfaces/builder.js +5 -0
  9. package/dist/server/flow-surfaces/catalog.js +27 -8
  10. package/dist/server/flow-surfaces/configure-options.js +17 -1
  11. package/dist/server/flow-surfaces/service-utils.d.ts +1 -0
  12. package/dist/server/flow-surfaces/service-utils.js +17 -0
  13. package/dist/server/flow-surfaces/service.js +9 -0
  14. package/dist/server/index.d.ts +2 -0
  15. package/dist/server/index.js +7 -0
  16. package/dist/server/plugin.d.ts +13 -0
  17. package/dist/server/plugin.js +90 -3
  18. package/dist/server/repository.d.ts +12 -0
  19. package/dist/server/repository.js +39 -2
  20. package/dist/server/template/context-keys.d.ts +14 -0
  21. package/dist/server/template/context-keys.js +86 -0
  22. package/dist/server/template/contexts.d.ts +5 -8
  23. package/dist/server/template/contexts.js +43 -51
  24. package/dist/server/template/resolver.d.ts +5 -13
  25. package/dist/server/template/resolver.js +188 -257
  26. package/dist/server/template/variable-expression.d.ts +72 -0
  27. package/dist/server/template/variable-expression.js +597 -0
  28. package/dist/server/variables/allow-list.d.ts +49 -0
  29. package/dist/server/variables/allow-list.js +492 -0
  30. package/dist/server/variables/form-item-record-slot-resolvers.d.ts +10 -0
  31. package/dist/server/variables/form-item-record-slot-resolvers.js +280 -0
  32. package/dist/server/variables/record-bindings.d.ts +42 -0
  33. package/dist/server/variables/record-bindings.js +196 -0
  34. package/dist/server/variables/record-projection.d.ts +10 -0
  35. package/dist/server/variables/record-projection.js +140 -0
  36. package/dist/server/variables/record-slot-policy.d.ts +23 -0
  37. package/dist/server/variables/record-slot-policy.js +100 -0
  38. package/dist/server/variables/record-slot-resolvers.d.ts +46 -0
  39. package/dist/server/variables/record-slot-resolvers.js +115 -0
  40. package/dist/server/variables/records.d.ts +49 -2
  41. package/dist/server/variables/records.js +309 -18
  42. package/dist/server/variables/registry.d.ts +33 -10
  43. package/dist/server/variables/registry.js +197 -362
  44. package/dist/server/variables/resolve.d.ts +20 -2
  45. package/dist/server/variables/resolve.js +85 -13
  46. package/dist/server/variables/runjs-variable-dependencies.d.ts +23 -0
  47. package/dist/server/variables/runjs-variable-dependencies.js +407 -0
  48. package/dist/server/variables/selects.js +6 -3
  49. package/dist/server/variables/utils.d.ts +3 -6
  50. package/dist/server/variables/utils.js +50 -111
  51. package/package.json +2 -2
@@ -37,14 +37,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
37
37
  var registry_exports = {};
38
38
  __export(registry_exports, {
39
39
  inferSelectsFromUsage: () => inferSelectsFromUsage,
40
+ isSafeRecordBinding: () => isSafeRecordBinding,
41
+ omitVariableContextParams: () => omitVariableContextParams,
42
+ registerBuiltInVariables: () => registerBuiltInVariables,
43
+ sanitizeRegisteredVariableContextParams: () => sanitizeRegisteredVariableContextParams,
40
44
  variables: () => variables
41
45
  });
42
46
  module.exports = __toCommonJS(registry_exports);
43
47
  var import_lodash = __toESM(require("lodash"));
44
- var import_contexts = require("../template/contexts");
45
48
  var import_utils = require("@nocobase/utils");
46
- var import_selects = require("./selects");
49
+ var import_context_keys = require("../template/context-keys");
50
+ var import_contexts = require("../template/contexts");
51
+ var import_variable_expression = require("../template/variable-expression");
52
+ var import_record_bindings = require("./record-bindings");
53
+ var import_record_projection = require("./record-projection");
47
54
  var import_records = require("./records");
55
+ var import_record_slot_policy = require("./record-slot-policy");
48
56
  class VariableRegistry {
49
57
  vars = /* @__PURE__ */ new Map();
50
58
  register(def) {
@@ -76,15 +84,31 @@ class VariableRegistry {
76
84
  return { ok: missing.length === 0, missing: missing.length ? missing : void 0 };
77
85
  }
78
86
  async attachUsedVariables(ctx, koaCtx, template, contextParams) {
79
- const usage = this.extractUsage(template);
87
+ const usage = (0, import_variable_expression.analyzeVariableTemplate)(template).usage;
88
+ return this.attachUsedVariablesFromUsage(ctx, koaCtx, usage, contextParams);
89
+ }
90
+ async attachUsedVariablesFromUsage(ctx, koaCtx, usage, contextParams) {
91
+ const policies = await (0, import_record_slot_policy.compileRecordSlotPolicies)(
92
+ { paths: Object.values(usage).flat() },
93
+ { app: koaCtx.app, ctx: koaCtx }
94
+ );
95
+ const plan = (0, import_record_bindings.planRecordBindings)({
96
+ contextParams,
97
+ koaCtx,
98
+ policies,
99
+ usage
100
+ });
101
+ return this.attachUsedVariablesFromPlan(ctx, koaCtx, usage, await plan);
102
+ }
103
+ async attachUsedVariablesFromPlan(ctx, koaCtx, usage, plan) {
80
104
  for (const varName of Object.keys(usage)) {
81
105
  const def = this.get(varName);
82
- const params = import_lodash.default.get(contextParams, varName);
106
+ const params = import_lodash.default.get(plan.contextParams, varName);
83
107
  if (def) {
84
108
  await def.attach(ctx, koaCtx, params, { [varName]: usage[varName] });
85
109
  }
86
110
  }
87
- attachGenericRecordVariables(ctx, koaCtx, usage, contextParams);
111
+ attachAuthorizedRecordBindings(ctx, koaCtx, plan.bindings);
88
112
  }
89
113
  }
90
114
  const GLOBAL_KEY = "__ncbVarRegistry__";
@@ -96,6 +120,29 @@ if (!g[GLOBAL_KEY]) {
96
120
  g[GLOBAL_KEY] = new VariableRegistry();
97
121
  }
98
122
  const variables = g[GLOBAL_KEY];
123
+ function omitVariableContextParams(contextParams, varName) {
124
+ if (!contextParams || typeof contextParams !== "object") return {};
125
+ const prefix = `${varName}.`;
126
+ const next = {};
127
+ for (const [key, value] of Object.entries(contextParams)) {
128
+ if (key === varName || key.startsWith(prefix)) continue;
129
+ next[key] = value;
130
+ }
131
+ return next;
132
+ }
133
+ function sanitizeRegisteredVariableContextParams(contextParams, registry = variables) {
134
+ let next = contextParams;
135
+ const defs = typeof registry.list === "function" ? registry.list() : ["user"].map((name) => {
136
+ var _a;
137
+ return (_a = registry.get) == null ? void 0 : _a.call(registry, name);
138
+ }).filter((def) => !!def);
139
+ for (const def of defs) {
140
+ if (def.name === "user") {
141
+ next = omitVariableContextParams(next, def.name);
142
+ }
143
+ }
144
+ return next;
145
+ }
99
146
  function inferSelectsFromUsage(paths = [], _params) {
100
147
  if (!Array.isArray(paths) || paths.length === 0) {
101
148
  return { generatedAppends: void 0, generatedFields: void 0 };
@@ -111,14 +158,23 @@ function inferSelectsFromUsage(paths = [], _params) {
111
158
  s = s.replace(/^\./, "").replace(/\.$/, "");
112
159
  return s;
113
160
  };
114
- for (let path of paths) {
115
- if (!path) continue;
116
- while (/^\[(\d+)\](\.|$)/.test(path)) {
117
- path = path.replace(/^\[(\d+)\]\.?/, "");
161
+ for (const value of paths) {
162
+ let segments;
163
+ if (Array.isArray(value)) {
164
+ if (value.some((segment) => typeof segment === "string" && segment.includes("."))) {
165
+ return { generatedAppends: void 0, generatedFields: void 0 };
166
+ }
167
+ segments = value.filter((segment) => typeof segment === "string" && !!segment);
168
+ } else {
169
+ let path = value;
170
+ if (!path) continue;
171
+ while (/^\[(\d+)\](\.|$)/.test(path)) {
172
+ path = path.replace(/^\[(\d+)\]\.?/, "");
173
+ }
174
+ const norm = normalizePath(path);
175
+ if (!norm) continue;
176
+ segments = norm.split(".").filter(Boolean);
118
177
  }
119
- const norm = normalizePath(path);
120
- if (!norm) continue;
121
- const segments = norm.split(".").filter(Boolean);
122
178
  if (segments.length === 0) continue;
123
179
  if (segments.length === 1) {
124
180
  fieldSet.add(segments[0]);
@@ -133,345 +189,111 @@ function inferSelectsFromUsage(paths = [], _params) {
133
189
  const generatedFields = fieldSet.size ? Array.from(fieldSet) : void 0;
134
190
  return { generatedAppends, generatedFields };
135
191
  }
136
- async function fetchRecordWithRequestCache(koaCtx, dataSourceKey, collection, filterByTk, fields, appends, strictSelects, preferFullRecord, associationName, sourceId) {
137
- var _a, _b, _c, _d, _e;
138
- try {
139
- const log = (_b = (_a = koaCtx.app) == null ? void 0 : _a.logger) == null ? void 0 : _b.child({
140
- module: "plugin-flow-engine",
141
- submodule: "variables.resolve",
142
- method: "fetchRecordWithRequestCache"
143
- });
144
- const kctx = koaCtx;
145
- if (!kctx.state) kctx.state = {};
146
- if (!kctx.state["__varResolveBatchCache"]) {
147
- kctx.state["__varResolveBatchCache"] = /* @__PURE__ */ new Map();
192
+ function createRecordProviderNode() {
193
+ return { children: /* @__PURE__ */ new Map() };
194
+ }
195
+ function addRecordProviderBinding(root, binding) {
196
+ let node = root;
197
+ for (const segment of binding.prefix) {
198
+ let child = node.children.get(segment);
199
+ if (!child) {
200
+ child = createRecordProviderNode();
201
+ node.children.set(segment, child);
148
202
  }
149
- const cache = kctx.state.__varResolveBatchCache || null;
150
- const ds = koaCtx.app.dataSourceManager.get(dataSourceKey || "main");
151
- const cm = ds.collectionManager;
152
- if (!(cm == null ? void 0 : cm.db)) return void 0;
153
- const repo = associationName && typeof sourceId !== "undefined" ? cm.db.getRepository(associationName, sourceId) : cm.db.getRepository(collection);
154
- const modelInfo = (_c = repo.collection) == null ? void 0 : _c.model;
155
- const pkAttr = modelInfo == null ? void 0 : modelInfo.primaryKeyAttribute;
156
- const pkIsValid = pkAttr && (modelInfo == null ? void 0 : modelInfo.rawAttributes) && Object.prototype.hasOwnProperty.call(modelInfo.rawAttributes, pkAttr);
157
- const collectionInfo = repo == null ? void 0 : repo.collection;
158
- const filterTargetKey = collectionInfo == null ? void 0 : collectionInfo.filterTargetKey;
159
- const extraKeys = (0, import_records.getExtraKeyFieldsForSelect)(filterByTk, {
160
- filterTargetKey,
161
- pkAttr,
162
- pkIsValid,
163
- rawAttributes: (modelInfo == null ? void 0 : modelInfo.rawAttributes) || void 0
164
- });
165
- const effectiveExtras = strictSelects && Array.isArray(extraKeys) && extraKeys.length ? extraKeys.filter((k) => k === pkAttr) : extraKeys;
166
- const fieldsWithExtras = (0, import_records.mergeFieldsWithExtras)(fields, effectiveExtras);
167
- const cacheKeyFields = preferFullRecord && pkIsValid ? void 0 : Array.isArray(fieldsWithExtras) ? [...fieldsWithExtras].sort() : void 0;
168
- const cacheKeyAppends = preferFullRecord ? void 0 : Array.isArray(appends) ? [...appends].sort() : void 0;
169
- const keyObj = {
170
- ds: dataSourceKey || "main",
171
- c: collection,
172
- tk: filterByTk,
173
- f: cacheKeyFields,
174
- a: cacheKeyAppends,
175
- full: preferFullRecord ? true : void 0,
176
- assoc: associationName,
177
- sid: typeof sourceId === "undefined" ? void 0 : sourceId
178
- };
179
- const key = JSON.stringify(keyObj);
180
- if (cache) {
181
- if (cache.has(key)) {
182
- return cache.get(key);
183
- }
184
- if (!strictSelects) {
185
- const needFields = !preferFullRecord && Array.isArray(fieldsWithExtras) ? [...new Set(fieldsWithExtras)] : void 0;
186
- const needAppends = !preferFullRecord && Array.isArray(appends) ? new Set(appends) : void 0;
187
- for (const [cacheKey, cacheVal] of cache.entries()) {
188
- const parsed = JSON.parse(cacheKey);
189
- if (!parsed || parsed.ds !== keyObj.ds || parsed.c !== keyObj.c || !import_lodash.default.isEqual(parsed.tk, keyObj.tk) || parsed.assoc !== keyObj.assoc || !import_lodash.default.isEqual(parsed.sid, keyObj.sid))
190
- continue;
191
- const cachedFields = new Set(parsed.f || []);
192
- const cachedAppends = new Set(parsed.a || []);
193
- const fieldCoveredByAppends = (fieldPath) => {
194
- const p = String(fieldPath || "");
195
- for (const a of cachedAppends) {
196
- if (!a) continue;
197
- if (p === a || p.startsWith(a + ".")) return true;
198
- }
199
- return false;
200
- };
201
- const fieldsOk = needFields ? needFields.every((f) => cachedFields.has(f) || fieldCoveredByAppends(f)) : parsed.f === void 0;
202
- const appendsOk = !needAppends || [...needAppends].every((a) => cachedAppends.has(a));
203
- const fullOk = preferFullRecord ? parsed.full === true : true;
204
- if (fieldsOk && appendsOk && fullOk) {
205
- return cacheVal;
206
- }
207
- }
208
- }
203
+ node = child;
204
+ }
205
+ if (node.binding) return false;
206
+ node.binding = binding;
207
+ return true;
208
+ }
209
+ function isProvidedByDescendant(node, path) {
210
+ let current = node;
211
+ for (const segment of path) {
212
+ const child = current.children.get(segment);
213
+ if (!child) return false;
214
+ if (child.binding) return true;
215
+ current = child;
216
+ }
217
+ return false;
218
+ }
219
+ function materializeRecordProvider(node, values, inherited) {
220
+ const base = node.binding ? values.get(node) : inherited;
221
+ if (!node.children.size) return base;
222
+ if (Array.isArray(base)) {
223
+ const result2 = Array.from(node.children.keys()).every(
224
+ (segment) => typeof segment === "number"
225
+ ) ? [...base] : Object.assign(/* @__PURE__ */ Object.create(null), base);
226
+ for (const [segment, child] of node.children) {
227
+ if (typeof segment !== "number") continue;
228
+ result2[segment] = materializeRecordProvider(child, values, result2[segment]);
209
229
  }
210
- const json = await (0, import_records.fetchRecordOrRecordsJson)(repo, {
211
- filterByTk,
212
- preferFullRecord,
213
- fields: fieldsWithExtras,
214
- appends,
215
- filterTargetKey,
216
- pkAttr,
217
- pkIsValid
218
- });
219
- if (cache) cache.set(key, json);
220
- return json;
221
- } catch (e) {
222
- const log = (_e = (_d = koaCtx.app) == null ? void 0 : _d.logger) == null ? void 0 : _e.child({
223
- module: "plugin-flow-engine",
224
- submodule: "variables.resolve",
225
- method: "fetchRecordWithRequestCache"
226
- });
227
- const errMsg = e instanceof Error ? e.message : String(e);
228
- log == null ? void 0 : log.warn("[variables.resolve] fetchRecordWithRequestCache error", {
229
- ds: dataSourceKey,
230
- collection,
231
- tk: filterByTk,
232
- fields,
233
- appends,
234
- error: errMsg
235
- });
236
- return void 0;
230
+ return result2;
237
231
  }
232
+ const result = import_lodash.default.isPlainObject(base) ? { ...base } : /* @__PURE__ */ Object.create(null);
233
+ for (const [segment, child] of node.children) {
234
+ const key = String(segment);
235
+ result[key] = materializeRecordProvider(child, values, result[key]);
236
+ }
237
+ return result;
238
238
  }
239
- function isRecordParams(val) {
240
- return val && typeof val === "object" && "collection" in val && "filterByTk" in val;
239
+ function isSafeRecordBinding(binding) {
240
+ return !(0, import_context_keys.isProtectedServerContextKey)(binding.varName);
241
241
  }
242
- function attachGenericRecordVariables(flowCtx, koaCtx, usage, contextParams) {
243
- const parseIndexSegment = (segment) => {
244
- const m = segment.match(/^\[(\d+)\]$/);
245
- return m ? m[1] : void 0;
246
- };
247
- for (const varName of Object.keys(usage)) {
248
- const usedPaths = usage[varName] || [];
249
- const topParams = import_lodash.default.get(contextParams, varName);
250
- const deepRecordMap = /* @__PURE__ */ new Map();
251
- const cp = contextParams;
252
- if (cp && typeof cp === "object") {
253
- const cpRec = cp;
254
- for (const key of Object.keys(cpRec)) {
255
- if (!key || key !== varName && !key.startsWith(`${varName}.`)) continue;
256
- if (key === varName) continue;
257
- const val = cpRec[key];
258
- if (!isRecordParams(val)) continue;
259
- const relative = key.slice(varName.length + 1);
260
- if (!relative) continue;
261
- deepRecordMap.set(relative, val);
262
- }
242
+ async function fetchBindingRecord(koaCtx, binding, relativePaths = binding.relativePaths) {
243
+ const strictSelects = Array.isArray(binding.params.fields) || Array.isArray(binding.params.appends);
244
+ let { generatedAppends, generatedFields } = inferSelectsFromUsage(relativePaths);
245
+ if (Array.isArray(binding.params.fields)) generatedFields = binding.params.fields;
246
+ if (Array.isArray(binding.params.appends)) generatedAppends = binding.params.appends;
247
+ const raw = await (0, import_records.fetchRecordWithRequestCache)(
248
+ koaCtx,
249
+ binding.params,
250
+ generatedFields,
251
+ generatedAppends,
252
+ strictSelects,
253
+ binding.preferFullRecord || relativePaths.some((path) => path.some((segment) => typeof segment === "string" && segment.includes(".")))
254
+ );
255
+ return (0, import_record_projection.projectRecord)(raw, relativePaths);
256
+ }
257
+ function attachAuthorizedRecordBindings(flowCtx, koaCtx, bindings) {
258
+ const byVariable = /* @__PURE__ */ new Map();
259
+ const conflicts = /* @__PURE__ */ new Set();
260
+ for (const binding of bindings) {
261
+ if (!isSafeRecordBinding(binding)) continue;
262
+ let root = byVariable.get(binding.varName);
263
+ if (!root) {
264
+ root = createRecordProviderNode();
265
+ byVariable.set(binding.varName, root);
263
266
  }
264
- if (isRecordParams(topParams)) {
265
- const usedPathsForBase = deepRecordMap.size ? (usedPaths || []).filter((p) => {
266
- if (!p) return true;
267
- for (const relative of deepRecordMap.keys()) {
268
- if (!relative) continue;
269
- if (p === relative || p.startsWith(relative + ".") || p.startsWith(relative + "[")) return false;
267
+ if (!addRecordProviderBinding(root, binding)) conflicts.add(binding.varName);
268
+ }
269
+ for (const [varName, root] of byVariable) {
270
+ if (conflicts.has(varName)) continue;
271
+ const resolveNode = async (node) => {
272
+ const entries = [];
273
+ const collect = (current) => {
274
+ if (current.binding) {
275
+ const paths = current.binding.relativePaths.filter((path) => !isProvidedByDescendant(current, path));
276
+ entries.push([current, fetchBindingRecord(koaCtx, current.binding, paths)]);
270
277
  }
271
- return true;
272
- }) : usedPaths || [];
273
- const hasDirectRefTop = usedPathsForBase.some((p) => p === "");
274
- flowCtx.defineProperty(varName, {
275
- get: async () => {
276
- const dataSourceKey = (topParams == null ? void 0 : topParams.dataSourceKey) || "main";
277
- const strictSelects = Array.isArray(topParams == null ? void 0 : topParams.fields) || Array.isArray(topParams == null ? void 0 : topParams.appends);
278
- let { generatedAppends, generatedFields } = inferSelectsFromUsage(usedPathsForBase, topParams);
279
- if (Array.isArray(topParams == null ? void 0 : topParams.fields)) generatedFields = topParams.fields;
280
- if (Array.isArray(topParams == null ? void 0 : topParams.appends)) generatedAppends = topParams.appends;
281
- const fixed = (0, import_selects.adjustSelectsForCollection)(
282
- koaCtx,
283
- dataSourceKey,
284
- topParams.collection,
285
- generatedFields,
286
- generatedAppends
287
- );
288
- const base = await fetchRecordWithRequestCache(
289
- koaCtx,
290
- dataSourceKey,
291
- topParams.collection,
292
- topParams.filterByTk,
293
- fixed.fields,
294
- fixed.appends,
295
- strictSelects,
296
- hasDirectRefTop,
297
- topParams.associationName,
298
- topParams.sourceId
299
- );
300
- if (!deepRecordMap.size) return base;
301
- const merged = base && typeof base === "object" && !Array.isArray(base) ? { ...base } : {};
302
- const setClonedPath = (obj, path, value) => {
303
- const segs = String(path || "").split(".").filter(Boolean);
304
- if (!segs.length) return;
305
- if (segs.length === 1) {
306
- obj[segs[0]] = value;
307
- return;
308
- }
309
- let cur = obj;
310
- for (let i = 0; i < segs.length - 1; i++) {
311
- const seg = segs[i];
312
- const prev = cur[seg];
313
- const next = prev && typeof prev === "object" && !Array.isArray(prev) ? { ...prev } : {};
314
- cur[seg] = next;
315
- cur = next;
316
- }
317
- cur[segs[segs.length - 1]] = value;
318
- };
319
- const buildNestedPromise = (recordParams, relative) => {
320
- const subPaths = (usedPaths || []).map((p) => p === relative ? "" : p.startsWith(relative + ".") ? p.slice(relative.length + 1) : "").filter((x) => x !== "");
321
- const hasDirectRef = (usedPaths || []).some((p) => p === relative);
322
- const dataSourceKey2 = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
323
- const strictSelects2 = Array.isArray(recordParams == null ? void 0 : recordParams.fields) || Array.isArray(recordParams == null ? void 0 : recordParams.appends);
324
- let { generatedAppends: generatedAppends2, generatedFields: generatedFields2 } = inferSelectsFromUsage(subPaths, recordParams);
325
- if (Array.isArray(recordParams == null ? void 0 : recordParams.fields)) generatedFields2 = recordParams.fields;
326
- if (Array.isArray(recordParams == null ? void 0 : recordParams.appends)) generatedAppends2 = recordParams.appends;
327
- const fixed2 = (0, import_selects.adjustSelectsForCollection)(
328
- koaCtx,
329
- dataSourceKey2,
330
- recordParams.collection,
331
- generatedFields2,
332
- generatedAppends2
333
- );
334
- return fetchRecordWithRequestCache(
335
- koaCtx,
336
- dataSourceKey2,
337
- recordParams.collection,
338
- recordParams.filterByTk,
339
- fixed2.fields,
340
- fixed2.appends,
341
- strictSelects2,
342
- hasDirectRef,
343
- recordParams.associationName,
344
- recordParams.sourceId
345
- );
346
- };
347
- for (const [relative, recordParams] of deepRecordMap.entries()) {
348
- setClonedPath(merged, relative, buildNestedPromise(recordParams, relative));
349
- }
350
- return merged;
351
- },
352
- cache: true
353
- });
354
- continue;
355
- }
356
- const segmentMap = /* @__PURE__ */ new Map();
357
- const splitHead = (path) => {
358
- if (!path) return { seg: "", remainder: "" };
359
- const mIdx = path.match(/^\[(\d+)\](?:\.(.*))?$/);
360
- if (mIdx) {
361
- return { seg: `[${mIdx[1]}]`, remainder: mIdx[2] || "" };
362
- }
363
- const m = path.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(\[(?:\d+|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\])?(?:\.(.*))?$/);
364
- if (m) {
365
- const seg2 = m[1];
366
- const idxPart = m[2] || "";
367
- const tail = m[3] || "";
368
- const remainder = (idxPart ? `${idxPart}${tail ? `.${tail}` : ""}` : tail) || "";
369
- return { seg: seg2, remainder };
278
+ for (const child of current.children.values()) collect(child);
279
+ };
280
+ collect(node);
281
+ const values = /* @__PURE__ */ new Map();
282
+ await Promise.all(entries.map(async ([current, pending]) => values.set(current, await pending)));
283
+ return materializeRecordProvider(node, values);
284
+ };
285
+ const createProxy = (node) => {
286
+ const context = new import_contexts.ServerBaseContext();
287
+ for (const [segment, child] of node.children) {
288
+ context.defineProperty(String(segment), {
289
+ get: () => child.binding ? resolveNode(child) : createProxy(child),
290
+ cache: true
291
+ });
370
292
  }
371
- const [seg, ...rest] = path.split(".");
372
- return { seg, remainder: rest.join(".") };
293
+ return context.createProxy();
373
294
  };
374
- for (const p of usedPaths) {
375
- if (!p) continue;
376
- const { seg, remainder } = splitHead(p);
377
- if (!seg) continue;
378
- const arr = segmentMap.get(seg) || [];
379
- arr.push(remainder);
380
- segmentMap.set(seg, arr);
381
- }
382
- const segEntries = Array.from(segmentMap.entries());
383
- const oneLevelRecordChildren = segEntries.filter(([seg]) => {
384
- const idx = parseIndexSegment(seg);
385
- const nestedObj = import_lodash.default.get(contextParams, [varName, seg]) ?? (idx ? import_lodash.default.get(contextParams, [varName, idx]) : void 0);
386
- const dotted = (contextParams || {})[`${varName}.${seg}`] ?? (idx ? (contextParams || {})[`${varName}.${idx}`] : void 0);
387
- return isRecordParams(nestedObj) || isRecordParams(dotted);
388
- });
389
- if (!oneLevelRecordChildren.length && deepRecordMap.size === 0) continue;
390
295
  flowCtx.defineProperty(varName, {
391
- get: () => {
392
- const root = new import_contexts.ServerBaseContext();
393
- const definedFirstLevel = /* @__PURE__ */ new Set();
394
- const defineRecordGetter = (container, key, recordParams, subPaths = [], preferFull) => {
395
- const strictSelects = Array.isArray(recordParams == null ? void 0 : recordParams.fields) || Array.isArray(recordParams == null ? void 0 : recordParams.appends);
396
- let { generatedAppends, generatedFields } = inferSelectsFromUsage(subPaths, recordParams);
397
- if (Array.isArray(recordParams == null ? void 0 : recordParams.fields)) generatedFields = recordParams.fields;
398
- if (Array.isArray(recordParams == null ? void 0 : recordParams.appends)) generatedAppends = recordParams.appends;
399
- container.defineProperty(key, {
400
- get: async () => {
401
- const dataSourceKey = (recordParams == null ? void 0 : recordParams.dataSourceKey) || "main";
402
- const fixed = (0, import_selects.adjustSelectsForCollection)(
403
- koaCtx,
404
- dataSourceKey,
405
- recordParams.collection,
406
- generatedFields,
407
- generatedAppends
408
- );
409
- return await fetchRecordWithRequestCache(
410
- koaCtx,
411
- dataSourceKey,
412
- recordParams.collection,
413
- recordParams.filterByTk,
414
- fixed.fields,
415
- fixed.appends,
416
- strictSelects,
417
- preferFull || ((subPaths == null ? void 0 : subPaths.length) ?? 0) === 0,
418
- recordParams.associationName,
419
- recordParams.sourceId
420
- );
421
- },
422
- cache: true
423
- });
424
- };
425
- const subContainers = /* @__PURE__ */ new Map();
426
- const ensureSubContainer = (parent, key) => {
427
- let map = subContainers.get(parent);
428
- if (!map) {
429
- map = /* @__PURE__ */ new Map();
430
- subContainers.set(parent, map);
431
- }
432
- let child = map.get(key);
433
- if (!child) {
434
- const inst = new import_contexts.ServerBaseContext();
435
- parent.defineProperty(key, { get: () => inst.createProxy(), cache: true });
436
- map.set(key, inst);
437
- child = inst;
438
- }
439
- return child;
440
- };
441
- for (const [seg, remainders] of oneLevelRecordChildren) {
442
- const idx = parseIndexSegment(seg);
443
- 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);
444
- let effRemainders = (remainders || []).filter((r) => !!r);
445
- if (!effRemainders.length) {
446
- const all = usedPaths.map(
447
- (p) => p.startsWith(`${seg}.`) ? p.slice(seg.length + 1) : p.startsWith(`${seg}[`) ? p.slice(seg.length) : ""
448
- ).filter((x) => !!x);
449
- if (all.length) effRemainders = all;
450
- }
451
- const hasDirectRefOne = (usedPaths || []).some((p) => p === seg || !!idx && p === `[${idx}]`);
452
- defineRecordGetter(root, idx ?? seg, recordParams, effRemainders, hasDirectRefOne);
453
- definedFirstLevel.add(idx ?? seg);
454
- }
455
- for (const [relative, recordParams] of deepRecordMap.entries()) {
456
- const segs = String(relative).split(".").filter(Boolean);
457
- if (segs.length === 0) continue;
458
- const first = segs[0];
459
- let container;
460
- if (definedFirstLevel.has(first)) {
461
- continue;
462
- } else {
463
- container = root;
464
- for (let i = 0; i < segs.length - 1; i++) {
465
- container = ensureSubContainer(container, segs[i]);
466
- }
467
- }
468
- const leaf = segs[segs.length - 1];
469
- const subPaths = (usedPaths || []).map((p) => p === relative ? "" : p.startsWith(relative + ".") ? p.slice(relative.length + 1) : "").filter((x) => x !== "");
470
- const hasDirectRef = (usedPaths || []).some((p) => p === relative);
471
- defineRecordGetter(container, leaf, recordParams, subPaths, hasDirectRef);
472
- }
473
- return root.createProxy();
474
- },
296
+ get: () => root.binding ? resolveNode(root) : createProxy(root),
475
297
  cache: true
476
298
  });
477
299
  }
@@ -481,30 +303,39 @@ function registerBuiltInVariables(reg) {
481
303
  name: "user",
482
304
  scope: "request",
483
305
  // no requiredParams: frontend will not pass context params for user
306
+ validateContextParams: ({ contextParams }) => ({
307
+ allowed: true,
308
+ contextParams: omitVariableContextParams(contextParams, "user")
309
+ }),
484
310
  attach: (flowCtx, koaCtx, _params, usage) => {
485
- const paths = (usage == null ? void 0 : usage["user"]) || [];
311
+ const paths = ((usage == null ? void 0 : usage.user) || []).map((path) => path.runtimeSegments);
486
312
  const { generatedAppends, generatedFields } = inferSelectsFromUsage(paths);
487
- flowCtx.defineProperty("user", {
488
- get: async () => {
489
- var _a;
490
- const authObj = koaCtx.auth;
491
- const uid = (_a = authObj == null ? void 0 : authObj.user) == null ? void 0 : _a.id;
492
- if (typeof uid === "undefined" || uid === null) return void 0;
493
- return await fetchRecordWithRequestCache(
494
- koaCtx,
495
- "main",
496
- "users",
497
- uid,
498
- generatedFields,
499
- generatedAppends,
500
- false,
501
- void 0,
502
- void 0,
503
- void 0
504
- );
313
+ flowCtx.defineProperty(
314
+ "user",
315
+ {
316
+ get: async () => {
317
+ var _a;
318
+ const authObj = koaCtx.auth;
319
+ const uid = (_a = authObj == null ? void 0 : authObj.user) == null ? void 0 : _a.id;
320
+ if (typeof uid === "undefined" || uid === null) return void 0;
321
+ return await (0, import_records.fetchRecordWithRequestCache)(
322
+ koaCtx,
323
+ { collection: "users", dataSourceKey: "main", filterByTk: uid },
324
+ generatedFields,
325
+ generatedAppends,
326
+ false
327
+ );
328
+ },
329
+ cache: true
505
330
  },
506
- cache: true
507
- });
331
+ import_contexts.SERVER_CONTEXT_PROVIDER_TOKEN
332
+ );
333
+ }
334
+ });
335
+ reg.register({
336
+ name: "popup",
337
+ scope: "request",
338
+ attach: () => {
508
339
  }
509
340
  });
510
341
  }
@@ -512,5 +343,9 @@ registerBuiltInVariables(variables);
512
343
  // Annotate the CommonJS export names for ESM import in node:
513
344
  0 && (module.exports = {
514
345
  inferSelectsFromUsage,
346
+ isSafeRecordBinding,
347
+ omitVariableContextParams,
348
+ registerBuiltInVariables,
349
+ sanitizeRegisteredVariableContextParams,
515
350
  variables
516
351
  });