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