@flusys/nestjs-entity-builder 9.2.0 → 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 (52) hide show
  1. package/README.md +40 -22
  2. package/config/entity-builder.constants.d.ts +1 -0
  3. package/config/message-keys.d.ts +11 -2
  4. package/controllers/definition-bundle.controller.d.ts +2 -2
  5. package/controllers/entity-definition.controller.d.ts +0 -1
  6. package/controllers/field-definition.controller.d.ts +0 -1
  7. package/controllers/flow-definition.controller.d.ts +2 -1
  8. package/controllers/flow-runtime.controller.d.ts +4 -3
  9. package/fesm/21.js +13 -5
  10. package/fesm/{458.js → 28.js} +104 -62
  11. package/fesm/677.js +1234 -469
  12. package/fesm/794.js +1 -1
  13. package/fesm/857.js +1 -2
  14. package/fesm/870.js +415 -332
  15. package/fesm/897.js +23 -29
  16. package/fesm/946.js +67 -42
  17. package/fesm/config/index.js +10 -14
  18. package/fesm/docs/index.js +1 -1
  19. package/fesm/guards/index.js +15 -9
  20. package/fesm/index.js +25 -25
  21. package/fesm/modules/index.js +4 -3
  22. package/fesm/services/index.js +29 -58
  23. package/flow-engine/flow-code-runner.d.ts +16 -0
  24. package/flow-engine/flow-graph.validator.d.ts +1 -0
  25. package/flow-engine/flow-input.validator.d.ts +14 -0
  26. package/flow-engine/flow-run.types.d.ts +15 -0
  27. package/flow-engine/flow.types.d.ts +1 -1
  28. package/guards/designer-writable.interceptor.d.ts +1 -0
  29. package/interfaces/definition-bundle.interface.d.ts +2 -0
  30. package/interfaces/entity-builder-module.interface.d.ts +9 -0
  31. package/modules/entity-builder.module.d.ts +1 -0
  32. package/modules/index.d.ts +1 -0
  33. package/modules/tenant-context-id.strategy.d.ts +16 -0
  34. package/package.json +3 -4
  35. package/services/definition-bundle.diff.d.ts +0 -2
  36. package/services/definition-bundle.file.d.ts +2 -1
  37. package/services/definition-bundle.service.d.ts +0 -1
  38. package/services/entity-builder-config.service.d.ts +3 -0
  39. package/services/entity-builder-datasource.provider.d.ts +1 -0
  40. package/services/field-value-validator.d.ts +7 -0
  41. package/services/flow-definition.service.d.ts +10 -2
  42. package/services/flow-executor.service.d.ts +3 -0
  43. package/services/flow-runtime.service.d.ts +6 -1
  44. package/services/generic-entity-service.factory.d.ts +5 -1
  45. package/services/generic-entity.service.d.ts +24 -2
  46. package/services/identifier-rules.d.ts +0 -1
  47. package/services/save-loaded.d.ts +2 -0
  48. package/services/schema-columns.d.ts +7 -2
  49. package/services/schema-evolution.service.d.ts +8 -2
  50. package/services/schema-registry.service.d.ts +12 -0
  51. package/services/schema-sync.service.d.ts +3 -1
  52. 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';
197
- }
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);
229
+ return 'unknown error';
204
230
  }
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]);
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
+ },
225
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)
802
+ };
803
+ }
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
+ });
695
816
  };
696
817
  }
697
- async function runCode(node, cfg, state) {
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,10 +847,16 @@ function writeOptions(state) {
723
847
  return state.qr ? {
724
848
  queryRunner: state.qr,
725
849
  publishEvents: false
726
- } : {};
850
+ } : {
851
+ statementTimeoutMs: state.deadline - Date.now()
852
+ };
727
853
  }
728
- /** A get_all request's own query: what a `fromRequest` query reads. A list body has none. */ function requestQuery(state) {
729
- return Array.isArray(state.input) ? {} : state.input;
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;
730
860
  }
731
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) {
732
862
  return Object.fromEntries(Object.entries(filter ?? {}).map(([key, value])=>[
@@ -759,7 +889,7 @@ async function runQuery(node, cfg, env, state) {
759
889
  [cfg.sort.field]: cfg.sort.direction === 'DESC' ? 'DESC' : 'ASC'
760
890
  } : request.sort;
761
891
  const page = request.pagination?.currentPage ?? 0;
762
- const pageSize = Math.min(request.pagination?.pageSize ?? cfg.limit ?? 50, flow_types/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT);
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);
763
893
  const withDeleted = request.withDeleted === true ? {
764
894
  withDeleted: true
765
895
  } : {};
@@ -829,7 +959,8 @@ async function runQuery(node, cfg, env, state) {
829
959
  }
830
960
  };
831
961
  }
832
- const pagination = requestQuery(state).pagination;
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;
833
964
  const total = await count('total', data.length);
834
965
  const page = await count('page', pagination?.currentPage ?? 0);
835
966
  const pageSize = await count('pageSize', pagination?.pageSize ?? data.length);
@@ -915,6 +1046,20 @@ async function updateRecord(entityCode, service, values, state) {
915
1046
  });
916
1047
  return saved;
917
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
+ }
918
1063
  /** @returns false when the record does not exist and `skip` allows that. */ async function deleteRecord(entityCode, service, id, skipMissing, state) {
919
1064
  countWrite(state);
920
1065
  let action;
@@ -944,11 +1089,12 @@ async function updateRecord(entityCode, service, values, state) {
944
1089
  });
945
1090
  }
946
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.
947
1093
  const { data, total } = await service.getAll(undefined, {
948
1094
  filter,
949
1095
  pagination: {
950
1096
  currentPage: 0,
951
- pageSize: limit
1097
+ pageSize: limit + 1
952
1098
  }
953
1099
  }, readOptions(state));
