@flusys/nestjs-entity-builder 9.1.2 → 9.2.1

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 (59) hide show
  1. package/README.md +49 -25
  2. package/config/entity-builder.constants.d.ts +1 -0
  3. package/config/message-keys.d.ts +37 -2
  4. package/controllers/definition-bundle.controller.d.ts +2 -2
  5. package/controllers/entity-definition.controller.d.ts +1 -1
  6. package/controllers/field-definition.controller.d.ts +1 -1
  7. package/controllers/flow-definition.controller.d.ts +3 -1
  8. package/controllers/flow-runtime.controller.d.ts +4 -3
  9. package/dtos/definition-bundle.dto.d.ts +1 -0
  10. package/dtos/entity-definition.dto.d.ts +1 -2
  11. package/fesm/21.js +39 -5
  12. package/fesm/{458.js → 28.js} +110 -63
  13. package/fesm/677.js +1543 -513
  14. package/fesm/794.js +1 -1
  15. package/fesm/857.js +3 -3
  16. package/fesm/870.js +974 -558
  17. package/fesm/897.js +187 -51
  18. package/fesm/946.js +72 -43
  19. package/fesm/config/index.js +10 -14
  20. package/fesm/docs/index.js +1 -1
  21. package/fesm/dtos/index.js +11 -0
  22. package/fesm/guards/index.js +16 -10
  23. package/fesm/index.js +44 -26
  24. package/fesm/modules/index.js +4 -3
  25. package/fesm/services/index.js +40 -59
  26. package/flow-engine/flow-code-runner.d.ts +16 -0
  27. package/flow-engine/flow-graph.validator.d.ts +1 -0
  28. package/flow-engine/flow-input.validator.d.ts +16 -1
  29. package/flow-engine/flow-run.types.d.ts +16 -0
  30. package/flow-engine/flow.types.d.ts +18 -1
  31. package/guards/designer-writable.interceptor.d.ts +1 -0
  32. package/interfaces/definition-bundle.interface.d.ts +4 -1
  33. package/interfaces/entity-builder-module.interface.d.ts +9 -0
  34. package/modules/entity-builder.module.d.ts +1 -0
  35. package/modules/index.d.ts +1 -0
  36. package/modules/tenant-context-id.strategy.d.ts +16 -0
  37. package/package.json +3 -4
  38. package/services/definition-bundle.diff.d.ts +6 -2
  39. package/services/definition-bundle.file.d.ts +2 -1
  40. package/services/definition-bundle.service.d.ts +10 -3
  41. package/services/entity-builder-config.service.d.ts +3 -0
  42. package/services/entity-builder-datasource.provider.d.ts +1 -0
  43. package/services/entity-flow-scaffold.d.ts +2 -1
  44. package/services/entity-permission.service.d.ts +1 -0
  45. package/services/field-value-validator.d.ts +7 -0
  46. package/services/flow-definition.service.d.ts +11 -3
  47. package/services/flow-executor.service.d.ts +3 -0
  48. package/services/flow-runtime.service.d.ts +7 -1
  49. package/services/generic-entity-service.factory.d.ts +5 -1
  50. package/services/generic-entity.service.d.ts +25 -2
  51. package/services/identifier-rules.d.ts +0 -1
  52. package/services/save-loaded.d.ts +2 -0
  53. package/services/schema-change-executor.service.d.ts +1 -0
  54. package/services/schema-columns.d.ts +7 -2
  55. package/services/schema-dialect-adapter.service.d.ts +2 -0
  56. package/services/schema-evolution.service.d.ts +8 -2
  57. package/services/schema-registry.service.d.ts +12 -0
  58. package/services/schema-sync.service.d.ts +3 -1
  59. package/flow-engine/flow-code-sandbox.d.ts +0 -5
package/fesm/677.js CHANGED
@@ -9,11 +9,13 @@ import * as __rspack_external_node_url_3991086a from "node:url";
9
9
  import * as __rspack_external_node_worker_threads_28ca7740 from "node:worker_threads";
10
10
  import * as __rspack_external__flusys_nestjs_shared_exceptions_3edf22e9 from "@flusys/nestjs-shared/exceptions";
11
11
  import * as __rspack_external_node_dns_c6249ebb from "node:dns";
12
+ import * as __rspack_external_typeorm_persistence_Subject_js_50dfc7e4 from "typeorm/persistence/Subject.js";
13
+ import * as __rspack_external_typeorm_persistence_SubjectExecutor_js_7cea48f1 from "typeorm/persistence/SubjectExecutor.js";
12
14
  export const __rspack_esm_id = 677;
13
15
  export const __rspack_esm_ids = [677];