954
1100
  if (total > limit) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.TOO_MANY_MATCHES */.zr.TOO_MANY_MATCHES, {
@@ -984,6 +1130,9 @@ async function insertChildren(node, child, parentId, env, state) {
984
1130
  if (writeTarget(cfg) === 'one') return {
985
1131
  output: children.length ? await env.atomic(state, insertOne) : await insertOne()
986
1132
  };
1133
+ if (!children.length && insertsInBatch(service, cfg)) return {
1134
+ output: await env.atomic(state, ()=>insertBatch(node, cfg, service, env, state))
1135
+ };
987
1136
  return {
988
1137
  output: await env.atomic(state, async ()=>{
989
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);
@@ -994,6 +1143,54 @@ async function insertChildren(node, child, parentId, env, state) {
994
1143
  })
995
1144
  };
996
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
+ }
997
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) {
998
1195
  const onNotFound = cfg.onNotFound ?? 'error';
999
1196
  const raw = cfg.id === undefined ? fallbackId : await env.ruleEngine.resolveExpression(cfg.id, evaluationContext(state, env));
@@ -1031,10 +1228,7 @@ async function insertChildren(node, child, parentId, env, state) {
1031
1228
  return {
1032
1229
  output: await env.atomic(state, async ()=>{
1033
1230
  const outcomes = target === 'filter' ? await eachItem(state, await matchingRecords(node, cfg, service, env, state), async (row)=>({
1034
- row: await updateRecord(cfg.entityCode, service, {
1035
- ...await mapFieldValues(cfg.fields, env, state),
1036
- id: row.id
1037
- }, state),
1231
+ row: await updateMatched(cfg.entityCode, service, row, await mapFieldValues(cfg.fields, env, state), state),
1038
1232
  result: 'updated'
1039
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));
1040
1234
  const items = outcomes.flatMap((o)=>o.row ? [
@@ -1465,7 +1659,7 @@ const nobody = (extra = {})=>({
1465
1659
  case 'validate':
1466
1660
  return runValidate(c, env, state);
1467
1661
  case 'code':
1468
- return runCode(node, c, state);
1662
+ return runCode(node, c, env, state);
1469
1663
  case 'permission_check':
1470
1664
  return runPermissionCheck(c, env, state);
1471
1665
  case 'company_branch_check':
@@ -2650,6 +2844,12 @@ function conditionProblem(node, options) {
2650
2844
  validateInputFields(field.fields, itemType === 'object' ? `${name}[].` : `${name}.`, depth + 1, err);
2651
2845
  }
2652
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
+ }
2653
2853
  function validateNodeConfig(node, flow, context, err, warn) {
2654
2854
  const at = {
2655
2855
  node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
@@ -2768,6 +2968,8 @@ function validateNodeConfig(node, flow, context, err, warn) {
2768
2968
  const serviceRunning = (service, unavailable)=>{
2769
2969
  if (context.messageServices && !context.messageServices[service]) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(unavailable, at));
2770
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));
2771
2973
  switch(node.type){
2772
2974
  case 'trigger':
2773
2975
  {
@@ -3093,6 +3295,16 @@ function validateNodeConfig(node, flow, context, err, warn) {
3093
3295
  max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS
3094
3296
  }));
3095
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
+ }
3096
3308
  break;
3097
3309
  }
3098
3310
  }
@@ -3625,29 +3837,116 @@ function keepRest(source, values) {
3625
3837
  }
3626
3838
  function contractErrors(values) {
3627
3839
  const errors = [];
3628
- const sort = values['sort'];
3629
- if (isPlainObject(sort)) {
3630
- for (const [field, direction] of Object.entries(sort)){
3631
- if (!FIELD_NAME.test(field) || direction !== 'ASC' && direction !== 'DESC') errors.push({
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
+ {
3632
3847
  field: `sort.${field}`,
3633
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, {
3634
3849
  field
3635
3850
  })
3636
- });
3637
- }
3638
- }
3639
- const select = values['select'];
3640
- if (Array.isArray(select)) {
3641
- select.forEach((field, i)=>{
3642
- if (typeof field !== 'string' || !FIELD_NAME.test(field)) errors.push({
3851
+ }
3852
+ ] : []);
3853
+ }
3854
+ function selectErrors(select) {
3855
+ return select.flatMap((field, i)=>typeof field !== 'string' || !FIELD_NAME.test(field) ? [
3856
+ {
3643
3857
  field: `select[${i}]`,
3644
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, {
3645
3859
  field: String(field)
3646
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
+ })
3647
3904
  });
3648
- });
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
+ }
3649
3931
  }
3650
- return errors;
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
+ };
3651
3950
  }
3652
3951
  function checkList(schema, body) {
3653
3952
  if (!Array.isArray(body)) return {
@@ -3803,7 +4102,8 @@ function checkValue(field, supplied, raw, path, label) {
3803
4102
  }
3804
4103
 
3805
4104
  __webpack_require__.d(__webpack_exports__, {
3806
- G: () => (validateEndpointInput)
4105
+ GP: () => (validateEndpointInput),
4106
+ M2: () => (readRequestQuery)
3807
4107
  });
3808
4108
 
3809
4109
 
@@ -3904,9 +4204,17 @@ __webpack_require__.d(__webpack_exports__, {
3904
4204
  },
3905
4205
  2678(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3906
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
+ ]);
3907
4215
  /** Copy of `value` with the content of sensitive-looking keys replaced, safe to store in a log. */ function redactSensitive(value, depth = 0) {
3908
- if (depth > 8) return '[deep]';
3909
- 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));
3910
4218
  // Dates (and anything else with its own JSON form) have no own keys: walked as objects they would log as `{}`.
3911
4219
  if (value && typeof value.toJSON === 'function') return redactSensitive(value.toJSON(), depth + 1);
3912
4220
  if (value && typeof value === 'object') {
@@ -3918,13 +4226,43 @@ const SENSITIVE_KEY = /pass(word)?|secret|token|authorization|api[-_]?key|cookie
3918
4226
  }
3919
4227
  return value;
3920
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
+ }
3921
4255
  /** Redacted, size-limited text for a trace/log entry. */ function preview(value, maxChars) {
3922
4256
  if (value === undefined) return '';
3923
4257
  let text;
3924
4258
  try {
3925
- text = JSON.stringify(redactSensitive(value)) ?? '';
4259
+ text = redactedJson(value) ?? '';
3926
4260
  } catch {
3927
- text = '[unserializable]';
4261
+ try {
4262
+ text = JSON.stringify(redactSensitive(value)) ?? '';
4263
+ } catch {
4264
+ text = '[unserializable]';
4265
+ }
3928
4266
  }
3929
4267
  return text.length > maxChars ? `${text.slice(0, maxChars)}... (${text.length} chars)` : text;
3930
4268
  }
@@ -4177,9 +4515,29 @@ class EntityBuilderConfigService {
4177
4515
  getConfig() {
4178
4516
  return this.options.config;
4179
4517
  }
4518
+ isFastUpdatesEnabled() {
4519
+ return this.getConfig()?.performance?.fastUpdates ?? true;
4520
+ }
4521
+ isPartialIndexesEnabled() {
4522
+ return this.getConfig()?.performance?.partialIndexes ?? false;
4523
+ }
4180
4524
  /** `designer.readOnly`, else the `ENTITY_BUILDER_READ_ONLY=true` env variable (production). */ isDesignerReadOnly() {
4181
4525
  return this.getConfig()?.designer?.readOnly ?? process.env['ENTITY_BUILDER_READ_ONLY'] === 'true';
4182
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
+ }
4183
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() {
4184
4542
  const fromEnv = Object.entries(process.env).filter((entry)=>entry[0].startsWith(VARIABLE_ENV_PREFIX) && !!entry[1]).map(([key, value])=>[
4185
4543
  key.slice(VARIABLE_ENV_PREFIX.length),
@@ -4296,26 +4654,52 @@ class EntityBuilderDataSourceProvider extends _flusys_nestjs_shared_modules__rsp
4296
4654
  * just by instance identity, so a fresh schema instance with the same name still
4297
4655
  * resolves correctly after a rebuild.
4298
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) {
4299
4666
  const dataSource = await this.getDataSource();
4300
4667
  const perDsMap = this.statics.registeredSchemas.get(dataSource) ?? new Map();
4301
4668
  this.statics.registeredSchemas.set(dataSource, perDsMap);
4302
- if (perDsMap.get(schema.options.name) === schema) return;
4669
+ if (schemas.every((schema)=>perDsMap.get(schema.options.name) === schema)) return;
4303
4670
  // Serialize concurrent registrations against the SAME DataSource - buildMetadatas()
4304
4671
  // does a full rebuild from `options.entities`, so two racing pushes must not interleave.
4305
4672
  const prior = this.statics.rebuildLocks.get(dataSource) ?? Promise.resolve();
4306
4673
  const next = prior.then(async ()=>{
4307
- perDsMap.set(schema.options.name, schema);
4674
+ const pending = schemas.filter((schema)=>perDsMap.get(schema.options.name) !== schema);
4675
+ if (!pending.length) return;
4308
4676
  const entitiesArr = dataSource.options.entities;
4309
- const idx = entitiesArr.findIndex((e)=>e instanceof Object && e.options?.name === schema.options.name);
4310
- if (idx >= 0) entitiesArr[idx] = schema;
4311
- 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
+ }
4312
4690
  // buildMetadatas() is `protected` in TypeORM's public .d.ts (a compile-time-only
4313
4691
  // restriction, not enforced by the JS runtime) but is the only way to rebuild
4314
4692
  // entityMetadatas from options.entities after DataSource.initialize() has run.
4693
+ const rebuild = ()=>dataSource.buildMetadatas();
4315
4694
  try {
4316
- await dataSource.buildMetadatas();
4695
+ await rebuild();
4317
4696
  } catch (error) {
4318
- if (perDsMap.get(schema.options.name) === schema) perDsMap.delete(schema.options.name);
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);
4319
4703
  throw error;
4320
4704
  }
4321
4705
  });
@@ -4354,11 +4738,22 @@ __webpack_require__.d(__webpack_exports__, {
4354
4738
  },
4355
4739
  3525(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4356
4740
  /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
4357
- /* import */ var _config_message_keys_js__rspack_import_2 = __webpack_require__(5732);
4358
- /* 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);
4359
4744
 
4360
4745
 
4361
4746
 
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
+ });
4362
4757
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4363
4758
  const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4364
4759
  const DATE = /^\d{4}-\d{2}-\d{2}$/;
@@ -4390,18 +4785,18 @@ function isEmpty(value) {
4390
4785
  })
4391
4786
  });
4392
4787
  switch(field.fieldType){
4393
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.TEXT */.P.TEXT:
4394
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.EMAIL */.P.EMAIL:
4395
- 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:
4396
4791
  {
4397
- if (typeof value !== 'string') return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MUST_BE_TEXT */.KE.MUST_BE_TEXT);
4398
- 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, {
4399
4794
  max: TEXT_MAX
4400
4795
  });
4401
- 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);
4402
- 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) {
4403
4798
  const options = optionValues(field);
4404
- 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, {
4405
4800
  options: options.join(', ')
4406
4801
  });
4407
4802
  }
@@ -4409,19 +4804,19 @@ function isEmpty(value) {
4409
4804
  value
4410
4805
  };
4411
4806
  }
4412
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.LONG_TEXT */.P.LONG_TEXT:
4413
- if (typeof value !== 'string') return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.MUST_BE_TEXT */.KE.MUST_BE_TEXT);
4414
- 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, {
4415
4810
  max: LONG_TEXT_MAX
4416
4811
  });
4417
4812
  return {
4418
4813
  value
4419
4814
  };
4420
- 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:
4421
4816
  {
4422
4817
  const n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
4423
- if (typeof n !== 'number' || !Number.isInteger(n)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.WHOLE_NUMBER */.KE.WHOLE_NUMBER);
4424
- 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, {
4425
4820
  min: INT_MIN,
4426
4821
  max: INT_MAX
4427
4822
  });
@@ -4429,16 +4824,16 @@ function isEmpty(value) {
4429
4824
  value: n
4430
4825
  };
4431
4826
  }
4432
- 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:
4433
4828
  {
4434
4829
  const n = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
4435
- if (typeof n !== 'number' || !Number.isFinite(n)) return fail(_config_message_keys_js__rspack_import_2/* .FIELD_VALIDATION_MESSAGES.NUMBER */.KE.NUMBER);
4436
- 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);
4437
4832
  return {
4438
4833
  value: n
4439
4834
  };
4440
4835
  }
4441
- 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:
4442
4837
  {
4443
4838
  if (typeof value === 'boolean') return {
4444
4839
  value
@@ -4449,46 +4844,47 @@ function isEmpty(value) {
4449
4844
  if (value === 'false') return {
4450
4845
  value: false
4451
4846
  };
4452
- 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);
4453
4848
  }
4454
- 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:
4455
4850
  if (typeof value !== 'string' || !DATE.test(value) || !isRealCalendarDate(value)) {
4456
- 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);
4457
4852
  }
4458
4853
  return {
4459
4854
  value
4460
4855
  };
4461
- 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:
4462
4857
  {
4463
- const parsed = typeof value === 'string' || value instanceof Date ? new Date(value) : null;
4464
- 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);
4465
4861
  return {
4466
- value: parsed.toISOString()
4862
+ value: new Date(time).toISOString()
4467
4863
  };
4468
4864
  }
4469
- 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:
4470
4866
  {
4471
- 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);
4472
4868
  const options = optionValues(field);
4473
4869
  const invalid = options ? value.filter((v)=>!options.includes(v)) : [];
4474
- 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, {
4475
4871
  options: invalid.join(', ')
4476
4872
  });
4477
4873
  return {
4478
4874
  value
4479
4875
  };
4480
4876
  }
4481
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.RELATION */.P.RELATION:
4482
- case _enums_field_type_enum_js__rspack_import_1/* .FieldType.FILE */.P.FILE:
4483
- 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);
4484
4880
  return {
4485
4881
  value
4486
4882
  };
4487
- 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:
4488
4884
  try {
4489
4885
  JSON.stringify(value);
4490
4886
  } catch {
4491
- 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);
4492
4888
  }
4493
4889
  return {
4494
4890
  value
@@ -4503,38 +4899,38 @@ function checkRules(field, value) {
4503
4899
  const rules = field.validationRules;
4504
4900
  if (!rules) return null;
4505
4901
  const label = field.label;
4506
- 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;
4507
4903
  const text = typeof value === 'string';
4508
4904
  if (numeric && typeof value === 'number') {
4509
- 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, {
4510
4906
  label,
4511
4907
  min: rules.min
4512
4908
  });
4513
- 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, {
4514
4910
  label,
4515
4911
  max: rules.max
4516
4912
  });
4517
4913
  }
4518
4914
  if (text) {
4519
4915
  if (typeof rules.minLength === 'number' && value.length < rules.minLength) {
4520
- 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, {
4521
4917
  label,
4522
4918
  min: rules.minLength
4523
4919
  });
4524
4920
  }
4525
4921
  if (typeof rules.maxLength === 'number' && value.length > rules.maxLength) {
4526
- 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, {
4527
4923
  label,
4528
4924
  max: rules.maxLength
4529
4925
  });
4530
4926
  }
4531
4927
  }
4532
4928
  if (Array.isArray(value)) {
4533
- 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, {
4534
4930
  label,
4535
4931
  min: rules.minItems
4536
4932
  });
4537
- 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, {
4538
4934
  label,
4539
4935
  max: rules.maxItems
4540
4936
  });
@@ -4564,7 +4960,7 @@ function checkRules(field, value) {
4564
4960
  if (isEmpty(raw)) {
4565
4961
  if (!field.nullable) errors.push({
4566
4962
  field: field.code,
4567
- ...(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, {
4568
4964
  label: field.label
4569
4965
  })
4570
4966
  });
@@ -4597,6 +4993,8 @@ function checkRules(field, value) {
4597
4993
 
4598
4994
  __webpack_require__.d(__webpack_exports__, {
4599
4995
  e: () => (validateRecord)
4996
+ }, {
4997
+ C: valueRulesOf
4600
4998
  });
4601
4999
 
4602
5000
 
@@ -4608,17 +5006,19 @@ __webpack_require__.d(__webpack_exports__, {
4608
5006
  /* import */ var _flusys_nestjs_shared_modules__rspack_import_3 = __webpack_require__(1236);
4609
5007
  /* import */ var _nestjs_common__rspack_import_4 = __webpack_require__(6157);
4610
5008
  /* import */ var typeorm__rspack_import_5 = __webpack_require__(7158);
4611
- /* import */ var _config_event_actions_js__rspack_import_16 = __webpack_require__(982);
4612
- /* 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);
4613
5011
  /* import */ var _entities_entity_definition_entity_js__rspack_import_6 = __webpack_require__(3189);
4614
5012
  /* import */ var _entities_flow_definition_entity_js__rspack_import_7 = __webpack_require__(3770);
4615
- /* import */ var _entities_flow_version_entity_js__rspack_import_8 = __webpack_require__(2399);
4616
- /* import */ var _flow_engine_flow_api_key_js__rspack_import_9 = __webpack_require__(4816);
4617
- /* import */ var _flow_engine_flow_permission_logic_js__rspack_import_13 = __webpack_require__(8220);
4618
- /* import */ var _flow_engine_flow_graph_validator_js__rspack_import_10 = __webpack_require__(3186);
4619
- /* import */ var _flow_engine_flow_types_js__rspack_import_11 = __webpack_require__(6256);
4620
- /* import */ var _interfaces_flow_definition_interface_js__rspack_import_15 = __webpack_require__(9609);
4621
- /* 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);
4622
5022
  function _ts_decorate(decorators, target, key, desc) {
4623
5023
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4624
5024
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -4659,16 +5059,19 @@ function _ts_param(paramIndex, decorator) {
4659
5059
 
4660
5060
 
4661
5061
 
5062
+
5063
+
4662
5064
  const defaultFlowPermissionCode = (slug)=>`entity_builder.flow.${slug}.execute`;
4663
- /** 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));
4664
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');
4665
5068
  const flowNotFound = ()=>new _nestjs_common__rspack_import_4.NotFoundException({
4666
5069
  message: 'Flow not found.',
4667
- 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
4668
5071
  });
4669
5072
  /** A flow that has been published at least once: its columns are what callers run. */ const isPublished = (flow)=>flow.version > 0;
4670
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) {
4671
- 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)=>[
4672
5075
  key,
4673
5076
  source[key]
4674
5077
  ]));
@@ -4694,8 +5097,9 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4694
5097
  notifications;
4695
5098
  email;
4696
5099
  companyAccess;
4697
- constructor(cacheManager, utilsService, provider, permissionCache, actionRegistry, notifications, email, companyAccess){
4698
- 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;
4699
5103
  }
4700
5104
  async findOrFail(id) {
4701
5105
  const flow = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
@@ -4713,7 +5117,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4713
5117
  }
4714
5118
  });
4715
5119
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find();
4716
- 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, {
4717
5121
  knownEntityCodes: new Set(entities.map((e)=>e.code)),
4718
5122
  knownFlows: new Map(flows.map((flow)=>[
4719
5123
  flow.slug,
@@ -4723,9 +5127,13 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4723
5127
  notification: !!this.notifications,
4724
5128
  email: !!this.email
4725
5129
  },
4726
- companyMembers: !!this.companyAccess
5130
+ companyMembers: !!this.companyAccess,
5131
+ codeModules: this.config ? this.codePackages() : undefined
4727
5132
  });
4728
5133
  }
5134
+ /** The names a code step may `require`, as the app configured them. */ codePackages() {
5135
+ return Object.keys(this.config?.getCodeModules() ?? {});
5136
+ }
4729
5137
  /** Names of the other flows whose `call_flow` nodes run `slug`. */ async callersOf(slug, excludeIds) {
4730
5138
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4731
5139
  select: {
@@ -4741,7 +5149,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4741
5149
  if (!callers.length) return;
4742
5150
  throw new _nestjs_common__rspack_import_4.ConflictException({
4743
5151
  message: `The flow '${slug}' is called by: ${callers.join(', ')}. Change those flows first.`,
4744
- 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,
4745
5153
  messageVariables: {
4746
5154
  slug,
4747
5155
  flows: callers.join(', ')
@@ -4780,11 +5188,11 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4780
5188
  const settings = toFlowSettings(caller);
4781
5189
  // Counted, not a set: two steps with the same label can share a problem, and only the new one is added.
4782
5190
  const existing = new Map();
4783
- 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)){
4784
5192
  const key = JSON.stringify(problem);
4785
5193
  existing.set(key, (existing.get(key) ?? 0) + 1);
4786
5194
  }
4787
- 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)=>{
4788
5196
  const key = JSON.stringify(problem);
4789
5197
  const left = existing.get(key) ?? 0;
4790
5198
  existing.set(key, left - 1);
@@ -4799,7 +5207,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4799
5207
  const names = broken.map((b)=>b.flow).join(', ');
4800
5208
  throw new _nestjs_common__rspack_import_4.ConflictException({
4801
5209
  message: `This change breaks flows that call '${before.slug}': ${names}. Change those flows first.`,
4802
- 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,
4803
5211
  messageVariables: {
4804
5212
  slug: before.slug,
4805
5213
  flows: names
@@ -4812,8 +5220,8 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4812
5220
  });
4813
5221
  }
4814
5222
  /** A URL other flows' Call flow steps start at cannot be removed or renamed from under them. */ async assertEndpointsNotCalled(before, after) {
4815
- const kept = new Set((0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(after).map((e)=>e.path));
4816
- 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));
4817
5225
  if (!removed.length) return;
4818
5226
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4819
5227
  select: {
@@ -4828,7 +5236,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4828
5236
  const url = `api-flows/${before.slug}${path ? `/${path}` : ''}`;
4829
5237
  throw new _nestjs_common__rspack_import_4.ConflictException({
4830
5238
  message: `The URL ${url} is called by: ${callers.join(', ')}. Change those flows first.`,
4831
- 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,
4832
5240
  messageVariables: {
4833
5241
  url,
4834
5242
  flows: callers.join(', ')
@@ -4841,7 +5249,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4841
5249
  if (errors.length) {
4842
5250
  throw new _nestjs_common__rspack_import_4.BadRequestException({
4843
5251
  message: 'The flow has problems.',
4844
- 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,
4845
5253
  errors: errors.map((error)=>({
4846
5254
  field: 'flow',
4847
5255
  ...error
@@ -4852,13 +5260,12 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4852
5260
  const clash = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
4853
5261
  where: {
4854
5262
  slug: settings.slug
4855
- },
4856
- withDeleted: true
5263
+ }
4857
5264
  });
4858
5265
  if (clash && clash.id !== selfId) {
4859
5266
  throw new _nestjs_common__rspack_import_4.ConflictException({
4860
5267
  message: `The URL name '${settings.slug}' is already used by another flow.`,
4861
- 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,
4862
5269
  messageVariables: {
4863
5270
  slug: settings.slug
4864
5271
  }
@@ -4890,7 +5297,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4890
5297
  dto
4891
5298
  ]){
4892
5299
  await this.assertSaveable(toFlowSettings(d), user);
4893
- 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);
4894
5301
  d.version = 0;
4895
5302
  }
4896
5303
  }
@@ -4911,7 +5318,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4911
5318
  isActive: d.isActive ?? existing.isActive,
4912
5319
  version: existing.version
4913
5320
  });
4914
- 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);
4915
5322
  }
4916
5323
  }
4917
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) {
@@ -4935,7 +5342,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4935
5342
  const published = isPublished(existing);
4936
5343
  if (published && !existing.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
4937
5344
  message: 'There are no changes to publish.',
4938
- 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
4939
5346
  });
4940
5347
  const next = workingCopy(existing);
4941
5348
  const settings = toFlowSettings(next);
@@ -4948,7 +5355,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4948
5355
  try {
4949
5356
  await qr.connect();
4950
5357
  await qr.startTransaction();
4951
- 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);
4952
5359
  Object.assign(existing, next, {
4953
5360
  draft: null,
4954
5361
  version: existing.version + 1,
@@ -4978,7 +5385,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4978
5385
  }
4979
5386
  /** Newest first, without the definitions (`getVersion` returns one in full). */ async listVersions(flowId) {
4980
5387
  await this.findOrFail(flowId);
4981
- 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({
4982
5389
  where: {
4983
5390
  flowId
4984
5391
  },
@@ -4996,7 +5403,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4996
5403
  });
4997
5404
  }
4998
5405
  async getVersion(flowId, version) {
4999
- 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({
5000
5407
  where: {
5001
5408
  flowId,
5002
5409
  version
@@ -5004,7 +5411,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
5004
5411
  });
5005
5412
  if (!row) throw new _nestjs_common__rspack_import_4.NotFoundException({
5006
5413
  message: `Version ${version} of this flow was not found.`,
5007
- 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,
5008
5415
  messageVariables: {
5009
5416
  version
5010
5417
  }
@@ -5026,7 +5433,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
5026
5433
  const flow = await this.findOrFail(flowId);
5027
5434
  if (!flow.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
5028
5435
  message: 'This flow has no unpublished changes.',
5029
- 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
5030
5437
  });
5031
5438
  flow.draft = null;
5032
5439
  return this.saveDraft(flow, user);
@@ -5039,25 +5446,66 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
5039
5446
  await this.invalidateCache();
5040
5447
  return flow;
5041
5448
  }
5042
- async beforeDeleteOperation(dto, user, _qr) {
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;
5474
+ }
5475
+ async beforeDeleteOperation(dto, _user, _qr) {
5043
5476
  const ids = Array.isArray(dto.id) ? dto.id : [
5044
5477
  dto.id
5045
5478
  ];
5046
5479
  const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
5047
5480
  where: {
5048
5481
  id: (0,typeorm__rspack_import_5.In)(ids)
5049
- },
5050
- withDeleted: true
5482
+ }
5051
5483
  });
5052
5484
  if (flows.length !== ids.length) throw flowNotFound();
5053
- if (dto.type === 'restore') return;
5054
5485
  for (const flow of flows)await this.assertNotCalled(flow.slug, ids);
5055
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
+ }
5056
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) {
5057
5505
  if (!this.actionRegistry) return;
5058
5506
  const actions = new Map();
5059
5507
  for (const flow of flows){
5060
- for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow)){
5508
+ for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_12/* .flowEndpoints */.Xr)(flow)){
5061
5509
  if (!asksExecutePermission(endpoint)) continue;
5062
5510
  const code = defaultFlowPermissionCode(flow.slug);
5063
5511
  if (!actions.has(code)) actions.set(code, endpoint.path ? `Run ${flow.name} (${endpoint.path})` : `Run ${flow.name}`);
@@ -5077,7 +5525,7 @@ FlowDefinitionService = _ts_decorate([
5077
5525
  }),
5078
5526
  _ts_param(0, (0,_nestjs_common__rspack_import_4.Inject)('CACHE_INSTANCE')),
5079
5527
  _ts_param(1, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.UtilsService)),
5080
- _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)),
5081
5529
  _ts_param(3, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService)),