14
16
  export const __webpack_modules__ = {
15
17
  982(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
16
- /** Every entity the entity builder module publishes under */ const ENTITY_BUILDER_EVENT_ENTITIES = {
18
+ /** The entity names the module publishes events under; also reserved as entity codes. */ const ENTITY_BUILDER_EVENT_ENTITIES = {
17
19
  ENTITY_DEFINITION: 'entity_definition',
18
20
  FIELD_DEFINITION: 'field_definition',
19
21
  SCHEMA_CHANGE_LOG: 'schema_change_log',
@@ -84,7 +86,7 @@ __webpack_require__.d(__webpack_exports__, {
84
86
 
85
87
 
86
88
  },
87
- 8333(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
89
+ 3931(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
88
90
 
89
91
  // EXPORTS
90
92
  __webpack_require__.d(__webpack_exports__, {
@@ -114,7 +116,7 @@ var flow_types = __webpack_require__(6256);
114
116
 
115
117
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/redact.ts
116
118
  var redact = __webpack_require__(2678);
117
- ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-code-sandbox.ts
119
+ ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-code-runner.ts
118
120
 
119
121
 
120
122
 
@@ -123,7 +125,7 @@ var redact = __webpack_require__(2678);
123
125
 
124
126
 
125
127
 
126
- const PRELUDE = `(function (input, hostLog, sensitive, maxChars) {
128
+ const PRELUDE = `(function (input, hostLog, sensitive, maxChars, hostRequire, hostFetch, hostTimers) {
127
129
  'use strict';
128
130
  const freeze = (o) => {
129
131
  if (o && typeof o === 'object' && !Object.isFrozen(o)) {
@@ -148,136 +150,191 @@ const PRELUDE = `(function (input, hostLog, sensitive, maxChars) {
148
150
  const held = (code) => typeof code === 'string' && granted.some((p) => p === code || p === '*' || (p.endsWith('.*') && code.startsWith(p.slice(0, -1))));
149
151
  const codes = (args) => args.flat().filter((c) => typeof c === 'string' && c);
150
152
  const constant = (name, value) => Object.defineProperty(globalThis, name, { value, writable: false, configurable: false });
153
+ const textOf = (body) => (typeof body === 'string' ? body : JSON.stringify(body));
154
+ const timer = (repeat) => (fn, ms, ...args) => (typeof fn === 'function' ? hostTimers.set(() => fn(...args), Number(ms) || 0, repeat) : 0);
155
+ const clear = (id) => { hostTimers.clear(id); };
151
156
  Object.defineProperty(globalThis, 'ctx', { value: ctx, writable: false, configurable: false, enumerable: true });
152
- constant('console', freeze({ log, info: log, warn: log, error: log }));
157
+ constant('console', freeze({ log, info: log, warn: log, error: log, debug: log }));
153
158
  constant('hasPermission', (code) => held(code));
154
159
  constant('hasAnyPermission', (...list) => codes(list).some(held));
155
160
  constant('hasAllPermissions', (...list) => { const c = codes(list); return c.length > 0 && c.every(held); });
161
+ constant('require', (name) => hostRequire(String(name)));
162
+ constant('fetch', async (url, init) => {
163
+ const options = init && typeof init === 'object' ? init : {};
164
+ const headers = {};
165
+ for (const [k, v] of Object.entries(options.headers && typeof options.headers === 'object' ? options.headers : {})) headers[k] = String(v);
166
+ const body = options.body === undefined || options.body === null ? undefined : textOf(options.body);
167
+ const r = JSON.parse(await hostFetch(String(url), String(options.method || 'GET').toUpperCase(), JSON.stringify(headers), body));
168
+ return freeze({
169
+ status: r.status,
170
+ ok: r.ok,
171
+ headers: r.headers,
172
+ text: async () => textOf(r.body),
173
+ json: async () => (typeof r.body === 'string' ? JSON.parse(r.body) : r.body),
174
+ });
175
+ });
176
+ constant('setTimeout', timer(false));
177
+ constant('setInterval', timer(true));
178
+ constant('clearTimeout', clear);
179
+ constant('clearInterval', clear);
156
180
  })`;
157
- const SERIALIZE = `(function (v) { const s = JSON.stringify(v); return s === undefined ? 'null' : s; })`;
158
- /** Runs in a worker thread, so a busy QuickJS loop never blocks the server's event loop. */ const WORKER_SOURCE = `
181
+ /** Runs in a worker thread, so a busy code step never blocks the server's event loop. */ const WORKER_SOURCE = `
159
182
  'use strict';
183
+ const vm = require('node:vm');
160
184
  const { parentPort, workerData } = require('node:worker_threads');
161
185
  const { createRequire } = require('node:module');
162
- const { bases, prelude, serialize, sensitive, limits, keys } = workerData;
163
- let quickJs;
164
-
165
- function load() {
166
- if (!quickJs) {
167
- let lastError = new Error('quickjs-emscripten could not be resolved');
168
- for (const base of bases) {
186
+ const { bases, prelude, sensitive, limits, keys } = workerData;
187
+ const requirers = bases.map((base) => createRequire(base));
188
+ const packages = new Map();
189
+ const fetches = new Map();
190
+ let fetchCalls = 0;
191
+ const TIMED_OUT = Symbol('timed out');
192
+ const HOST_GLOBALS = { Buffer, URL, URLSearchParams, TextEncoder, TextDecoder, AbortController, AbortSignal, atob, btoa, structuredClone };
193
+
194
+ // Code that drops a rejected promise must not take the worker down.
195
+ process.on('unhandledRejection', () => {});
196
+
197
+ function load(specifier) {
198
+ if (!packages.has(specifier)) {
199
+ let outcome = { error: new Error('The package "' + specifier + '" is not installed on the server.') };
200
+ for (const req of requirers) {
169
201
  try {
170
- quickJs = createRequire(base)('quickjs-emscripten').getQuickJS();
202
+ outcome = { value: req(specifier) };
171
203
  break;
172
204
  } catch (error) {
173
- lastError = error;
205
+ if (!error || error.code !== 'MODULE_NOT_FOUND') {
206
+ outcome = { error };
207
+ break;
208
+ }
174
209
  }
175
210
  }
176
- if (!quickJs) throw lastError;
177
- quickJs.catch(() => { quickJs = undefined; });
211
+ packages.set(specifier, outcome);
178
212
  }
179
- return quickJs;
213
+ const outcome = packages.get(specifier);
214
+ if (outcome.error) throw outcome.error;
215
+ return outcome.value;
180
216
  }
181
217
 
182
- const failure = (key, vars, detail, logs) => ({ failure: { key, vars, detail, logs } });
218
+ const failure = (key, vars, detail, logs, recycle) => ({ failure: { key, vars, detail, logs }, recycle });
183
219
 
184
- function toFailure(vm, errorHandle, job, deadline, logs) {
185
- let name = '';
186
- let message = '';
220
+ function describe(error) {
187
221
  try {
188
- const dumped = vm.dump(errorHandle);
189
- if (dumped && typeof dumped === 'object') {
190
- name = String(dumped.name ?? '');
191
- message = String(dumped.message ?? '');
192
- } else {
193
- message = String(dumped);
222
+ if (error && typeof error === 'object') {
223
+ const name = typeof error.name === 'string' ? error.name : '';
224
+ const message = typeof error.message === 'string' ? error.message : String(error);
225
+ return (name ? name + ': ' + message : message).slice(0, limits.logLineChars);
194
226
  }
227
+ return String(error).slice(0, limits.logLineChars);
195
228
  } catch (e) {
196
- message = 'unknown error';
229
+ return 'unknown error';
197
230
  }
198
- if (/out of memory/i.test(message)) {
199
- const mb = limits.memoryBytes / (1024 * 1024);
200
- return failure(keys.outOfMemory, { node: job.node, mb }, 'The code used more than ' + mb + ' MB of memory.', logs);
201
- }
202
- if ((name === 'InternalError' && message === 'interrupted') || Date.now() > deadline) {
203
- return failure(keys.timeout, { node: job.node, ms: job.timeoutMs }, 'The code ran longer than ' + job.timeoutMs + ' ms.', logs);
204
- }
205
- const detail = (name ? name + ': ' + message : message).slice(0, limits.logLineChars);
206
- return failure(keys.threw, { node: job.node, detail }, detail, logs);
207
231
  }
208
232
 
209
- function run(module, job) {
233
+ async function run(job) {
210
234
  const logs = [];
211
235
  const deadline = Date.now() + job.timeoutMs;
212
- const runtime = module.newRuntime();
213
- runtime.setMemoryLimit(limits.memoryBytes);
214
- runtime.setMaxStackSize(limits.stackBytes);
215
- runtime.setInterruptHandler(() => Date.now() > deadline);
216
- const vm = runtime.newContext();
217
- const handles = [];
218
- const keep = (handle) => (handles.push(handle), handle);
219
- const unwrap = (result) => {
220
- if (result.error) {
221
- keep(result.error);
222
- throw toFailure(vm, result.error, job, deadline, logs);
223
- }
224
- return keep(result.value);
236
+ const remaining = () => Math.max(1, deadline - Date.now());
237
+ const timedOut = (recycle) => failure(keys.timeout, { node: job.node, ms: job.timeoutMs }, 'The code ran longer than ' + job.timeoutMs + ' ms.', logs, recycle);
238
+ const timers = new Map();
239
+ let nextTimer = 1;
240
+ let failAsync;
241
+ const asyncFailure = new Promise((resolve, reject) => { failAsync = reject; });
242
+ asyncFailure.catch(() => {});
243
+
244
+ const modules = job.modules;
245
+ // Loaded before the clock starts, so a package's first load never eats the code's time limit.
246
+ for (const specifier of Object.values(modules)) {
247
+ try { load(specifier); } catch (e) { /* reported when the code asks for it */ }
248
+ }
249
+ const hostRequire = (name) => {
250
+ if (!Object.hasOwn(modules, name)) {
251
+ const allowed = Object.keys(modules).join(', ') || 'none';
252
+ throw new Error('The package "' + name + '" is not allowed. Allowed: ' + allowed + '.');
253
+ }
254
+ return load(modules[name]);
225
255
  };
256
+ const hostFetch = (url, method, headers, body) =>
257
+ new Promise((resolve, reject) => {
258
+ const call = ++fetchCalls;
259
+ fetches.set(call, { resolve, reject });
260
+ parentPort.postMessage({ type: 'fetch', id: job.id, call, request: { url, method, headers: JSON.parse(headers), body } });
261
+ });
262
+ const hostTimers = {
263
+ set(fn, ms, repeat) {
264
+ const id = nextTimer++;
265
+ const tick = () => {
266
+ if (!repeat) timers.delete(id);
267
+ try { fn(); } catch (error) { failAsync(error); }
268
+ };
269
+ timers.set(id, repeat ? setInterval(tick, Math.max(0, ms)) : setTimeout(tick, Math.max(0, ms)));
270
+ return id;
271
+ },
272
+ clear(id) {
273
+ const handle = timers.get(id);
274
+ if (handle === undefined) return;
275
+ clearTimeout(handle);
276
+ timers.delete(id);
277
+ },
278
+ };
279
+ const hostLog = (line) => {
280
+ if (logs.length < limits.logLines) logs.push(String(line));
281
+ };
282
+
283
+ const context = vm.createContext({ ...HOST_GLOBALS }, { name: 'flow-code', codeGeneration: { strings: true, wasm: false } });
284
+ let watch;
226
285
  try {
227
- const hostLog = keep(vm.newFunction('log', (line) => {
228
- if (logs.length < limits.logLines) logs.push(vm.getString(line));
229
- }));
230
- const preludeFn = unwrap(vm.evalCode(prelude, 'flow-prelude.js'));
231
- unwrap(vm.callFunction(preludeFn, vm.undefined, keep(vm.newString(job.input)), hostLog, keep(vm.newString(sensitive)), keep(vm.newNumber(limits.logLineChars))));
232
-
233
- const userFn = unwrap(vm.evalCode('(function () {\\n\\'use strict\\';\\n' + job.code + '\\n})', 'flow-code.js'));
234
- if (vm.typeof(userFn) !== 'function') return failure(keys.threw, { node: job.node, detail: 'The code is not a function body.' }, 'The code is not a function body.', logs);
235
- const resultHandle = unwrap(vm.callFunction(userFn, vm.undefined));
236
-
237
- const serializer = unwrap(vm.evalCode(serialize, 'flow-serialize.js'));
238
- const serialized = vm.callFunction(serializer, vm.undefined, resultHandle);
239
- if (serialized.error) {
240
- keep(serialized.error);
241
- if (Date.now() > deadline) return toFailure(vm, serialized.error, job, deadline, logs);
286
+ vm.runInContext(prelude, context, { filename: 'flow-prelude.js' })(job.input, hostLog, sensitive, limits.logLineChars, hostRequire, hostFetch, hostTimers);
287
+ const pending = vm.runInContext("(async function () {\\n'use strict';\\n" + job.code + '\\n})()', context, { filename: 'flow-code.js', timeout: job.timeoutMs });
288
+ const clock = new Promise((resolve, reject) => { watch = setTimeout(() => reject(TIMED_OUT), remaining()); });
289
+ const value = await Promise.race([pending, clock, asyncFailure]);
290
+ clearTimeout(watch);
291
+
292
+ const slot = '__flow_result_' + job.id;
293
+ Object.defineProperty(context, slot, { value, configurable: true });
294
+ let json;
295
+ try {
296
+ json = vm.runInContext('(function (v) { const s = JSON.stringify(v); return s === undefined ? "null" : s; })(globalThis.' + slot + ')', context, { filename: 'flow-serialize.js', timeout: remaining() });
297
+ } catch (error) {
298
+ if (error && error.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT') return timedOut(false);
242
299
  return failure(keys.outputInvalid, { node: job.node }, 'The code returned a value that is not JSON.', logs);
243
300
  }
244
- const json = vm.getString(keep(serialized.value));
245
301
  if (Buffer.byteLength(json, 'utf8') > limits.outputBytes) {
246
302
  return failure(keys.outputTooLarge, { node: job.node, bytes: limits.outputBytes }, 'The code output is larger than ' + limits.outputBytes + ' bytes.', logs);
247
303
  }
248
304
  return { json, logs };
249
305
  } catch (error) {
250
- if (error && error.failure) return error;
251
- const detail = String((error && error.message) || error).slice(0, limits.logLineChars);
306
+ // Work the code left running after its deadline would carry on into the next run, so that worker is replaced.
307
+ if (error === TIMED_OUT) return timedOut(true);
308
+ if (error && error.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT') return timedOut(false);
309
+ const detail = describe(error);
252
310
  return failure(keys.threw, { node: job.node, detail }, detail, logs);
253
311
  } finally {
254
- for (let i = handles.length - 1; i >= 0; i--) {
255
- if (handles[i].alive) handles[i].dispose();
256
- }
257
- vm.dispose();
258
- runtime.dispose();
312
+ clearTimeout(watch);
313
+ for (const handle of timers.values()) clearTimeout(handle);
314
+ timers.clear();
315
+ fetches.clear();
259
316
  }
260
317
  }
261
318
 
262
- parentPort.on('message', async (job) => {
263
- let module;
264
- try {
265
- module = await load();
266
- } catch (error) {
267
- const detail = 'The code sandbox could not be loaded: ' + String((error && error.message) || error);
268
- parentPort.postMessage({ id: job.id, ...failure(keys.unavailable, { node: job.node }, detail, []) });
319
+ parentPort.on('message', async (message) => {
320
+ if (message.type === 'fetch') {
321
+ const pending = fetches.get(message.call);
322
+ if (!pending) return;
323
+ fetches.delete(message.call);
324
+ if (message.error !== undefined) pending.reject(new Error(message.error));
325
+ else pending.resolve(message.json);
269
326
  return;
270
327
  }
271
- parentPort.postMessage({ id: job.id, ...run(module, job) });
328
+ parentPort.postMessage({ type: 'done', id: message.id, ...(await run(message)) });
272
329
  });
273
330
  `;
274
- const CODE_SANDBOX_LIMITS = {
331
+ const CODE_RUNNER_LIMITS = {
275
332
  MAX_WORKERS: Math.max(1, Math.min(4, (0,__rspack_external_node_os_4f3c9d58.availableParallelism)() - 1)),
276
333
  MAX_QUEUED_RUNS: 200,
277
- /** Extra time the host waits past the in-sandbox deadline before killing the worker thread. */ WATCHDOG_GRACE_MS: 1000,
278
- WORKER_HEAP_MB: 64
334
+ /** Extra time the host waits past the code's deadline before killing the worker thread. */ WATCHDOG_GRACE_MS: 1000,
335
+ /** How often the host checks a running worker's memory: buffers live outside the heap limit a worker can set itself. */ MEMORY_CHECK_MS: 20
279
336
  };
280
- /** Where the worker resolves `quickjs-emscripten` from: this module's own file first, then the app. */ function resolutionBases() {
337
+ /** Where the worker resolves the configured packages from: this module's own file first, then the app. */ function resolutionBases() {
281
338
  const bases = [];
282
339
  const original = Error.prepareStackTrace;
283
340
  try {
@@ -297,39 +354,51 @@ const CODE_SANDBOX_LIMITS = {
297
354
  const WORKER_DATA = {
298
355
  bases: resolutionBases(),
299
356
  prelude: PRELUDE,
300
- serialize: SERIALIZE,
301
357
  sensitive: redact/* .SENSITIVE_KEY.source */.X6.source,
302
358
  limits: {
303
- memoryBytes: flow_types/* .FLOW_LIMITS.CODE_MEMORY_LIMIT_BYTES */.Or.CODE_MEMORY_LIMIT_BYTES,
304
- stackBytes: flow_types/* .FLOW_LIMITS.CODE_STACK_SIZE_BYTES */.Or.CODE_STACK_SIZE_BYTES,
305
359
  outputBytes: flow_types/* .FLOW_LIMITS.MAX_CODE_OUTPUT_BYTES */.Or.MAX_CODE_OUTPUT_BYTES,
306
360
  logLines: flow_types/* .FLOW_LIMITS.MAX_CODE_LOG_LINES */.Or.MAX_CODE_LOG_LINES,
307
361
  logLineChars: flow_types/* .FLOW_LIMITS.MAX_CODE_LOG_LINE_CHARS */.Or.MAX_CODE_LOG_LINE_CHARS
308
362
  },
309
363
  keys: {
310
- unavailable: message_keys/* .FLOW_ERROR_MESSAGES.CODE_UNAVAILABLE */.zr.CODE_UNAVAILABLE,
311
364
  threw: message_keys/* .FLOW_ERROR_MESSAGES.CODE_THREW */.zr.CODE_THREW,
312
365
  timeout: message_keys/* .FLOW_ERROR_MESSAGES.CODE_TIMEOUT */.zr.CODE_TIMEOUT,
313
- outOfMemory: message_keys/* .FLOW_ERROR_MESSAGES.CODE_OUT_OF_MEMORY */.zr.CODE_OUT_OF_MEMORY,
314
366
  outputInvalid: message_keys/* .FLOW_ERROR_MESSAGES.CODE_OUTPUT_INVALID */.zr.CODE_OUTPUT_INVALID,
315
367
  outputTooLarge: message_keys/* .FLOW_ERROR_MESSAGES.CODE_OUTPUT_TOO_LARGE */.zr.CODE_OUTPUT_TOO_LARGE
316
368
  }
317
369
  };
370
+ /** The worker gets none of the server's secrets: only the variables packages commonly read. */ function workerEnv() {
371
+ return Object.fromEntries([
372
+ 'NODE_ENV',
373
+ 'TZ',
374
+ 'LANG'
375
+ ].flatMap((name)=>process.env[name] ? [
376
+ [
377
+ name,
378
+ process.env[name]
379
+ ]
380
+ ] : []));
381
+ }
382
+ const MEMORY_MB = flow_types/* .FLOW_LIMITS.CODE_MEMORY_LIMIT_BYTES */.Or.CODE_MEMORY_LIMIT_BYTES / (1024 * 1024);
318
383
  const timeoutError = (job)=>new flow_errors/* .FlowCodeError */.yL(message_keys/* .FLOW_ERROR_MESSAGES.CODE_TIMEOUT */.zr.CODE_TIMEOUT, {
319
384
  node: job.node,
320
385
  ms: job.timeoutMs
321
386
  }, `The code ran longer than ${job.timeoutMs} ms.`);
387
+ const outOfMemoryError = (job)=>new flow_errors/* .FlowCodeError */.yL(message_keys/* .FLOW_ERROR_MESSAGES.CODE_OUT_OF_MEMORY */.zr.CODE_OUT_OF_MEMORY, {
388
+ node: job.node,
389
+ mb: MEMORY_MB
390
+ }, `The code used more than ${MEMORY_MB} MB of memory.`);
322
391
  const unavailableError = (node, detail)=>new flow_errors/* .FlowCodeError */.yL(message_keys/* .FLOW_ERROR_MESSAGES.CODE_UNAVAILABLE */.zr.CODE_UNAVAILABLE, {
323
392
  node
324
- }, `The code sandbox could not run the code: ${detail}`);
325
- let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
393
+ }, `The code runner could not run the code: ${detail}`);
394
+ let flow_code_runner_CodeRunnerPool = class CodeRunnerPool {
326
395
  idle = [];
327
396
  running = new Map();
328
397
  queue = [];
329
398
  workers = 0;
330
399
  nextId = 1;
331
- run(code, input, timeoutMs, node) {
332
- if (this.queue.length >= CODE_SANDBOX_LIMITS.MAX_QUEUED_RUNS) {
400
+ run(code, input, timeoutMs, node, options) {
401
+ if (this.queue.length >= CODE_RUNNER_LIMITS.MAX_QUEUED_RUNS) {
333
402
  return Promise.reject(unavailableError(node, 'too many code steps are waiting to run.'));
334
403
  }
335
404
  return new Promise((resolve, reject)=>{
@@ -339,6 +408,8 @@ let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
339
408
  input,
340
409
  timeoutMs,
341
410
  node,
411
+ modules: options.modules ?? {},
412
+ http: options.http,
342
413
  resolve,
343
414
  reject
344
415
  });
@@ -346,7 +417,7 @@ let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
346
417
  });
347
418
  }
348
419
  dispatch() {
349
- while(this.queue.length && (this.idle.length || this.workers < CODE_SANDBOX_LIMITS.MAX_WORKERS)){
420
+ while(this.queue.length && (this.idle.length || this.workers < CODE_RUNNER_LIMITS.MAX_WORKERS)){
350
421
  const job = this.queue.shift();
351
422
  let worker;
352
423
  try {
@@ -361,66 +432,96 @@ let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
361
432
  start(worker, job) {
362
433
  this.running.set(worker, job);
363
434
  worker.ref();
364
- job.watchdog = setTimeout(()=>{
365
- this.running.delete(worker);
366
- job.reject(timeoutError(job));
367
- void worker.terminate();
368
- }, job.timeoutMs + CODE_SANDBOX_LIMITS.WATCHDOG_GRACE_MS);
435
+ job.watchdog = setTimeout(()=>this.kill(worker, job, timeoutError(job)), job.timeoutMs + CODE_RUNNER_LIMITS.WATCHDOG_GRACE_MS);
436
+ job.memoryWatch = setInterval(()=>{
437
+ worker.getHeapStatistics().then((stats)=>{
438
+ const external = stats.external_memory ?? 0;
439
+ if (stats.used_heap_size + external > flow_types/* .FLOW_LIMITS.CODE_MEMORY_LIMIT_BYTES */.Or.CODE_MEMORY_LIMIT_BYTES) this.kill(worker, job, outOfMemoryError(job));
440
+ }, ()=>undefined);
441
+ }, CODE_RUNNER_LIMITS.MEMORY_CHECK_MS);
369
442
  // oxlint-disable-next-line unicorn/require-post-message-target-origin -- a worker_threads Worker has no origin
370
443
  worker.postMessage({
371
444
  id: job.id,
372
445
  code: job.code,
373
446
  input: job.input,
374
447
  timeoutMs: job.timeoutMs,
375
- node: job.node
448
+ node: job.node,
449
+ modules: job.modules
376
450
  });
377
451
  }
452
+ kill(worker, job, error) {
453
+ if (this.running.get(worker) !== job) return;
454
+ this.take(worker);
455
+ job.reject(error);
456
+ void worker.terminate();
457
+ }
378
458
  spawn() {
379
459
  const worker = new __rspack_external_node_worker_threads_28ca7740.Worker(WORKER_SOURCE, {
380
460
  eval: true,
381
461
  workerData: WORKER_DATA,
462
+ env: workerEnv(),
382
463
  resourceLimits: {
383
- maxOldGenerationSizeMb: CODE_SANDBOX_LIMITS.WORKER_HEAP_MB
464
+ maxOldGenerationSizeMb: MEMORY_MB
384
465
  }
385
466
  });
386
467
  this.workers++;
387
- worker.on('message', (reply)=>this.settle(worker, reply));
468
+ worker.on('message', (message)=>message.type === 'fetch' ? this.fetch(worker, message) : this.settle(worker, message));
388
469
  worker.on('error', (error)=>{
389
470
  const job = this.take(worker);
390
471
  if (!job) return;
391
- if (error.code === 'ERR_WORKER_OUT_OF_MEMORY') {
392
- const mb = flow_types/* .FLOW_LIMITS.CODE_MEMORY_LIMIT_BYTES */.Or.CODE_MEMORY_LIMIT_BYTES / (1024 * 1024);
393
- job.reject(new flow_errors/* .FlowCodeError */.yL(message_keys/* .FLOW_ERROR_MESSAGES.CODE_OUT_OF_MEMORY */.zr.CODE_OUT_OF_MEMORY, {
394
- node: job.node,
395
- mb
396
- }, `The code used more than ${mb} MB of memory.`));
397
- } else {
398
- job.reject(unavailableError(job.node, error.message));
399
- }
472
+ job.reject(error.code === 'ERR_WORKER_OUT_OF_MEMORY' ? outOfMemoryError(job) : unavailableError(job.node, error.message));
400
473
  });
401
474
  worker.on('exit', ()=>{
402
475
  this.workers--;
403
476
  const index = this.idle.indexOf(worker);
404
477
  if (index >= 0) this.idle.splice(index, 1);
405
478
  const job = this.take(worker);
406
- job?.reject(unavailableError(job.node, 'the sandbox worker stopped.'));
479
+ job?.reject(unavailableError(job.node, 'the code worker stopped.'));
407
480
  this.dispatch();
408
481
  });
409
482
  return worker;
410
483
  }
484
+ fetch(worker, message) {
485
+ const job = this.running.get(worker);
486
+ if (!job || job.id !== message.id) return;
487
+ const reply = (answer)=>{
488
+ // oxlint-disable-next-line unicorn/require-post-message-target-origin -- a worker_threads Worker has no origin
489
+ if (this.running.get(worker) === job) worker.postMessage({
490
+ type: 'fetch',
491
+ call: message.call,
492
+ ...answer
493
+ });
494
+ };
495
+ if (!job.http) {
496
+ reply({
497
+ error: 'fetch is not available here.'
498
+ });
499
+ return;
500
+ }
501
+ job.http(message.request).then((response)=>reply({
502
+ json: JSON.stringify(response)
503
+ }), (error)=>reply({
504
+ error: error instanceof Error ? error.message : String(error)
505
+ }));
506
+ }
411
507
  take(worker) {
412
508
  const job = this.running.get(worker);
413
509
  if (!job) return undefined;
414
510
  this.running.delete(worker);
415
511
  clearTimeout(job.watchdog);
512
+ clearInterval(job.memoryWatch);
416
513
  return job;
417
514
  }
418
515
  settle(worker, reply) {
419
516
  const job = this.running.get(worker);
420
517
  if (!job || job.id !== reply.id) return;
421
518
  this.take(worker);
422
- worker.unref();
423
- this.idle.push(worker);
519
+ if (reply.recycle) {
520
+ void worker.terminate();
521
+ } else {
522
+ worker.unref();
523
+ this.idle.push(worker);
524
+ }
424
525
  if (reply.failure) {
425
526
  const { key, vars, detail, logs } = reply.failure;
426
527
  job.reject(new flow_errors/* .FlowCodeError */.yL(key, vars, detail, logs));
@@ -435,14 +536,16 @@ let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
435
536
  };
436
537
  let pool;
437
538
  /**
438
- * Runs author-written JavaScript in a QuickJS (WebAssembly) sandbox on a worker thread: a fresh
439
- * runtime + context per run, a memory limit, a stack limit and an interrupt deadline, plus a host
440
- * watchdog that kills the worker if the deadline is overrun. Nothing of the host is reachable -
441
- * no require/process/network/filesystem - except a console.log that is captured. The code gets a
442
- * frozen JSON copy of its input as `ctx` and must return JSON-serializable data.
443
- * `hasPermission` / `hasAnyPermission` / `hasAllPermissions` are plain JavaScript inside the sandbox,
444
- * reading `ctx.user.permissions` with the same `*` / `prefix.*` wildcards as the server.
445
- */ async function runSandboxedCode(code, ctx, timeoutMs, node) {
539
+ * Runs author-written JavaScript on the server, in Node, on a worker thread (a small pool, so a
540
+ * busy code step never blocks the event loop). Each run gets a fresh `vm` context holding a frozen
541
+ * JSON copy of its input as `ctx`, a captured `console`, the permission helpers, `require` for the
542
+ * packages the app allows, a `fetch` the host carries out with the HTTP step's rules, timers, and
543
+ * the common web globals. The code body is async and must return JSON-serializable data.
544
+ * Limits: the time limit (synchronous code is interrupted; a run still busy after it plus a grace
545
+ * period has its worker killed), the worker's memory (heap plus buffers, checked by the host) and
546
+ * the output size. The worker sees none of the server's env variables. This is not a security
547
+ * boundary: a package's objects lead back to Node itself, so code steps are for trusted authors.
548
+ */ async function runFlowCode(code, ctx, timeoutMs, node, options = {}) {
446
549
  let input;
447
550
  try {
448
551
  input = JSON.stringify(ctx) ?? '{}';
@@ -452,12 +555,14 @@ let pool;
452
555
  detail: error.message
453
556
  }, error.message);
454
557
  }
455
- pool ??= new flow_code_sandbox_CodeSandboxPool();
456
- return pool.run(code, input, timeoutMs, node);
558
+ pool ??= new flow_code_runner_CodeRunnerPool();
559
+ return pool.run(code, input, timeoutMs, node, options);
457
560
  }
458
561
 
459
562
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-graph.validator.ts
460
563
  var flow_graph_validator = __webpack_require__(3186);
564
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-input.validator.ts
565
+ var flow_input_validator = __webpack_require__(6390);
461
566
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/rule-values.ts
462
567
  var rule_values = __webpack_require__(6622);
463
568
  ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-node-handlers.ts
@@ -469,6 +574,7 @@ var rule_values = __webpack_require__(6622);
469
574
 
470
575
 
471
576
 
577
+
472
578
  /** With `env`, `lookup` expressions read through the flow (its transaction). */ function evaluationContext(state, env) {
473
579
  const base = {
474
580
  input: state.input,
@@ -678,7 +784,7 @@ async function isGrantedScope(userId, companyId, branchId, env) {
678
784
  ]);
679
785
  }
680
786
  const USES_PERMISSIONS = /\b(permissions|hasPermission|hasAnyPermission|hasAllPermissions)\b/;
681
- /** The caller as a code step sees it; permission codes (current branch) are fetched only for code that reads them. */ async function codeUser(code, state) {
787
+ /** The caller as a code step sees it; permission codes (current branch) are fetched only for code that reads them. */ async function codeUser(code, env, state) {
682
788
  const user = state.user;
683
789
  if (!user) return null;
684
790
  const base = {
@@ -689,12 +795,27 @@ const USES_PERMISSIONS = /\b(permissions|hasPermission|hasAnyPermission|hasAllPe
689
795
  branchId: user.branchId ?? null
690
796
  };
691
797
  if (!USES_PERMISSIONS.test(code)) return base;
798
+ const permissions = state.permissions ?? env.ruleEngine.createPermissionResolver(user);
692
799
  return {
693
800
  ...base,
694
- permissions: await state.permissions?.granted(undefined) ?? []
801
+ permissions: await permissions.granted(undefined)
695
802
  };
696
803
  }
697
- async function runCode(node, cfg, state) {
804
+ /** The code's `fetch`, held to the rules of an HTTP request step: skipped in a test run, counted, private addresses refused. */ function codeHttp(env, state) {
805
+ return (request)=>{
806
+ if (state.mode === 'dry_run') return Promise.reject(new Error('fetch does not run in a test run, like an HTTP request step.'));
807
+ if (++state.httpCalls > flow_types/* .FLOW_LIMITS.MAX_HTTP_CALLS */.Or.MAX_HTTP_CALLS) return Promise.reject(new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.TOO_MANY_HTTP_CALLS */.zr.TOO_MANY_HTTP_CALLS, {
808
+ max: flow_types/* .FLOW_LIMITS.MAX_HTTP_CALLS */.Or.MAX_HTTP_CALLS
809
+ }));
810
+ const timeoutMs = Math.max(1, Math.min(flow_types/* .FLOW_LIMITS.MAX_HTTP_TIMEOUT_MS */.Or.MAX_HTTP_TIMEOUT_MS, state.deadline - Date.now()));
811
+ return env.http({
812
+ ...request,
813
+ timeoutMs,
814
+ allowPrivate: env.allowPrivateHttp
815
+ });
816
+ };
817
+ }
818
+ async function runCode(node, cfg, env, state) {
698
819
  const remaining = state.deadline - Date.now();
699
820
  if (remaining <= 0) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.OUT_OF_TIME */.zr.OUT_OF_TIME);
700
821
  const timeoutMs = Math.min(cfg.timeoutMs ?? flow_types/* .FLOW_LIMITS.DEFAULT_CODE_TIMEOUT_MS */.Or.DEFAULT_CODE_TIMEOUT_MS, flow_types/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS, remaining);
@@ -703,9 +824,12 @@ async function runCode(node, cfg, state) {
703
824
  vars: state.vars,
704
825
  context: state.context,
705
826
  loop: state.loop ?? null,
706
- user: await codeUser(cfg.code, state)
827
+ user: await codeUser(cfg.code, env, state)
707
828
  };
708
- const { output, logs } = await runSandboxedCode(cfg.code, ctx, timeoutMs, (0,flow_types/* .nodeLabel */.dA)(node));
829
+ const { output, logs } = await runFlowCode(cfg.code, ctx, timeoutMs, (0,flow_types/* .nodeLabel */.dA)(node), {
830
+ modules: env.codeModules,
831
+ http: codeHttp(env, state)
832
+ });
709
833
  return {
710
834
  output,
711
835
  ...logs.length ? {
@@ -723,7 +847,136 @@ function writeOptions(state) {
723
847
  return state.qr ? {
724
848
  queryRunner: state.qr,
725
849
  publishEvents: false
850
+ } : {
851
+ statementTimeoutMs: state.deadline - Date.now()
852
+ };
853
+ }
854
+ /** A get_all request's own query: what a `fromRequest` step reads, checked by the get_all rules whatever the Request step's type. A list body has none. */ function requestQuery(state) {
855
+ const { query, errors } = (0,flow_input_validator/* .readRequestQuery */.M2)(state.input);
856
+ if (errors.length) throw new flow_errors/* .FlowHttpError */.hk(400, 'Validation failed', errors, {
857
+ messageKey: message_keys/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID
858
+ });
859
+ return query;
860
+ }
861
+ /** A request's filter the way the API controller reads it: a value matches as equal, a list as one of its items - never an operator the caller sent. */ function requestFilter(filter) {
862
+ return Object.fromEntries(Object.entries(filter ?? {}).map(([key, value])=>[
863
+ key,
864
+ {
865
+ op: Array.isArray(value) ? 'in' : 'eq',
866
+ value
867
+ }
868
+ ]));
869
+ }
870
+ /** Only the fields a request's `select` names, `id` always kept; every field when it names none. */ function selectFields(rows, select) {
871
+ if (!select?.length) return rows;
872
+ const kept = new Set([
873
+ 'id',
874
+ ...select
875
+ ]);
876
+ return rows.map((row)=>Object.fromEntries(Object.entries(row).filter(([key])=>kept.has(key))));
877
+ }
878
+ async function runQuery(node, cfg, env, state) {
879
+ const ctx = evaluationContext(state, env);
880
+ const service = await env.entityService(cfg.entityCode, state.qr);
881
+ const request = cfg.fromRequest ? requestQuery(state) : {};
882
+ const filter = {
883
+ ...requestFilter(request.filter),
884
+ ...await mapFilter(node, cfg.filter, env, state, 'skip')
885
+ };
886
+ const searched = cfg.search ? await env.ruleEngine.resolveExpression(cfg.search, ctx) : cfg.fromRequest ? state.request['query']?.['q'] : undefined;
887
+ const search = typeof searched === 'string' && searched ? searched : undefined;
888
+ const sort = cfg.sort ? {
889
+ [cfg.sort.field]: cfg.sort.direction === 'DESC' ? 'DESC' : 'ASC'
890
+ } : request.sort;
891
+ const page = request.pagination?.currentPage ?? 0;
892
+ const pageSize = Math.min(request.pagination?.pageSize ?? cfg.limit ?? flow_types/* .FLOW_LIMITS.DEFAULT_QUERY_LIMIT */.Or.DEFAULT_QUERY_LIMIT, flow_types/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT);
893
+ const withDeleted = request.withDeleted === true ? {
894
+ withDeleted: true
726
895
  } : {};
896
+ const { data, total } = await service.getAll(search, {
897
+ filter,
898
+ sort,
899
+ pagination: {
900
+ currentPage: page,
901
+ pageSize
902
+ },
903
+ ...withDeleted
904
+ }, readOptions(state));
905
+ return {
906
+ output: {
907
+ items: selectFields(data, request.select),
908
+ total,
909
+ page,
910
+ pageSize
911
+ }
912
+ };
913
+ }
914
+ /** A Respond step's body: as built (raw), or the API controller envelope around it. */ async function respondBody(node, cfg, env, state) {
915
+ const ctx = evaluationContext(state, env);
916
+ const format = cfg.format ?? 'raw';
917
+ const built = async ()=>cfg.bodyExpression ? env.ruleEngine.resolveExpression(cfg.bodyExpression, ctx) : mapValues(cfg.body, env, state);
918
+ if (format === 'raw') return built();
919
+ const variables = await mapValues(cfg.messageVariables, env, state);
920
+ const envelope = {
921
+ success: cfg.statusCode < 400,
922
+ message: cfg.message ?? '',
923
+ messageKey: cfg.messageKey ?? '',
924
+ ...Object.keys(variables).length ? {
925
+ messageVariables: variables
926
+ } : {}
927
+ };
928
+ if (format === 'message') return envelope;
929
+ if (format === 'single') return {
930
+ ...envelope,
931
+ data: await built()
932
+ };
933
+ const value = await built();
934
+ if (value !== null && value !== undefined && !Array.isArray(value)) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.RESPOND_DATA_NOT_LIST */.zr.RESPOND_DATA_NOT_LIST, {
935
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
936
+ });
937
+ const data = value ?? [];
938
+ const count = async (name, fallback)=>{
939
+ const expression = cfg[name];
940
+ if (expression === undefined) return fallback;
941
+ const resolved = await env.ruleEngine.resolveExpression(expression, ctx);
942
+ if (resolved === null || resolved === undefined || resolved === '') return fallback;
943
+ const n = Number(resolved);
944
+ if (!Number.isInteger(n) || n < 0) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.RESPOND_META_INVALID */.zr.RESPOND_META_INVALID, {
945
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
946
+ name
947
+ });
948
+ return n;
949
+ };
950
+ if (format === 'bulk') {
951
+ const total = await count('total', Array.isArray(state.input) ? state.input.length : data.length);
952
+ return {
953
+ ...envelope,
954
+ data,
955
+ meta: {
956
+ count: data.length,
957
+ total,
958
+ failed: Math.max(total - data.length, 0)
959
+ }
960
+ };
961
+ }
962
+ // Only the defaults of the list meta: a request whose paging cannot be read falls back to them.
963
+ const pagination = (0,flow_input_validator/* .readRequestQuery */.M2)(state.input).query.pagination;
964
+ const total = await count('total', data.length);
965
+ const page = await count('page', pagination?.currentPage ?? 0);
966
+ const pageSize = await count('pageSize', pagination?.pageSize ?? data.length);
967
+ const totalPages = pageSize > 0 ? Math.ceil(total / pageSize) : 1;
968
+ return {
969
+ ...envelope,
970
+ data,
971
+ meta: {
972
+ total,
973
+ page,
974
+ pageSize,
975
+ count: data.length,
976
+ hasMore: page < totalPages - 1,
977
+ totalPages
978
+ }
979
+ };
727
980
  }
728
981
  function readOptions(state) {
729
982
  return {
@@ -793,6 +1046,20 @@ async function updateRecord(entityCode, service, values, state) {
793
1046
  });
794
1047
  return saved;
795
1048
  }
1049
+ /** An update by filter's match: the row was just read on this transaction, so the service need not read it again. */ async function updateMatched(entityCode, service, row, values, state) {
1050
+ if (!service.updateLoaded) return updateRecord(entityCode, service, {
1051
+ ...values,
1052
+ id: row.id
1053
+ }, state);
1054
+ countWrite(state);
1055
+ const saved = await service.updateLoaded(row, values, state.user, writeOptions(state));
1056
+ if (state.qr) state.pendingEvents.push({
1057
+ entityCode,
1058
+ action: 'updated',
1059
+ row: saved
1060
+ });
1061
+ return saved;
1062
+ }
796
1063
  /** @returns false when the record does not exist and `skip` allows that. */ async function deleteRecord(entityCode, service, id, skipMissing, state) {
797
1064
  countWrite(state);
798
1065
  let action;
@@ -822,11 +1089,12 @@ async function updateRecord(entityCode, service, values, state) {
822
1089
  });
823
1090
  }
824
1091
  const limit = Math.min(cfg.limit ?? flow_types/* .FLOW_LIMITS.DEFAULT_WRITE_FILTER_LIMIT */.Or.DEFAULT_WRITE_FILTER_LIMIT, flow_types/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS);
1092
+ // One row past the limit: getAll then counts only when there are too many, to name how many.
825
1093
  const { data, total } = await service.getAll(undefined, {
826
1094
  filter,
827
1095
  pagination: {
828
1096
  currentPage: 0,
829
- pageSize: limit
1097
+ pageSize: limit + 1
830
1098
  }
831
1099
  }, readOptions(state));
832
1100
  if (total > limit) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.TOO_MANY_MATCHES */.zr.TOO_MANY_MATCHES, {
@@ -862,6 +1130,9 @@ async function insertChildren(node, child, parentId, env, state) {
862
1130
  if (writeTarget(cfg) === 'one') return {
863
1131
  output: children.length ? await env.atomic(state, insertOne) : await insertOne()
864
1132
  };
1133
+ if (!children.length && insertsInBatch(service, cfg)) return {
1134
+ output: await env.atomic(state, ()=>insertBatch(node, cfg, service, env, state))
1135
+ };
865
1136
  return {
866
1137
  output: await env.atomic(state, async ()=>{
867
1138
  const items = await eachItem(state, await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS, env, state), insertOne);
@@ -872,6 +1143,54 @@ async function insertChildren(node, child, parentId, env, state) {
872
1143
  })
873
1144
  };
874
1145
  }
1146
+ /** A field mapping that reads records (a lookup) must see the rows inserted before it, so it keeps the row-by-row insert. */ function readsRecords(value) {
1147
+ if (Array.isArray(value)) return value.some(readsRecords);
1148
+ if (!value || typeof value !== 'object') return false;
1149
+ return value.type === 'lookup' || Object.values(value).some(readsRecords);
1150
+ }
1151
+ function insertsInBatch(service, cfg) {
1152
+ return service.batchInsertable === true && !!service.prepareInsert && !!service.firstMissingRelation && !!service.insertValidated && !readsRecords(cfg.fields);
1153
+ }
1154
+ /**
1155
+ * `many` in one statement per chunk, answering exactly as row by row would: each row's values are mapped, counted
1156
+ * and checked in order, and the first row that would have failed (a mapping, a check or a missing related record)
1157
+ * is the failure reported, with the write count it had reached.
1158
+ */ async function insertBatch(node, cfg, service, env, state) {
1159
+ const items = await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS, env, state);
1160
+ const writesBefore = state.writes;
1161
+ const rows = [];
1162
+ let failure;
1163
+ try {
1164
+ await eachItem(state, items, async ()=>{
1165
+ const values = await mapFieldValues(cfg.fields, env, state);
1166
+ countWrite(state);
1167
+ rows.push(service.prepareInsert(values));
1168
+ });
1169
+ } catch (error) {
1170
+ failure = {
1171
+ error
1172
+ };
1173
+ }
1174
+ const missing = await service.firstMissingRelation(rows, state.qr);
1175
+ if (missing) {
1176
+ state.writes = writesBefore + missing.index + 1;
1177
+ throw missing.error;
1178
+ }
1179
+ if (failure) throw failure.error;
1180
+ const saved = await service.insertValidated(rows, state.user, {
1181
+ queryRunner: state.qr,
1182
+ publishEvents: false
1183
+ });
1184
+ for (const row of saved)state.pendingEvents.push({
1185
+ entityCode: cfg.entityCode,
1186
+ action: 'created',
1187
+ row
1188
+ });
1189
+ return {
1190
+ items: saved,
1191
+ count: saved.length
1192
+ };
1193
+ }
875
1194
  /** Updates the record `id` names; one that does not exist (or an empty id, for insert) is handled as `onNotFound` says. */ async function updateOrCreate(cfg, service, fallbackId, env, state) {
876
1195
  const onNotFound = cfg.onNotFound ?? 'error';
877
1196
  const raw = cfg.id === undefined ? fallbackId : await env.ruleEngine.resolveExpression(cfg.id, evaluationContext(state, env));
@@ -909,10 +1228,7 @@ async function insertChildren(node, child, parentId, env, state) {
909
1228
  return {
910
1229
  output: await env.atomic(state, async ()=>{
911
1230
  const outcomes = target === 'filter' ? await eachItem(state, await matchingRecords(node, cfg, service, env, state), async (row)=>({
912
- row: await updateRecord(cfg.entityCode, service, {
913
- ...await mapFieldValues(cfg.fields, env, state),
914
- id: row.id
915
- }, state),
1231
+ row: await updateMatched(cfg.entityCode, service, row, await mapFieldValues(cfg.fields, env, state), state),
916
1232
  result: 'updated'
917
1233
  })) : await eachItem(state, await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS, env, state), (item)=>updateOrCreate(cfg, service, itemId(item), env, state));
918
1234
  const items = outcomes.flatMap((o)=>o.row ? [
@@ -1343,7 +1659,7 @@ const nobody = (extra = {})=>({
1343
1659
  case 'validate':
1344
1660
  return runValidate(c, env, state);
1345
1661
  case 'code':
1346
- return runCode(node, c, state);
1662
+ return runCode(node, c, env, state);
1347
1663
  case 'permission_check':
1348
1664
  return runPermissionCheck(c, env, state);
1349
1665
  case 'company_branch_check':
@@ -1407,8 +1723,11 @@ const nobody = (extra = {})=>({
1407
1723
  const id = asId(await env.ruleEngine.resolveExpression(cfg.id, ctx));
1408
1724
  const service = await env.entityService(cfg.entityCode, state.qr);
1409
1725
  try {
1726
+ const record = await service.findById(id, readOptions(state));
1410
1727
  return {
1411
- output: await service.findById(id, readOptions(state))
1728
+ output: cfg.fromRequest ? selectFields([
1729
+ record
1730
+ ], requestQuery(state).select)[0] : record
1412
1731
  };
1413
1732
  } catch (error) {
1414
1733
  if (error instanceof common_.NotFoundException && cfg.onNotFound === 'null') return {
@@ -1418,29 +1737,7 @@ const nobody = (extra = {})=>({
1418
1737
  }
1419
1738
  }
1420
1739
  case 'entity_query':
1421
- {
1422
- const cfg = c;
1423
- const service = await env.entityService(cfg.entityCode, state.qr);
1424
- const filter = await mapFilter(node, cfg.filter, env, state, 'skip');
1425
- const search = cfg.search ? await env.ruleEngine.resolveExpression(cfg.search, ctx) : undefined;
1426
- const limit = Math.min(cfg.limit ?? 50, flow_types/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT);
1427
- const { data, total } = await service.getAll(typeof search === 'string' && search ? search : undefined, {
1428
- filter,
1429
- sort: cfg.sort ? {
1430
- [cfg.sort.field]: cfg.sort.direction === 'DESC' ? 'DESC' : 'ASC'
1431
- } : undefined,
1432
- pagination: {
1433
- currentPage: 0,
1434
- pageSize: limit
1435
- }
1436
- }, readOptions(state));
1437
- return {
1438
- output: {
1439
- items: data,
1440
- total
1441
- }
1442
- };
1443
- }
1740
+ return runQuery(node, c, env, state);
1444
1741
  case 'commit':
1445
1742
  {
1446
1743
  const reasonKey = state.mode === 'dry_run' ? message_keys/* .FLOW_SKIP_MESSAGES.COMMIT_DRY_RUN */.CB.COMMIT_DRY_RUN : !state.qr ? message_keys/* .FLOW_SKIP_MESSAGES.COMMIT_NO_TRANSACTION */.CB.COMMIT_NO_TRANSACTION : !state.ownsTransaction ? message_keys/* .FLOW_SKIP_MESSAGES.COMMIT_JOINED */.CB.COMMIT_JOINED : null;
@@ -1542,7 +1839,7 @@ const nobody = (extra = {})=>({
1542
1839
  case 'respond':
1543
1840
  {
1544
1841
  const cfg = c;
1545
- const body = cfg.bodyExpression ? await env.ruleEngine.resolveExpression(cfg.bodyExpression, ctx) : await mapValues(cfg.body, env, state);
1842
+ const body = await respondBody(node, cfg, env, state);
1546
1843
  return {
1547
1844
  output: body,
1548
1845
  respond: {
@@ -2203,19 +2500,29 @@ function conditionProblem(node, options) {
2203
2500
  const warnings = [];
2204
2501
  const err = (message)=>errors.push(message);
2205
2502
  if (!_flow_types_js__rspack_import_5/* .FLOW_SLUG_PATTERN.test */.YD.test(flow.slug ?? '')) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.SLUG_INVALID */.qj.SLUG_INVALID));
2206
- const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
2207
- const edges = Array.isArray(flow.edges) ? flow.edges : [];
2208
- if (!nodes.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NO_NODES */.qj.NO_NODES));
2209
- if (nodes.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_NODES */.qj.TOO_MANY_NODES, {
2503
+ const allNodes = Array.isArray(flow.nodes) ? flow.nodes : [];
2504
+ const allEdges = Array.isArray(flow.edges) ? flow.edges : [];
2505
+ if (!allNodes.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NO_NODES */.qj.NO_NODES));
2506
+ if (allNodes.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_NODES */.qj.TOO_MANY_NODES, {
2210
2507
  max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES
2211
2508
  }));
2212
- if (edges.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_EDGES */.qj.TOO_MANY_EDGES, {
2509
+ if (allEdges.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_EDGES */.qj.TOO_MANY_EDGES, {
2213
2510
  max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES
2214
2511
  }));
2215
- if (errors.length && (!nodes.length || nodes.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES || edges.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES)) return {
2512
+ if (errors.length && (!allNodes.length || allNodes.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES || allEdges.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES)) return {
2216
2513
  errors,
2217
2514
  warnings
2218
2515
  };
2516
+ const isEntry = (item)=>!!item && typeof item === 'object';
2517
+ const nodes = allNodes.filter(isEntry);
2518
+ const edges = allEdges.filter(isEntry);
2519
+ if (nodes.length < allNodes.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_ID_INVALID */.qj.NODE_ID_INVALID, {
2520
+ id: undefined
2521
+ }));
2522
+ if (edges.length < allEdges.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_DANGLING */.qj.EDGE_DANGLING, {
2523
+ from: undefined,
2524
+ to: undefined
2525
+ }));
2219
2526
  const byId = new Map();
2220
2527
  for (const node of nodes){
2221
2528
  if (!_flow_types_js__rspack_import_5/* .FLOW_NODE_ID_PATTERN.test */.z6.test(node?.id ?? '')) {
@@ -2313,7 +2620,7 @@ function conditionProblem(node, options) {
2313
2620
  }
2314
2621
  /** The entities a step reads or writes: its own, its child inserts' and those its lookups read. */ function entitiesOf(node) {
2315
2622
  const codes = collectLookupEntities(node.config);
2316
- if (node.type.startsWith('entity_') && typeof node.config?.['entityCode'] === 'string') codes.add(node.config['entityCode']);
2623
+ if (String(node.type).startsWith('entity_') && typeof node.config?.['entityCode'] === 'string') codes.add(node.config['entityCode']);
2317
2624
  const children = node.type === 'entity_insert' && Array.isArray(node.config?.['children']) ? node.config['children'] : [];
2318
2625
  for (const child of children)if (typeof child?.entityCode === 'string') codes.add(child.entityCode);
2319
2626
  return [
@@ -2343,6 +2650,17 @@ function conditionProblem(node, options) {
2343
2650
  }));
2344
2651
  }
2345
2652
  if (endpoint.authMode === 'public' && steps.some((n)=>(0,_flow_types_js__rspack_import_5/* .isMessageNode */.SE)(n.type))) warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.PUBLIC_MESSAGE */.qj.PUBLIC_MESSAGE));
2653
+ for (const n of steps){
2654
+ if (n.config?.['fromRequest'] !== true) continue;
2655
+ if (n.type === 'entity_query' && endpoint.contract !== 'get_all' && endpoint.contract !== 'get_by_ids') warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_QUERY_NOT_GET_ALL */.qj.NODE_QUERY_NOT_GET_ALL, {
2656
+ node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(n),
2657
+ endpoint: label
2658
+ }));
2659
+ if (n.type === 'entity_get' && endpoint.contract !== 'get_by_id') warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_GET_NOT_GET_BY_ID */.qj.NODE_GET_NOT_GET_BY_ID, {
2660
+ node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(n),
2661
+ endpoint: label
2662
+ }));
2663
+ }
2346
2664
  // A URL callers reach without signing in has no user: user checks deny them, and its record steps are open to anyone who can call it.
2347
2665
  if (!_flow_types_js__rspack_import_5/* .KEYLESS_AUTH_MODES.has */.d7.has(endpoint.authMode)) continue;
2348
2666
  if (steps.some((n)=>(n.type === 'permission_check' || n.type === 'company_branch_check') && n.config?.['subject'] !== 'user' || usesPermissionFunction(n.config))) warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.PERMISSION_WITHOUT_USER */.qj.PERMISSION_WITHOUT_USER));
@@ -2526,6 +2844,12 @@ function conditionProblem(node, options) {
2526
2844
  validateInputFields(field.fields, itemType === 'object' ? `${name}[].` : `${name}.`, depth + 1, err);
2527
2845
  }
2528
2846
  }
2847
+ const REQUIRE_CALL = /\brequire\s*\(\s*(['"`])([^'"`\n]+)\1\s*\)/g;
2848
+ /** The package names a code step asks for with a literal `require('name')`. */ function requiredPackages(code) {
2849
+ return [
2850
+ ...code.matchAll(REQUIRE_CALL)
2851
+ ].map((match)=>match[2]);
2852
+ }
2529
2853
  function validateNodeConfig(node, flow, context, err, warn) {
2530
2854
  const at = {
2531
2855
  node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
@@ -2624,6 +2948,10 @@ function validateNodeConfig(node, flow, context, err, warn) {
2624
2948
  template: {
2625
2949
  list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.TEMPLATE_VALUES */.bc.TEMPLATE_VALUES,
2626
2950
  item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.TEMPLATE_VALUE */.bc.TEMPLATE_VALUE
2951
+ },
2952
+ messageVariable: {
2953
+ list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.MESSAGE_VARIABLES */.bc.MESSAGE_VARIABLES,
2954
+ item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.MESSAGE_VARIABLE */.bc.MESSAGE_VARIABLE
2627
2955
  }
2628
2956
  };
2629
2957
  const optionalExpr = (value, what)=>{
@@ -2640,6 +2968,8 @@ function validateNodeConfig(node, flow, context, err, warn) {
2640
2968
  const serviceRunning = (service, unavailable)=>{
2641
2969
  if (context.messageServices && !context.messageServices[service]) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(unavailable, at));
2642
2970
  };
2971
+ // A failure carried past a step with no `error` port would follow `out`, which such a step does not have (or, for a check, is never carried past).
2972
+ if (node.onError === 'continue' && node.type !== 'commit' && (node.type === 'switch' || !(0,_flow_types_js__rspack_import_5/* .portsOf */.a5)(node).includes('error'))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CONTINUE_UNSUPPORTED */.qj.NODE_CONTINUE_UNSUPPORTED, at));
2643
2973
  switch(node.type){
2644
2974
  case 'trigger':
2645
2975
  {
@@ -2665,10 +2995,24 @@ function validateNodeConfig(node, flow, context, err, warn) {
2665
2995
  else if (c.apiKey !== undefined && !(0,_flow_api_key_js__rspack_import_6/* .looksLikeApiKey */.Jb)(c.apiKey) && !(0,_flow_types_js__rspack_import_5/* .isStoredApiKey */.xA)(c.apiKey)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_API_KEY_INVALID */.qj.TRIGGER_API_KEY_INVALID, at));
2666
2996
  // Field errors are named after the URL (`<path>: <field>`) once a flow has several.
2667
2997
  const path = (0,_flow_types_js__rspack_import_5/* .triggerPath */.um)(node);
2998
+ const prefix = path ? `${path}: ` : '';
2668
2999
  if (c.bodyType !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_BODY_TYPES.includes */.Y3.includes(c.bodyType)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.BODY_TYPE_UNKNOWN */.qj.BODY_TYPE_UNKNOWN, {
2669
3000
  type: String(c.bodyType)
2670
3001
  }));
2671
- validateInputFields(Array.isArray(c.inputSchema) ? c.inputSchema : [], path ? `${path}: ` : '', 1, err);
3002
+ const declared = Array.isArray(c.inputSchema) ? c.inputSchema : [];
3003
+ if (c.contract !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_REQUEST_CONTRACTS.includes */.j_.includes(c.contract)) {
3004
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_CONTRACT_INVALID */.qj.TRIGGER_CONTRACT_INVALID, {
3005
+ ...at,
3006
+ contract: String(c.contract)
3007
+ }));
3008
+ } else {
3009
+ // A declared field may not take the name of one the request type already brings.
3010
+ const reserved = new Set((0,_flow_types_js__rspack_import_5/* .contractFields */._h)(c.contract ?? 'custom').map((f)=>f.name));
3011
+ for (const field of declared)if (reserved.has(field?.name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_NAME_RESERVED */.qj.INPUT_NAME_RESERVED, {
3012
+ name: `${prefix}${field.name}`
3013
+ }));
3014
+ }
3015
+ validateInputFields(declared, prefix, 1, err);
2672
3016
  break;
2673
3017
  }
2674
3018
  case 'validate':
@@ -2809,6 +3153,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
2809
3153
  case 'entity_get':
2810
3154
  entity(c.entityCode);
2811
3155
  expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
3156
+ if (c.fromRequest !== undefined && typeof c.fromRequest !== 'boolean') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_QUERY_FROM_REQUEST_INVALID */.qj.NODE_QUERY_FROM_REQUEST_INVALID, at));
2812
3157
  break;
2813
3158
  case 'entity_query':
2814
3159
  entity(c.entityCode);
@@ -2824,6 +3169,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
2824
3169
  max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT
2825
3170
  }));
2826
3171
  }
3172
+ if (c.fromRequest !== undefined && typeof c.fromRequest !== 'boolean') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_QUERY_FROM_REQUEST_INVALID */.qj.NODE_QUERY_FROM_REQUEST_INVALID, at));
2827
3173
  break;
2828
3174
  case 'call_flow':
2829
3175
  if (typeof c.flowSlug !== 'string' || !_flow_types_js__rspack_import_5/* .FLOW_SLUG_PATTERN.test */.YD.test(c.flowSlug)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHOOSE_FLOW */.qj.NODE_CHOOSE_FLOW, at));
@@ -2909,8 +3255,28 @@ function validateNodeConfig(node, flow, context, err, warn) {
2909
3255
  {
2910
3256
  const status = c.statusCode;
2911
3257
  if (!(Number.isInteger(status) && status >= 200 && status <= 599)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_2XX_5XX */.qj.NODE_STATUS_2XX_5XX, at));
2912
- exprMap(c.body, KIND.response);
2913
- if (c.bodyExpression !== undefined) expr(c.bodyExpression, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RESPONSE */.bc.THE_RESPONSE));
3258
+ const format = c.format ?? 'raw';
3259
+ if (!_flow_types_js__rspack_import_5/* .FLOW_RESPONSE_FORMATS.includes */.A4.includes(format)) {
3260
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_RESPOND_FORMAT_INVALID */.qj.NODE_RESPOND_FORMAT_INVALID, {
3261
+ ...at,
3262
+ format: String(format)
3263
+ }));
3264
+ break;
3265
+ }
3266
+ const what = (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(format === 'raw' ? _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RESPONSE */.bc.THE_RESPONSE : _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_DATA */.bc.THE_DATA);
3267
+ if (format !== 'message') {
3268
+ exprMap(c.body, KIND.response);
3269
+ if (c.bodyExpression !== undefined) expr(c.bodyExpression, what);
3270
+ }
3271
+ if (format === 'raw') break;
3272
+ if (typeof c.message !== 'string' || !c.message.trim() || typeof c.messageKey !== 'string' || !c.messageKey.trim()) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_RESPOND_MESSAGE_REQUIRED */.qj.NODE_RESPOND_MESSAGE_REQUIRED, at));
3273
+ exprMap(c.messageVariables, KIND.messageVariable);
3274
+ if ((format === 'list' || format === 'bulk') && c.bodyExpression === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_RESPOND_LIST_VALUE_REQUIRED */.qj.NODE_RESPOND_LIST_VALUE_REQUIRED, at));
3275
+ if (format === 'list' || format === 'bulk') optionalExpr(c.total, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_TOTAL */.bc.THE_TOTAL));
3276
+ if (format === 'list') {
3277
+ optionalExpr(c.page, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_PAGE */.bc.THE_PAGE));
3278
+ optionalExpr(c.pageSize, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_PAGE_SIZE */.bc.THE_PAGE_SIZE));
3279
+ }
2914
3280
  break;
2915
3281
  }
2916
3282
  case 'commit':
@@ -2929,6 +3295,16 @@ function validateNodeConfig(node, flow, context, err, warn) {
2929
3295
  max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS
2930
3296
  }));
2931
3297
  }
3298
+ if (context.codeModules && typeof c.code === 'string') {
3299
+ const allowed = context.codeModules;
3300
+ for (const name of new Set(requiredPackages(c.code))){
3301
+ if (!allowed.includes(name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_PACKAGE_NOT_ALLOWED */.qj.NODE_CODE_PACKAGE_NOT_ALLOWED, {
3302
+ ...at,
3303
+ package: name,
3304
+ allowed: allowed.join(', ') || '-'
3305
+ }));
3306
+ }
3307
+ }
2932
3308
  break;
2933
3309
  }
2934
3310
  }
@@ -3369,26 +3745,28 @@ __webpack_require__.d(__webpack_exports__, {
3369
3745
  },
3370
3746
  6390(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3371
3747
  /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
3372
- /* import */ var _config_message_keys_js__rspack_import_3 = __webpack_require__(5732);
3373
- /* import */ var _enums_field_type_enum_js__rspack_import_2 = __webpack_require__(9614);
3748
+ /* import */ var _config_message_keys_js__rspack_import_4 = __webpack_require__(5732);
3749
+ /* import */ var _enums_field_type_enum_js__rspack_import_3 = __webpack_require__(9614);
3374
3750
  /* import */ var _services_field_value_validator_js__rspack_import_1 = __webpack_require__(3525);
3751
+ /* import */ var _flow_types_js__rspack_import_2 = __webpack_require__(6256);
3752
+
3375
3753
 
3376
3754
 
3377
3755
 
3378
3756
 
3379
3757
  const TYPE_MAP = {
3380
- string: _enums_field_type_enum_js__rspack_import_2/* .FieldType.TEXT */.P.TEXT,
3381
- text: _enums_field_type_enum_js__rspack_import_2/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
3382
- number: _enums_field_type_enum_js__rspack_import_2/* .FieldType.DECIMAL */.P.DECIMAL,
3383
- integer: _enums_field_type_enum_js__rspack_import_2/* .FieldType.INTEGER */.P.INTEGER,
3384
- boolean: _enums_field_type_enum_js__rspack_import_2/* .FieldType.BOOLEAN */.P.BOOLEAN,
3385
- date: _enums_field_type_enum_js__rspack_import_2/* .FieldType.DATE */.P.DATE,
3386
- datetime: _enums_field_type_enum_js__rspack_import_2/* .FieldType.DATETIME */.P.DATETIME,
3387
- email: _enums_field_type_enum_js__rspack_import_2/* .FieldType.EMAIL */.P.EMAIL,
3388
- uuid: _enums_field_type_enum_js__rspack_import_2/* .FieldType.RELATION */.P.RELATION,
3389
- object: _enums_field_type_enum_js__rspack_import_2/* .FieldType.JSON */.P.JSON,
3390
- array: _enums_field_type_enum_js__rspack_import_2/* .FieldType.JSON */.P.JSON,
3391
- choice: _enums_field_type_enum_js__rspack_import_2/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT
3758
+ string: _enums_field_type_enum_js__rspack_import_3/* .FieldType.TEXT */.P.TEXT,
3759
+ text: _enums_field_type_enum_js__rspack_import_3/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
3760
+ number: _enums_field_type_enum_js__rspack_import_3/* .FieldType.DECIMAL */.P.DECIMAL,
3761
+ integer: _enums_field_type_enum_js__rspack_import_3/* .FieldType.INTEGER */.P.INTEGER,
3762
+ boolean: _enums_field_type_enum_js__rspack_import_3/* .FieldType.BOOLEAN */.P.BOOLEAN,
3763
+ date: _enums_field_type_enum_js__rspack_import_3/* .FieldType.DATE */.P.DATE,
3764
+ datetime: _enums_field_type_enum_js__rspack_import_3/* .FieldType.DATETIME */.P.DATETIME,
3765
+ email: _enums_field_type_enum_js__rspack_import_3/* .FieldType.EMAIL */.P.EMAIL,
3766
+ uuid: _enums_field_type_enum_js__rspack_import_3/* .FieldType.RELATION */.P.RELATION,
3767
+ object: _enums_field_type_enum_js__rspack_import_3/* .FieldType.JSON */.P.JSON,
3768
+ array: _enums_field_type_enum_js__rspack_import_3/* .FieldType.JSON */.P.JSON,
3769
+ choice: _enums_field_type_enum_js__rspack_import_3/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT
3392
3770
  };
3393
3771
  const isPlainObject = (v)=>typeof v === 'object' && v !== null && !Array.isArray(v);
3394
3772
  /**
@@ -3407,13 +3785,176 @@ const isPlainObject = (v)=>typeof v === 'object' && v !== null && !Array.isArray
3407
3785
  };
3408
3786
  return checkFields(schema, source, '');
3409
3787
  }
3788
+ const FIELD_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
3789
+ const OPEN_CONTRACTS = new Set([
3790
+ 'update',
3791
+ 'update_many'
3792
+ ]);
3793
+ const QUERY_CONTRACTS = new Set([
3794
+ 'get_all',
3795
+ 'get_by_id',
3796
+ 'get_by_ids'
3797
+ ]);
3798
+ /**
3799
+ * Checks a request body against a Request step, the way its API controller endpoint's DTO would: a
3800
+ * delete `id` sent as one id is read as a list and a get_by_ids `select` may be comma-separated; `sort`
3801
+ * values must be ASC / DESC and `select` names plain field names. An update / update_many with no
3802
+ * declared fields keeps the rest of the body, with only its `id` checked.
3803
+ */ function validateEndpointInput(endpoint, body) {
3804
+ const { contract } = endpoint;
3805
+ const source = normalizeBody(contract, body);
3806
+ const result = validateFlowInput(endpoint.inputSchema, source, endpoint.bodyType);
3807
+ if (OPEN_CONTRACTS.has(contract) && endpoint.inputSchema.length === (0,_flow_types_js__rspack_import_2/* .contractFields */._h)(contract).length) result.values = keepRest(source, result.values);
3808
+ if (QUERY_CONTRACTS.has(contract) && !Array.isArray(result.values)) result.errors.push(...contractErrors(result.values));
3809
+ return result;
3810
+ }
3811
+ function normalizeBody(contract, body) {
3812
+ if (!isPlainObject(body)) return body;
3813
+ const source = body;
3814
+ if (contract === 'delete' && typeof source['id'] === 'string') return {
3815
+ ...source,
3816
+ id: [
3817
+ source['id']
3818
+ ]
3819
+ };
3820
+ if (contract === 'get_by_ids' && typeof source['select'] === 'string') {
3821
+ return {
3822
+ ...source,
3823
+ select: source['select'].split(',').map((name)=>name.trim()).filter(Boolean)
3824
+ };
3825
+ }
3826
+ return body;
3827
+ }
3828
+ function keepRest(source, values) {
3829
+ if (Array.isArray(values)) return values.map((item, i)=>isPlainObject(item) && Array.isArray(source) && isPlainObject(source[i]) ? {
3830
+ ...source[i],
3831
+ ...item
3832
+ } : item);
3833
+ return isPlainObject(source) ? {
3834
+ ...source,
3835
+ ...values
3836
+ } : values;
3837
+ }
3838
+ function contractErrors(values) {
3839
+ const errors = [];
3840
+ if (isPlainObject(values['sort'])) errors.push(...sortErrors(values['sort']));
3841
+ if (Array.isArray(values['select'])) errors.push(...selectErrors(values['select']));
3842
+ return errors;
3843
+ }
3844
+ function sortErrors(sort) {
3845
+ return Object.entries(sort).flatMap(([field, direction])=>!FIELD_NAME.test(field) || direction !== 'ASC' && direction !== 'DESC' ? [
3846
+ {
3847
+ field: `sort.${field}`,
3848
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_SORT_INVALID */.wu.INPUT_SORT_INVALID, {
3849
+ field
3850
+ })
3851
+ }
3852
+ ] : []);
3853
+ }
3854
+ function selectErrors(select) {
3855
+ return select.flatMap((field, i)=>typeof field !== 'string' || !FIELD_NAME.test(field) ? [
3856
+ {
3857
+ field: `select[${i}]`,
3858
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_SELECT_INVALID */.wu.INPUT_SELECT_INVALID, {
3859
+ field: String(field)
3860
+ })
3861
+ }
3862
+ ] : []);
3863
+ }
3864
+ /** A whole number, from a number or numeric text; null for anything else. */ function wholeNumber(value) {
3865
+ const n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
3866
+ return typeof n === 'number' && Number.isInteger(n) ? n : null;
3867
+ }
3868
+ /**
3869
+ * Reads `filter`, `sort`, `pagination`, `select` and `withDeleted` from a request body by the get_all
3870
+ * contract's rules, whatever the Request step's type: `currentPage` a whole number of 0 or more (left
3871
+ * out: 0), `pageSize` one from 1 to MAX_QUERY_LIMIT (left out: the step's own limit), numeric text
3872
+ * accepted; `sort` ASC / DESC per plain field name; `select` plain field names. A list body has none.
3873
+ */ function readRequestQuery(body) {
3874
+ const query = {};
3875
+ const errors = [];
3876
+ if (!isPlainObject(body)) return {
3877
+ query,
3878
+ errors
3879
+ };
3880
+ const source = body;
3881
+ const present = (key)=>source[key] !== undefined && source[key] !== null;
3882
+ if (isPlainObject(source['filter'])) query.filter = source['filter'];
3883
+ if (present('sort')) {
3884
+ const sort = source['sort'];
3885
+ const problems = isPlainObject(sort) ? sortErrors(sort) : [
3886
+ {
3887
+ field: 'sort',
3888
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_SORT_INVALID */.wu.INPUT_SORT_INVALID, {
3889
+ field: 'sort'
3890
+ })
3891
+ }
3892
+ ];
3893
+ if (problems.length) errors.push(...problems);
3894
+ else query.sort = sort;
3895
+ }
3896
+ if (present('pagination')) {
3897
+ const pagination = source['pagination'];
3898
+ if (!isPlainObject(pagination)) {
3899
+ errors.push({
3900
+ field: 'pagination',
3901
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FIELD_VALIDATION_MESSAGES.MUST_BE_OBJECT */.KE.MUST_BE_OBJECT, {
3902
+ label: 'pagination'
3903
+ })
3904
+ });
3905
+ } else {
3906
+ const { currentPage: rawPage, pageSize: rawSize } = pagination;
3907
+ const currentPage = rawPage === undefined || rawPage === null ? 0 : wholeNumber(rawPage);
3908
+ const pageSize = rawSize === undefined || rawSize === null ? undefined : wholeNumber(rawSize);
3909
+ if (currentPage === null || currentPage < 0) errors.push({
3910
+ field: 'pagination.currentPage',
3911
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_PAGE_INVALID */.wu.INPUT_PAGE_INVALID, {
3912
+ field: 'pagination.currentPage'
3913
+ })
3914
+ });
3915
+ if (pageSize === null || pageSize !== undefined && (pageSize < 1 || pageSize > _flow_types_js__rspack_import_2/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT)) {
3916
+ errors.push({
3917
+ field: 'pagination.pageSize',
3918
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_PAGE_SIZE_INVALID */.wu.INPUT_PAGE_SIZE_INVALID, {
3919
+ field: 'pagination.pageSize',
3920
+ max: _flow_types_js__rspack_import_2/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT
3921
+ })
3922
+ });
3923
+ }
3924
+ if (currentPage !== null && currentPage >= 0 && pageSize !== null) query.pagination = {
3925
+ currentPage,
3926
+ ...pageSize !== undefined ? {
3927
+ pageSize
3928
+ } : {}
3929
+ };
3930
+ }
3931
+ }
3932
+ if (present('select')) {
3933
+ const select = source['select'];
3934
+ const problems = Array.isArray(select) ? selectErrors(select) : [
3935
+ {
3936
+ field: 'select',
3937
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_SELECT_INVALID */.wu.INPUT_SELECT_INVALID, {
3938
+ field: 'select'
3939
+ })
3940
+ }
3941
+ ];
3942
+ if (problems.length) errors.push(...problems);
3943
+ else query.select = select;
3944
+ }
3945
+ if (source['withDeleted'] === true) query.withDeleted = true;
3946
+ return {
3947
+ query,
3948
+ errors
3949
+ };
3950
+ }
3410
3951
  function checkList(schema, body) {
3411
3952
  if (!Array.isArray(body)) return {
3412
3953
  values: [],
3413
3954
  errors: [
3414
3955
  {
3415
3956
  field: 'body',
3416
- ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FLOW_MESSAGES.INPUT_BODY_NOT_LIST */.wu.INPUT_BODY_NOT_LIST)
3957
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_BODY_NOT_LIST */.wu.INPUT_BODY_NOT_LIST)
3417
3958
  }
3418
3959
  ]