5082
5530
  _ts_param(4, (0,_nestjs_common__rspack_import_4.Optional)()),
5083
5531
  _ts_param(4, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared__rspack_import_0.PERMISSION_ACTION_REGISTRY)),
@@ -5087,16 +5535,19 @@ FlowDefinitionService = _ts_decorate([
5087
5535
  _ts_param(6, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.EMAIL_ADAPTER)),
5088
5536
  _ts_param(7, (0,_nestjs_common__rspack_import_4.Optional)()),
5089
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)),
5090
5540
  _ts_metadata("design:type", Function),
5091
5541
  _ts_metadata("design:paramtypes", [
5092
5542
  typeof _flusys_nestjs_shared_classes__rspack_import_1.HybridCache === "undefined" ? Object : _flusys_nestjs_shared_classes__rspack_import_1.HybridCache,
5093
5543
  typeof _flusys_nestjs_shared_modules__rspack_import_3.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.UtilsService,
5094
- 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,
5095
5545
  typeof _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService,
5096
5546
  typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry,
5097
5547
  typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter,
5098
5548
  typeof IEmailAdapter === "undefined" ? Object : IEmailAdapter,
5099
- 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
5100
5551
  ])
5101
5552
  ], FlowDefinitionService);
5102
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) {
@@ -5111,7 +5562,7 @@ FlowDefinitionService = _ts_decorate([
5111
5562
  /** What a Call flow step checks about a flow: it runs only once published and switched on. */ function callableOf(flow) {
5112
5563
  return {
5113
5564
  isActive: flow.isActive && isPublished(flow),
5114
- 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),
5115
5566
  calls: calledSlugs(flow.nodes)
5116
5567
  };
5117
5568
  }
@@ -5141,9 +5592,9 @@ __webpack_require__.d(__webpack_exports__, {
5141
5592
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
5142
5593
  /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
5143
5594
  /* import */ var _entities_flow_execution_entity_js__rspack_import_2 = __webpack_require__(9641);
5144
- /* import */ var _flow_engine_flow_engine_js__rspack_import_3 = __webpack_require__(8333);
5145
- /* import */ var _flow_engine_redact_js__rspack_import_5 = __webpack_require__(2678);
5146
- /* 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);
5147
5598
  function _ts_decorate(decorators, target, key, desc) {
5148
5599
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5149
5600
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -5187,7 +5638,7 @@ class FlowExecutionService {
5187
5638
  /** Never throws: a logging problem must not change the answer the caller gets. */ async record(entry) {
5188
5639
  try {
5189
5640
  const repo = await this.repo();
5190
- 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);
5191
5642
  const inputText = JSON.stringify(redactedInput ?? null);
5192
5643
  await repo.save(repo.create({
5193
5644
  flowId: entry.flowId,
@@ -5203,7 +5654,7 @@ class FlowExecutionService {
5203
5654
  _truncated: true,
5204
5655
  preview: inputText.slice(0, INPUT_LIMIT_CHARS)
5205
5656
  } : redactedInput,
5206
- 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),
5207
5658
  error: entry.result.error?.slice(0, 2000) ?? null,
5208
5659
  trace: entry.result.trace,
5209
5660
  durationMs: entry.result.durationMs,
@@ -5297,10 +5748,10 @@ FlowExecutionService = _ts_decorate([
5297
5748
  (0,_nestjs_common__rspack_import_0.Injectable)({
5298
5749
  scope: _nestjs_common__rspack_import_0.Scope.REQUEST
5299
5750
  }),
5300
- _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)),
5301
5752
  _ts_metadata("design:type", Function),
5302
5753
  _ts_metadata("design:paramtypes", [
5303
- 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
5304
5755
  ])
5305
5756
  ], FlowExecutionService);
5306
5757
 
@@ -5343,7 +5794,7 @@ var message_keys = __webpack_require__(5732);
5343
5794
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/entities/flow-definition.entity.ts
5344
5795
  var flow_definition_entity = __webpack_require__(3770);
5345
5796
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-engine.ts + 6 modules
5346
- var flow_engine = __webpack_require__(8333);
5797
+ var flow_engine = __webpack_require__(3931);
5347
5798
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow.errors.ts
5348
5799
  var flow_errors = __webpack_require__(8542);
5349
5800
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-input.validator.ts
@@ -5573,8 +6024,8 @@ var entity_builder_config_service = __webpack_require__(4281);
5573
6024
  var entity_builder_datasource_provider = __webpack_require__(424);
5574
6025
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/flow-definition.service.ts
5575
6026
  var flow_definition_service = __webpack_require__(5229);
5576
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/generic-entity.service.ts
5577
- 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);
5578
6029
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/generic-entity-service.factory.ts
5579
6030
  var generic_entity_service_factory = __webpack_require__(7705);
5580
6031
  ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/flow-executor.service.ts
@@ -5643,6 +6094,7 @@ class FlowExecutorService {
5643
6094
  email;
5644
6095
  branchHierarchy;
5645
6096
  logger = new common_.Logger(FlowExecutorService.name);
6097
+ /** Sends still going out after their answers, for `whenPublished`. */ inFlight = new Set();
5646
6098
  constructor(provider, ruleEngine, moduleRef, permissionCache, config, companyAccess, notifications, email, branchHierarchy){
5647
6099
  this.provider = provider;
5648
6100
  this.ruleEngine = ruleEngine;
@@ -5736,17 +6188,17 @@ class FlowExecutorService {
5736
6188
  state.ownsTransaction = false;
5737
6189
  await qr.release().catch(()=>undefined);
5738
6190
  }
5739
- if (state.mode === 'live') await this.publishPendingEvents(state.pendingEvents, state.user);
6191
+ if (state.mode === 'live') this.publishAfterCommit(state, state.pendingEvents.splice(0));
5740
6192
  }
5741
- /** `SET LOCAL` lasts one transaction, so every transaction a run opens sets it again. */ async limitStatementTime(qr, timeoutMs) {
5742
- 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));
5743
6195
  }
5744
6196
  async commitProgress(state) {
5745
6197
  const qr = state.qr;
5746
6198
  await qr.commitTransaction();
5747
6199
  await qr.startTransaction();
5748
6200
  await this.limitStatementTime(qr, state.deadline - Date.now());
5749
- await this.publishPendingEvents(state.pendingEvents.splice(0), state.user);
6201
+ this.publishAfterCommit(state, state.pendingEvents.splice(0));
5750
6202
  }
5751
6203
  /** A multi-record step outside a transactional run: its own transaction, its record events published after it commits. */ async atomic(state, work) {
5752
6204
  if (state.qr) return work();
@@ -5757,6 +6209,7 @@ class FlowExecutorService {
5757
6209
  try {
5758
6210
  await qr.connect();
5759
6211
  await qr.startTransaction();
6212
+ await this.limitStatementTime(qr, state.deadline - Date.now());
5760
6213
  state.qr = qr;
5761
6214
  result = await work();
5762
6215
  await qr.commitTransaction();
@@ -5769,7 +6222,7 @@ class FlowExecutorService {
5769
6222
  state.qr = undefined;
5770
6223
  await qr.release().catch(()=>undefined);
5771
6224
  }
5772
- await this.publishPendingEvents(state.pendingEvents.splice(queued), state.user);
6225
+ this.publishAfterCommit(state, state.pendingEvents.splice(queued));
5773
6226
  return result;
5774
6227
  }
5775
6228
  /**
@@ -5800,7 +6253,7 @@ class FlowExecutorService {
5800
6253
  }, `Flow '${slug}' has no URL '${endpoint ?? ''}'.`);
5801
6254
  }
5802
6255
  await this.assertMayCall(flow, start, parent, env);
5803
- const checked = (0,flow_input_validator/* .validateEndpointInput */.G)(start, input);
6256
+ const checked = (0,flow_input_validator/* .validateEndpointInput */.GP)(start, input);
5804
6257
  if (checked.errors.length) {
5805
6258
  throw new flow_errors/* .FlowHttpError */.hk(400, 'Validation failed', checked.errors, {
5806
6259
  messageKey: message_keys/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
@@ -5830,6 +6283,9 @@ class FlowExecutorService {
5830
6283
  flow.slug
5831
6284
  ],
5832
6285
  pendingEvents: parent.qr ? parent.pendingEvents : [],
6286
+ publisher: parent.publisher ??= {
6287
+ chain: Promise.resolve()
6288
+ },
5833
6289
  trace: []
5834
6290
  };
5835
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.
@@ -5864,7 +6320,6 @@ class FlowExecutorService {
5864
6320
  where: {
5865
6321
  slug,
5866
6322
  isActive: true,
5867
- deletedAt: (0,external_typeorm_.IsNull)(),
5868
6323
  version: (0,external_typeorm_.MoreThan)(0)
5869
6324
  }
5870
6325
  });
@@ -5890,18 +6345,23 @@ class FlowExecutorService {
5890
6345
  }
5891
6346
  createEnv() {
5892
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;
5893
6349
  /** One lookup per user for the whole run, however many steps ask. */ const access = new Map();
5894
6350
  let permissions;
5895
6351
  const env = {
5896
6352
  ruleEngine: this.ruleEngine,
5897
6353
  allowPrivateHttp: this.config.getConfig()?.flows?.allowPrivateHttp === true || process.env['ENTITY_BUILDER_FLOW_ALLOW_PRIVATE_HTTP'] === 'true',
6354
+ codeModules: this.config.getCodeModules(),
5898
6355
  entityService: (entityCode, queryRunner)=>{
5899
6356
  if (!entityServices.has(entityCode)) {
5900
6357
  const loading = (async ()=>{
5901
- const factory = await this.moduleRef.resolve(generic_entity_service_factory/* .GenericEntityServiceFactory */.G, core_.ContextIdFactory.create(), {
5902
- strict: false
5903
- });
5904
- 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);
5905
6365
  })();
5906
6366
  entityServices.set(entityCode, loading);
5907
6367
  // A failed load (e.g. on a transaction that later rolled back) must not be remembered for the rest of the run.
@@ -5948,6 +6408,23 @@ class FlowExecutorService {
5948
6408
  };
5949
6409
  return env;
5950
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
+ }
5951
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) {
5952
6429
  for (const event of events){
5953
6430
  try {
@@ -6042,17 +6519,20 @@ FlowExecutorService = _ts_decorate([
6042
6519
 
6043
6520
  },
6044
6521
  2300(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6045
- /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
6046
- /* import */ var _nestjs_common__rspack_import_1 = __webpack_require__(6157);
6047
- /* import */ var typeorm__rspack_import_2 = __webpack_require__(7158);
6048
- /* import */ var _config_message_keys_js__rspack_import_10 = __webpack_require__(5732);
6049
- /* import */ var _entities_flow_definition_entity_js__rspack_import_3 = __webpack_require__(3770);
6050
- /* import */ var _flow_engine_flow_input_validator_js__rspack_import_4 = __webpack_require__(6390);
6051
- /* import */ var _flow_engine_flow_types_js__rspack_import_5 = __webpack_require__(6256);
6052
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_6 = __webpack_require__(424);
6053
- /* import */ var _flow_definition_service_js__rspack_import_7 = __webpack_require__(5229);
6054
- /* import */ var _flow_execution_service_js__rspack_import_8 = __webpack_require__(7810);
6055
- /* import */ var _flow_executor_service_js__rspack_import_9 = __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);
6056
6536
  function _ts_decorate(decorators, target, key, desc) {
6057
6537
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6058
6538
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -6087,6 +6567,10 @@ function _ts_param(paramIndex, decorator) {
6087
6567
 
6088
6568
 
6089
6569
 
6570
+
6571
+
6572
+
6573
+ const ACTIVE_FLOW_TTL_MS = 60_000;
6090
6574
  const HIDDEN_HEADERS = new Set([
6091
6575
  'authorization',
6092
6576
  'cookie',
@@ -6150,7 +6634,7 @@ const inputRejected = (errors)=>({
6150
6634
  body: {
6151
6635
  success: false,
6152
6636
  message: 'Validation failed',
6153
- messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
6637
+ messageKey: _config_message_keys_js__rspack_import_12/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
6154
6638
  errors
6155
6639
  },
6156
6640
  trace: [],
@@ -6161,34 +6645,64 @@ class FlowRuntimeService {
6161
6645
  executor;
6162
6646
  executions;
6163
6647
  definitions;
6164
- constructor(provider, executor, executions, definitions){
6648
+ cache;
6649
+ utils;
6650
+ constructor(provider, executor, executions, definitions, cache, utils){
6165
6651
  this.provider = provider;
6166
6652
  this.executor = executor;
6167
6653
  this.executions = executions;
6168
6654
  this.definitions = definitions;
6655
+ this.cache = cache;
6656
+ this.utils = utils;
6169
6657
  }
6170
- /** A flow never published is not live yet: it looks the same as an unknown one. */ async findActive(slug) {
6171
- return (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_3/* .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({
6172
6669
  where: {
6173
6670
  slug,
6174
6671
  isActive: true,
6175
- deletedAt: (0,typeorm__rspack_import_2.IsNull)(),
6176
- version: (0,typeorm__rspack_import_2.MoreThan)(0)
6672
+ version: (0,typeorm__rspack_import_4.MoreThan)(0)
6177
6673
  }
6178
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
+ }
6179
6693
  }
6180
6694
  /** The tenant of this request ('' for a single database): tenant databases cloned from one template share flow ids. */ tenantScope() {
6181
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.cacheTenantId)(this.provider) ?? '';
6695
+ return (0,_flusys_nestjs_shared_utils__rspack_import_2.cacheTenantId)(this.provider) ?? '';
6182
6696
  }
6183
6697
  /** The public entry: a caller hit `POST api-flows/:slug` or `POST api-flows/:slug/:endpoint`. */ async handle(flow, endpoint, body, caller, user) {
6184
6698
  const startedAt = new Date();
6185
- const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_4/* .validateEndpointInput */.G)(endpoint, body);
6699
+ const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_6/* .validateEndpointInput */.GP)(endpoint, body);
6186
6700
  if (checked.errors.length) {
6187
6701
  const result = inputRejected(checked.errors);
6188
- await this.logWebhook(flow, endpoint, body, result, startedAt, user, caller);
6702
+ this.logWebhook(flow, endpoint, body, result, startedAt, user, caller);
6189
6703
  return result;
6190
6704
  }
6191
- const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_7/* .toFlowSettings */.RK)(flow), {
6705
+ const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_9/* .toFlowSettings */.RK)(flow), {
6192
6706
  mode: 'live',
6193
6707
  user,
6194
6708
  input: checked.values,
@@ -6197,7 +6711,7 @@ class FlowRuntimeService {
6197
6711
  authorization: callerAuthorization(caller, user),
6198
6712
  exposeDetails: false
6199
6713
  });
6200
- await this.logWebhook(flow, endpoint, checked.values, result, startedAt, user, caller);
6714
+ this.logWebhook(flow, endpoint, checked.values, result, startedAt, user, caller);
6201
6715
  return result;
6202
6716
  }
6203
6717
  /** The designer's "Test run": a saved flow or an unsaved draft. */ async testRun(dto, user, caller) {
@@ -6208,17 +6722,17 @@ class FlowRuntimeService {
6208
6722
  }
6209
6723
  if (dto.definition) {
6210
6724
  // The designer's current draft, even for a saved flow: `flow` above is kept only to attribute the run.
6211
- settings = (0,_flow_definition_service_js__rspack_import_7/* .toFlowSettings */.RK)(dto.definition);
6725
+ settings = (0,_flow_definition_service_js__rspack_import_9/* .toFlowSettings */.RK)(dto.definition);
6212
6726
  } else if (flow) {
6213
- settings = (0,_flow_definition_service_js__rspack_import_7/* .toFlowSettings */.RK)(flow);
6727
+ settings = (0,_flow_definition_service_js__rspack_import_9/* .toFlowSettings */.RK)(flow);
6214
6728
  } else {
6215
- throw new _nestjs_common__rspack_import_1.BadRequestException({
6729
+ throw new _nestjs_common__rspack_import_3.BadRequestException({
6216
6730
  message: 'Send a flowId or a definition to test.',
6217
- messageKey: _config_message_keys_js__rspack_import_10/* .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
6218
6732
  });
6219
6733
  }
6220
6734
  const mode = dto.mode ?? 'dry_run';
6221
- const endpoint = (0,_flow_engine_flow_types_js__rspack_import_5/* .findEndpoint */.vS)(settings, dto.endpoint);
6735
+ const endpoint = (0,_flow_engine_flow_types_js__rspack_import_7/* .findEndpoint */.vS)(settings, dto.endpoint);
6222
6736
  // Checked before anything runs.
6223
6737
  await this.definitions.assertMayTest(settings, endpoint, user, {
6224
6738
  mode,
@@ -6226,9 +6740,9 @@ class FlowRuntimeService {
6226
6740
  saved: !!flow
6227
6741
  });
6228
6742
  if (!endpoint) {
6229
- throw new _nestjs_common__rspack_import_1.BadRequestException({
6743
+ throw new _nestjs_common__rspack_import_3.BadRequestException({
6230
6744
  message: `This flow has no URL '${dto.endpoint ?? ''}'.`,
6231
- messageKey: _config_message_keys_js__rspack_import_10/* .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,
6232
6746
  messageVariables: {
6233
6747
  endpoint: dto.endpoint ?? ''
6234
6748
  }
@@ -6236,9 +6750,9 @@ class FlowRuntimeService {
6236
6750
  }
6237
6751
  const validation = await this.definitions.validate(settings);
6238
6752
  if (validation.errors.length) {
6239
- throw new _nestjs_common__rspack_import_1.BadRequestException({
6753
+ throw new _nestjs_common__rspack_import_3.BadRequestException({
6240
6754
  message: 'The flow has problems.',
6241
- messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
6755
+ messageKey: _config_message_keys_js__rspack_import_12/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
6242
6756
  errors: validation.errors.map((error)=>({
6243
6757
  field: 'flow',
6244
6758
  ...error
@@ -6248,7 +6762,7 @@ class FlowRuntimeService {
6248
6762
  const startedAt = new Date();
6249
6763
  const simulated = simulatedCaller(caller, dto.request);
6250
6764
  const body = dto.input ?? (endpoint.bodyType === 'list' ? [] : {});
6251
- const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_4/* .validateEndpointInput */.G)(endpoint, body);
6765
+ const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_6/* .validateEndpointInput */.GP)(endpoint, body);
6252
6766
  const result = checked.errors.length ? inputRejected(checked.errors) : await this.executor.run(settings, {
6253
6767
  mode,
6254
6768
  user,
@@ -6277,8 +6791,8 @@ class FlowRuntimeService {
6277
6791
  warnings: validation.warnings
6278
6792
  };
6279
6793
  }
6280
- async logWebhook(flow, endpoint, input, result, startedAt, user, caller) {
6281
- 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({
6282
6796
  flowId: flow.id,
6283
6797
  flowSlug: flow.slug,
6284
6798
  flowVersion: flow.version,
@@ -6295,19 +6809,25 @@ class FlowRuntimeService {
6295
6809
  }
6296
6810
  }
6297
6811
  FlowRuntimeService = _ts_decorate([
6298
- (0,_nestjs_common__rspack_import_1.Injectable)({
6299
- scope: _nestjs_common__rspack_import_1.Scope.REQUEST
6812
+ (0,_nestjs_common__rspack_import_3.Injectable)({
6813
+ scope: _nestjs_common__rspack_import_3.Scope.REQUEST
6300
6814
  }),
6301
- _ts_param(0, (0,_nestjs_common__rspack_import_1.Inject)(_entity_builder_datasource_provider_js__rspack_import_6/* .EntityBuilderDataSourceProvider */.D)),
6302
- _ts_param(1, (0,_nestjs_common__rspack_import_1.Inject)(_flow_executor_service_js__rspack_import_9/* .FlowExecutorService */.F)),
6303
- _ts_param(2, (0,_nestjs_common__rspack_import_1.Inject)(_flow_execution_service_js__rspack_import_8/* .FlowExecutionService */.A)),
6304
- _ts_param(3, (0,_nestjs_common__rspack_import_1.Inject)(_flow_definition_service_js__rspack_import_7/* .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)),
6305
6823
  _ts_metadata("design:type", Function),
6306
6824
  _ts_metadata("design:paramtypes", [
6307
- typeof _entity_builder_datasource_provider_js__rspack_import_6/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_6/* .EntityBuilderDataSourceProvider */.D,
6308
- typeof _flow_executor_service_js__rspack_import_9/* .FlowExecutorService */.F === "undefined" ? Object : _flow_executor_service_js__rspack_import_9/* .FlowExecutorService */.F,
6309
- typeof _flow_execution_service_js__rspack_import_8/* .FlowExecutionService */.A === "undefined" ? Object : _flow_execution_service_js__rspack_import_8/* .FlowExecutionService */.A,
6310
- typeof _flow_definition_service_js__rspack_import_7/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_7/* .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
6311
6831
  ])
6312
6832
  ], FlowRuntimeService);
6313
6833
 
@@ -6320,13 +6840,12 @@ __webpack_require__.d(__webpack_exports__, {
6320
6840
  },
6321
6841
  7705(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6322
6842
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
6323
- /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
6324
- /* import */ var _config_message_keys_js__rspack_import_7 = __webpack_require__(5732);
6325
- /* import */ var _entities_entity_definition_entity_js__rspack_import_2 = __webpack_require__(3189);
6326
- /* import */ var _entities_field_definition_entity_js__rspack_import_3 = __webpack_require__(2206);
6327
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_4 = __webpack_require__(424);
6328
- /* import */ var _generic_entity_service_js__rspack_import_5 = __webpack_require__(3998);
6329
- /* 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);
6330
6849
  function _ts_decorate(decorators, target, key, desc) {
6331
6850
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6332
6851
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -6357,13 +6876,15 @@ function _ts_param(paramIndex, decorator) {
6357
6876
 
6358
6877
 
6359
6878
 
6360
-
6361
6879
  class GenericEntityServiceFactory {
6362
6880
  schemaRegistry;
6363
6881
  dataSourceProvider;
6364
- 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){
6365
6885
  this.schemaRegistry = schemaRegistry;
6366
6886
  this.dataSourceProvider = dataSourceProvider;
6887
+ this.config = config;
6367
6888
  }
6368
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) {
6369
6890
  return this.build({
@@ -6375,44 +6896,49 @@ class GenericEntityServiceFactory {
6375
6896
  id: entityDefinitionId
6376
6897
  }, entityDefinitionId, queryRunner);
6377
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
+ }
6378
6908
  async build(where, reference, queryRunner) {
6379
- 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);
6380
6911
  const definition = await definitionRepository.findOne({
6381
6912
  where
6382
6913
  });
6383
6914
  if (!definition) {
6384
6915
  throw new _nestjs_common__rspack_import_0.NotFoundException({
6385
6916
  message: `Entity '${reference}' not found.`,
6386
- 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,
6387
6918
  messageVariables: {
6388
6919
  id: reference
6389
6920
  }
6390
6921
  });
6391
6922
  }
6392
- 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);
6393
- const fields = await fieldRepository.find({
6394
- where: {
6395
- entityDefinitionId: definition.id,
6396
- deprecatedAt: (0,typeorm__rspack_import_1.IsNull)()
6397
- },
6398
- order: {
6399
- order: 'ASC'
6400
- }
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
6401
6926
  });
6402
- const schema = await this.schemaRegistry.getSchema(definition.id, queryRunner);
6403
- return new _generic_entity_service_js__rspack_import_5/* .GenericEntityService */.GK(definition, schema, fields, this.dataSourceProvider, (targetId, runner)=>this.forEntityDefinitionId(targetId, runner));
6404
6927
  }
6405
6928
  }
6406
6929
  GenericEntityServiceFactory = _ts_decorate([
6407
6930
  (0,_nestjs_common__rspack_import_0.Injectable)({
6408
6931
  scope: _nestjs_common__rspack_import_0.Scope.REQUEST
6409
6932
  }),
6410
- _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_schema_registry_service_js__rspack_import_6/* .SchemaRegistryService */.C)),
6411
- _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)),
6412
6937
  _ts_metadata("design:type", Function),
6413
6938
  _ts_metadata("design:paramtypes", [
6414
- typeof _schema_registry_service_js__rspack_import_6/* .SchemaRegistryService */.C === "undefined" ? Object : _schema_registry_service_js__rspack_import_6/* .SchemaRegistryService */.C,
6415
- 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
6416
6942
  ])
6417
6943
  ], GenericEntityServiceFactory);
6418
6944
 
@@ -6422,19 +6948,80 @@ __webpack_require__.d(__webpack_exports__, {
6422
6948
 
6423
6949
 
6424
6950
  },
6425
- 3998(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6426
- /* import */ var _flusys_nestjs_shared_classes__rspack_import_0 = __webpack_require__(6937);
6427
- /* import */ var _flusys_nestjs_shared_utils__rspack_import_1 = __webpack_require__(9216);
6428
- /* import */ var _nestjs_common__rspack_import_2 = __webpack_require__(6157);
6429
- /* import */ var _config_event_actions_js__rspack_import_10 = __webpack_require__(982);
6430
- /* import */ var _config_message_keys_js__rspack_import_8 = __webpack_require__(5732);
6431
- /* import */ var _enums_entity_status_enum_js__rspack_import_9 = __webpack_require__(1313);
6432
- /* import */ var _enums_field_type_enum_js__rspack_import_7 = __webpack_require__(9614);
6433
- /* import */ var _interfaces_generic_record_interface_js__rspack_import_3 = __webpack_require__(3372);
6434
- /* import */ var _database_error_mapper_js__rspack_import_4 = __webpack_require__(8323);
6435
- /* import */ var _field_value_validator_js__rspack_import_5 = __webpack_require__(3525);
6436
- /* import */ var _schema_columns_js__rspack_import_6 = __webpack_require__(5313);
6951
+ 3052(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
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
+ });
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"
6982
+
6983
+ ;// CONCATENATED MODULE: external "typeorm/persistence/SubjectExecutor.js"
6984
+
6985
+ ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/save-loaded.ts
6986
+
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
+ }
6437
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
6438
7025
 
6439
7026
 
6440
7027
 
@@ -6445,6 +7032,17 @@ __webpack_require__.d(__webpack_exports__, {
6445
7032
 
6446
7033
 
6447
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
+ };
6448
7046
  /** The event envelope's actor carries userId/companyId but not branchId, and permissions are
6449
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';
6450
7048
  function actorMetadata(user) {
@@ -6452,7 +7050,9 @@ function actorMetadata(user) {
6452
7050
  [ACTOR_BRANCH_METADATA_KEY]: user.branchId
6453
7051
  } : {};
6454
7052
  }
6455
- 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;
6456
7056
  const FILTER_COMPARISON_SQL = {
6457
7057
  eq: '=',
6458
7058
  ne: '<>',
@@ -6468,18 +7068,13 @@ const FILTER_COMPARISON_SQL = {
6468
7068
  ends_with: (t)=>`%${t}`
6469
7069
  };
6470
7070
  const TEXT_FIELD_TYPES = new Set([
6471
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.TEXT */.P.TEXT,
6472
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
6473
- _enums_field_type_enum_js__rspack_import_7/* .FieldType.EMAIL */.P.EMAIL,
6474
- _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
6475
7075
  ]);
6476
7076
  /**
6477
- * Generic CRUD for one dynamically-defined entity, mirroring ApiService's shape
6478
- * (transactional writes, lifecycle event publishing) but
6479
- * operating on Record<string, unknown> against a runtime EntitySchema instead of a
6480
- * compile-time entity class. One instance per request, produced by
6481
- * GenericEntityServiceFactory - see that class for why this isn't itself `@Injectable`.
6482
- *
7077
+ * Reads and writes the records of one runtime entity (built per call by GenericEntityServiceFactory).
6483
7078
  * Records hold only the fields the entity designer defines: scoping them to a company or branch
6484
7079
  * is the flow's job, through fields such as `company_id` mapped from the caller.
6485
7080
  */ class GenericEntityService {
@@ -6488,15 +7083,17 @@ const TEXT_FIELD_TYPES = new Set([
6488
7083
  fields;
6489
7084
  dataSourceProvider;
6490
7085
  resolveRelated;
7086
+ options;
6491
7087
  repository;
6492
7088
  activeFieldCodes;
6493
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;
6494
- constructor(definition, schema, fields, dataSourceProvider, resolveRelated){
7090
+ constructor(definition, schema, fields, dataSourceProvider, resolveRelated, options = {}){
6495
7091
  this.definition = definition;
6496
7092
  this.schema = schema;
6497
7093
  this.fields = fields;
6498
7094
  this.dataSourceProvider = dataSourceProvider;
6499
7095
  this.resolveRelated = resolveRelated;
7096
+ this.options = options;
6500
7097
  this.activeFieldCodes = new Set(fields.filter((f)=>!f.deprecatedAt && !f.isSystemField).map((f)=>f.code));
6501
7098
  this.presentSystemColumns = new Set([
6502
7099
  'created_at',
@@ -6516,15 +7113,17 @@ const TEXT_FIELD_TYPES = new Set([
6516
7113
  }
6517
7114
  validateValues(values, mode) {
6518
7115
  const fields = this.fields.filter((f)=>!f.deprecatedAt && !f.isSystemField);
6519
- 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);
6520
7117
  this.throwIfInvalid(errors);
6521
7118
  return normalized;
6522
7119
  }
6523
7120
  throwIfInvalid(errors) {
6524
- if (errors.length === 0) return;
6525
- 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({
6526
7125
  message: 'Validation failed',
6527
- 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,
6528
7127
  errors
6529
7128
  });
6530
7129
  }
@@ -6533,24 +7132,43 @@ const TEXT_FIELD_TYPES = new Set([
6533
7132
  const errors = [];
6534
7133
  for (const field of this.fields){
6535
7134
  const value = values[field.code];
6536
- 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;
6537
7136
  const related = await this.resolveRelated(field.relationTargetEntityId, queryRunner);
6538
7137
  const found = await related.exists(value, {
6539
7138
  queryRunner
6540
7139
  });
6541
- if (!found) errors.push({
6542
- field: field.code,
6543
- ...(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, {
6544
- label: field.label
6545
- })
6546
- });
7140
+ if (!found) errors.push(this.relatedNotFound(field));
6547
7141
  }
6548
7142
  this.throwIfInvalid(errors);
6549
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
+ }
6550
7168
  notFound(id) {
6551
- return new _nestjs_common__rspack_import_2.NotFoundException({
7169
+ return new common_.NotFoundException({
6552
7170
  message: `Record '${id}' not found on entity '${this.entityCode}'.`,
6553
- 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,
6554
7172
  messageVariables: {
6555
7173
  id,
6556
7174
  entity: this.entityCode
@@ -6566,19 +7184,11 @@ const TEXT_FIELD_TYPES = new Set([
6566
7184
  updated_by_id: user.id
6567
7185
  };
6568
7186
  }
6569
- async stampDeletedBy(queryRunner, target, id, userId) {
6570
- if (!this.definition.isAuditEnabled) return;
6571
- await queryRunner.manager.update(target, [
6572
- id
6573
- ], {
6574
- deleted_by_id: userId
6575
- });
6576
- }
6577
7187
  ensureActive() {
6578
- if (this.definition.status === _enums_entity_status_enum_js__rspack_import_9/* .EntityStatus.DEPRECATED */.c.DEPRECATED) {
6579
- 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({
6580
7190
  message: `Entity '${this.definition.code}' has been deprecated and no longer accepts writes.`,
6581
- messageKey: _config_message_keys_js__rspack_import_8/* .ENTITY_DEFINITION_MESSAGES.DEPRECATED */.pL.DEPRECATED,
7191
+ messageKey: message_keys/* .ENTITY_DEFINITION_MESSAGES.DEPRECATED */.pL.DEPRECATED,
6582
7192
  messageVariables: {
6583
7193
  code: this.definition.code
6584
7194
  }
@@ -6598,24 +7208,22 @@ const TEXT_FIELD_TYPES = new Set([
6598
7208
  }
6599
7209
  return this.repository;
6600
7210
  }
6601
- /** Rejects any key that isn't a real, active field on this entity - the whitelist that
6602
- * keeps mass-assignment and column-name-as-SQL-injection (see getFilterQuery/getSortQuery
6603
- * 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) {
6604
7212
  for (const key of Object.keys(dto)){
6605
7213
  // `id` included: an insert that names an id would overwrite the record that has it.
6606
7214
  if (SYSTEM_COLUMNS.has(key)) {
6607
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7215
+ throw new common_.BadRequestException({
6608
7216
  message: `'${key}' is a system column and cannot be set directly.`,
6609
- 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,
6610
7218
  messageVariables: {
6611
7219
  key
6612
7220
  }
6613
7221
  });
6614
7222
  }
6615
7223
  if (!this.activeFieldCodes.has(key)) {
6616
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7224
+ throw new common_.BadRequestException({
6617
7225
  message: `'${key}' is not a field on entity '${this.entityCode}'.`,
6618
- 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,
6619
7227
  messageVariables: {
6620
7228
  key,
6621
7229
  entity: this.entityCode
@@ -6643,6 +7251,71 @@ const TEXT_FIELD_TYPES = new Set([
6643
7251
  };
6644
7252
  }, user);
6645
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
+ }
6646
7319
  async update(dto, user, options = {}) {
6647
7320
  this.ensureActive();
6648
7321
  this.assertUpdatable(dto);
@@ -6671,6 +7344,42 @@ const TEXT_FIELD_TYPES = new Set([
6671
7344
  });
6672
7345
  return queryRunner.manager.save(repository.target, entity);
6673
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
+ }
6674
7383
  async updateRow(dto, user, repository, queryRunner) {
6675
7384
  const { id, ...rest } = dto;
6676
7385
  const existing = await queryRunner.manager.findOne(repository.target, {
@@ -6681,40 +7390,40 @@ const TEXT_FIELD_TYPES = new Set([
6681
7390
  if (!existing) throw this.notFound(id);
6682
7391
  const validated = this.validateValues(rest, 'update');
6683
7392
  await this.assertRelationsExist(validated, queryRunner);
6684
- const merged = Object.assign(existing, validated, this.auditFields('updated', user));
6685
- 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);
6686
7400
  }
6687
7401
  assertUpdatable(dto) {
6688
7402
  if (!dto.id) {
6689
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7403
+ throw new common_.BadRequestException({
6690
7404
  message: 'id is required to update a record.',
6691
- 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
6692
7406
  });
6693
7407
  }
6694
7408
  const { id: _id, ...rest } = dto;
6695
7409
  this.validateKeys(rest);
6696
7410
  }
6697
- /** 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 = {}) {
6698
7412
  this.ensureActive();
6699
7413
  const repository = await this.ensureRepository();
6700
7414
  const soft = this.definition.isSoftDeleteEnabled;
6701
7415
  await this.withTransaction(options, async (queryRunner)=>{
6702
- const existing = await queryRunner.manager.findOne(repository.target, {
6703
- where: {
6704
- id
6705
- }
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
6706
7425
  });
6707
- if (!existing) throw this.notFound(id);
6708
- if (soft) {
6709
- await queryRunner.manager.softDelete(repository.target, [
6710
- id
6711
- ]);
6712
- await this.stampDeletedBy(queryRunner, repository.target, id, user?.id ?? null);
6713
- } else {
6714
- await queryRunner.manager.delete(repository.target, [
6715
- id
6716
- ]);
6717
- }
7426
+ if (!result.affected) throw this.notFound(id);
6718
7427
  const rows = [
6719
7428
  {
6720
7429
  id
@@ -6750,6 +7459,19 @@ const TEXT_FIELD_TYPES = new Set([
6750
7459
  }
6751
7460
  });
6752
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
+ }
6753
7475
  async getAll(search, query, options = {}) {
6754
7476
  const repository = await this.repositoryFor(options.queryRunner);
6755
7477
  let builder = repository.createQueryBuilder(this.entityCode);
@@ -6767,25 +7489,27 @@ const TEXT_FIELD_TYPES = new Set([
6767
7489
  const { currentPage, pageSize } = query.pagination;
6768
7490
  builder.skip(currentPage * pageSize).take(pageSize);
6769
7491
  }
6770
- const [data, total] = await builder.getManyAndCount();
7492
+ const data = await builder.getMany();
6771
7493
  return {
6772
7494
  data,
6773
- total
7495
+ total: await this.totalOf(builder, data.length, query.pagination)
6774
7496
  };
6775
7497
  }
6776
- /** Every filter/sort/select key is checked against the real field-code whitelist before
6777
- * being interpolated into the query - the same class of bug flagged in ApiService's
6778
- * getFilterQuery/getSortQuery (raw client-supplied keys concatenated into SQL) is
6779
- * structurally impossible here since an unknown key is rejected outright. Operators map
6780
- * 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) {
6781
7505
  const postgres = query.connection.options.type === 'postgres';
6782
7506
  Object.entries(filter).forEach(([key, value], index)=>{
6783
7507
  this.assertKnownColumn(key);
6784
- 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') {
6785
7509
  const op = value.op;
6786
- 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);
6787
7511
  }
6788
- 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 : {
6789
7513
  op: 'eq',
6790
7514
  value
6791
7515
  };
@@ -6796,8 +7520,8 @@ const TEXT_FIELD_TYPES = new Set([
6796
7520
  return query;
6797
7521
  }
6798
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) {
6799
- const multiSelect = this.fieldTypeOf(key) === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT;
6800
- 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]){
6801
7525
  case 'none':
6802
7526
  if (op === 'is_null') return [
6803
7527
  `${column} IS NULL`,
@@ -6915,13 +7639,13 @@ const TEXT_FIELD_TYPES = new Set([
6915
7639
  operand
6916
7640
  ] : operand;
6917
7641
  if (!Array.isArray(list) || !list.every(isFilterScalar)) throw this.invalidFilterValue(key, op);
6918
- if (list.length > _interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES) {
6919
- throw new _nestjs_common__rspack_import_2.BadRequestException({
6920
- 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.`,
6921
- 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,
6922
7646
  messageVariables: {
6923
7647
  key,
6924
- 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
6925
7649
  }
6926
7650
  });
6927
7651
  }
@@ -6933,11 +7657,11 @@ const TEXT_FIELD_TYPES = new Set([
6933
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) {
6934
7658
  const type = this.fieldTypeOf(key);
6935
7659
  const textField = type !== undefined && TEXT_FIELD_TYPES.has(type);
6936
- 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;
6937
7661
  if (!fits) {
6938
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7662
+ throw new common_.BadRequestException({
6939
7663
  message: `The '${op}' filter cannot be used on '${key}'.`,
6940
- 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,
6941
7665
  messageVariables: {
6942
7666
  key,
6943
7667
  op
@@ -6946,20 +7670,20 @@ const TEXT_FIELD_TYPES = new Set([
6946
7670
  }
6947
7671
  }
6948
7672
  unknownOperator(key, op) {
6949
- return new _nestjs_common__rspack_import_2.BadRequestException({
7673
+ return new common_.BadRequestException({
6950
7674
  message: `'${op}' is not a filter operator.`,
6951
- 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,
6952
7676
  messageVariables: {
6953
7677
  key,
6954
7678
  op,
6955
- 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(', ')
6956
7680
  }
6957
7681
  });
6958
7682
  }
6959
7683
  invalidFilterValue(key, op) {
6960
- return new _nestjs_common__rspack_import_2.BadRequestException({
7684
+ return new common_.BadRequestException({
6961
7685
  message: `The '${key}' filter has an invalid value for '${op}'.`,
6962
- 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,
6963
7687
  messageVariables: {
6964
7688
  key,
6965
7689
  op
@@ -6986,9 +7710,9 @@ const TEXT_FIELD_TYPES = new Set([
6986
7710
  }
6987
7711
  assertKnownColumn(key) {
6988
7712
  if (key !== 'id' && !this.presentSystemColumns.has(key) && !this.activeFieldCodes.has(key)) {
6989
- throw new _nestjs_common__rspack_import_2.BadRequestException({
7713
+ throw new common_.BadRequestException({
6990
7714
  message: `'${key}' is not a field on entity '${this.entityCode}'.`,
6991
- 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,
6992
7716
  messageVariables: {
6993
7717
  key,
6994
7718
  entity: this.entityCode
@@ -7007,6 +7731,9 @@ const TEXT_FIELD_TYPES = new Set([
7007
7731
  await queryRunner.connect();
7008
7732
  await queryRunner.startTransaction();
7009
7733
  started = true;
7734
+ if (options.statementTimeoutMs !== undefined && queryRunner.connection.options.type === 'postgres') {
7735
+ await queryRunner.query(statementTimeoutSql(options.statementTimeoutMs));
7736
+ }
7010
7737
  }
7011
7738
  const { saved, events } = await fn(queryRunner);
7012
7739
  if (!externalQueryRunner) {
@@ -7016,8 +7743,8 @@ const TEXT_FIELD_TYPES = new Set([
7016
7743
  if (options.publishEvents !== false) {
7017
7744
  for (const { action, rows } of events){
7018
7745
  if (!rows.length) continue;
7019
- await (0,_flusys_nestjs_shared_classes__rspack_import_0.publishDomainEvent)({
7020
- 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,
7021
7748
  entity: this.entityCode,
7022
7749
  action,
7023
7750
  ids: rows.map((r)=>r.id),
@@ -7034,7 +7761,7 @@ const TEXT_FIELD_TYPES = new Set([
7034
7761
  if (started && !committed) {
7035
7762
  await queryRunner.rollbackTransaction();
7036
7763
  }
7037
- throw (0,_database_error_mapper_js__rspack_import_4/* .mapDatabaseError */.E)(error, [
7764
+ throw (0,database_error_mapper/* .mapDatabaseError */.E)(error, [
7038
7765
  ...this.activeFieldCodes
7039
7766
  ]);
7040
7767
  } finally{
@@ -7048,13 +7775,6 @@ function isFilterScalar(value) {
7048
7775
  return typeof value === 'string' || typeof value === 'number' && Number.isFinite(value) || typeof value === 'boolean';
7049
7776
  }
7050
7777
 
7051
- __webpack_require__.d(__webpack_exports__, {
7052
- DG: () => (actorMetadata),
7053
- GK: () => (GenericEntityService)
7054
- }, {
7055
- zb: ACTOR_BRANCH_METADATA_KEY
7056
- });
7057
-
7058
7778
 
7059
7779
  },
7060
7780
  7259(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
@@ -7282,6 +8002,8 @@ class SchemaRegistryService {
7282
8002
  dataSourceProvider;
7283
8003
  dialectAdapter;
7284
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);
7285
8007
  constructor(dataSourceProvider, dialectAdapter){
7286
8008
  this.dataSourceProvider = dataSourceProvider;
7287
8009
  this.dialectAdapter = dialectAdapter;
@@ -7293,10 +8015,9 @@ class SchemaRegistryService {
7293
8015
  id: entityDefinitionId
7294
8016
  }
7295
8017
  });
7296
- const cacheKey = this.cacheKey(entityDefinitionId);
7297
8018
  if (!definition) {
7298
8019
  // Dropped, possibly on another instance: its schema is never asked for again.
7299
- SchemaRegistryService.cache.delete(cacheKey);
8020
+ SchemaRegistryService.cache.delete(this.cacheKey(entityDefinitionId));
7300
8021
  throw new _nestjs_common__rspack_import_1.NotFoundException({
7301
8022
  message: `Entity definition '${entityDefinitionId}' not found.`,
7302
8023
  messageKey: _config_message_keys_js__rspack_import_8/* .ENTITY_DEFINITION_MESSAGES.NOT_FOUND */.pL.NOT_FOUND,
@@ -7305,26 +8026,70 @@ class SchemaRegistryService {
7305
8026
  }
7306
8027
  });
7307
8028
  }
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);
7308
8033
  const cached = SchemaRegistryService.cache.get(cacheKey);
7309
- if (cached && cached.version === definition.schemaVersion) {
7310
- return cached.schema;
7311
- }
8034
+ if (cached && cached.version === definition.schemaVersion) return cached;
7312
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);
7313
8036
  const fields = await fieldRepository.find({
7314
8037
  where: {
7315
- entityDefinitionId,
8038
+ entityDefinitionId: definition.id,
7316
8039
  deprecatedAt: (0,typeorm__rspack_import_2.IsNull)()
7317
8040
  },
7318
8041
  order: {
7319
8042
  order: 'ASC'
7320
8043
  }
7321
8044
  });
7322
- const schema = await this.buildEntitySchema(definition, fields);
7323
- 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), {
7324
8089
  version: definition.schemaVersion,
7325
- schema
8090
+ ...resolved
7326
8091
  });
7327
- return schema;
8092
+ return resolved;
7328
8093
  }
7329
8094
  invalidate(entityDefinitionId) {
7330
8095
  SchemaRegistryService.cache.delete(this.cacheKey(entityDefinitionId));