3419
3960
  };
@@ -3428,7 +3969,7 @@ function checkList(schema, body) {
3428
3969
  values.push(item);
3429
3970
  errors.push({
3430
3971
  field: `[${i}]`,
3431
- ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FLOW_MESSAGES.INPUT_BODY_ITEM_NOT_OBJECT */.wu.INPUT_BODY_ITEM_NOT_OBJECT, {
3972
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FLOW_MESSAGES.INPUT_BODY_ITEM_NOT_OBJECT */.wu.INPUT_BODY_ITEM_NOT_OBJECT, {
3432
3973
  index: i + 1
3433
3974
  })
3434
3975
  });
@@ -3448,7 +3989,7 @@ function toValidatable(field, code, label) {
3448
3989
  return {
3449
3990
  code,
3450
3991
  label,
3451
- fieldType: TYPE_MAP[field.type] ?? _enums_field_type_enum_js__rspack_import_2/* .FieldType.TEXT */.P.TEXT,
3992
+ fieldType: TYPE_MAP[field.type] ?? _enums_field_type_enum_js__rspack_import_3/* .FieldType.TEXT */.P.TEXT,
3452
3993
  nullable: !field.required,
3453
3994
  defaultValue: field.default ?? null,
3454
3995
  selectOptions: field.type === 'choice' ? field.options ?? [] : null,
@@ -3503,7 +4044,7 @@ function checkValue(field, supplied, raw, path, label) {
3503
4044
  errors: [
3504
4045
  {
3505
4046
  field: path,
3506
- ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MUST_BE_OBJECT */.KE.MUST_BE_OBJECT, {
4047
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FIELD_VALIDATION_MESSAGES.MUST_BE_OBJECT */.KE.MUST_BE_OBJECT, {
3507
4048
  label
3508
4049
  })
3509
4050
  }
@@ -3528,7 +4069,7 @@ function checkValue(field, supplied, raw, path, label) {
3528
4069
  errors: [
3529
4070
  {
3530
4071
  field: path,
3531
- ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MUST_BE_LIST */.KE.MUST_BE_LIST, {
4072
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_4/* .FIELD_VALIDATION_MESSAGES.MUST_BE_LIST */.KE.MUST_BE_LIST, {
3532
4073
  label
3533
4074
  })
3534
4075
  }
@@ -3561,7 +4102,8 @@ function checkValue(field, supplied, raw, path, label) {
3561
4102
  }
3562
4103
 
3563
4104
  __webpack_require__.d(__webpack_exports__, {
3564
- v: () => (validateFlowInput)
4105
+ GP: () => (validateEndpointInput),
4106
+ M2: () => (readRequestQuery)
3565
4107
  });
3566
4108
 
3567
4109
 
@@ -3662,9 +4204,17 @@ __webpack_require__.d(__webpack_exports__, {
3662
4204
  },
3663
4205
  2678(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3664
4206
  const SENSITIVE_KEY = /pass(word)?|secret|token|authorization|api[-_]?key|cookie|credential|private[-_]?key/i;
4207
+ const MAX_DEPTH = 8;
4208
+ const MAX_ITEMS = 100;
4209
+ const TWO_PASS = new Error('preview: needs the two-pass path');
4210
+ const BOXED = new Set([
4211
+ '[object String]',
4212
+ '[object Number]',
4213
+ '[object Boolean]'
4214
+ ]);
3665
4215
  /** Copy of `value` with the content of sensitive-looking keys replaced, safe to store in a log. */ function redactSensitive(value, depth = 0) {
3666
- if (depth > 8) return '[deep]';
3667
- if (Array.isArray(value)) return value.slice(0, 100).map((v)=>redactSensitive(v, depth + 1));
4216
+ if (depth > MAX_DEPTH) return '[deep]';
4217
+ if (Array.isArray(value)) return value.slice(0, MAX_ITEMS).map((v)=>redactSensitive(v, depth + 1));
3668
4218
  // Dates (and anything else with its own JSON form) have no own keys: walked as objects they would log as `{}`.
3669
4219
  if (value && typeof value.toJSON === 'function') return redactSensitive(value.toJSON(), depth + 1);
3670
4220
  if (value && typeof value === 'object') {
@@ -3676,13 +4226,43 @@ const SENSITIVE_KEY = /pass(word)?|secret|token|authorization|api[-_]?key|cookie
3676
4226
  }
3677
4227
  return value;
3678
4228
  }
4229
+ /**
4230
+ * `JSON.stringify(redactSensitive(value))` in one pass, without building the redacted copy first: the same
4231
+ * text for every value a flow step returns. Shapes one pass cannot write the same way (cycles, boxed
4232
+ * primitives, a `toJSON` result with a `toJSON` of its own) throw, and the caller takes the two-pass path.
4233
+ */ function redactedJson(value) {
4234
+ const depths = new WeakMap();
4235
+ return JSON.stringify(value, function(key, current) {
4236
+ const holder = this;
4237
+ const inArray = Array.isArray(holder);
4238
+ if (!inArray && SENSITIVE_KEY.test(key)) return '***';
4239
+ if (inArray && !(key in holder)) return undefined;
4240
+ const original = holder[key];
4241
+ const viaToJSON = original !== null && typeof original === 'object' && typeof original.toJSON === 'function';
4242
+ if (viaToJSON && current !== null && typeof current === 'object' && typeof current.toJSON === 'function') throw TWO_PASS;
4243
+ if (current !== null && typeof current === 'object' && BOXED.has(Object.prototype.toString.call(current))) throw TWO_PASS;
4244
+ const depth = (depths.get(holder) ?? -1) + 1 + (viaToJSON ? 1 : 0);
4245
+ if (depth > MAX_DEPTH) return '[deep]';
4246
+ if (Array.isArray(current)) {
4247
+ const items = current.length > MAX_ITEMS ? current.slice(0, MAX_ITEMS) : current;
4248
+ depths.set(items, depth);
4249
+ return items;
4250
+ }
4251
+ if (current !== null && typeof current === 'object') depths.set(current, depth);
4252
+ return current;
4253
+ });
4254
+ }
3679
4255
  /** Redacted, size-limited text for a trace/log entry. */ function preview(value, maxChars) {
3680
4256
  if (value === undefined) return '';
3681
4257
  let text;
3682
4258
  try {
3683
- text = JSON.stringify(redactSensitive(value)) ?? '';
4259
+ text = redactedJson(value) ?? '';
3684
4260
  } catch {
3685
- text = '[unserializable]';
4261
+ try {
4262
+ text = JSON.stringify(redactSensitive(value)) ?? '';
4263
+ } catch {
4264
+ text = '[unserializable]';
4265
+ }
3686
4266
  }
3687
4267
  return text.length > maxChars ? `${text.slice(0, maxChars)}... (${text.length} chars)` : text;
3688
4268
  }
@@ -3935,9 +4515,29 @@ class EntityBuilderConfigService {
3935
4515
  getConfig() {
3936
4516
  return this.options.config;
3937
4517
  }
4518
+ isFastUpdatesEnabled() {
4519
+ return this.getConfig()?.performance?.fastUpdates ?? true;
4520
+ }
4521
+ isPartialIndexesEnabled() {
4522
+ return this.getConfig()?.performance?.partialIndexes ?? false;
4523
+ }
3938
4524
  /** `designer.readOnly`, else the `ENTITY_BUILDER_READ_ONLY=true` env variable (production). */ isDesignerReadOnly() {
3939
4525
  return this.getConfig()?.designer?.readOnly ?? process.env['ENTITY_BUILDER_READ_ONLY'] === 'true';
3940
4526
  }
4527
+ /** The packages a code step may `require`: `ENTITY_BUILDER_CODE_MODULES` (`dayjs,_=lodash`: a name, or name=specifier), overridden by `flows.code.modules`. */ getCodeModules() {
4528
+ const fromEnv = (process.env['ENTITY_BUILDER_CODE_MODULES'] ?? '').split(',').map((entry)=>entry.trim()).filter(Boolean).map((entry)=>{
4529
+ const [name, specifier] = entry.split('=').map((part)=>part.trim());
4530
+ return [
4531
+ name,
4532
+ specifier || name
4533
+ ];
4534
+ });
4535
+ const modules = {
4536
+ ...Object.fromEntries(fromEnv),
4537
+ ...this.getConfig()?.flows?.code?.modules
4538
+ };
4539
+ return Object.fromEntries(Object.entries(modules).filter((entry)=>!!entry[0] && typeof entry[1] === 'string' && !!entry[1].trim()));
4540
+ }
3941
4541
  /** The values behind `${var:NAME}` in promoted flows: every non-empty `ENTITY_BUILDER_VAR_<NAME>` env variable, overridden by `bundles.variables`; names a placeholder cannot carry are left out. */ getBundleVariables() {
3942
4542
  const fromEnv = Object.entries(process.env).filter((entry)=>entry[0].startsWith(VARIABLE_ENV_PREFIX) && !!entry[1]).map(([key, value])=>[
3943
4543
  key.slice(VARIABLE_ENV_PREFIX.length),
@@ -4054,23 +4654,54 @@ class EntityBuilderDataSourceProvider extends _flusys_nestjs_shared_modules__rsp
4054
4654
  * just by instance identity, so a fresh schema instance with the same name still
4055
4655
  * resolves correctly after a rebuild.
4056
4656
  */ async registerDynamicSchema(schema) {
4657
+ await this.registerDynamicSchemas([
4658
+ schema
4659
+ ]);
4660
+ }
4661
+ /**
4662
+ * Several schemas with ONE metadata rebuild - each rebuild reads every entity of the DataSource, so
4663
+ * registering N entities one by one costs N full rebuilds. A failed rebuild puts `options.entities`
4664
+ * back as it was and rebuilds it, so one bad schema never breaks later registrations.
4665
+ */ async registerDynamicSchemas(schemas) {
4057
4666
  const dataSource = await this.getDataSource();
4058
4667
  const perDsMap = this.statics.registeredSchemas.get(dataSource) ?? new Map();
4059
4668
  this.statics.registeredSchemas.set(dataSource, perDsMap);
4060
- if (perDsMap.get(schema.options.name) === schema) return;
4669
+ if (schemas.every((schema)=>perDsMap.get(schema.options.name) === schema)) return;
4061
4670
  // Serialize concurrent registrations against the SAME DataSource - buildMetadatas()
4062
4671
  // does a full rebuild from `options.entities`, so two racing pushes must not interleave.
4063
4672
  const prior = this.statics.rebuildLocks.get(dataSource) ?? Promise.resolve();
4064
4673
  const next = prior.then(async ()=>{
4065
- perDsMap.set(schema.options.name, schema);
4674
+ const pending = schemas.filter((schema)=>perDsMap.get(schema.options.name) !== schema);
4675
+ if (!pending.length) return;
4066
4676
  const entitiesArr = dataSource.options.entities;
4067
- const idx = entitiesArr.findIndex((e)=>e instanceof Object && e.options?.name === schema.options.name);
4068
- if (idx >= 0) entitiesArr[idx] = schema;
4069
- else entitiesArr.push(schema);
4677
+ const before = [
4678
+ ...entitiesArr
4679
+ ];
4680
+ const replaced = new Map(pending.map((schema)=>[
4681
+ schema.options.name,
4682
+ perDsMap.get(schema.options.name)
4683
+ ]));
4684
+ for (const schema of pending){
4685
+ perDsMap.set(schema.options.name, schema);
4686
+ const idx = entitiesArr.findIndex((e)=>e instanceof Object && e.options?.name === schema.options.name);
4687
+ if (idx >= 0) entitiesArr[idx] = schema;
4688
+ else entitiesArr.push(schema);
4689
+ }
4070
4690
  // buildMetadatas() is `protected` in TypeORM's public .d.ts (a compile-time-only
4071
4691
  // restriction, not enforced by the JS runtime) but is the only way to rebuild
4072
4692
  // entityMetadatas from options.entities after DataSource.initialize() has run.
4073
- await dataSource.buildMetadatas();
4693
+ const rebuild = ()=>dataSource.buildMetadatas();
4694
+ try {
4695
+ await rebuild();
4696
+ } catch (error) {
4697
+ entitiesArr.splice(0, entitiesArr.length, ...before);
4698
+ for (const [name, previous] of replaced){
4699
+ if (previous) perDsMap.set(name, previous);
4700
+ else perDsMap.delete(name);
4701
+ }
4702
+ await rebuild().catch(()=>undefined);
4703
+ throw error;
4704
+ }
4074
4705
  });
4075
4706
  this.statics.rebuildLocks.set(dataSource, next.catch(()=>undefined));
4076
4707
  await next;
@@ -4107,11 +4738,22 @@ __webpack_require__.d(__webpack_exports__, {
4107
4738
  },
4108
4739
  3525(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4109
4740
  /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
4110
- /* import */ var _config_message_keys_js__rspack_import_2 = __webpack_require__(5732);
4111
- /* import */ var _enums_field_type_enum_js__rspack_import_1 = __webpack_require__(9614);
4741
+ /* import */ var _config_message_keys_js__rspack_import_3 = __webpack_require__(5732);
4742
+ /* import */ var _enums_field_type_enum_js__rspack_import_2 = __webpack_require__(9614);
4743
+ /* import */ var _rule_engine_rule_values_js__rspack_import_1 = __webpack_require__(6622);
4744
+
4112
4745
 
4113
4746
 
4114
4747
 
4748
+ /** A field's value rules alone, optional and without a default: checks one value (a default, a backfill) as a record field would. */ const valueRulesOf = (field)=>({
4749
+ code: field.code,
4750
+ label: field.label ?? field.code,
4751
+ fieldType: field.fieldType,
4752
+ nullable: true,
4753
+ defaultValue: null,
4754
+ selectOptions: field.selectOptions ?? null,
4755
+ validationRules: field.validationRules ?? null
4756
+ });
4115
4757
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4116
4758
  const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4117
4759
  const DATE = /^\d{4}-\d{2}-\d{2}$/;
@@ -4143,18 +4785,18 @@ function isEmpty(value) {
4143
4785
  })
4144
4786
  });
4145
4787
  switch(field.fieldType){
4146
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.TEXT */.P.TEXT:
4147
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.EMAIL */.P.EMAIL:
4148
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT:
4788
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.TEXT */.P.TEXT:
4789
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.EMAIL */.P.EMAIL:
4790
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT:
4149
4791
  {
4150
- if (typeof value !== 'string') return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MUST_BE_TEXT */.KE.MUST_BE_TEXT);
4151
- if (value.length > TEXT_MAX) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MAX_LENGTH */.KE.MAX_LENGTH, {
4792
+ if (typeof value !== 'string') return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MUST_BE_TEXT */.KE.MUST_BE_TEXT);
4793
+ if (value.length > TEXT_MAX) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MAX_LENGTH */.KE.MAX_LENGTH, {
4152
4794
  max: TEXT_MAX
4153
4795
  });
4154
- if (field.fieldType === _enums_field_type_enum_js__rspack_import_1/* .FieldType.EMAIL */.P.EMAIL && !EMAIL.test(value)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.INVALID_EMAIL */.KE.INVALID_EMAIL);
4155
- if (field.fieldType === _enums_field_type_enum_js__rspack_import_1/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT) {
4796
+ if (field.fieldType === _enums_field_type_enum_js__rspack_import_2/* .FieldType.EMAIL */.P.EMAIL && !EMAIL.test(value)) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.INVALID_EMAIL */.KE.INVALID_EMAIL);
4797
+ if (field.fieldType === _enums_field_type_enum_js__rspack_import_2/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT) {
4156
4798
  const options = optionValues(field);
4157
- if (options && !options.includes(value)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.ONE_OF */.KE.ONE_OF, {
4799
+ if (options && !options.includes(value)) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.ONE_OF */.KE.ONE_OF, {
4158
4800
  options: options.join(', ')
4159
4801
  });
4160
4802
  }
@@ -4162,19 +4804,19 @@ function isEmpty(value) {
4162
4804
  value
4163
4805
  };
4164
4806
  }
4165
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.LONG_TEXT */.P.LONG_TEXT:
4166
- if (typeof value !== 'string') return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MUST_BE_TEXT */.KE.MUST_BE_TEXT);
4167
- if (value.length > LONG_TEXT_MAX) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MAX_LENGTH */.KE.MAX_LENGTH, {
4807
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.LONG_TEXT */.P.LONG_TEXT:
4808
+ if (typeof value !== 'string') return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MUST_BE_TEXT */.KE.MUST_BE_TEXT);
4809
+ if (value.length > LONG_TEXT_MAX) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MAX_LENGTH */.KE.MAX_LENGTH, {
4168
4810
  max: LONG_TEXT_MAX
4169
4811
  });
4170
4812
  return {
4171
4813
  value
4172
4814
  };
4173
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.INTEGER */.P.INTEGER:
4815
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.INTEGER */.P.INTEGER:
4174
4816
  {
4175
4817
  const n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
4176
- if (typeof n !== 'number' || !Number.isInteger(n)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.WHOLE_NUMBER */.KE.WHOLE_NUMBER);
4177
- if (n < INT_MIN || n > INT_MAX) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.NUMBER_BETWEEN */.KE.NUMBER_BETWEEN, {
4818
+ if (typeof n !== 'number' || !Number.isInteger(n)) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.WHOLE_NUMBER */.KE.WHOLE_NUMBER);
4819
+ if (n < INT_MIN || n > INT_MAX) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.NUMBER_BETWEEN */.KE.NUMBER_BETWEEN, {
4178
4820
  min: INT_MIN,
4179
4821
  max: INT_MAX
4180
4822
  });
@@ -4182,16 +4824,16 @@ function isEmpty(value) {
4182
4824
  value: n
4183
4825
  };
4184
4826
  }
4185
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.DECIMAL */.P.DECIMAL:
4827
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.DECIMAL */.P.DECIMAL:
4186
4828
  {
4187
4829
  const n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
4188
- if (typeof n !== 'number' || !Number.isFinite(n)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.NUMBER */.KE.NUMBER);
4189
- if (Math.abs(n) >= DECIMAL_LIMIT) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.NUMBER_TOO_LARGE */.KE.NUMBER_TOO_LARGE);
4830
+ if (typeof n !== 'number' || !Number.isFinite(n)) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.NUMBER */.KE.NUMBER);
4831
+ if (Math.abs(n) >= DECIMAL_LIMIT) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.NUMBER_TOO_LARGE */.KE.NUMBER_TOO_LARGE);
4190
4832
  return {
4191
4833
  value: n
4192
4834
  };
4193
4835
  }
4194
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.BOOLEAN */.P.BOOLEAN:
4836
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.BOOLEAN */.P.BOOLEAN:
4195
4837
  {
4196
4838
  if (typeof value === 'boolean') return {
4197
4839
  value
@@ -4202,46 +4844,47 @@ function isEmpty(value) {
4202
4844
  if (value === 'false') return {
4203
4845
  value: false
4204
4846
  };
4205
- return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.BOOLEAN */.KE.BOOLEAN);
4847
+ return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.BOOLEAN */.KE.BOOLEAN);
4206
4848
  }
4207
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.DATE */.P.DATE:
4849
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.DATE */.P.DATE:
4208
4850
  if (typeof value !== 'string' || !DATE.test(value) || !isRealCalendarDate(value)) {
4209
- return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.REAL_DATE */.KE.REAL_DATE);
4851
+ return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.REAL_DATE */.KE.REAL_DATE);
4210
4852
  }
4211
4853
  return {
4212
4854
  value
4213
4855
  };
4214
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.DATETIME */.P.DATETIME:
4856
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.DATETIME */.P.DATETIME:
4215
4857
  {
4216
- const parsed = typeof value === 'string' || value instanceof Date ? new Date(value) : null;
4217
- if (!parsed || Number.isNaN(parsed.getTime())) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.ISO_DATETIME */.KE.ISO_DATETIME);
4858
+ // Zone-less ISO text is UTC, never the server's local time.
4859
+ const time = value instanceof Date ? value.getTime() : typeof value === 'string' ? (0,_rule_engine_rule_values_js__rspack_import_1/* .parseDateText */.hz)(value) : Number.NaN;
4860
+ if (!Number.isFinite(time)) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.ISO_DATETIME */.KE.ISO_DATETIME);
4218
4861
  return {
4219
- value: parsed.toISOString()
4862
+ value: new Date(time).toISOString()
4220
4863
  };
4221
4864
  }
4222
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT:
4865
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT:
4223
4866
  {
4224
- if (!Array.isArray(value) || value.some((v)=>typeof v !== 'string')) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.TEXT_LIST */.KE.TEXT_LIST);
4867
+ if (!Array.isArray(value) || value.some((v)=>typeof v !== 'string')) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.TEXT_LIST */.KE.TEXT_LIST);
4225
4868
  const options = optionValues(field);
4226
4869
  const invalid = options ? value.filter((v)=>!options.includes(v)) : [];
4227
- if (invalid.length) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.UNKNOWN_OPTIONS */.KE.UNKNOWN_OPTIONS, {
4870
+ if (invalid.length) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.UNKNOWN_OPTIONS */.KE.UNKNOWN_OPTIONS, {
4228
4871
  options: invalid.join(', ')
4229
4872
  });
4230
4873
  return {
4231
4874
  value
4232
4875
  };
4233
4876
  }
4234
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.RELATION */.P.RELATION:
4235
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.FILE */.P.FILE:
4236
- if (typeof value !== 'string' || !UUID.test(value)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.UUID */.KE.UUID);
4877
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.RELATION */.P.RELATION:
4878
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.FILE */.P.FILE:
4879
+ if (typeof value !== 'string' || !UUID.test(value)) return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.UUID */.KE.UUID);
4237
4880
  return {
4238
4881
  value
4239
4882
  };
4240
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.JSON */.P.JSON:
4883
+ case _enums_field_type_enum_js__rspack_import_2/* .FieldType.JSON */.P.JSON:
4241
4884
  try {
4242
4885
  JSON.stringify(value);
4243
4886
  } catch {
4244
- return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.JSON */.KE.JSON);
4887
+ return fail(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.JSON */.KE.JSON);
4245
4888
  }
4246
4889
  return {
4247
4890
  value
@@ -4256,38 +4899,38 @@ function checkRules(field, value) {
4256
4899
  const rules = field.validationRules;
4257
4900
  if (!rules) return null;
4258
4901
  const label = field.label;
4259
- const numeric = field.fieldType === _enums_field_type_enum_js__rspack_import_1/* .FieldType.INTEGER */.P.INTEGER || field.fieldType === _enums_field_type_enum_js__rspack_import_1/* .FieldType.DECIMAL */.P.DECIMAL;
4902
+ const numeric = field.fieldType === _enums_field_type_enum_js__rspack_import_2/* .FieldType.INTEGER */.P.INTEGER || field.fieldType === _enums_field_type_enum_js__rspack_import_2/* .FieldType.DECIMAL */.P.DECIMAL;
4260
4903
  const text = typeof value === 'string';
4261
4904
  if (numeric && typeof value === 'number') {
4262
- if (typeof rules.min === 'number' && value < rules.min) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MIN_VALUE */.KE.MIN_VALUE, {
4905
+ if (typeof rules.min === 'number' && value < rules.min) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MIN_VALUE */.KE.MIN_VALUE, {
4263
4906
  label,
4264
4907
  min: rules.min
4265
4908
  });
4266
- if (typeof rules.max === 'number' && value > rules.max) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MAX_VALUE */.KE.MAX_VALUE, {
4909
+ if (typeof rules.max === 'number' && value > rules.max) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MAX_VALUE */.KE.MAX_VALUE, {
4267
4910
  label,
4268
4911
  max: rules.max
4269
4912
  });
4270
4913
  }
4271
4914
  if (text) {
4272
4915
  if (typeof rules.minLength === 'number' && value.length < rules.minLength) {
4273
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MIN_LENGTH */.KE.MIN_LENGTH, {
4916
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MIN_LENGTH */.KE.MIN_LENGTH, {
4274
4917
  label,
4275
4918
  min: rules.minLength
4276
4919
  });
4277
4920
  }
4278
4921
  if (typeof rules.maxLength === 'number' && value.length > rules.maxLength) {
4279
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MAX_LENGTH */.KE.MAX_LENGTH, {
4922
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MAX_LENGTH */.KE.MAX_LENGTH, {
4280
4923
  label,
4281
4924
  max: rules.maxLength
4282
4925
  });
4283
4926
  }
4284
4927
  }
4285
4928
  if (Array.isArray(value)) {
4286
- if (typeof rules.minItems === 'number' && value.length < rules.minItems) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MIN_ITEMS */.KE.MIN_ITEMS, {
4929
+ if (typeof rules.minItems === 'number' && value.length < rules.minItems) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MIN_ITEMS */.KE.MIN_ITEMS, {
4287
4930
  label,
4288
4931
  min: rules.minItems
4289
4932
  });
4290
- if (typeof rules.maxItems === 'number' && value.length > rules.maxItems) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MAX_ITEMS */.KE.MAX_ITEMS, {
4933
+ if (typeof rules.maxItems === 'number' && value.length > rules.maxItems) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.MAX_ITEMS */.KE.MAX_ITEMS, {
4291
4934
  label,
4292
4935
  max: rules.maxItems
4293
4936
  });
@@ -4317,7 +4960,7 @@ function checkRules(field, value) {
4317
4960
  if (isEmpty(raw)) {
4318
4961
  if (!field.nullable) errors.push({
4319
4962
  field: field.code,
4320
- ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.REQUIRED */.KE.REQUIRED, {
4963
+ ...(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_3/* .FIELD_VALIDATION_MESSAGES.REQUIRED */.KE.REQUIRED, {
4321
4964
  label: field.label
4322
4965
  })
4323
4966
  });
@@ -4350,6 +4993,8 @@ function checkRules(field, value) {
4350
4993
 
4351
4994
  __webpack_require__.d(__webpack_exports__, {
4352
4995
  e: () => (validateRecord)
4996
+ }, {
4997
+ C: valueRulesOf
4353
4998
  });
4354
4999
 
4355
5000
 
@@ -4361,17 +5006,19 @@ __webpack_require__.d(__webpack_exports__, {
4361
5006
  /* import */ var _flusys_nestjs_shared_modules__rspack_import_3 = __webpack_require__(1236);
4362
5007
  /* import */ var _nestjs_common__rspack_import_4 = __webpack_require__(6157);
4363
5008
  /* import */ var typeorm__rspack_import_5 = __webpack_require__(7158);
4364
- /* import */ var _config_event_actions_js__rspack_import_16 = __webpack_require__(982);
4365
- /* import */ var _config_message_keys_js__rspack_import_14 = __webpack_require__(5732);
5009
+ /* import */ var _config_event_actions_js__rspack_import_18 = __webpack_require__(982);
5010
+ /* import */ var _config_message_keys_js__rspack_import_16 = __webpack_require__(5732);
4366
5011
  /* import */ var _entities_entity_definition_entity_js__rspack_import_6 = __webpack_require__(3189);
4367
5012
  /* import */ var _entities_flow_definition_entity_js__rspack_import_7 = __webpack_require__(3770);
4368
- /* import */ var _entities_flow_version_entity_js__rspack_import_8 = __webpack_require__(2399);
4369
- /* import */ var _flow_engine_flow_api_key_js__rspack_import_9 = __webpack_require__(4816);
4370
- /* import */ var _flow_engine_flow_permission_logic_js__rspack_import_13 = __webpack_require__(8220);
4371
- /* import */ var _flow_engine_flow_graph_validator_js__rspack_import_10 = __webpack_require__(3186);
4372
- /* import */ var _flow_engine_flow_types_js__rspack_import_11 = __webpack_require__(6256);
4373
- /* import */ var _interfaces_flow_definition_interface_js__rspack_import_15 = __webpack_require__(9609);
4374
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_12 = __webpack_require__(424);
5013
+ /* import */ var _entities_flow_execution_entity_js__rspack_import_8 = __webpack_require__(9641);
5014
+ /* import */ var _entities_flow_version_entity_js__rspack_import_9 = __webpack_require__(2399);
5015
+ /* import */ var _flow_engine_flow_api_key_js__rspack_import_10 = __webpack_require__(4816);
5016
+ /* import */ var _flow_engine_flow_permission_logic_js__rspack_import_15 = __webpack_require__(8220);
5017
+ /* import */ var _flow_engine_flow_graph_validator_js__rspack_import_11 = __webpack_require__(3186);
5018
+ /* import */ var _flow_engine_flow_types_js__rspack_import_12 = __webpack_require__(6256);
5019
+ /* import */ var _interfaces_flow_definition_interface_js__rspack_import_17 = __webpack_require__(9609);
5020
+ /* import */ var _entity_builder_config_service_js__rspack_import_13 = __webpack_require__(4281);
5021
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_14 = __webpack_require__(424);
4375
5022
  function _ts_decorate(decorators, target, key, desc) {
4376
5023
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4377
5024
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -4412,15 +5059,19 @@ function _ts_param(paramIndex, decorator) {
4412
5059
 
4413
5060
 
4414
5061
 
5062
+
5063
+
4415
5064
  const defaultFlowPermissionCode = (slug)=>`entity_builder.flow.${slug}.execute`;
4416
- /** The rule a `permission` URL asks callers to pass: its own, or the flow's execute permission. */ const flowPermissions = (endpoint, slug)=>endpoint.permissions ?? (0,_flow_engine_flow_permission_logic_js__rspack_import_13/* .permissionAction */.s)(defaultFlowPermissionCode(slug));
5065
+ /** The rule a `permission` URL asks callers to pass: its own, or the flow's execute permission. */ const flowPermissions = (endpoint, slug)=>endpoint.permissions ?? (0,_flow_engine_flow_permission_logic_js__rspack_import_15/* .permissionAction */.s)(defaultFlowPermissionCode(slug));
5066
+ /** A `permission` URL without a rule of its own: it asks for the flow's execute permission, which publishing registers. */ const asksExecutePermission = (endpoint)=>endpoint.authMode === 'permission' && !endpoint.permissions;
5067
+ const logger = new _nestjs_common__rspack_import_4.Logger('FlowDefinitionService');
4417
5068
  const flowNotFound = ()=>new _nestjs_common__rspack_import_4.NotFoundException({
4418
5069
  message: 'Flow not found.',
4419
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NOT_FOUND */.wu.NOT_FOUND
5070
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.NOT_FOUND */.wu.NOT_FOUND
4420
5071
  });
4421
5072
  /** A flow that has been published at least once: its columns are what callers run. */ const isPublished = (flow)=>flow.version > 0;
4422
5073
  /** The versioned part of a flow (or of a draft), in a fixed key order so two snapshots compare as text. */ function snapshotOf(source) {
4423
- return Object.fromEntries(_interfaces_flow_definition_interface_js__rspack_import_15/* .FLOW_VERSIONED_FIELDS.filter */.v.filter((key)=>source[key] !== undefined).map((key)=>[
5074
+ return Object.fromEntries(_interfaces_flow_definition_interface_js__rspack_import_17/* .FLOW_VERSIONED_FIELDS.filter */.v.filter((key)=>source[key] !== undefined).map((key)=>[
4424
5075
  key,
4425
5076
  source[key]
4426
5077
  ]));
@@ -4446,8 +5097,9 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4446
5097
  notifications;
4447
5098
  email;
4448
5099
  companyAccess;
4449
- constructor(cacheManager, utilsService, provider, permissionCache, actionRegistry, notifications, email, companyAccess){
4450
- super(_config_event_actions_js__rspack_import_16/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION, cacheManager, utilsService, FlowDefinitionService.name, true, _config_event_actions_js__rspack_import_16/* .ENTITY_BUILDER_EVENT_MODULE */.S, provider, _entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G), this.cacheManager = cacheManager, this.utilsService = utilsService, this.provider = provider, this.permissionCache = permissionCache, this.actionRegistry = actionRegistry, this.notifications = notifications, this.email = email, this.companyAccess = companyAccess;
5100
+ config;
5101
+ constructor(cacheManager, utilsService, provider, permissionCache, actionRegistry, notifications, email, companyAccess, config){
5102
+ super(_config_event_actions_js__rspack_import_18/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION, cacheManager, utilsService, FlowDefinitionService.name, true, _config_event_actions_js__rspack_import_18/* .ENTITY_BUILDER_EVENT_MODULE */.S, provider, _entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G), this.cacheManager = cacheManager, this.utilsService = utilsService, this.provider = provider, this.permissionCache = permissionCache, this.actionRegistry = actionRegistry, this.notifications = notifications, this.email = email, this.companyAccess = companyAccess, this.config = config;
4451
5103
  }
4452
5104
  async findOrFail(id) {
4453
5105
  const flow = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
@@ -4465,7 +5117,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4465
5117
  }
4466
5118
  });
4467
5119
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find();
4468
- return (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .validateFlow */.nC)(settings, {
5120
+ return (0,_flow_engine_flow_graph_validator_js__rspack_import_11/* .validateFlow */.nC)(settings, {
4469
5121
  knownEntityCodes: new Set(entities.map((e)=>e.code)),
4470
5122
  knownFlows: new Map(flows.map((flow)=>[
4471
5123
  flow.slug,
@@ -4475,9 +5127,13 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4475
5127
  notification: !!this.notifications,
4476
5128
  email: !!this.email
4477
5129
  },
4478
- companyMembers: !!this.companyAccess
5130
+ companyMembers: !!this.companyAccess,
5131
+ codeModules: this.config ? this.codePackages() : undefined
4479
5132
  });
4480
5133
  }
5134
+ /** The names a code step may `require`, as the app configured them. */ codePackages() {
5135
+ return Object.keys(this.config?.getCodeModules() ?? {});
5136
+ }
4481
5137
  /** Names of the other flows whose `call_flow` nodes run `slug`. */ async callersOf(slug, excludeIds) {
4482
5138
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4483
5139
  select: {
@@ -4493,7 +5149,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4493
5149
  if (!callers.length) return;
4494
5150
  throw new _nestjs_common__rspack_import_4.ConflictException({
4495
5151
  message: `The flow '${slug}' is called by: ${callers.join(', ')}. Change those flows first.`,
4496
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.CALLED_BY_OTHER_FLOWS */.wu.CALLED_BY_OTHER_FLOWS,
5152
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.CALLED_BY_OTHER_FLOWS */.wu.CALLED_BY_OTHER_FLOWS,
4497
5153
  messageVariables: {
4498
5154
  slug,
4499
5155
  flows: callers.join(', ')
@@ -4532,11 +5188,11 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4532
5188
  const settings = toFlowSettings(caller);
4533
5189
  // Counted, not a set: two steps with the same label can share a problem, and only the new one is added.
4534
5190
  const existing = new Map();
4535
- for (const problem of (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .callProblems */.r7)(settings, before.slug, now)){
5191
+ for (const problem of (0,_flow_engine_flow_graph_validator_js__rspack_import_11/* .callProblems */.r7)(settings, before.slug, now)){
4536
5192
  const key = JSON.stringify(problem);
4537
5193
  existing.set(key, (existing.get(key) ?? 0) + 1);
4538
5194
  }
4539
- const added = (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .callProblems */.r7)(settings, before.slug, next).filter((problem)=>{
5195
+ const added = (0,_flow_engine_flow_graph_validator_js__rspack_import_11/* .callProblems */.r7)(settings, before.slug, next).filter((problem)=>{
4540
5196
  const key = JSON.stringify(problem);
4541
5197
  const left = existing.get(key) ?? 0;
4542
5198
  existing.set(key, left - 1);
@@ -4551,7 +5207,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4551
5207
  const names = broken.map((b)=>b.flow).join(', ');
4552
5208
  throw new _nestjs_common__rspack_import_4.ConflictException({
4553
5209
  message: `This change breaks flows that call '${before.slug}': ${names}. Change those flows first.`,
4554
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.CALLERS_BROKEN */.wu.CALLERS_BROKEN,
5210
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.CALLERS_BROKEN */.wu.CALLERS_BROKEN,
4555
5211
  messageVariables: {
4556
5212
  slug: before.slug,
4557
5213
  flows: names
@@ -4564,8 +5220,8 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4564
5220
  });
4565
5221
  }
4566
5222
  /** A URL other flows' Call flow steps start at cannot be removed or renamed from under them. */ async assertEndpointsNotCalled(before, after) {
4567
- const kept = new Set((0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(after).map((e)=>e.path));
4568
- const removed = (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(before).map((e)=>e.path).filter((path)=>!kept.has(path));
5223
+ const kept = new Set((0,_flow_engine_flow_types_js__rspack_import_12/* .flowEndpoints */.Xr)(after).map((e)=>e.path));
5224
+ const removed = (0,_flow_engine_flow_types_js__rspack_import_12/* .flowEndpoints */.Xr)(before).map((e)=>e.path).filter((path)=>!kept.has(path));
4569
5225
  if (!removed.length) return;
4570
5226
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4571
5227
  select: {
@@ -4580,7 +5236,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4580
5236
  const url = `api-flows/${before.slug}${path ? `/${path}` : ''}`;
4581
5237
  throw new _nestjs_common__rspack_import_4.ConflictException({
4582
5238
  message: `The URL ${url} is called by: ${callers.join(', ')}. Change those flows first.`,
4583
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.ENDPOINT_CALLED_BY_OTHER_FLOWS */.wu.ENDPOINT_CALLED_BY_OTHER_FLOWS,
5239
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.ENDPOINT_CALLED_BY_OTHER_FLOWS */.wu.ENDPOINT_CALLED_BY_OTHER_FLOWS,
4584
5240
  messageVariables: {
4585
5241
  url,
4586
5242
  flows: callers.join(', ')
@@ -4593,7 +5249,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4593
5249
  if (errors.length) {
4594
5250
  throw new _nestjs_common__rspack_import_4.BadRequestException({
4595
5251
  message: 'The flow has problems.',
4596
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
5252
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
4597
5253
  errors: errors.map((error)=>({
4598
5254
  field: 'flow',
4599
5255
  ...error
@@ -4604,13 +5260,12 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4604
5260
  const clash = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
4605
5261
  where: {
4606
5262
  slug: settings.slug
4607
- },
4608
- withDeleted: true
5263
+ }
4609
5264
  });
4610
5265
  if (clash && clash.id !== selfId) {
4611
5266
  throw new _nestjs_common__rspack_import_4.ConflictException({
4612
5267
  message: `The URL name '${settings.slug}' is already used by another flow.`,
4613
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.SLUG_TAKEN */.wu.SLUG_TAKEN,
5268
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.SLUG_TAKEN */.wu.SLUG_TAKEN,
4614
5269
  messageVariables: {
4615
5270
  slug: settings.slug
4616
5271
  }
@@ -4642,7 +5297,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4642
5297
  dto
4643
5298
  ]){
4644
5299
  await this.assertSaveable(toFlowSettings(d), user);
4645
- d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_9/* .sealApiKeys */.ue)(d.nodes);
5300
+ d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_10/* .sealApiKeys */.ue)(d.nodes);
4646
5301
  d.version = 0;
4647
5302
  }
4648
5303
  }
@@ -4663,7 +5318,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4663
5318
  isActive: d.isActive ?? existing.isActive,
4664
5319
  version: existing.version
4665
5320
  });
4666
- if (d.nodes) d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_9/* .sealApiKeys */.ue)(d.nodes);
5321
+ if (d.nodes) d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_10/* .sealApiKeys */.ue)(d.nodes);
4667
5322
  }
4668
5323
  }
4669
5324
  /** A published flow keeps running as published: the edits go to its draft (none when they match what is live). */ async convertSingleDtoToEntity(dto, user) {
@@ -4687,7 +5342,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4687
5342
  const published = isPublished(existing);
4688
5343
  if (published && !existing.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
4689
5344
  message: 'There are no changes to publish.',
4690
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NOTHING_TO_PUBLISH */.wu.NOTHING_TO_PUBLISH
5345
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.NOTHING_TO_PUBLISH */.wu.NOTHING_TO_PUBLISH
4691
5346
  });
4692
5347
  const next = workingCopy(existing);
4693
5348
  const settings = toFlowSettings(next);
@@ -4700,7 +5355,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4700
5355
  try {
4701
5356
  await qr.connect();
4702
5357
  await qr.startTransaction();
4703
- const versions = qr.manager.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p);
5358
+ const versions = qr.manager.getRepository(_entities_flow_version_entity_js__rspack_import_9/* .FlowVersion */.p);
4704
5359
  Object.assign(existing, next, {
4705
5360
  draft: null,
4706
5361
  version: existing.version + 1,
@@ -4722,7 +5377,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4722
5377
  } finally{
4723
5378
  await qr.release().catch(()=>undefined);
4724
5379
  }
4725
- await this.clearCaches(existing);
5380
+ await this.invalidateCache();
4726
5381
  await this.provisionPermissions([
4727
5382
  existing
4728
5383
  ]);
@@ -4730,7 +5385,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4730
5385
  }
4731
5386
  /** Newest first, without the definitions (`getVersion` returns one in full). */ async listVersions(flowId) {
4732
5387
  await this.findOrFail(flowId);
4733
- return (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p)).find({
5388
+ return (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_9/* .FlowVersion */.p)).find({
4734
5389
  where: {
4735
5390
  flowId
4736
5391
  },
@@ -4748,7 +5403,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4748
5403
  });
4749
5404
  }
4750
5405
  async getVersion(flowId, version) {
4751
- const row = await (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p)).findOne({
5406
+ const row = await (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_9/* .FlowVersion */.p)).findOne({
4752
5407
  where: {
4753
5408
  flowId,
4754
5409
  version
@@ -4756,7 +5411,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4756
5411
  });
4757
5412
  if (!row) throw new _nestjs_common__rspack_import_4.NotFoundException({
4758
5413
  message: `Version ${version} of this flow was not found.`,
4759
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.VERSION_NOT_FOUND */.wu.VERSION_NOT_FOUND,
5414
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.VERSION_NOT_FOUND */.wu.VERSION_NOT_FOUND,
4760
5415
  messageVariables: {
4761
5416
  version
4762
5417
  }
@@ -4778,7 +5433,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4778
5433
  const flow = await this.findOrFail(flowId);
4779
5434
  if (!flow.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
4780
5435
  message: 'This flow has no unpublished changes.',
4781
- messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NO_DRAFT */.wu.NO_DRAFT
5436
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.NO_DRAFT */.wu.NO_DRAFT
4782
5437
  });
4783
5438
  flow.draft = null;
4784
5439
  return this.saveDraft(flow, user);
@@ -4788,38 +5443,70 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4788
5443
  draft: flow.draft,
4789
5444
  updatedById: user?.id ?? null
4790
5445
  });
4791
- await this.clearCaches(flow);
5446
+ await this.invalidateCache();
4792
5447
  return flow;
4793
5448
  }
4794
- async clearCaches(flow) {
4795
- if (!this.isCacheable) return;
4796
- await Promise.all([
4797
- this.clearCacheForAll(),
4798
- this.clearCacheForId([
4799
- flow
4800
- ])
4801
- ]);
5449
+ async delete(option, user) {
5450
+ if (option.type === 'restore') {
5451
+ throw new _nestjs_common__rspack_import_4.BadRequestException({
5452
+ message: 'A deleted flow is removed for good and cannot be restored.',
5453
+ messageKey: _config_message_keys_js__rspack_import_16/* .FLOW_MESSAGES.RESTORE_UNSUPPORTED */.wu.RESTORE_UNSUPPORTED
5454
+ });
5455
+ }
5456
+ const ids = Array.isArray(option.id) ? option.id : [
5457
+ option.id
5458
+ ];
5459
+ const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
5460
+ select: {
5461
+ id: true,
5462
+ slug: true
5463
+ },
5464
+ where: {
5465
+ id: (0,typeorm__rspack_import_5.In)(ids)
5466
+ }
5467
+ });
5468
+ await super.delete({
5469
+ ...option,
5470
+ type: 'permanent'
5471
+ }, user);
5472
+ await this.deprovisionPermissions(flows);
5473
+ return null;
4802
5474
  }
4803
- async beforeDeleteOperation(dto, user, _qr) {
5475
+ async beforeDeleteOperation(dto, _user, _qr) {
4804
5476
  const ids = Array.isArray(dto.id) ? dto.id : [
4805
5477
  dto.id
4806
5478
  ];
4807
5479
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4808
5480
  where: {
4809
5481
  id: (0,typeorm__rspack_import_5.In)(ids)
4810
- },
4811
- withDeleted: true
5482
+ }
4812
5483
  });
4813
5484
  if (flows.length !== ids.length) throw flowNotFound();
4814
- if (dto.type === 'restore') return;
4815
5485
  for (const flow of flows)await this.assertNotCalled(flow.slug, ids);
4816
5486
  }
5487
+ async afterDeleteOperation(deleted, _dto, _user, qr) {
5488
+ const flowId = (0,typeorm__rspack_import_5.In)(deleted.map((flow)=>flow.id));
5489
+ await qr.manager.getRepository(_entities_flow_version_entity_js__rspack_import_9/* .FlowVersion */.p).delete({
5490
+ flowId
5491
+ });
5492
+ await qr.manager.getRepository(_entities_flow_execution_entity_js__rspack_import_8/* .FlowExecution */.j).delete({
5493
+ flowId
5494
+ });
5495
+ }
5496
+ /** After the commit: a failure leaves an unused action behind, never a flow that is half deleted. */ async deprovisionPermissions(flows) {
5497
+ if (!this.actionRegistry || !flows.length) return;
5498
+ try {
5499
+ await this.actionRegistry.removeActionsByCodes(flows.map((flow)=>defaultFlowPermissionCode(flow.slug)));
5500
+ } catch (error) {
5501
+ logger.warn(`The execute permissions of deleted flows could not be removed: ${error.message}`);
5502
+ }
5503
+ }
4817
5504
  /** The flow's execute permission, registered so it can be granted, when a published URL asks for it (a URL with its own rule names existing permissions). */ async provisionPermissions(flows) {
4818
5505
  if (!this.actionRegistry) return;
4819
5506
  const actions = new Map();
4820
5507
  for (const flow of flows){
4821
- for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow)){
4822
- if (endpoint.authMode !== 'permission' || endpoint.permissions) continue;
5508
+ for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_12/* .flowEndpoints */.Xr)(flow)){
5509
+ if (!asksExecutePermission(endpoint)) continue;
4823
5510
  const code = defaultFlowPermissionCode(flow.slug);
4824
5511
  if (!actions.has(code)) actions.set(code, endpoint.path ? `Run ${flow.name} (${endpoint.path})` : `Run ${flow.name}`);
4825
5512
  }
@@ -4838,7 +5525,7 @@ FlowDefinitionService = _ts_decorate([
4838
5525
  }),
4839
5526
  _ts_param(0, (0,_nestjs_common__rspack_import_4.Inject)('CACHE_INSTANCE')),
4840
5527
  _ts_param(1, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.UtilsService)),
4841
- _ts_param(2, (0,_nestjs_common__rspack_import_4.Inject)(_entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D)),
5528
+ _ts_param(2, (0,_nestjs_common__rspack_import_4.Inject)(_entity_builder_datasource_provider_js__rspack_import_14/* .EntityBuilderDataSourceProvider */.D)),
4842
5529
  _ts_param(3, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService)),
4843
5530
  _ts_param(4, (0,_nestjs_common__rspack_import_4.Optional)()),
4844
5531
  _ts_param(4, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared__rspack_import_0.PERMISSION_ACTION_REGISTRY)),
@@ -4848,16 +5535,19 @@ FlowDefinitionService = _ts_decorate([
4848
5535
  _ts_param(6, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.EMAIL_ADAPTER)),
4849
5536
  _ts_param(7, (0,_nestjs_common__rspack_import_4.Optional)()),
4850
5537
  _ts_param(7, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.COMPANY_ACCESS_RESOLVER)),
5538
+ _ts_param(8, (0,_nestjs_common__rspack_import_4.Optional)()),
5539
+ _ts_param(8, (0,_nestjs_common__rspack_import_4.Inject)(_entity_builder_config_service_js__rspack_import_13/* .EntityBuilderConfigService */.E)),
4851
5540
  _ts_metadata("design:type", Function),
4852
5541
  _ts_metadata("design:paramtypes", [
4853
5542
  typeof _flusys_nestjs_shared_classes__rspack_import_1.HybridCache === "undefined" ? Object : _flusys_nestjs_shared_classes__rspack_import_1.HybridCache,
4854
5543
  typeof _flusys_nestjs_shared_modules__rspack_import_3.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.UtilsService,
4855
- typeof _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D,
5544
+ typeof _entity_builder_datasource_provider_js__rspack_import_14/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_14/* .EntityBuilderDataSourceProvider */.D,
4856
5545
  typeof _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService,
4857
5546
  typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry,
4858
5547
  typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter,
4859
5548
  typeof IEmailAdapter === "undefined" ? Object : IEmailAdapter,
4860
- typeof ICompanyAccessResolver === "undefined" ? Object : ICompanyAccessResolver
5549
+ typeof ICompanyAccessResolver === "undefined" ? Object : ICompanyAccessResolver,
5550
+ typeof _entity_builder_config_service_js__rspack_import_13/* .EntityBuilderConfigService */.E === "undefined" ? Object : _entity_builder_config_service_js__rspack_import_13/* .EntityBuilderConfigService */.E
4861
5551
  ])
4862
5552
  ], FlowDefinitionService);
4863
5553
  /** What the `call_flow` nodes of a flow run: a slug, and the path of the Request step they start at (null: the flow's own URL). */ function calledTargets(nodes) {
@@ -4872,7 +5562,7 @@ FlowDefinitionService = _ts_decorate([
4872
5562
  /** What a Call flow step checks about a flow: it runs only once published and switched on. */ function callableOf(flow) {
4873
5563
  return {
4874
5564
  isActive: flow.isActive && isPublished(flow),
4875
- endpoints: (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow),
5565
+ endpoints: (0,_flow_engine_flow_types_js__rspack_import_12/* .flowEndpoints */.Xr)(flow),
4876
5566
  calls: calledSlugs(flow.nodes)
4877
5567
  };
4878
5568
  }
@@ -4891,6 +5581,7 @@ __webpack_require__.d(__webpack_exports__, {
4891
5581
  Er: defaultFlowPermissionCode,
4892
5582
  HF: isPublished,
4893
5583
  YW: flowNotFound,
5584
+ b$: asksExecutePermission,
4894
5585
  j4: flowPermissions,
4895
5586
  lw: workingCopy
4896
5587
  });
@@ -4901,9 +5592,9 @@ __webpack_require__.d(__webpack_exports__, {
4901
5592
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
4902
5593
  /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
4903
5594
  /* import */ var _entities_flow_execution_entity_js__rspack_import_2 = __webpack_require__(9641);
4904
- /* import */ var _flow_engine_flow_engine_js__rspack_import_3 = __webpack_require__(8333);
4905
- /* import */ var _flow_engine_redact_js__rspack_import_5 = __webpack_require__(2678);
4906
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_4 = __webpack_require__(424);
5595
+ /* import */ var _flow_engine_flow_engine_js__rspack_import_3 = __webpack_require__(3931);
5596
+ /* import */ var _flow_engine_redact_js__rspack_import_4 = __webpack_require__(2678);
5597
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_5 = __webpack_require__(424);
4907
5598
  function _ts_decorate(decorators, target, key, desc) {
4908
5599
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4909
5600
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -4947,7 +5638,7 @@ class FlowExecutionService {
4947
5638
  /** Never throws: a logging problem must not change the answer the caller gets. */ async record(entry) {
4948
5639
  try {
4949
5640
  const repo = await this.repo();
4950
- const redactedInput = (0,_flow_engine_redact_js__rspack_import_5/* .redactSensitive */.Qr)(entry.input);
5641
+ const redactedInput = (0,_flow_engine_redact_js__rspack_import_4/* .redactSensitive */.Qr)(entry.input);
4951
5642
  const inputText = JSON.stringify(redactedInput ?? null);
4952
5643
  await repo.save(repo.create({
4953
5644
  flowId: entry.flowId,
@@ -4963,7 +5654,7 @@ class FlowExecutionService {
4963
5654
  _truncated: true,
4964
5655
  preview: inputText.slice(0, INPUT_LIMIT_CHARS)
4965
5656
  } : redactedInput,
4966
- output: (0,_flow_engine_redact_js__rspack_import_5/* .preview */.VH)(entry.result.body, OUTPUT_LIMIT_CHARS),
5657
+ output: (0,_flow_engine_redact_js__rspack_import_4/* .preview */.VH)(entry.result.body, OUTPUT_LIMIT_CHARS),
4967
5658
  error: entry.result.error?.slice(0, 2000) ?? null,
4968
5659
  trace: entry.result.trace,
4969
5660
  durationMs: entry.result.durationMs,
@@ -5057,10 +5748,10 @@ FlowExecutionService = _ts_decorate([
5057
5748
  (0,_nestjs_common__rspack_import_0.Injectable)({
5058
5749
  scope: _nestjs_common__rspack_import_0.Scope.REQUEST
5059
5750
  }),
5060
- _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D)),
5751
+ _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D)),
5061
5752
  _ts_metadata("design:type", Function),
5062
5753
  _ts_metadata("design:paramtypes", [
5063
- typeof _entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D
5754
+ typeof _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D
5064
5755
  ])
5065
5756
  ], FlowExecutionService);
5066
5757
 
@@ -5103,7 +5794,7 @@ var message_keys = __webpack_require__(5732);
5103
5794
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/entities/flow-definition.entity.ts
5104
5795
  var flow_definition_entity = __webpack_require__(3770);
5105
5796
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-engine.ts + 6 modules
5106
- var flow_engine = __webpack_require__(8333);
5797
+ var flow_engine = __webpack_require__(3931);
5107
5798
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow.errors.ts
5108
5799
  var flow_errors = __webpack_require__(8542);
5109
5800
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-input.validator.ts
@@ -5187,7 +5878,7 @@ class FlowUrlError extends flow_errors/* .FlowKeyedError */.Hp {
5187
5878
  }
5188
5879
  if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new FlowUrlError(message_keys/* .FLOW_ERROR_MESSAGES.URL_PROTOCOL */.zr.URL_PROTOCOL);
5189
5880
  if (url.username || url.password) throw new FlowUrlError(message_keys/* .FLOW_ERROR_MESSAGES.URL_CREDENTIALS */.zr.URL_CREDENTIALS);
5190
- const host = url.hostname.replace(/^\[|\]$/g, '');
5881
+ const host = url.hostname.replace(/^\[|\]$/g, '').replace(/\.+$/, '');
5191
5882
  if (!allowPrivate) {
5192
5883
  if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.internal') || host.endsWith('.local')) {
5193
5884
  throw new FlowUrlError(message_keys/* .FLOW_ERROR_MESSAGES.URL_PRIVATE */.zr.URL_PRIVATE);
@@ -5333,8 +6024,8 @@ var entity_builder_config_service = __webpack_require__(4281);
5333
6024
  var entity_builder_datasource_provider = __webpack_require__(424);
5334
6025
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/flow-definition.service.ts
5335
6026
  var flow_definition_service = __webpack_require__(5229);
5336
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/generic-entity.service.ts
5337
- var generic_entity_service = __webpack_require__(3998);
6027
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/generic-entity.service.ts + 3 modules
6028
+ var generic_entity_service = __webpack_require__(3052);
5338
6029
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/generic-entity-service.factory.ts
5339
6030
  var generic_entity_service_factory = __webpack_require__(7705);
5340
6031
  ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/flow-executor.service.ts
@@ -5403,6 +6094,7 @@ class FlowExecutorService {
5403
6094
  email;
5404
6095
  branchHierarchy;
5405
6096
  logger = new common_.Logger(FlowExecutorService.name);
6097
+ /** Sends still going out after their answers, for `whenPublished`. */ inFlight = new Set();
5406
6098
  constructor(provider, ruleEngine, moduleRef, permissionCache, config, companyAccess, notifications, email, branchHierarchy){
5407
6099
  this.provider = provider;
5408
6100
  this.ruleEngine = ruleEngine;
@@ -5496,17 +6188,17 @@ class FlowExecutorService {
5496
6188
  state.ownsTransaction = false;
5497
6189
  await qr.release().catch(()=>undefined);
5498
6190
  }
5499
- if (state.mode === 'live') await this.publishPendingEvents(state.pendingEvents, state.user);
6191
+ if (state.mode === 'live') this.publishAfterCommit(state, state.pendingEvents.splice(0));
5500
6192
  }
5501
- /** `SET LOCAL` lasts one transaction, so every transaction a run opens sets it again. */ async limitStatementTime(qr, timeoutMs) {
5502
- if (await this.provider.getDriver() === 'postgres') await qr.query(`SET LOCAL statement_timeout = ${Math.max(1000, Math.floor(timeoutMs))}`);
6193
+ async limitStatementTime(qr, timeoutMs) {
6194
+ if (await this.provider.getDriver() === 'postgres') await qr.query((0,generic_entity_service/* .statementTimeoutSql */.xV)(timeoutMs));
5503
6195
  }
5504
6196
  async commitProgress(state) {
5505
6197
  const qr = state.qr;
5506
6198
  await qr.commitTransaction();
5507
6199
  await qr.startTransaction();
5508
6200
  await this.limitStatementTime(qr, state.deadline - Date.now());
5509
- await this.publishPendingEvents(state.pendingEvents.splice(0), state.user);
6201
+ this.publishAfterCommit(state, state.pendingEvents.splice(0));
5510
6202
  }
5511
6203
  /** A multi-record step outside a transactional run: its own transaction, its record events published after it commits. */ async atomic(state, work) {
5512
6204
  if (state.qr) return work();
@@ -5517,6 +6209,7 @@ class FlowExecutorService {
5517
6209
  try {
5518
6210
  await qr.connect();
5519
6211
  await qr.startTransaction();
6212
+ await this.limitStatementTime(qr, state.deadline - Date.now());
5520
6213
  state.qr = qr;
5521
6214
  result = await work();
5522
6215
  await qr.commitTransaction();
@@ -5529,7 +6222,7 @@ class FlowExecutorService {
5529
6222
  state.qr = undefined;
5530
6223
  await qr.release().catch(()=>undefined);
5531
6224
  }
5532
- await this.publishPendingEvents(state.pendingEvents.splice(queued), state.user);
6225
+ this.publishAfterCommit(state, state.pendingEvents.splice(queued));
5533
6226
  return result;
5534
6227
  }
5535
6228
  /**
@@ -5560,7 +6253,7 @@ class FlowExecutorService {
5560
6253
  }, `Flow '${slug}' has no URL '${endpoint ?? ''}'.`);
5561
6254
  }
5562
6255
  await this.assertMayCall(flow, start, parent, env);
5563
- const checked = (0,flow_input_validator/* .validateFlowInput */.v)(start.inputSchema, input, start.bodyType);
6256
+ const checked = (0,flow_input_validator/* .validateEndpointInput */.GP)(start, input);
5564
6257
  if (checked.errors.length) {
5565
6258
  throw new flow_errors/* .FlowHttpError */.hk(400, 'Validation failed', checked.errors, {
5566
6259
  messageKey: message_keys/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
@@ -5590,6 +6283,9 @@ class FlowExecutorService {
5590
6283
  flow.slug
5591
6284
  ],
5592
6285
  pendingEvents: parent.qr ? parent.pendingEvents : [],
6286
+ publisher: parent.publisher ??= {
6287
+ chain: Promise.resolve()
6288
+ },
5593
6289
  trace: []
5594
6290
  };
5595
6291
  // A failed call inside the caller's transaction is rolled back with it (or to the step's savepoint), so its queued events must go too.
@@ -5624,7 +6320,6 @@ class FlowExecutorService {
5624
6320
  where: {
5625
6321
  slug,
5626
6322
  isActive: true,
5627
- deletedAt: (0,external_typeorm_.IsNull)(),
5628
6323
  version: (0,external_typeorm_.MoreThan)(0)
5629
6324
  }
5630
6325
  });
@@ -5650,18 +6345,23 @@ class FlowExecutorService {
5650
6345
  }
5651
6346
  createEnv() {
5652
6347
  const entityServices = new Map();
6348
+ /** One factory per run: one DI context, and related entities built once for every entity the run writes. */ let factory;
5653
6349
  /** One lookup per user for the whole run, however many steps ask. */ const access = new Map();
5654
6350
  let permissions;
5655
6351
  const env = {
5656
6352
  ruleEngine: this.ruleEngine,
5657
6353
  allowPrivateHttp: this.config.getConfig()?.flows?.allowPrivateHttp === true || process.env['ENTITY_BUILDER_FLOW_ALLOW_PRIVATE_HTTP'] === 'true',
6354
+ codeModules: this.config.getCodeModules(),
5658
6355
  entityService: (entityCode, queryRunner)=>{
5659
6356
  if (!entityServices.has(entityCode)) {
5660
6357
  const loading = (async ()=>{
5661
- const factory = await this.moduleRef.resolve(generic_entity_service_factory/* .GenericEntityServiceFactory */.G, core_.ContextIdFactory.create(), {
5662
- strict: false
5663
- });
5664
- return factory.forEntityCode(entityCode, queryRunner);
6358
+ if (!factory) {
6359
+ factory = this.moduleRef.resolve(generic_entity_service_factory/* .GenericEntityServiceFactory */.G, core_.ContextIdFactory.create(), {
6360
+ strict: false
6361
+ });
6362
+ factory.catch(()=>factory = undefined);
6363
+ }
6364
+ return (await factory).forEntityCode(entityCode, queryRunner);
5665
6365
  })();
5666
6366
  entityServices.set(entityCode, loading);
5667
6367
  // A failed load (e.g. on a transaction that later rolled back) must not be remembered for the rest of the run.
@@ -5708,6 +6408,23 @@ class FlowExecutorService {
5708
6408
  };
5709
6409
  return env;
5710
6410
  }
6411
+ /**
6412
+ * Queues `events` behind the ones this run already queued, so they go out in commit order, without holding up
6413
+ * the answer. Runs never wait on each other (one slow listener cannot stall another caller's events). Nothing
6414
+ * to wait for: a failing publish or send is logged, never turned into an error.
6415
+ */ publishAfterCommit(state, events) {
6416
+ if (!events.length) return;
6417
+ const publisher = state.publisher ??= {
6418
+ chain: Promise.resolve()
6419
+ };
6420
+ const sent = publisher.chain.then(()=>this.publishPendingEvents(events, state.user));
6421
+ publisher.chain = sent;
6422
+ this.inFlight.add(sent);
6423
+ void sent.finally(()=>this.inFlight.delete(sent));
6424
+ }
6425
+ /** Resolves once everything queued so far went out (or failed and was logged). */ async whenPublished() {
6426
+ while(this.inFlight.size)await Promise.all(this.inFlight);
6427
+ }
5711
6428
  /** After commit, so listeners see committed data and messages go out only for what was saved. A failing publish or send never turns a completed run into an error. */ async publishPendingEvents(events, user) {
5712
6429
  for (const event of events){
5713
6430
  try {
@@ -5802,16 +6519,20 @@ FlowExecutorService = _ts_decorate([
5802
6519
 
5803
6520
  },
5804
6521
  2300(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5805
- /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
5806
- /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
5807
- /* import */ var _config_message_keys_js__rspack_import_9 = __webpack_require__(5732);
5808
- /* import */ var _entities_flow_definition_entity_js__rspack_import_2 = __webpack_require__(3770);
5809
- /* import */ var _flow_engine_flow_input_validator_js__rspack_import_3 = __webpack_require__(6390);
5810
- /* import */ var _flow_engine_flow_types_js__rspack_import_4 = __webpack_require__(6256);
5811
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_5 = __webpack_require__(424);
5812
- /* import */ var _flow_definition_service_js__rspack_import_6 = __webpack_require__(5229);
5813
- /* import */ var _flow_execution_service_js__rspack_import_7 = __webpack_require__(7810);
5814
- /* import */ var _flow_executor_service_js__rspack_import_8 = __webpack_require__(3908);
6522
+ /* import */ var _flusys_nestjs_shared_classes__rspack_import_0 = __webpack_require__(6937);
6523
+ /* import */ var _flusys_nestjs_shared_modules__rspack_import_1 = __webpack_require__(1236);
6524
+ /* import */ var _flusys_nestjs_shared_utils__rspack_import_2 = __webpack_require__(9216);
6525
+ /* import */ var _nestjs_common__rspack_import_3 = __webpack_require__(6157);
6526
+ /* import */ var typeorm__rspack_import_4 = __webpack_require__(7158);
6527
+ /* import */ var _config_event_actions_js__rspack_import_13 = __webpack_require__(982);
6528
+ /* import */ var _config_message_keys_js__rspack_import_12 = __webpack_require__(5732);
6529
+ /* import */ var _entities_flow_definition_entity_js__rspack_import_5 = __webpack_require__(3770);
6530
+ /* import */ var _flow_engine_flow_input_validator_js__rspack_import_6 = __webpack_require__(6390);
6531
+ /* import */ var _flow_engine_flow_types_js__rspack_import_7 = __webpack_require__(6256);
6532
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_8 = __webpack_require__(424);
6533
+ /* import */ var _flow_definition_service_js__rspack_import_9 = __webpack_require__(5229);
6534
+ /* import */ var _flow_execution_service_js__rspack_import_10 = __webpack_require__(7810);
6535
+ /* import */ var _flow_executor_service_js__rspack_import_11 = __webpack_require__(3908);
5815
6536
  function _ts_decorate(decorators, target, key, desc) {
5816
6537
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5817
6538
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -5845,6 +6566,11 @@ function _ts_param(paramIndex, decorator) {
5845
6566
 
5846
6567
 
5847
6568
 
6569
+
6570
+
6571
+
6572
+
6573
+ const ACTIVE_FLOW_TTL_MS = 60_000;
5848
6574
  const HIDDEN_HEADERS = new Set([
5849
6575
  'authorization',
5850
6576
  'cookie',
@@ -5908,7 +6634,7 @@ const inputRejected = (errors)=>({
5908
6634
  body: {
5909
6635
  success: false,
5910
6636
  message: 'Validation failed',
5911
- messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
6637
+ messageKey: _config_message_keys_js__rspack_import_12/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
5912
6638
  errors
5913
6639
  },
5914
6640
  trace: [],
@@ -5919,31 +6645,64 @@ class FlowRuntimeService {
5919
6645
  executor;
5920
6646
  executions;
5921
6647
  definitions;
5922
- constructor(provider, executor, executions, definitions){
6648
+ cache;
6649
+ utils;
6650
+ constructor(provider, executor, executions, definitions, cache, utils){
5923
6651
  this.provider = provider;
5924
6652
  this.executor = executor;
5925
6653
  this.executions = executions;
5926
6654
  this.definitions = definitions;
6655
+ this.cache = cache;
6656
+ this.utils = utils;
5927
6657
  }
5928
- /** A flow never published is not live yet: it looks the same as an unknown one. */ async findActive(slug) {
5929
- return (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_2/* .FlowDefinition */.G)).findOne({
6658
+ /**
6659
+ * A flow never published is not live yet: it looks the same as an unknown one. Cached under the flow definitions'
6660
+ * stamp, which every flow save / publish / delete, schema change and bundle import moves - so no caller runs a
6661
+ * replaced flow. Without a reachable cache the read goes straight to the database.
6662
+ */ async findActive(slug) {
6663
+ const key = await this.activeFlowKey(slug);
6664
+ if (key) {
6665
+ const cached = await this.cache.get(key).catch(()=>undefined);
6666
+ if (cached) return cached.flow;
6667
+ }
6668
+ const flow = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_5/* .FlowDefinition */.G)).findOne({
5930
6669
  where: {
5931
6670
  slug,
5932
6671
  isActive: true,
5933
- deletedAt: (0,typeorm__rspack_import_1.IsNull)(),
5934
- version: (0,typeorm__rspack_import_1.MoreThan)(0)
6672
+ version: (0,typeorm__rspack_import_4.MoreThan)(0)
5935
6673
  }
5936
6674
  });
6675
+ if (key) await this.cache.set(key, {
6676
+ flow
6677
+ }, ACTIVE_FLOW_TTL_MS).catch(()=>undefined);
6678
+ return flow;
6679
+ }
6680
+ /** Read BEFORE the database, like ApiService: a write committing mid-read moves the stamp, so the stale row lands under a key nobody asks for. */ async activeFlowKey(slug) {
6681
+ if (!this.cache || !this.utils) return null;
6682
+ try {
6683
+ const tenant = (0,_flusys_nestjs_shared_utils__rspack_import_2.cacheTenantId)(this.provider);
6684
+ const stamp = await this.utils.getCacheStamp(this.cache, [
6685
+ _config_event_actions_js__rspack_import_13/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION
6686
+ ], tenant);
6687
+ return this.utils.getCacheKey(_config_event_actions_js__rspack_import_13/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION, {
6688
+ activeSlug: slug
6689
+ }, undefined, tenant, stamp);
6690
+ } catch {
6691
+ return null;
6692
+ }
6693
+ }
6694
+ /** The tenant of this request ('' for a single database): tenant databases cloned from one template share flow ids. */ tenantScope() {
6695
+ return (0,_flusys_nestjs_shared_utils__rspack_import_2.cacheTenantId)(this.provider) ?? '';
5937
6696
  }
5938
6697
  /** The public entry: a caller hit `POST api-flows/:slug` or `POST api-flows/:slug/:endpoint`. */ async handle(flow, endpoint, body, caller, user) {
5939
6698
  const startedAt = new Date();
5940
- const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
6699
+ const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_6/* .validateEndpointInput */.GP)(endpoint, body);
5941
6700
  if (checked.errors.length) {
5942
6701
  const result = inputRejected(checked.errors);
5943
- await this.logWebhook(flow, endpoint, body, result, startedAt, user, caller);
6702
+ this.logWebhook(flow, endpoint, body, result, startedAt, user, caller);
5944
6703
  return result;
5945
6704
  }
5946
- const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow), {
6705
+ const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_9/* .toFlowSettings */.RK)(flow), {
5947
6706
  mode: 'live',
5948
6707
  user,
5949
6708
  input: checked.values,
@@ -5952,7 +6711,7 @@ class FlowRuntimeService {
5952
6711
  authorization: callerAuthorization(caller, user),
5953
6712
  exposeDetails: false
5954
6713
  });
5955
- await this.logWebhook(flow, endpoint, checked.values, result, startedAt, user, caller);
6714
+ this.logWebhook(flow, endpoint, checked.values, result, startedAt, user, caller);
5956
6715
  return result;
5957
6716
  }
5958
6717
  /** The designer's "Test run": a saved flow or an unsaved draft. */ async testRun(dto, user, caller) {
@@ -5963,17 +6722,17 @@ class FlowRuntimeService {
5963
6722
  }
5964
6723
  if (dto.definition) {
5965
6724
  // The designer's current draft, even for a saved flow: `flow` above is kept only to attribute the run.
5966
- settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(dto.definition);
6725
+ settings = (0,_flow_definition_service_js__rspack_import_9/* .toFlowSettings */.RK)(dto.definition);
5967
6726
  } else if (flow) {
5968
- settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow);
6727
+ settings = (0,_flow_definition_service_js__rspack_import_9/* .toFlowSettings */.RK)(flow);
5969
6728
  } else {
5970
- throw new _nestjs_common__rspack_import_0.BadRequestException({
6729
+ throw new _nestjs_common__rspack_import_3.BadRequestException({
5971
6730
  message: 'Send a flowId or a definition to test.',
5972
- messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.NOTHING_TO_TEST */.wu.NOTHING_TO_TEST
6731
+ messageKey: _config_message_keys_js__rspack_import_12/* .FLOW_MESSAGES.NOTHING_TO_TEST */.wu.NOTHING_TO_TEST
5973
6732
  });
5974
6733
  }
5975
6734
  const mode = dto.mode ?? 'dry_run';
5976
- const endpoint = (0,_flow_engine_flow_types_js__rspack_import_4/* .findEndpoint */.vS)(settings, dto.endpoint);
6735
+ const endpoint = (0,_flow_engine_flow_types_js__rspack_import_7/* .findEndpoint */.vS)(settings, dto.endpoint);
5977
6736
  // Checked before anything runs.
5978
6737
  await this.definitions.assertMayTest(settings, endpoint, user, {
5979
6738
  mode,
@@ -5981,9 +6740,9 @@ class FlowRuntimeService {
5981
6740
  saved: !!flow
5982
6741
  });
5983
6742
  if (!endpoint) {
5984
- throw new _nestjs_common__rspack_import_0.BadRequestException({
6743
+ throw new _nestjs_common__rspack_import_3.BadRequestException({
5985
6744
  message: `This flow has no URL '${dto.endpoint ?? ''}'.`,
5986
- messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.ENDPOINT_NOT_FOUND */.wu.ENDPOINT_NOT_FOUND,
6745
+ messageKey: _config_message_keys_js__rspack_import_12/* .FLOW_MESSAGES.ENDPOINT_NOT_FOUND */.wu.ENDPOINT_NOT_FOUND,
5987
6746
  messageVariables: {
5988
6747
  endpoint: dto.endpoint ?? ''
5989
6748
  }
@@ -5991,9 +6750,9 @@ class FlowRuntimeService {
5991
6750
  }
5992
6751
  const validation = await this.definitions.validate(settings);
5993
6752
  if (validation.errors.length) {
5994
- throw new _nestjs_common__rspack_import_0.BadRequestException({
6753
+ throw new _nestjs_common__rspack_import_3.BadRequestException({
5995
6754
  message: 'The flow has problems.',
5996
- messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
6755
+ messageKey: _config_message_keys_js__rspack_import_12/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
5997
6756
  errors: validation.errors.map((error)=>({
5998
6757
  field: 'flow',
5999
6758
  ...error
@@ -6003,7 +6762,7 @@ class FlowRuntimeService {
6003
6762
  const startedAt = new Date();
6004
6763
  const simulated = simulatedCaller(caller, dto.request);
6005
6764
  const body = dto.input ?? (endpoint.bodyType === 'list' ? [] : {});
6006
- const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
6765
+ const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_6/* .validateEndpointInput */.GP)(endpoint, body);
6007
6766
  const result = checked.errors.length ? inputRejected(checked.errors) : await this.executor.run(settings, {
6008
6767
  mode,
6009
6768
  user,
@@ -6032,8 +6791,8 @@ class FlowRuntimeService {
6032
6791
  warnings: validation.warnings
6033
6792
  };
6034
6793
  }
6035
- async logWebhook(flow, endpoint, input, result, startedAt, user, caller) {
6036
- await this.executions.record({
6794
+ /** Written after the answer is on its way: `record` never throws, and a live caller never waits for the log. */ logWebhook(flow, endpoint, input, result, startedAt, user, caller) {
6795
+ void this.executions.record({
6037
6796
  flowId: flow.id,
6038
6797
  flowSlug: flow.slug,
6039
6798
  flowVersion: flow.version,
@@ -6050,19 +6809,25 @@ class FlowRuntimeService {
6050
6809
  }
6051
6810
  }
6052
6811
  FlowRuntimeService = _ts_decorate([
6053
- (0,_nestjs_common__rspack_import_0.Injectable)({
6054
- scope: _nestjs_common__rspack_import_0.Scope.REQUEST
6812
+ (0,_nestjs_common__rspack_import_3.Injectable)({
6813
+ scope: _nestjs_common__rspack_import_3.Scope.REQUEST
6055
6814
  }),
6056
- _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D)),
6057
- _ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F)),
6058
- _ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(_flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A)),
6059
- _ts_param(3, (0,_nestjs_common__rspack_import_0.Inject)(_flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk)),
6815
+ _ts_param(0, (0,_nestjs_common__rspack_import_3.Inject)(_entity_builder_datasource_provider_js__rspack_import_8/* .EntityBuilderDataSourceProvider */.D)),
6816
+ _ts_param(1, (0,_nestjs_common__rspack_import_3.Inject)(_flow_executor_service_js__rspack_import_11/* .FlowExecutorService */.F)),
6817
+ _ts_param(2, (0,_nestjs_common__rspack_import_3.Inject)(_flow_execution_service_js__rspack_import_10/* .FlowExecutionService */.A)),
6818
+ _ts_param(3, (0,_nestjs_common__rspack_import_3.Inject)(_flow_definition_service_js__rspack_import_9/* .FlowDefinitionService */.Tk)),
6819
+ _ts_param(4, (0,_nestjs_common__rspack_import_3.Optional)()),
6820
+ _ts_param(4, (0,_nestjs_common__rspack_import_3.Inject)('CACHE_INSTANCE')),
6821
+ _ts_param(5, (0,_nestjs_common__rspack_import_3.Optional)()),
6822
+ _ts_param(5, (0,_nestjs_common__rspack_import_3.Inject)(_flusys_nestjs_shared_modules__rspack_import_1.UtilsService)),
6060
6823
  _ts_metadata("design:type", Function),
6061
6824
  _ts_metadata("design:paramtypes", [
6062
- typeof _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D,
6063
- typeof _flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F === "undefined" ? Object : _flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F,
6064
- typeof _flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A === "undefined" ? Object : _flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A,
6065
- typeof _flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk
6825
+ typeof _entity_builder_datasource_provider_js__rspack_import_8/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_8/* .EntityBuilderDataSourceProvider */.D,
6826
+ typeof _flow_executor_service_js__rspack_import_11/* .FlowExecutorService */.F === "undefined" ? Object : _flow_executor_service_js__rspack_import_11/* .FlowExecutorService */.F,
6827
+ typeof _flow_execution_service_js__rspack_import_10/* .FlowExecutionService */.A === "undefined" ? Object : _flow_execution_service_js__rspack_import_10/* .FlowExecutionService */.A,
6828
+ typeof _flow_definition_service_js__rspack_import_9/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_9/* .FlowDefinitionService */.Tk,
6829
+ typeof _flusys_nestjs_shared_classes__rspack_import_0.HybridCache === "undefined" ? Object : _flusys_nestjs_shared_classes__rspack_import_0.HybridCache,
6830
+ typeof _flusys_nestjs_shared_modules__rspack_import_1.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_1.UtilsService
6066
6831
  ])
6067
6832
  ], FlowRuntimeService);
6068
6833
 
@@ -6075,13 +6840,12 @@ __webpack_require__.d(__webpack_exports__, {
6075
6840
  },
6076
6841
  7705(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6077
6842
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
6078
- /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
6079
- /* import */ var _config_message_keys_js__rspack_import_7 = __webpack_require__(5732);
6080
- /* import */ var _entities_entity_definition_entity_js__rspack_import_2 = __webpack_require__(3189);
6081
- /* import */ var _entities_field_definition_entity_js__rspack_import_3 = __webpack_require__(2206);
6082
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_4 = __webpack_require__(424);
6083
- /* import */ var _generic_entity_service_js__rspack_import_5 = __webpack_require__(3998);
6084
- /* import */ var _schema_registry_service_js__rspack_import_6 = __webpack_require__(2134);
6843
+ /* import */ var _config_message_keys_js__rspack_import_6 = __webpack_require__(5732);
6844
+ /* import */ var _entities_entity_definition_entity_js__rspack_import_1 = __webpack_require__(3189);
6845
+ /* import */ var _entity_builder_config_service_js__rspack_import_2 = __webpack_require__(4281);
6846
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_3 = __webpack_require__(424);
6847
+ /* import */ var _generic_entity_service_js__rspack_import_4 = __webpack_require__(3052);
6848
+ /* import */ var _schema_registry_service_js__rspack_import_5 = __webpack_require__(2134);
6085
6849
  function _ts_decorate(decorators, target, key, desc) {
6086
6850
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6087
6851
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -6112,13 +6876,15 @@ function _ts_param(paramIndex, decorator) {
6112
6876
 
6113
6877
 
6114
6878
 
6115
-
6116
6879
  class GenericEntityServiceFactory {
6117
6880
  schemaRegistry;
6118
6881
  dataSourceProvider;
6119
- constructor(schemaRegistry, dataSourceProvider){
6882
+ config;
6883
+ /** A RELATION target, built once per factory however many rows and fields point at it. */ relatedById = new Map();
6884
+ constructor(schemaRegistry, dataSourceProvider, config){
6120
6885
  this.schemaRegistry = schemaRegistry;
6121
6886
  this.dataSourceProvider = dataSourceProvider;
6887
+ this.config = config;
6122
6888
  }
6123
6889
  /** `queryRunner`: an open transaction to read the metadata on, so the caller never needs a second pooled connection while holding one. */ async forEntityCode(entityCode, queryRunner) {
6124
6890
  return this.build({
@@ -6130,44 +6896,49 @@ class GenericEntityServiceFactory {
6130
6896
  id: entityDefinitionId
6131
6897
  }, entityDefinitionId, queryRunner);
6132
6898
  }
6899
+ related(entityDefinitionId, queryRunner) {
6900
+ let loading = this.relatedById.get(entityDefinitionId);
6901
+ if (!loading) {
6902
+ loading = this.forEntityDefinitionId(entityDefinitionId, queryRunner);
6903
+ this.relatedById.set(entityDefinitionId, loading);
6904
+ loading.catch(()=>this.relatedById.delete(entityDefinitionId));
6905
+ }
6906
+ return loading;
6907
+ }
6133
6908
  async build(where, reference, queryRunner) {
6134
- const definitionRepository = queryRunner ? queryRunner.manager.getRepository(_entities_entity_definition_entity_js__rspack_import_2/* .EntityDefinition */.f) : await this.dataSourceProvider.getRepository(_entities_entity_definition_entity_js__rspack_import_2/* .EntityDefinition */.f);
6909
+ await this.schemaRegistry.warmUp(queryRunner);
6910
+ const definitionRepository = queryRunner ? queryRunner.manager.getRepository(_entities_entity_definition_entity_js__rspack_import_1/* .EntityDefinition */.f) : await this.dataSourceProvider.getRepository(_entities_entity_definition_entity_js__rspack_import_1/* .EntityDefinition */.f);
6135
6911
  const definition = await definitionRepository.findOne({
6136
6912
  where
6137
6913
  });
6138
6914
  if (!definition) {
6139
6915
  throw new _nestjs_common__rspack_import_0.NotFoundException({
6140
6916
  message: `Entity '${reference}' not found.`,
6141
- messageKey: _config_message_keys_js__rspack_import_7/* .ENTITY_DEFINITION_MESSAGES.NOT_FOUND */.pL.NOT_FOUND,
6917
+ messageKey: _config_message_keys_js__rspack_import_6/* .ENTITY_DEFINITION_MESSAGES.NOT_FOUND */.pL.NOT_FOUND,
6142
6918
  messageVariables: {
6143
6919
  id: reference
6144
6920
  }
6145
6921
  });
6146
6922
  }
6147
- const fieldRepository = queryRunner ? queryRunner.manager.getRepository(_entities_field_definition_entity_js__rspack_import_3/* .FieldDefinition */.c) : await this.dataSourceProvider.getRepository(_entities_field_definition_entity_js__rspack_import_3/* .FieldDefinition */.c);
6148
- const fields = await fieldRepository.find({
6149
- where: {
6150
- entityDefinitionId: definition.id,
6151
- deprecatedAt: (0,typeorm__rspack_import_1.IsNull)()
6152
- },
6153
- order: {
6154
- order: 'ASC'
6155
- }
6923
+ const { schema, fields } = await this.schemaRegistry.resolve(definition, queryRunner);
6924
+ return new _generic_entity_service_js__rspack_import_4/* .GenericEntityService */.GK(definition, schema, fields, this.dataSourceProvider, (targetId, runner)=>this.related(targetId, runner), {
6925
+ fastUpdates: this.config?.isFastUpdatesEnabled() ?? true
6156
6926
  });
6157
- const schema = await this.schemaRegistry.getSchema(definition.id, queryRunner);
6158
- return new _generic_entity_service_js__rspack_import_5/* .GenericEntityService */.GK(definition, schema, fields, this.dataSourceProvider, (targetId, runner)=>this.forEntityDefinitionId(targetId, runner));
6159
6927
  }
6160
6928
  }
6161
6929
  GenericEntityServiceFactory = _ts_decorate([
6162
6930
  (0,_nestjs_common__rspack_import_0.Injectable)({
6163
6931
  scope: _nestjs_common__rspack_import_0.Scope.REQUEST
6164
6932
  }),
6165
- _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_schema_registry_service_js__rspack_import_6/* .SchemaRegistryService */.C)),
6166
- _ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D)),
6933
+ _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_schema_registry_service_js__rspack_import_5/* .SchemaRegistryService */.C)),
6934
+ _ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_3/* .EntityBuilderDataSourceProvider */.D)),
6935
+ _ts_param(2, (0,_nestjs_common__rspack_import_0.Optional)()),
6936
+ _ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_config_service_js__rspack_import_2/* .EntityBuilderConfigService */.E)),
6167
6937
  _ts_metadata("design:type", Function),
6168
6938
  _ts_metadata("design:paramtypes", [
6169
- typeof _schema_registry_service_js__rspack_import_6/* .SchemaRegistryService */.C === "undefined" ? Object : _schema_registry_service_js__rspack_import_6/* .SchemaRegistryService */.C,
6170
- typeof _entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D
6939
+ typeof _schema_registry_service_js__rspack_import_5/* .SchemaRegistryService */.C === "undefined" ? Object : _schema_registry_service_js__rspack_import_5/* .SchemaRegistryService */.C,
6940
+ typeof _entity_builder_datasource_provider_js__rspack_import_3/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_3/* .EntityBuilderDataSourceProvider */.D,
6941
+ typeof _entity_builder_config_service_js__rspack_import_2/* .EntityBuilderConfigService */.E === "undefined" ? Object : _entity_builder_config_service_js__rspack_import_2/* .EntityBuilderConfigService */.E
6171
6942
  ])
6172
6943
  ], GenericEntityServiceFactory);
6173
6944
 
@@ -6177,29 +6948,101 @@ __webpack_require__.d(__webpack_exports__, {
6177
6948
 
6178
6949
 
6179
6950
  },
6180
- 3998(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6181
- /* import */ var _flusys_nestjs_shared_classes__rspack_import_0 = __webpack_require__(6937);
6182
- /* import */ var _flusys_nestjs_shared_utils__rspack_import_1 = __webpack_require__(9216);
6183
- /* import */ var _nestjs_common__rspack_import_2 = __webpack_require__(6157);
6184
- /* import */ var _config_event_actions_js__rspack_import_10 = __webpack_require__(982);
6185
- /* import */ var _config_message_keys_js__rspack_import_8 = __webpack_require__(5732);
6186
- /* import */ var _enums_entity_status_enum_js__rspack_import_9 = __webpack_require__(1313);
6187
- /* import */ var _enums_field_type_enum_js__rspack_import_7 = __webpack_require__(9614);
6188
- /* import */ var _interfaces_generic_record_interface_js__rspack_import_3 = __webpack_require__(3372);
6189
- /* import */ var _database_error_mapper_js__rspack_import_4 = __webpack_require__(8323);
6190
- /* import */ var _field_value_validator_js__rspack_import_5 = __webpack_require__(3525);
6191
- /* import */ var _schema_columns_js__rspack_import_6 = __webpack_require__(5313);
6951
+ 3052(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6192
6952
 
6953
+ // EXPORTS
6954
+ __webpack_require__.d(__webpack_exports__, {
6955
+ zb: () => (/* binding */ ACTOR_BRANCH_METADATA_KEY),
6956
+ GK: () => (/* binding */ GenericEntityService),
6957
+ DG: () => (/* binding */ actorMetadata),
6958
+ xV: () => (/* binding */ statementTimeoutSql)
6959
+ });
6193
6960
 
6961
+ // EXTERNAL MODULE: external "@flusys/nestjs-shared/classes"
6962
+ var classes_ = __webpack_require__(6937);
6963
+ // EXTERNAL MODULE: external "@flusys/nestjs-shared/utils"
6964
+ var utils_ = __webpack_require__(9216);
6965
+ // EXTERNAL MODULE: external "@nestjs/common"
6966
+ var common_ = __webpack_require__(6157);
6967
+ // EXTERNAL MODULE: external "typeorm"
6968
+ var external_typeorm_ = __webpack_require__(7158);
6969
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/config/event-actions.ts
6970
+ var event_actions = __webpack_require__(982);
6971
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/config/message-keys.ts
6972
+ var message_keys = __webpack_require__(5732);
6973
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/enums/entity-status.enum.ts
6974
+ var entity_status_enum = __webpack_require__(1313);
6975
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/enums/field-type.enum.ts
6976
+ var field_type_enum = __webpack_require__(9614);
6977
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/interfaces/generic-record.interface.ts
6978
+ var generic_record_interface = __webpack_require__(3372);
6979
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/database-error.mapper.ts
6980
+ var database_error_mapper = __webpack_require__(8323);
6981
+ ;// CONCATENATED MODULE: external "typeorm/persistence/Subject.js"
6194
6982
 
6983
+ ;// CONCATENATED MODULE: external "typeorm/persistence/SubjectExecutor.js"
6195
6984
 
6985
+ ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/save-loaded.ts
6196
6986
 
6197
6987
 
6988
+ /**
6989
+ * `manager.save(target, entity)` for a row the caller has already read in this transaction: TypeORM's own
6990
+ * persistence (`SubjectExecutor` - change detection, UPDATE of the changed columns, `updated_at`, reload of
6991
+ * generated values, nulls for nullable columns left out) run against `databaseEntity`, skipping the SELECT
6992
+ * `save()` would make to find it. `databaseEntity` must be the row as loaded, before `entity` was merged into it.
6993
+ * Entity-builder schemas have no relations, so the cascade / relation subject builders `save()` runs have nothing to do.
6994
+ */ async function saveLoaded(queryRunner, target, entity, databaseEntity) {
6995
+ const metadata = queryRunner.connection.getMetadata(target).findInheritanceMetadata(entity);
6996
+ const subject = new __rspack_external_typeorm_persistence_Subject_js_50dfc7e4.Subject({
6997
+ metadata,
6998
+ entity,
6999
+ canBeInserted: true,
7000
+ canBeUpdated: true
7001
+ });
7002
+ subject.databaseEntity = databaseEntity;
7003
+ subject.databaseEntityLoaded = true;
7004
+ const executor = new __rspack_external_typeorm_persistence_SubjectExecutor_js_7cea48f1.SubjectExecutor(queryRunner, [
7005
+ subject
7006
+ ]);
7007
+ if (!executor.hasExecutableOperations) return entity;
7008
+ const ownTransaction = !queryRunner.isTransactionActive;
7009
+ if (ownTransaction) await queryRunner.startTransaction();
7010
+ try {
7011
+ await executor.execute();
7012
+ if (ownTransaction) await queryRunner.commitTransaction();
7013
+ } catch (error) {
7014
+ if (ownTransaction) await queryRunner.rollbackTransaction();
7015
+ throw error;
7016
+ }
7017
+ return entity;
7018
+ }
6198
7019
 
7020
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/field-value-validator.ts
7021
+ var field_value_validator = __webpack_require__(3525);
7022
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/schema-columns.ts
7023
+ var schema_columns = __webpack_require__(5313);
7024
+ ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/generic-entity.service.ts
6199
7025
 
6200
7026
 
6201
7027
 
6202
7028
 
7029
+
7030
+
7031
+
7032
+
7033
+
7034
+
7035
+
7036
+
7037
+
7038
+ const IDLE_IN_TRANSACTION_MARGIN_MS = 10_000;
7039
+ /**
7040
+ * `SET LOCAL` lasts one transaction, so every transaction a flow run opens sets it again. The idle limit ends a
7041
+ * transaction left open past the run's time (a connection never released) instead of letting it hold its locks.
7042
+ */ const statementTimeoutSql = (timeoutMs)=>{
7043
+ const statementMs = Math.max(1000, Math.floor(timeoutMs));
7044
+ return `SET LOCAL statement_timeout = ${statementMs}; SET LOCAL idle_in_transaction_session_timeout = ${statementMs + IDLE_IN_TRANSACTION_MARGIN_MS}`;
7045
+ };
6203
7046
  /** The event envelope's actor carries userId/companyId but not branchId, and permissions are
6204
7047
  * cached per user+company+branch - so consumers that act as the original caller read the branch from here. */ const ACTOR_BRANCH_METADATA_KEY = '_actorBranchId';
6205
7048
  function actorMetadata(user) {
@@ -6207,7 +7050,9 @@ function actorMetadata(user) {
6207
7050
  [ACTOR_BRANCH_METADATA_KEY]: user.branchId
6208
7051
  } : {};
6209
7052
  }
6210
- const SYSTEM_COLUMNS = new Set(_schema_columns_js__rspack_import_6/* .SYSTEM_COLUMN_NAMES */.mq);
7053
+ const SYSTEM_COLUMNS = new Set(schema_columns/* .SYSTEM_COLUMN_NAMES */.mq);
7054
+ const INSERT_CHUNK_ROWS = 500;
7055
+ /** Bind parameters per statement, under PostgreSQL's 65535 (MySQL allows more). */ const INSERT_CHUNK_PARAMS = 30_000;
6211
7056
  const FILTER_COMPARISON_SQL = {
6212
7057
  eq: '=',
6213
7058
  ne: '<>',
@@ -6223,18 +7068,13 @@ const FILTER_COMPARISON_SQL = {
6223
7068
  ends_with: (t)=>`%${t}`
6224
7069
  };
6225
7070
  const TEXT_FIELD_TYPES = new Set([
6226
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.TEXT */.P.TEXT,
6227
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
6228
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.EMAIL */.P.EMAIL,
6229
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT
7071
+ field_type_enum/* .FieldType.TEXT */.P.TEXT,
7072
+ field_type_enum/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
7073
+ field_type_enum/* .FieldType.EMAIL */.P.EMAIL,
7074
+ field_type_enum/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT
6230
7075
  ]);
6231
7076
  /**
6232
- * Generic CRUD for one dynamically-defined entity, mirroring ApiService's shape
6233
- * (transactional writes, lifecycle event publishing) but
6234
- * operating on Record<string, unknown> against a runtime EntitySchema instead of a
6235
- * compile-time entity class. One instance per request, produced by
6236
- * GenericEntityServiceFactory - see that class for why this isn't itself `@Injectable`.
6237
- *
7077
+ * Reads and writes the records of one runtime entity (built per call by GenericEntityServiceFactory).
6238
7078
  * Records hold only the fields the entity designer defines: scoping them to a company or branch
6239
7079
  * is the flow's job, through fields such as `company_id` mapped from the caller.
6240
7080
  */ class GenericEntityService {
@@ -6243,15 +7083,17 @@ const TEXT_FIELD_TYPES = new Set([
6243
7083
  fields;
6244
7084
  dataSourceProvider;
6245
7085
  resolveRelated;
7086
+ options;
6246
7087
  repository;
6247
7088
  activeFieldCodes;
6248
7089
  /** The system columns this entity's table really has - a filter or sort on any other one is refused, not sent to SQL. */ presentSystemColumns;
6249
- constructor(definition, schema, fields, dataSourceProvider, resolveRelated){
7090
+ constructor(definition, schema, fields, dataSourceProvider, resolveRelated, options = {}){
6250
7091
  this.definition = definition;
6251
7092
  this.schema = schema;
6252
7093
  this.fields = fields;
6253
7094
  this.dataSourceProvider = dataSourceProvider;
6254
7095
  this.resolveRelated = resolveRelated;
7096
+ this.options = options;
6255
7097
  this.activeFieldCodes = new Set(fields.filter((f)=>!f.deprecatedAt && !f.isSystemField).map((f)=>f.code));
6256
7098
  this.presentSystemColumns = new Set([
6257
7099
  'created_at',
@@ -6271,15 +7113,17 @@ const TEXT_FIELD_TYPES = new Set([
6271
7113
  }
6272
7114
  validateValues(values, mode) {
6273
7115
  const fields = this.fields.filter((f)=>!f.deprecatedAt && !f.isSystemField);
6274
- const { values: normalized, errors } = (0,_field_value_validator_js__rspack_import_5/* .validateRecord */.e)(fields, values, mode);
7116
+ const { values: normalized, errors } = (0,field_value_validator/* .validateRecord */.e)(fields, values, mode);
6275
7117
  this.throwIfInvalid(errors);
6276
7118
  return normalized;
6277
7119
  }
6278
7120
  throwIfInvalid(errors) {
6279
- if (errors.length === 0) return;
6280
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7121
+ if (errors.length) throw this.invalid(errors);
7122
+ }
7123
+ invalid(errors) {
7124
+ return new common_.BadRequestException({
6281
7125
  message: 'Validation failed',
6282
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.VALIDATION_FAILED */.j4.VALIDATION_FAILED,
7126
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.VALIDATION_FAILED */.j4.VALIDATION_FAILED,
6283
7127
  errors
6284
7128
  });
6285
7129
  }
@@ -6288,24 +7132,43 @@ const TEXT_FIELD_TYPES = new Set([
6288
7132
  const errors = [];
6289
7133
  for (const field of this.fields){
6290
7134
  const value = values[field.code];
6291
- if (field.fieldType !== _enums_field_type_enum_js__rspack_import_7/* .FieldType.RELATION */.P.RELATION || !field.relationTargetEntityId || typeof value !== 'string') continue;
7135
+ if (field.fieldType !== field_type_enum/* .FieldType.RELATION */.P.RELATION || !field.relationTargetEntityId || typeof value !== 'string') continue;
6292
7136
  const related = await this.resolveRelated(field.relationTargetEntityId, queryRunner);
6293
7137
  const found = await related.exists(value, {
6294
7138
  queryRunner
6295
7139
  });
6296
- if (!found) errors.push({
6297
- field: field.code,
6298
- ...(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_8/* .FIELD_VALIDATION_MESSAGES.RELATED_NOT_FOUND */.KE.RELATED_NOT_FOUND, {
6299
- label: field.label
6300
- })
6301
- });
7140
+ if (!found) errors.push(this.relatedNotFound(field));
6302
7141
  }
6303
7142
  this.throwIfInvalid(errors);
6304
7143
  }
7144
+ relatedNotFound(field) {
7145
+ return {
7146
+ field: field.code,
7147
+ ...(0,utils_.messageRef)(message_keys/* .FIELD_VALIDATION_MESSAGES.RELATED_NOT_FOUND */.KE.RELATED_NOT_FOUND, {
7148
+ label: field.label
7149
+ })
7150
+ };
7151
+ }
7152
+ /** The ids each RELATION field of `rows` names that really exist - one query per field, however many rows. */ async existingRelatedIds(rows, queryRunner) {
7153
+ const found = new Map();
7154
+ if (!this.resolveRelated) return found;
7155
+ for (const field of this.fields){
7156
+ if (field.fieldType !== field_type_enum/* .FieldType.RELATION */.P.RELATION || !field.relationTargetEntityId) continue;
7157
+ const ids = [
7158
+ ...new Set(rows.map((row)=>row[field.code]).filter((value)=>typeof value === 'string'))
7159
+ ];
7160
+ if (!ids.length) continue;
7161
+ const related = await this.resolveRelated(field.relationTargetEntityId, queryRunner);
7162
+ found.set(field.code, await related.existingIds(ids, {
7163
+ queryRunner
7164
+ }));
7165
+ }
7166
+ return found;
7167
+ }
6305
7168
  notFound(id) {
6306
- return new _nestjs_common__rspack_import_2.NotFoundException({
7169
+ return new common_.NotFoundException({
6307
7170
  message: `Record '${id}' not found on entity '${this.entityCode}'.`,
6308
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.NOT_FOUND */.j4.NOT_FOUND,
7171
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.NOT_FOUND */.j4.NOT_FOUND,
6309
7172
  messageVariables: {
6310
7173
  id,
6311
7174
  entity: this.entityCode
@@ -6321,19 +7184,11 @@ const TEXT_FIELD_TYPES = new Set([
6321
7184
  updated_by_id: user.id
6322
7185
  };
6323
7186
  }
6324
- async stampDeletedBy(queryRunner, target, id, userId) {
6325
- if (!this.definition.isAuditEnabled) return;
6326
- await queryRunner.manager.update(target, [
6327
- id
6328
- ], {
6329
- deleted_by_id: userId
6330
- });
6331
- }
6332
7187
  ensureActive() {
6333
- if (this.definition.status === _enums_entity_status_enum_js__rspack_import_9/* .EntityStatus.DEPRECATED */.c.DEPRECATED) {
6334
- throw new _nestjs_common__rspack_import_2.ForbiddenException({
7188
+ if (this.definition.status === entity_status_enum/* .EntityStatus.DEPRECATED */.c.DEPRECATED) {
7189
+ throw new common_.ForbiddenException({
6335
7190
  message: `Entity '${this.definition.code}' has been deprecated and no longer accepts writes.`,
6336
- messageKey: _config_message_keys_js__rspack_import_8/* .ENTITY_DEFINITION_MESSAGES.DEPRECATED */.pL.DEPRECATED,
7191
+ messageKey: message_keys/* .ENTITY_DEFINITION_MESSAGES.DEPRECATED */.pL.DEPRECATED,
6337
7192
  messageVariables: {
6338
7193
  code: this.definition.code
6339
7194
  }
@@ -6353,24 +7208,22 @@ const TEXT_FIELD_TYPES = new Set([
6353
7208
  }
6354
7209
  return this.repository;
6355
7210
  }
6356
- /** Rejects any key that isn't a real, active field on this entity - the whitelist that
6357
- * keeps mass-assignment and column-name-as-SQL-injection (see getFilterQuery/getSortQuery
6358
- * below) both structurally impossible, since only known-good column names are ever used. */ validateKeys(dto) {
7211
+ /** Only active field codes may be written: no mass assignment, and no client key ever reaches SQL as a column name. */ validateKeys(dto) {
6359
7212
  for (const key of Object.keys(dto)){
6360
7213
  // `id` included: an insert that names an id would overwrite the record that has it.
6361
7214
  if (SYSTEM_COLUMNS.has(key)) {
6362
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7215
+ throw new common_.BadRequestException({
6363
7216
  message: `'${key}' is a system column and cannot be set directly.`,
6364
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.SYSTEM_COLUMN_READONLY */.j4.SYSTEM_COLUMN_READONLY,
7217
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.SYSTEM_COLUMN_READONLY */.j4.SYSTEM_COLUMN_READONLY,
6365
7218
  messageVariables: {
6366
7219
  key
6367
7220
  }
6368
7221
  });
6369
7222
  }
6370
7223
  if (!this.activeFieldCodes.has(key)) {
6371
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7224
+ throw new common_.BadRequestException({
6372
7225
  message: `'${key}' is not a field on entity '${this.entityCode}'.`,
6373
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.UNKNOWN_FIELD */.j4.UNKNOWN_FIELD,
7226
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.UNKNOWN_FIELD */.j4.UNKNOWN_FIELD,
6374
7227
  messageVariables: {
6375
7228
  key,
6376
7229
  entity: this.entityCode
@@ -6398,6 +7251,71 @@ const TEXT_FIELD_TYPES = new Set([
6398
7251
  };
6399
7252
  }, user);
6400
7253
  }
7254
+ /**
7255
+ * The checks `insert` makes before touching the table, for a caller that inserts several rows in one
7256
+ * statement (`insertValidated`): the same errors, thrown at the same row. Returns the values to insert.
7257
+ */ prepareInsert(dto) {
7258
+ this.ensureActive();
7259
+ this.validateKeys(dto);
7260
+ return this.validateValues(dto, 'insert');
7261
+ }
7262
+ /** The first of `rows` (in order) naming a related record that does not exist, as `insert` would reject it - one query per RELATION field. */ async firstMissingRelation(rows, queryRunner) {
7263
+ try {
7264
+ const existing = await this.existingRelatedIds(rows, queryRunner);
7265
+ for(let index = 0; index < rows.length; index++){
7266
+ const errors = this.missingRelations(rows[index], existing);
7267
+ if (errors.length) return {
7268
+ index,
7269
+ error: this.invalid(errors)
7270
+ };
7271
+ }
7272
+ return null;
7273
+ } catch (error) {
7274
+ throw (0,database_error_mapper/* .mapDatabaseError */.E)(error, [
7275
+ ...this.activeFieldCodes
7276
+ ]);
7277
+ }
7278
+ }
7279
+ /**
7280
+ * Rows from `prepareInsert`, whose relations were checked, in one multi-row statement per chunk - each saved record
7281
+ * comes back as `insert` returns it. On the caller's transaction, which publishes the events.
7282
+ */ async insertValidated(rows, user, options) {
7283
+ const repository = await this.ensureRepository();
7284
+ return this.withTransaction({
7285
+ ...options,
7286
+ publishEvents: false
7287
+ }, async (queryRunner)=>{
7288
+ const entities = rows.map((values)=>repository.create({
7289
+ ...values,
7290
+ ...this.auditFields('created', user)
7291
+ }));
7292
+ const chunk = Math.max(1, Math.min(INSERT_CHUNK_ROWS, Math.floor(INSERT_CHUNK_PARAMS / (this.activeFieldCodes.size + 6))));
7293
+ for(let start = 0; start < entities.length; start += chunk){
7294
+ await queryRunner.manager.createQueryBuilder().insert().into(repository.target).values(entities.slice(start, start + chunk)).execute();
7295
+ }
7296
+ // What `save()` adds to a record it inserted: a nullable column left out reads back as null.
7297
+ const nullable = repository.metadata.columns.filter((column)=>column.isNullable && !column.isVirtual && !column.isDeleteDate);
7298
+ for (const entity of entities){
7299
+ for (const column of nullable)if (column.getEntityValue(entity) === undefined) column.setEntityValue(entity, null);
7300
+ }
7301
+ return {
7302
+ saved: entities,
7303
+ events: []
7304
+ };
7305
+ }, user);
7306
+ }
7307
+ /** No unique field: a multi-row insert then fails only where a row-by-row one would, so the two answer alike. */ get batchInsertable() {
7308
+ return !this.fields.some((f)=>f.unique && !f.deprecatedAt && !f.isSystemField);
7309
+ }
7310
+ missingRelations(values, existing) {
7311
+ const errors = [];
7312
+ for (const field of this.fields){
7313
+ const value = values[field.code];
7314
+ const known = existing.get(field.code);
7315
+ if (known && typeof value === 'string' && !known.has(value)) errors.push(this.relatedNotFound(field));
7316
+ }
7317
+ return errors;
7318
+ }
6401
7319
  async update(dto, user, options = {}) {
6402
7320
  this.ensureActive();
6403
7321
  this.assertUpdatable(dto);
@@ -6426,6 +7344,42 @@ const TEXT_FIELD_TYPES = new Set([
6426
7344
  });
6427
7345
  return queryRunner.manager.save(repository.target, entity);
6428
7346
  }
7347
+ /**
7348
+ * `update` of a record the caller has just read in this transaction (an update by filter's matches): the same
7349
+ * checks and result, without reading it again. Falls back to `update` when `fastUpdates` is off.
7350
+ */ async updateLoaded(row, dto, user, options = {}) {
7351
+ const { id: _id, ...values } = dto;
7352
+ if (this.options.fastUpdates === false) return this.update({
7353
+ ...values,
7354
+ id: row.id
7355
+ }, user, options);
7356
+ this.ensureActive();
7357
+ this.assertUpdatable({
7358
+ ...values,
7359
+ id: row.id
7360
+ });
7361
+ const repository = await this.ensureRepository();
7362
+ return this.withTransaction(options, async (queryRunner)=>{
7363
+ const validated = this.validateValues(values, 'update');
7364
+ await this.assertRelationsExist(validated, queryRunner);
7365
+ const saved = await saveLoaded(queryRunner, repository.target, Object.assign({
7366
+ ...row
7367
+ }, validated, this.auditFields('updated', user)), {
7368
+ ...row
7369
+ });
7370
+ return {
7371
+ saved,
7372
+ events: [
7373
+ {
7374
+ action: 'updated',
7375
+ rows: [
7376
+ saved
7377
+ ]
7378
+ }
7379
+ ]
7380
+ };
7381
+ }, user);
7382
+ }
6429
7383
  async updateRow(dto, user, repository, queryRunner) {
6430
7384
  const { id, ...rest } = dto;
6431
7385
  const existing = await queryRunner.manager.findOne(repository.target, {
@@ -6436,40 +7390,40 @@ const TEXT_FIELD_TYPES = new Set([
6436
7390
  if (!existing) throw this.notFound(id);
6437
7391
  const validated = this.validateValues(rest, 'update');
6438
7392
  await this.assertRelationsExist(validated, queryRunner);
6439
- const merged = Object.assign(existing, validated, this.auditFields('updated', user));
6440
- return queryRunner.manager.save(repository.target, merged);
7393
+ if (this.options.fastUpdates === false) {
7394
+ return queryRunner.manager.save(repository.target, Object.assign(existing, validated, this.auditFields('updated', user)));
7395
+ }
7396
+ const loaded = {
7397
+ ...existing
7398
+ };
7399
+ return saveLoaded(queryRunner, repository.target, Object.assign(existing, validated, this.auditFields('updated', user)), loaded);
6441
7400
  }
6442
7401
  assertUpdatable(dto) {
6443
7402
  if (!dto.id) {
6444
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7403
+ throw new common_.BadRequestException({
6445
7404
  message: 'id is required to update a record.',
6446
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.ID_REQUIRED */.j4.ID_REQUIRED
7405
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.ID_REQUIRED */.j4.ID_REQUIRED
6447
7406
  });
6448
7407
  }
6449
7408
  const { id: _id, ...rest } = dto;
6450
7409
  this.validateKeys(rest);
6451
7410
  }
6452
- /** Soft-deletes the record, or deletes it for good when the entity has soft delete turned off. */ async delete(id, user, options = {}) {
7411
+ /** Soft-deletes the record, or deletes it for good when the entity has soft delete turned off. One statement either way: no row affected = not found. */ async delete(id, user, options = {}) {
6453
7412
  this.ensureActive();
6454
7413
  const repository = await this.ensureRepository();
6455
7414
  const soft = this.definition.isSoftDeleteEnabled;
6456
7415
  await this.withTransaction(options, async (queryRunner)=>{
6457
- const existing = await queryRunner.manager.findOne(repository.target, {
6458
- where: {
6459
- id
6460
- }
7416
+ const result = soft ? await queryRunner.manager.createQueryBuilder().update(repository.target).set({
7417
+ deleted_at: ()=>'CURRENT_TIMESTAMP',
7418
+ ...this.definition.isAuditEnabled ? {
7419
+ deleted_by_id: user?.id ?? null
7420
+ } : {}
7421
+ }).where('id = :id', {
7422
+ id
7423
+ }).andWhere('deleted_at IS NULL').execute() : await queryRunner.manager.delete(repository.target, {
7424
+ id
6461
7425
  });
6462
- if (!existing) throw this.notFound(id);
6463
- if (soft) {
6464
- await queryRunner.manager.softDelete(repository.target, [
6465
- id
6466
- ]);
6467
- await this.stampDeletedBy(queryRunner, repository.target, id, user?.id ?? null);
6468
- } else {
6469
- await queryRunner.manager.delete(repository.target, [
6470
- id
6471
- ]);
6472
- }
7426
+ if (!result.affected) throw this.notFound(id);
6473
7427
  const rows = [
6474
7428
  {
6475
7429
  id
@@ -6505,9 +7459,23 @@ const TEXT_FIELD_TYPES = new Set([
6505
7459
  }
6506
7460
  });
6507
7461
  }
7462
+ /** Which of `ids` exist (soft-deleted ones do not), in one query. */ async existingIds(ids, options = {}) {
7463
+ if (!ids.length) return new Set();
7464
+ const repository = await this.repositoryFor(options.queryRunner);
7465
+ const rows = await repository.find({
7466
+ select: {
7467
+ id: true
7468
+ },
7469
+ where: {
7470
+ id: (0,external_typeorm_.In)(ids)
7471
+ }
7472
+ });
7473
+ return new Set(rows.map((row)=>row.id));
7474
+ }
6508
7475
  async getAll(search, query, options = {}) {
6509
7476
  const repository = await this.repositoryFor(options.queryRunner);
6510
7477
  let builder = repository.createQueryBuilder(this.entityCode);
7478
+ if (query.withDeleted) builder.withDeleted();
6511
7479
  if (query.filter) {
6512
7480
  builder = this.applyFilter(builder, query.filter);
6513
7481
  }
@@ -6521,25 +7489,27 @@ const TEXT_FIELD_TYPES = new Set([
6521
7489
  const { currentPage, pageSize } = query.pagination;
6522
7490
  builder.skip(currentPage * pageSize).take(pageSize);
6523
7491
  }
6524
- const [data, total] = await builder.getManyAndCount();
7492
+ const data = await builder.getMany();
6525
7493
  return {
6526
7494
  data,
6527
- total
7495
+ total: await this.totalOf(builder, data.length, query.pagination)
6528
7496
  };
6529
7497
  }
6530
- /** Every filter/sort/select key is checked against the real field-code whitelist before
6531
- * being interpolated into the query - the same class of bug flagged in ApiService's
6532
- * getFilterQuery/getSortQuery (raw client-supplied keys concatenated into SQL) is
6533
- * structurally impossible here since an unknown key is rejected outright. Operators map
6534
- * to a fixed SQL table and every value is a bound parameter. */ applyFilter(query, filter) {
7498
+ /** The page itself tells the total unless it came back full (or empty past the first page): only then is COUNT run. */ async totalOf(builder, rows, pagination) {
7499
+ if (!pagination) return rows;
7500
+ const offset = pagination.currentPage * pagination.pageSize;
7501
+ if (rows < pagination.pageSize && (rows > 0 || offset === 0)) return offset + rows;
7502
+ return builder.getCount();
7503
+ }
7504
+ /** Every key is checked against the field-code whitelist before it reaches SQL; operators map to fixed SQL and every value is a bound parameter. */ applyFilter(query, filter) {
6535
7505
  const postgres = query.connection.options.type === 'postgres';
6536
7506
  Object.entries(filter).forEach(([key, value], index)=>{
6537
7507
  this.assertKnownColumn(key);
6538
- if (!(0,_interfaces_generic_record_interface_js__rspack_import_3/* .isRecordFilterCondition */.t1)(value) && value !== null && typeof value === 'object') {
7508
+ if (!(0,generic_record_interface/* .isRecordFilterCondition */.t1)(value) && value !== null && typeof value === 'object') {
6539
7509
  const op = value.op;
6540
- if (typeof op === 'string' && !('type' in value) && !_interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_OPS.includes */.ox.includes(op)) throw this.unknownOperator(key, op);
7510
+ if (typeof op === 'string' && !('type' in value) && !generic_record_interface/* .RECORD_FILTER_OPS.includes */.ox.includes(op)) throw this.unknownOperator(key, op);
6541
7511
  }
6542
- const { op, value: operand } = (0,_interfaces_generic_record_interface_js__rspack_import_3/* .isRecordFilterCondition */.t1)(value) ? value : {
7512
+ const { op, value: operand } = (0,generic_record_interface/* .isRecordFilterCondition */.t1)(value) ? value : {
6543
7513
  op: 'eq',
6544
7514
  value
6545
7515
  };
@@ -6550,8 +7520,8 @@ const TEXT_FIELD_TYPES = new Set([
6550
7520
  return query;
6551
7521
  }
6552
7522
  /** One filter as SQL: the column is a checked field code and every value is a bound parameter. */ conditionSql(column, key, op, operand, param, postgres) {
6553
- const multiSelect = this.fieldTypeOf(key) === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT;
6554
- switch(_interfaces_generic_record_interface_js__rspack_import_3/* .FILTER_OP_VALUE_KIND */.w9[op]){
7523
+ const multiSelect = this.fieldTypeOf(key) === field_type_enum/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT;
7524
+ switch(generic_record_interface/* .FILTER_OP_VALUE_KIND */.w9[op]){
6555
7525
  case 'none':
6556
7526
  if (op === 'is_null') return [
6557
7527
  `${column} IS NULL`,
@@ -6669,13 +7639,13 @@ const TEXT_FIELD_TYPES = new Set([
6669
7639
  operand
6670
7640
  ] : operand;
6671
7641
  if (!Array.isArray(list) || !list.every(isFilterScalar)) throw this.invalidFilterValue(key, op);
6672
- if (list.length > _interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES) {
6673
- throw new _nestjs_common__rspack_import_2.BadRequestException({
6674
- message: `The '${key}' filter lists more than ${_interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES} values.`,
6675
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_TOO_MANY_VALUES */.j4.FILTER_TOO_MANY_VALUES,
7642
+ if (list.length > generic_record_interface/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES) {
7643
+ throw new common_.BadRequestException({
7644
+ message: `The '${key}' filter lists more than ${generic_record_interface/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES} values.`,
7645
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.FILTER_TOO_MANY_VALUES */.j4.FILTER_TOO_MANY_VALUES,
6676
7646
  messageVariables: {
6677
7647
  key,
6678
- max: _interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES
7648
+ max: generic_record_interface/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES
6679
7649
  }
6680
7650
  });
6681
7651
  }
@@ -6687,11 +7657,11 @@ const TEXT_FIELD_TYPES = new Set([
6687
7657
  /** Text operators need a text field, multi-select operators a multi-select one, and nothing but null / blank checks reads a list or JSON field. */ assertOperatorFits(key, op) {
6688
7658
  const type = this.fieldTypeOf(key);
6689
7659
  const textField = type !== undefined && TEXT_FIELD_TYPES.has(type);
6690
- const fits = _interfaces_generic_record_interface_js__rspack_import_3/* .TEXT_FILTER_OPS.includes */.wk.includes(op) ? textField : _interfaces_generic_record_interface_js__rspack_import_3/* .LIST_FIELD_FILTER_OPS.includes */.yV.includes(op) ? type === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT : _interfaces_generic_record_interface_js__rspack_import_3/* .EMPTY_FILTER_OPS.includes */.Oh.includes(op) ? textField || type === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT : op === 'is_null' || op === 'not_null' || type !== _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT && type !== _enums_field_type_enum_js__rspack_import_7/* .FieldType.JSON */.P.JSON;
7660
+ const fits = generic_record_interface/* .TEXT_FILTER_OPS.includes */.wk.includes(op) ? textField : generic_record_interface/* .LIST_FIELD_FILTER_OPS.includes */.yV.includes(op) ? type === field_type_enum/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT : generic_record_interface/* .EMPTY_FILTER_OPS.includes */.Oh.includes(op) ? textField || type === field_type_enum/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT : op === 'is_null' || op === 'not_null' || type !== field_type_enum/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT && type !== field_type_enum/* .FieldType.JSON */.P.JSON;
6691
7661
  if (!fits) {
6692
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7662
+ throw new common_.BadRequestException({
6693
7663
  message: `The '${op}' filter cannot be used on '${key}'.`,
6694
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_OPERATOR_NOT_FOR_FIELD */.j4.FILTER_OPERATOR_NOT_FOR_FIELD,
7664
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.FILTER_OPERATOR_NOT_FOR_FIELD */.j4.FILTER_OPERATOR_NOT_FOR_FIELD,
6695
7665
  messageVariables: {
6696
7666
  key,
6697
7667
  op
@@ -6700,20 +7670,20 @@ const TEXT_FIELD_TYPES = new Set([
6700
7670
  }
6701
7671
  }
6702
7672
  unknownOperator(key, op) {
6703
- return new _nestjs_common__rspack_import_2.BadRequestException({
7673
+ return new common_.BadRequestException({
6704
7674
  message: `'${op}' is not a filter operator.`,
6705
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_OPERATOR_UNKNOWN */.j4.FILTER_OPERATOR_UNKNOWN,
7675
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.FILTER_OPERATOR_UNKNOWN */.j4.FILTER_OPERATOR_UNKNOWN,
6706
7676
  messageVariables: {
6707
7677
  key,
6708
7678
  op,
6709
- ops: _interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_OPS.join */.ox.join(', ')
7679
+ ops: generic_record_interface/* .RECORD_FILTER_OPS.join */.ox.join(', ')
6710
7680
  }
6711
7681
  });
6712
7682
  }
6713
7683
  invalidFilterValue(key, op) {
6714
- return new _nestjs_common__rspack_import_2.BadRequestException({
7684
+ return new common_.BadRequestException({
6715
7685
  message: `The '${key}' filter has an invalid value for '${op}'.`,
6716
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_VALUE_INVALID */.j4.FILTER_VALUE_INVALID,
7686
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.FILTER_VALUE_INVALID */.j4.FILTER_VALUE_INVALID,
6717
7687
  messageVariables: {
6718
7688
  key,
6719
7689
  op
@@ -6740,9 +7710,9 @@ const TEXT_FIELD_TYPES = new Set([
6740
7710
  }
6741
7711
  assertKnownColumn(key) {
6742
7712
  if (key !== 'id' && !this.presentSystemColumns.has(key) && !this.activeFieldCodes.has(key)) {
6743
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7713
+ throw new common_.BadRequestException({
6744
7714
  message: `'${key}' is not a field on entity '${this.entityCode}'.`,
6745
- messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.UNKNOWN_FIELD */.j4.UNKNOWN_FIELD,
7715
+ messageKey: message_keys/* .GENERIC_RECORD_MESSAGES.UNKNOWN_FIELD */.j4.UNKNOWN_FIELD,
6746
7716
  messageVariables: {
6747
7717
  key,
6748
7718
  entity: this.entityCode
@@ -6761,6 +7731,9 @@ const TEXT_FIELD_TYPES = new Set([
6761
7731
  await queryRunner.connect();
6762
7732
  await queryRunner.startTransaction();
6763
7733
  started = true;
7734
+ if (options.statementTimeoutMs !== undefined && queryRunner.connection.options.type === 'postgres') {
7735
+ await queryRunner.query(statementTimeoutSql(options.statementTimeoutMs));
7736
+ }
6764
7737
  }
6765
7738
  const { saved, events } = await fn(queryRunner);
6766
7739
  if (!externalQueryRunner) {
@@ -6770,8 +7743,8 @@ const TEXT_FIELD_TYPES = new Set([
6770
7743
  if (options.publishEvents !== false) {
6771
7744
  for (const { action, rows } of events){
6772
7745
  if (!rows.length) continue;
6773
- await (0,_flusys_nestjs_shared_classes__rspack_import_0.publishDomainEvent)({
6774
- module: _config_event_actions_js__rspack_import_10/* .ENTITY_BUILDER_EVENT_MODULE */.S,
7746
+ await (0,classes_.publishDomainEvent)({
7747
+ module: event_actions/* .ENTITY_BUILDER_EVENT_MODULE */.S,
6775
7748
  entity: this.entityCode,
6776
7749
  action,
6777
7750
  ids: rows.map((r)=>r.id),
@@ -6788,7 +7761,7 @@ const TEXT_FIELD_TYPES = new Set([
6788
7761
  if (started && !committed) {
6789
7762
  await queryRunner.rollbackTransaction();
6790
7763
  }
6791
- throw (0,_database_error_mapper_js__rspack_import_4/* .mapDatabaseError */.E)(error, [
7764
+ throw (0,database_error_mapper/* .mapDatabaseError */.E)(error, [
6792
7765
  ...this.activeFieldCodes
6793
7766
  ]);
6794
7767
  } finally{
@@ -6802,13 +7775,6 @@ function isFilterScalar(value) {
6802
7775
  return typeof value === 'string' || typeof value === 'number' && Number.isFinite(value) || typeof value === 'boolean';
6803
7776
  }
6804
7777
 
6805
- __webpack_require__.d(__webpack_exports__, {
6806
- DG: () => (actorMetadata),
6807
- GK: () => (GenericEntityService)
6808
- }, {
6809
- zb: ACTOR_BRANCH_METADATA_KEY
6810
- });
6811
-
6812
7778
 
6813
7779
  },
6814
7780
  7259(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
@@ -6950,6 +7916,23 @@ class SchemaDialectAdapterService {
6950
7916
  });
6951
7917
  }
6952
7918
  }
7919
+ /** Takes `lockKey` for the connection's session (it outlives any transaction) without waiting: false when another session holds it. */ async tryAcquireSessionLock(queryRunner, lockKey, driver) {
7920
+ const rows = driver === 'postgres' ? await queryRunner.query('SELECT pg_try_advisory_lock(hashtext($1)) AS acquired', [
7921
+ lockKey
7922
+ ]) : await queryRunner.query('SELECT GET_LOCK(?, 0) AS acquired', [
7923
+ this.lockName(lockKey)
7924
+ ]);
7925
+ const acquired = rows?.[0]?.acquired;
7926
+ return acquired === true || Number(acquired) === 1;
7927
+ }
7928
+ /** Must run before the connection goes back to the pool, or the lock stays with it. */ async releaseSessionLock(queryRunner, lockKey, driver) {
7929
+ if (driver === 'postgres') await queryRunner.query('SELECT pg_advisory_unlock(hashtext($1))', [
7930
+ lockKey
7931
+ ]);
7932
+ else await queryRunner.query('SELECT RELEASE_LOCK(?)', [
7933
+ this.lockName(lockKey)
7934
+ ]);
7935
+ }
6953
7936
  async releaseEntityLock(queryRunner, lockKey, driver) {
6954
7937
  if (driver === 'postgres') {
6955
7938
  // pg_advisory_xact_lock is transaction-scoped - released automatically on commit/rollback.
@@ -7019,6 +8002,8 @@ class SchemaRegistryService {
7019
8002
  dataSourceProvider;
7020
8003
  dialectAdapter;
7021
8004
  static cache = new Map();
8005
+ /** Per tenant: the one-time registration of every entity's schema (see warmUp). */ static warming = new Map();
8006
+ logger = new _nestjs_common__rspack_import_1.Logger(SchemaRegistryService.name);
7022
8007
  constructor(dataSourceProvider, dialectAdapter){
7023
8008
  this.dataSourceProvider = dataSourceProvider;
7024
8009
  this.dialectAdapter = dialectAdapter;
@@ -7031,6 +8016,8 @@ class SchemaRegistryService {
7031
8016
  }
7032
8017
  });
7033
8018
  if (!definition) {
8019
+ // Dropped, possibly on another instance: its schema is never asked for again.
8020
+ SchemaRegistryService.cache.delete(this.cacheKey(entityDefinitionId));
7034
8021
  throw new _nestjs_common__rspack_import_1.NotFoundException({
7035
8022
  message: `Entity definition '${entityDefinitionId}' not found.`,
7036
8023
  messageKey: _config_message_keys_js__rspack_import_8/* .ENTITY_DEFINITION_MESSAGES.NOT_FOUND */.pL.NOT_FOUND,
@@ -7039,27 +8026,70 @@ class SchemaRegistryService {
7039
8026
  }
7040
8027
  });
7041
8028
  }
7042
- const cacheKey = this.cacheKey(entityDefinitionId);
8029
+ return (await this.resolve(definition, queryRunner)).schema;
8030
+ }
8031
+ /** For a definition the caller has just read: its fields are read only when its schemaVersion is not cached yet. */ async resolve(definition, queryRunner) {
8032
+ const cacheKey = this.cacheKey(definition.id);
7043
8033
  const cached = SchemaRegistryService.cache.get(cacheKey);
7044
- if (cached && cached.version === definition.schemaVersion) {
7045
- return cached.schema;
7046
- }
8034
+ if (cached && cached.version === definition.schemaVersion) return cached;
7047
8035
  const fieldRepository = queryRunner ? queryRunner.manager.getRepository(_entities_field_definition_entity_js__rspack_import_4/* .FieldDefinition */.c) : await this.dataSourceProvider.getRepository(_entities_field_definition_entity_js__rspack_import_4/* .FieldDefinition */.c);
7048
8036
  const fields = await fieldRepository.find({
7049
8037
  where: {
7050
- entityDefinitionId,
8038
+ entityDefinitionId: definition.id,
7051
8039
  deprecatedAt: (0,typeorm__rspack_import_2.IsNull)()
7052
8040
  },
7053
8041
  order: {
7054
8042
  order: 'ASC'
7055
8043
  }
7056
8044
  });
7057
- const schema = await this.buildEntitySchema(definition, fields);
7058
- SchemaRegistryService.cache.set(cacheKey, {
8045
+ return this.store(definition, fields);
8046
+ }
8047
+ /**
8048
+ * Once per tenant per process, before its first entity is used: every entity's schema, built from two queries
8049
+ * and registered with ONE metadata rebuild - one by one, each first use would rebuild the metadata of the
8050
+ * whole DataSource. Never throws (logged instead, and not retried): the per-entity path works without it.
8051
+ */ async warmUp(queryRunner) {
8052
+ const tenant = (0,_flusys_nestjs_shared_utils__rspack_import_0.cacheTenantId)(this.dataSourceProvider) ?? '';
8053
+ let warming = SchemaRegistryService.warming.get(tenant);
8054
+ if (!warming) {
8055
+ warming = this.registerAll(queryRunner).catch((error)=>this.logger.warn(`Entity schemas are registered one by one instead: ${error.message}`));
8056
+ SchemaRegistryService.warming.set(tenant, warming);
8057
+ }
8058
+ await warming;
8059
+ }
8060
+ async registerAll(queryRunner) {
8061
+ const repositoryOf = async (entity)=>queryRunner ? queryRunner.manager.getRepository(entity) : this.dataSourceProvider.getRepository(entity);
8062
+ const definitions = await (await repositoryOf(_entities_entity_definition_entity_js__rspack_import_3/* .EntityDefinition */.f)).find();
8063
+ const fields = await (await repositoryOf(_entities_field_definition_entity_js__rspack_import_4/* .FieldDefinition */.c)).find({
8064
+ where: {
8065
+ deprecatedAt: (0,typeorm__rspack_import_2.IsNull)()
8066
+ },
8067
+ order: {
8068
+ order: 'ASC'
8069
+ }
8070
+ });
8071
+ const fieldsOf = new Map();
8072
+ for (const field of fields)fieldsOf.set(field.entityDefinitionId, [
8073
+ ...fieldsOf.get(field.entityDefinitionId) ?? [],
8074
+ field
8075
+ ]);
8076
+ const schemas = [];
8077
+ for (const definition of definitions){
8078
+ const cached = SchemaRegistryService.cache.get(this.cacheKey(definition.id));
8079
+ schemas.push(cached && cached.version === definition.schemaVersion ? cached.schema : (await this.store(definition, fieldsOf.get(definition.id) ?? [])).schema);
8080
+ }
8081
+ await this.dataSourceProvider.registerDynamicSchemas(schemas);
8082
+ }
8083
+ async store(definition, fields) {
8084
+ const resolved = {
8085
+ schema: await this.buildEntitySchema(definition, fields),
8086
+ fields
8087
+ };
8088
+ SchemaRegistryService.cache.set(this.cacheKey(definition.id), {
7059
8089
  version: definition.schemaVersion,
7060
- schema
8090
+ ...resolved
7061
8091
  });
7062
- return schema;
8092
+ return resolved;
7063
8093
  }
7064
8094
  invalidate(entityDefinitionId) {
7065
8095
  SchemaRegistryService.cache.delete(this.cacheKey(entityDefinitionId));