@flusys/nestjs-entity-builder 9.1.0 → 9.1.2
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 +59 -26
- package/config/entity-builder.constants.d.ts +2 -0
- package/config/message-keys.d.ts +96 -12
- package/controllers/definition-bundle.controller.d.ts +15 -0
- package/controllers/flow-definition.controller.d.ts +7 -1
- package/controllers/index.d.ts +1 -1
- package/dtos/definition-bundle.dto.d.ts +57 -0
- package/dtos/flow.dto.d.ts +12 -20
- package/dtos/index.d.ts +1 -0
- package/entities/flow-definition.entity.d.ts +3 -9
- package/entities/flow-execution.entity.d.ts +3 -0
- package/entities/flow-version.entity.d.ts +10 -0
- package/entities/index.d.ts +3 -3
- package/fesm/21.js +101 -16
- package/fesm/458.js +695 -0
- package/fesm/{996.js → 677.js} +1582 -1047
- package/fesm/794.js +135 -240
- package/fesm/{489.js → 857.js} +216 -289
- package/fesm/{719.js → 870.js} +1955 -220
- package/fesm/{362.js → 897.js} +566 -2
- package/fesm/{606.js → 946.js} +564 -290
- package/fesm/config/index.js +15 -2
- package/fesm/controllers/index.js +9 -9
- package/fesm/docs/index.js +7 -4
- package/fesm/dtos/index.js +451 -256
- package/fesm/entities/index.js +58 -3
- package/fesm/guards/index.js +101 -22
- package/fesm/index.js +444 -948
- package/fesm/interfaces/index.js +92 -4
- package/fesm/modules/index.js +10 -628
- package/fesm/rule-engine/index.js +12 -3
- package/fesm/services/index.js +97 -27
- package/flow-engine/flow-api-key.d.ts +3 -7
- package/flow-engine/flow-graph.validator.d.ts +4 -2
- package/flow-engine/flow-permission-logic.d.ts +2 -0
- package/flow-engine/flow-run.types.d.ts +17 -8
- package/flow-engine/flow.types.d.ts +107 -15
- package/guards/designer-writable.interceptor.d.ts +12 -0
- package/guards/index.d.ts +1 -0
- package/interfaces/definition-bundle.interface.d.ts +111 -0
- package/interfaces/entity-builder-module.interface.d.ts +6 -0
- package/interfaces/flow-definition.interface.d.ts +13 -9
- package/interfaces/generic-record.interface.d.ts +11 -1
- package/interfaces/index.d.ts +1 -0
- package/package.json +5 -6
- package/rule-engine/rule-engine.service.d.ts +3 -1
- package/rule-engine/rule-permission.d.ts +3 -7
- package/rule-engine/rule-regex.d.ts +0 -2
- package/rule-engine/rule-values.d.ts +4 -1
- package/rule-engine/rule.interface.d.ts +2 -3
- package/services/definition-bundle.diff.d.ts +62 -0
- package/services/definition-bundle.file.d.ts +7 -0
- package/services/definition-bundle.service.d.ts +32 -0
- package/services/entity-builder-config.service.d.ts +2 -0
- package/services/entity-flow-scaffold.d.ts +1 -1
- package/services/flow-definition.service.d.ts +25 -12
- package/services/flow-execution.service.d.ts +7 -0
- package/services/flow-executor.service.d.ts +8 -8
- package/services/generic-entity.service.d.ts +5 -1
- package/services/index.d.ts +3 -1
- package/services/schema-evolution.service.d.ts +3 -1
- package/services/schema-sync.service.d.ts +5 -1
- package/controllers/flow-api-key.controller.d.ts +0 -14
- package/entities/flow-api-key.entity.d.ts +0 -10
- package/services/flow-api-key.service.d.ts +0 -31
package/fesm/{996.js → 677.js}
RENAMED
|
@@ -7,41 +7,11 @@ import * as __rspack_external_node_os_4f3c9d58 from "node:os";
|
|
|
7
7
|
import * as __rspack_external_node_path_806ed179 from "node:path";
|
|
8
8
|
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
|
+
import * as __rspack_external__flusys_nestjs_shared_exceptions_3edf22e9 from "@flusys/nestjs-shared/exceptions";
|
|
10
11
|
import * as __rspack_external_node_dns_c6249ebb from "node:dns";
|
|
11
|
-
export const __rspack_esm_id =
|
|
12
|
-
export const __rspack_esm_ids = [
|
|
12
|
+
export const __rspack_esm_id = 677;
|
|
13
|
+
export const __rspack_esm_ids = [677];
|
|
13
14
|
export const __webpack_modules__ = {
|
|
14
|
-
6576(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
15
|
-
const ENTITY_BUILDER_MODULE_OPTIONS = 'ENTITY_BUILDER_MODULE_OPTIONS';
|
|
16
|
-
/**
|
|
17
|
-
* Identifier whitelist enforced on every EntityDefinition/FieldDefinition `code`.
|
|
18
|
-
* Also the exact contract SchemaSyncService's DDL engine validates against
|
|
19
|
-
* before generating a table/column name.
|
|
20
|
-
*/ const ENTITY_CODE_PATTERN = /^[a-z][a-z0-9_]{2,59}$/;
|
|
21
|
-
/** Matches the `code` column length on both EntityDefinition and FieldDefinition. */ const ENTITY_CODE_MAX_LENGTH = 63;
|
|
22
|
-
/** Defense-in-depth prefix applied to every dynamically created table name. */ const ENTITY_TABLE_PREFIX = 'eb_';
|
|
23
|
-
/** The generic API controller's record endpoints - each can be created as a ready-made flow with the entity. */ const ENTITY_FLOW_ENDPOINTS = [
|
|
24
|
-
'insert',
|
|
25
|
-
'insertMany',
|
|
26
|
-
'getById',
|
|
27
|
-
'getByIds',
|
|
28
|
-
'getAll',
|
|
29
|
-
'getByFilter',
|
|
30
|
-
'update',
|
|
31
|
-
'updateMany',
|
|
32
|
-
'bulkUpsert',
|
|
33
|
-
'delete'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
37
|
-
}, {
|
|
38
|
-
CP: ENTITY_BUILDER_MODULE_OPTIONS,
|
|
39
|
-
WT: ENTITY_FLOW_ENDPOINTS,
|
|
40
|
-
vs: ENTITY_CODE_PATTERN
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
15
|
982(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
46
16
|
/** Every entity the entity builder module publishes under */ const ENTITY_BUILDER_EVENT_ENTITIES = {
|
|
47
17
|
ENTITY_DEFINITION: 'entity_definition',
|
|
@@ -59,6 +29,60 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
59
29
|
});
|
|
60
30
|
|
|
61
31
|
|
|
32
|
+
},
|
|
33
|
+
4816(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
34
|
+
/* import */ var node_crypto__rspack_import_0 = __webpack_require__(2561);
|
|
35
|
+
/* import */ var _flow_types_js__rspack_import_1 = __webpack_require__(6256);
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const API_KEY_PREFIX_LENGTH = 11; // "fk_" + 8 hex
|
|
39
|
+
const hashApiKey = (key)=>(0,node_crypto__rspack_import_0.createHash)('sha256').update(key).digest('hex');
|
|
40
|
+
const apiKeyPrefix = (key)=>key.slice(0, API_KEY_PREFIX_LENGTH);
|
|
41
|
+
/** A key as the designer makes it: `fk_<8 hex>_<32 hex>`. */ function looksLikeApiKey(key) {
|
|
42
|
+
return typeof key === 'string' && /^fk_[0-9a-f]{8}_[0-9a-f]{32}$/.test(key);
|
|
43
|
+
}
|
|
44
|
+
/** Constant-time comparison of a presented key against a stored hash. */ function apiKeyMatches(presented, storedHash) {
|
|
45
|
+
const a = Buffer.from(hashApiKey(presented), 'hex');
|
|
46
|
+
const b = Buffer.from(storedHash, 'hex');
|
|
47
|
+
return a.length === b.length && (0,node_crypto__rspack_import_0.timingSafeEqual)(a, b);
|
|
48
|
+
}
|
|
49
|
+
/** Whether `presented` is the key a Request step stores. */ const apiKeyOpens = (stored, presented)=>!!stored && looksLikeApiKey(presented) && apiKeyMatches(presented, stored.hash);
|
|
50
|
+
/**
|
|
51
|
+
* What is saved of the Request steps' API keys: a plain key the designer made becomes its prefix and
|
|
52
|
+
* hash, so the key itself is never stored; a step without `api_key` access keeps no key at all.
|
|
53
|
+
*/ function sealApiKeys(nodes) {
|
|
54
|
+
return nodes.map((node)=>{
|
|
55
|
+
if (node.type !== 'trigger' || node.config?.['apiKey'] === undefined) return node;
|
|
56
|
+
const { apiKey, ...config } = node.config;
|
|
57
|
+
if (config['authMode'] !== 'api_key') return {
|
|
58
|
+
...node,
|
|
59
|
+
config
|
|
60
|
+
};
|
|
61
|
+
if (looksLikeApiKey(apiKey)) return {
|
|
62
|
+
...node,
|
|
63
|
+
config: {
|
|
64
|
+
...config,
|
|
65
|
+
apiKey: {
|
|
66
|
+
prefix: apiKeyPrefix(apiKey),
|
|
67
|
+
hash: hashApiKey(apiKey)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return (0,_flow_types_js__rspack_import_1/* .isStoredApiKey */.xA)(apiKey) ? node : {
|
|
72
|
+
...node,
|
|
73
|
+
config
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
79
|
+
Jb: () => (looksLikeApiKey),
|
|
80
|
+
ue: () => (sealApiKeys)
|
|
81
|
+
}, {
|
|
82
|
+
NT: apiKeyOpens
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
|
|
62
86
|
},
|
|
63
87
|
8333(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
64
88
|
|
|
@@ -80,10 +104,6 @@ var message_keys = __webpack_require__(5732);
|
|
|
80
104
|
var flow_errors = __webpack_require__(8542);
|
|
81
105
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow.types.ts
|
|
82
106
|
var flow_types = __webpack_require__(6256);
|
|
83
|
-
// EXTERNAL MODULE: external "@flusys/nestjs-shared/constants"
|
|
84
|
-
var constants_ = __webpack_require__(9140);
|
|
85
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/interfaces/generic-record.interface.ts
|
|
86
|
-
var generic_record_interface = __webpack_require__(3372);
|
|
87
107
|
;// CONCATENATED MODULE: external "node:os"
|
|
88
108
|
|
|
89
109
|
;// CONCATENATED MODULE: external "node:path"
|
|
@@ -302,7 +322,7 @@ const timeoutError = (job)=>new flow_errors/* .FlowCodeError */.yL(message_keys/
|
|
|
302
322
|
const unavailableError = (node, detail)=>new flow_errors/* .FlowCodeError */.yL(message_keys/* .FLOW_ERROR_MESSAGES.CODE_UNAVAILABLE */.zr.CODE_UNAVAILABLE, {
|
|
303
323
|
node
|
|
304
324
|
}, `The code sandbox could not run the code: ${detail}`);
|
|
305
|
-
let
|
|
325
|
+
let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
|
|
306
326
|
idle = [];
|
|
307
327
|
running = new Map();
|
|
308
328
|
queue = [];
|
|
@@ -432,14 +452,12 @@ let pool;
|
|
|
432
452
|
detail: error.message
|
|
433
453
|
}, error.message);
|
|
434
454
|
}
|
|
435
|
-
pool ??= new
|
|
455
|
+
pool ??= new flow_code_sandbox_CodeSandboxPool();
|
|
436
456
|
return pool.run(code, input, timeoutMs, node);
|
|
437
457
|
}
|
|
438
458
|
|
|
439
459
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-graph.validator.ts
|
|
440
460
|
var flow_graph_validator = __webpack_require__(3186);
|
|
441
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/rule-permission.ts
|
|
442
|
-
var rule_permission = __webpack_require__(5191);
|
|
443
461
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/rule-values.ts
|
|
444
462
|
var rule_values = __webpack_require__(6622);
|
|
445
463
|
;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-node-handlers.ts
|
|
@@ -451,16 +469,7 @@ var rule_values = __webpack_require__(6622);
|
|
|
451
469
|
|
|
452
470
|
|
|
453
471
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const PERMISSION_ACTION = {
|
|
458
|
-
insert: 'create',
|
|
459
|
-
update: 'update',
|
|
460
|
-
delete: 'delete',
|
|
461
|
-
read: 'read'
|
|
462
|
-
};
|
|
463
|
-
/** With `env`, `lookup` expressions read through the flow (permission check, transaction). */ function evaluationContext(state, env) {
|
|
472
|
+
/** With `env`, `lookup` expressions read through the flow (its transaction). */ function evaluationContext(state, env) {
|
|
464
473
|
const base = {
|
|
465
474
|
input: state.input,
|
|
466
475
|
vars: state.vars,
|
|
@@ -478,7 +487,6 @@ const PERMISSION_ACTION = {
|
|
|
478
487
|
function lookupResolver(env, state) {
|
|
479
488
|
return {
|
|
480
489
|
find: async (entityCode, query)=>{
|
|
481
|
-
await authorize(env, state, entityCode, 'read');
|
|
482
490
|
const service = await env.entityService(entityCode, state.qr);
|
|
483
491
|
const { data, total } = await service.getAll(undefined, {
|
|
484
492
|
filter: query.filter,
|
|
@@ -522,24 +530,9 @@ async function mapValues(map, env, state) {
|
|
|
522
530
|
});
|
|
523
531
|
};
|
|
524
532
|
for (const [key, item] of Object.entries(map ?? {})){
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
op: item.op
|
|
529
|
-
};
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
const value = item.value === undefined ? undefined : await env.ruleEngine.resolveExpression(item.value, ctx);
|
|
533
|
-
if (value === undefined) absent(key);
|
|
534
|
-
else out[key] = {
|
|
535
|
-
op: item.op,
|
|
536
|
-
value
|
|
537
|
-
};
|
|
538
|
-
} else {
|
|
539
|
-
const value = await env.ruleEngine.resolveExpression(item, ctx);
|
|
540
|
-
if (value === undefined) absent(key);
|
|
541
|
-
else out[key] = (0,rule_values/* .equalsOperand */.$b)(value);
|
|
542
|
-
}
|
|
533
|
+
const entry = await (0,rule_values/* .resolveFilterEntry */.vY)(item, (expression)=>env.ruleEngine.resolveExpression(expression, ctx));
|
|
534
|
+
if ((0,rule_values/* .isFilterValueMissing */.kp)(entry)) absent(key);
|
|
535
|
+
else out[key] = entry;
|
|
543
536
|
}
|
|
544
537
|
return out;
|
|
545
538
|
}
|
|
@@ -556,7 +549,7 @@ async function runValidate(cfg, env, state) {
|
|
|
556
549
|
passed: true
|
|
557
550
|
}
|
|
558
551
|
};
|
|
559
|
-
const status = cfg.statusCode ?? (failed.every((check)=>(0,flow_graph_validator/* .usesPermissionFunction */.
|
|
552
|
+
const status = cfg.statusCode ?? (failed.every((check)=>(0,flow_graph_validator/* .usesPermissionFunction */.u9)(check.rule)) ? 403 : 400);
|
|
560
553
|
const errors = failed.map((check)=>({
|
|
561
554
|
field: check.field || check.id,
|
|
562
555
|
message: check.message
|
|
@@ -569,21 +562,44 @@ async function runValidate(cfg, env, state) {
|
|
|
569
562
|
}
|
|
570
563
|
});
|
|
571
564
|
}
|
|
572
|
-
/** `{ allowed, missing }` - `missing` lists the codes the
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
await env.ruleEngine.resolveExpression(cfg.branch, evaluationContext(state, env))
|
|
578
|
-
], 0);
|
|
579
|
-
const permissions = state.permissions ?? env.ruleEngine.createPermissionResolver(state.user);
|
|
580
|
-
const granted = state.user ? await permissions.granted(branch) : [];
|
|
581
|
-
const missing = codes.filter((code)=>!(0,utils_.matchesPermission)(granted, code));
|
|
582
|
-
const allowed = codes.length > 0 && (cfg.match === 'all' ? missing.length === 0 : missing.length < codes.length);
|
|
583
|
-
const output = {
|
|
565
|
+
/** `{ allowed, missing }` - whether the user passes the AND / OR rule; `missing` lists the codes it names that the user does not hold. */ async function runPermissionCheck(cfg, env, state) {
|
|
566
|
+
const granted = await permissionCodes(cfg, env, state);
|
|
567
|
+
const missing = (0,utils_.permissionLogicCodes)(cfg.permissions).filter((code)=>!(0,utils_.matchesPermission)(granted, code));
|
|
568
|
+
const allowed = (0,utils_.evaluatePermissionLogic)(cfg.permissions, granted).passed;
|
|
569
|
+
return deniedResult(cfg, {
|
|
584
570
|
allowed,
|
|
585
571
|
missing
|
|
586
|
-
};
|
|
572
|
+
}, allowed);
|
|
573
|
+
}
|
|
574
|
+
/** The codes the check's user holds in its company / branch; none without a user, for a set company that resolves to nothing, or outside the user's granted companies and branches. */ async function permissionCodes(cfg, env, state) {
|
|
575
|
+
const caller = state.user;
|
|
576
|
+
const asCaller = cfg.subject !== 'user';
|
|
577
|
+
const ctx = evaluationContext(state, env);
|
|
578
|
+
const resolveId = async (expression)=>expression === undefined ? null : oneId(await env.ruleEngine.resolveExpression(expression, ctx));
|
|
579
|
+
const userId = asCaller ? caller?.id ?? null : await resolveId(cfg.userId);
|
|
580
|
+
if (!userId) return [];
|
|
581
|
+
const currentCompanyId = caller?.companyId ?? null;
|
|
582
|
+
const companyId = cfg.companyId === undefined ? currentCompanyId : await resolveId(cfg.companyId);
|
|
583
|
+
if (cfg.companyId !== undefined && !companyId) return [];
|
|
584
|
+
const branchId = await resolveId(cfg.branchId);
|
|
585
|
+
if (asCaller && companyId === currentCompanyId) {
|
|
586
|
+
const permissions = state.permissions ?? env.ruleEngine.createPermissionResolver(caller);
|
|
587
|
+
if (cfg.branchId === undefined) return permissions.granted(undefined);
|
|
588
|
+
if (branchId === null || branchId === caller?.branchId) return permissions.granted(branchId);
|
|
589
|
+
}
|
|
590
|
+
if (companyId && !await isGrantedScope(userId, companyId, branchId, env)) return [];
|
|
591
|
+
return env.ruleEngine.permissionCodes({
|
|
592
|
+
userId,
|
|
593
|
+
companyId,
|
|
594
|
+
branchId
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
async function isGrantedScope(userId, companyId, branchId, env) {
|
|
598
|
+
const { companies, branches } = await env.companyAccess(userId);
|
|
599
|
+
if (!companies.some((company)=>company.id === companyId)) return false;
|
|
600
|
+
return branchId === null || branches.some((branch)=>branch.id === branchId && branch.companyId === companyId);
|
|
601
|
+
}
|
|
602
|
+
/** A denied check: `onDenied: branch` follows the `denied` port, otherwise the run is answered `statusCode` (403). */ function deniedResult(cfg, output, allowed) {
|
|
587
603
|
if (cfg.onDenied === 'branch') return {
|
|
588
604
|
output,
|
|
589
605
|
port: allowed ? 'allowed' : 'denied'
|
|
@@ -599,6 +615,68 @@ async function runValidate(cfg, env, state) {
|
|
|
599
615
|
output
|
|
600
616
|
};
|
|
601
617
|
}
|
|
618
|
+
/** One id, a list of ids, or nothing (undefined / null / empty text), each id once. */ function idList(value) {
|
|
619
|
+
const items = Array.isArray(value) ? value : [
|
|
620
|
+
value
|
|
621
|
+
];
|
|
622
|
+
return [
|
|
623
|
+
...new Set(items.filter((item)=>item !== undefined && item !== null && item !== '').map(String))
|
|
624
|
+
];
|
|
625
|
+
}
|
|
626
|
+
/** One id from an expression's value; anything but non-empty text is none. */ function oneId(value) {
|
|
627
|
+
return typeof value === 'string' && value ? value : null;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* `{ userId, currentCompanyId, currentBranchId, companies, branches }` - what the user (the signed-in
|
|
631
|
+
* caller, or the one `userId` names) may work in; none without a user or the company feature. With
|
|
632
|
+
* `companyIds` / `branchIds` it also answers `allowed` and `missing`: every id asked for must be within
|
|
633
|
+
* reach (`branchMatch`, among the branches of `branchCompany`), and a target that resolves to nothing
|
|
634
|
+
* is a denial, never a pass.
|
|
635
|
+
*/ async function runCompanyBranchCheck(cfg, env, state) {
|
|
636
|
+
const ctx = evaluationContext(state, env);
|
|
637
|
+
const resolve = async (expression)=>expression === undefined ? undefined : env.ruleEngine.resolveExpression(expression, ctx);
|
|
638
|
+
const asCaller = cfg.subject !== 'user';
|
|
639
|
+
const userId = asCaller ? state.user?.id ?? null : oneId(await resolve(cfg.userId));
|
|
640
|
+
const { companies, branches } = userId ? await env.companyAccess(userId) : {
|
|
641
|
+
companies: [],
|
|
642
|
+
branches: []
|
|
643
|
+
};
|
|
644
|
+
const currentCompanyId = asCaller ? state.user?.companyId ?? null : null;
|
|
645
|
+
const currentBranchId = asCaller ? state.user?.branchId ?? null : null;
|
|
646
|
+
const output = {
|
|
647
|
+
userId,
|
|
648
|
+
currentCompanyId,
|
|
649
|
+
currentBranchId,
|
|
650
|
+
companies,
|
|
651
|
+
branches
|
|
652
|
+
};
|
|
653
|
+
if (cfg.companyIds === undefined && cfg.branchIds === undefined) return {
|
|
654
|
+
output
|
|
655
|
+
};
|
|
656
|
+
const wanted = async (expression)=>expression === undefined ? null : idList(await resolve(expression));
|
|
657
|
+
const wantedCompanies = await wanted(cfg.companyIds);
|
|
658
|
+
const wantedBranches = await wanted(cfg.branchIds);
|
|
659
|
+
const granted = branches.filter((branch)=>cfg.branchCompany !== 'current' || branch.companyId === currentCompanyId).map((branch)=>branch.id);
|
|
660
|
+
const reachable = await reachableBranches(cfg.branchMatch ?? 'direct', granted, wantedBranches, env);
|
|
661
|
+
const missing = {
|
|
662
|
+
companyIds: (wantedCompanies ?? []).filter((id)=>!companies.some((company)=>company.id === id)),
|
|
663
|
+
branchIds: (wantedBranches ?? []).filter((id)=>!reachable.has(id))
|
|
664
|
+
};
|
|
665
|
+
const nothingToCheck = wantedCompanies?.length === 0 || wantedBranches?.length === 0;
|
|
666
|
+
const allowed = !nothingToCheck && !missing.companyIds.length && !missing.branchIds.length;
|
|
667
|
+
return deniedResult(cfg, {
|
|
668
|
+
...output,
|
|
669
|
+
allowed,
|
|
670
|
+
missing
|
|
671
|
+
}, allowed);
|
|
672
|
+
}
|
|
673
|
+
/** The branches a check passes: the granted ones (direct), and with `within` every branch under them too. */ async function reachableBranches(match, granted, wanted, env) {
|
|
674
|
+
if (match !== 'within' || !wanted?.length || !granted.length || !env.branchTree || wanted.every((id)=>granted.includes(id))) return new Set(granted);
|
|
675
|
+
return new Set([
|
|
676
|
+
...granted,
|
|
677
|
+
...await env.branchTree(granted)
|
|
678
|
+
]);
|
|
679
|
+
}
|
|
602
680
|
const USES_PERMISSIONS = /\b(permissions|hasPermission|hasAnyPermission|hasAllPermissions)\b/;
|
|
603
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) {
|
|
604
682
|
const user = state.user;
|
|
@@ -641,16 +719,6 @@ function asId(value) {
|
|
|
641
719
|
});
|
|
642
720
|
return value;
|
|
643
721
|
}
|
|
644
|
-
/** Fails closed: the caller needs the entity permission; the system only when that was granted for this run; anything else is refused. */ async function authorize(env, state, entityCode, action) {
|
|
645
|
-
if (state.runAs === 'caller') return env.assertPermission((0,constants_.dynamicEntityPermission)(entityCode, PERMISSION_ACTION[action]), state.user);
|
|
646
|
-
if (state.runAs === 'system' && state.systemAuthorized === true) return;
|
|
647
|
-
throw new flow_errors/* .FlowHttpError */.hk(403, 'This flow is not allowed to read or write records as the system.', undefined, {
|
|
648
|
-
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.RUN_AS_NOT_ALLOWED */.zr.RUN_AS_NOT_ALLOWED,
|
|
649
|
-
messageVariables: {
|
|
650
|
-
runAs: String(state.runAs)
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
722
|
function writeOptions(state) {
|
|
655
723
|
return state.qr ? {
|
|
656
724
|
queryRunner: state.qr,
|
|
@@ -727,15 +795,16 @@ async function updateRecord(entityCode, service, values, state) {
|
|
|
727
795
|
}
|
|
728
796
|
/** @returns false when the record does not exist and `skip` allows that. */ async function deleteRecord(entityCode, service, id, skipMissing, state) {
|
|
729
797
|
countWrite(state);
|
|
798
|
+
let action;
|
|
730
799
|
try {
|
|
731
|
-
await service.delete(id, state.user, writeOptions(state));
|
|
800
|
+
action = await service.delete(id, state.user, writeOptions(state));
|
|
732
801
|
} catch (error) {
|
|
733
802
|
if (skipMissing && error instanceof common_.NotFoundException) return false;
|
|
734
803
|
throw error;
|
|
735
804
|
}
|
|
736
805
|
if (state.qr) state.pendingEvents.push({
|
|
737
806
|
entityCode,
|
|
738
|
-
action
|
|
807
|
+
action,
|
|
739
808
|
row: {
|
|
740
809
|
id
|
|
741
810
|
}
|
|
@@ -780,8 +849,6 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
780
849
|
* A write of several records (many, or any children) is all-or-nothing.
|
|
781
850
|
*/ async function runInsert(node, cfg, env, state) {
|
|
782
851
|
const children = cfg.children ?? [];
|
|
783
|
-
await authorize(env, state, cfg.entityCode, 'insert');
|
|
784
|
-
for (const child of children)await authorize(env, state, child.entityCode, 'insert');
|
|
785
852
|
const service = await env.entityService(cfg.entityCode, state.qr);
|
|
786
853
|
const insertOne = async ()=>{
|
|
787
854
|
const saved = await insertRecord(cfg.entityCode, service, await mapFieldValues(cfg.fields, env, state), state);
|
|
@@ -835,8 +902,6 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
835
902
|
}
|
|
836
903
|
/** one: the saved record (null when skipped). many / filter: `{ items, count, updated, created, skipped }`, all-or-nothing. */ async function runUpdate(node, cfg, env, state) {
|
|
837
904
|
const target = writeTarget(cfg);
|
|
838
|
-
await authorize(env, state, cfg.entityCode, 'update');
|
|
839
|
-
if (target !== 'filter' && cfg.onNotFound === 'insert') await authorize(env, state, cfg.entityCode, 'insert');
|
|
840
905
|
const service = await env.entityService(cfg.entityCode, state.qr);
|
|
841
906
|
if (target === 'one') return {
|
|
842
907
|
output: (await updateOrCreate(cfg, service, undefined, env, state)).row
|
|
@@ -867,7 +932,6 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
867
932
|
/** one: `{ id, deleted }`. many / filter: `{ ids, count, skipped }`, all-or-nothing; an id listed twice is deleted once. */ async function runDelete(node, cfg, env, state) {
|
|
868
933
|
const target = writeTarget(cfg);
|
|
869
934
|
const skipMissing = cfg.onNotFound === 'skip';
|
|
870
|
-
await authorize(env, state, cfg.entityCode, 'delete');
|
|
871
935
|
const service = await env.entityService(cfg.entityCode, state.qr);
|
|
872
936
|
const idOf = async (item)=>asId(cfg.id === undefined ? itemId(item) : await env.ruleEngine.resolveExpression(cfg.id, evaluationContext(state, env)));
|
|
873
937
|
if (target === 'one') {
|
|
@@ -897,6 +961,377 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
897
961
|
})
|
|
898
962
|
};
|
|
899
963
|
}
|
|
964
|
+
/** As strict as the email module's own check, and without the characters that would break an address header. */ const EMAIL_PATTERN = /^[^\s@,;<>"]+@[^\s@,;<>"]+\.[^\s@,;<>"]+$/;
|
|
965
|
+
const MAX_EMAIL_LENGTH = 254;
|
|
966
|
+
const shown = (value)=>String(typeof value === 'object' && value !== null ? JSON.stringify(value) : value).slice(0, 100);
|
|
967
|
+
/** A value as message text: nothing for null / undefined, JSON for an object. */ function asText(value) {
|
|
968
|
+
if (value === null || value === undefined) return '';
|
|
969
|
+
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
970
|
+
}
|
|
971
|
+
/** Reserves `count` sends of the run's budget before any goes out, so a step never sends only some of its messages because of the limit. */ function countMessages(state, count) {
|
|
972
|
+
if (state.messages + count > flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.TOO_MANY_MESSAGES */.zr.TOO_MANY_MESSAGES, {
|
|
973
|
+
max: flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN
|
|
974
|
+
});
|
|
975
|
+
state.messages += count;
|
|
976
|
+
}
|
|
977
|
+
function assertRecipientCount(node, count, max) {
|
|
978
|
+
if (count > max) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.TOO_MANY_RECIPIENTS */.zr.TOO_MANY_RECIPIENTS, {
|
|
979
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
980
|
+
count,
|
|
981
|
+
max
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
/** Whatever the module threw, or its `success: false`, fails the step with the reason (a limit stays a limit). */ function sendFailure(key, node, error) {
|
|
985
|
+
if (error instanceof flow_errors/* .FlowKeyedError */.Hp || error instanceof flow_errors/* .FlowHttpError */.hk) return error;
|
|
986
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
987
|
+
return new flow_errors/* .FlowKeyedError */.Hp(key, {
|
|
988
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
989
|
+
detail
|
|
990
|
+
}, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" could not send: ${detail}`);
|
|
991
|
+
}
|
|
992
|
+
/** Inside a transaction the message waits for the commit, so a run that rolls back sends nothing. */ async function deliver(node, state, send) {
|
|
993
|
+
if (state.qr) {
|
|
994
|
+
state.pendingEvents.push({
|
|
995
|
+
delivery: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
996
|
+
send
|
|
997
|
+
});
|
|
998
|
+
return 'queued';
|
|
999
|
+
}
|
|
1000
|
+
await send();
|
|
1001
|
+
return 'sent';
|
|
1002
|
+
}
|
|
1003
|
+
/** One id or a list of them, each once; null / undefined / empty text is none. Anything but an id rejects with `key`. */ function idsOf(node, value, key, text) {
|
|
1004
|
+
const items = (Array.isArray(value) ? value : [
|
|
1005
|
+
value
|
|
1006
|
+
]).filter((item)=>item !== undefined && item !== null && item !== '');
|
|
1007
|
+
for (const item of items){
|
|
1008
|
+
if (typeof item !== 'string' || !flow_types/* .FLOW_ID_PATTERN.test */.E2.test(item)) {
|
|
1009
|
+
throw new flow_errors/* .FlowHttpError */.hk(400, text(shown(item)), undefined, {
|
|
1010
|
+
messageKey: key,
|
|
1011
|
+
messageVariables: {
|
|
1012
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
1013
|
+
value: shown(item)
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return [
|
|
1019
|
+
...new Set(items)
|
|
1020
|
+
];
|
|
1021
|
+
}
|
|
1022
|
+
const notificationRecipients = (node, value)=>idsOf(node, value, message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_RECIPIENT_INVALID */.zr.NOTIFICATION_RECIPIENT_INVALID, (shownValue)=>`"${(0,flow_types/* .nodeLabel */.dA)(node)}" cannot notify "${shownValue}": it is not a user id.`);
|
|
1023
|
+
/** Addresses: one, a list, or comma / semicolon separated text; each once (case aside), in the order given. */ function emailAddresses(node, value) {
|
|
1024
|
+
const items = (Array.isArray(value) ? value : [
|
|
1025
|
+
value
|
|
1026
|
+
]).filter((item)=>item !== undefined && item !== null);
|
|
1027
|
+
const seen = new Map();
|
|
1028
|
+
for (const item of items){
|
|
1029
|
+
const parts = typeof item === 'string' ? item.split(/[,;]/) : [
|
|
1030
|
+
item
|
|
1031
|
+
];
|
|
1032
|
+
for (const part of parts){
|
|
1033
|
+
const address = typeof part === 'string' ? part.trim() : part;
|
|
1034
|
+
if (address === '') continue;
|
|
1035
|
+
if (typeof address !== 'string' || address.length > MAX_EMAIL_LENGTH || !EMAIL_PATTERN.test(address)) {
|
|
1036
|
+
throw new flow_errors/* .FlowHttpError */.hk(400, `"${shown(address)}" is not a valid email address.`, undefined, {
|
|
1037
|
+
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_ADDRESS_INVALID */.zr.EMAIL_ADDRESS_INVALID,
|
|
1038
|
+
messageVariables: {
|
|
1039
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
1040
|
+
address: shown(address)
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
if (!seen.has(address.toLowerCase())) seen.set(address.toLowerCase(), address);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return [
|
|
1048
|
+
...seen.values()
|
|
1049
|
+
];
|
|
1050
|
+
}
|
|
1051
|
+
/** The company a notification shows under: the one given, else the caller's current one. */ function notificationCompany(node, value, given, state) {
|
|
1052
|
+
if (!given) return state.user?.companyId ?? null;
|
|
1053
|
+
if (value === undefined || value === null || value === '') return null;
|
|
1054
|
+
if (typeof value === 'string' && flow_types/* .FLOW_ID_PATTERN.test */.E2.test(value)) return value;
|
|
1055
|
+
throw new flow_errors/* .FlowHttpError */.hk(400, `"${(0,flow_types/* .nodeLabel */.dA)(node)}": "${shown(value)}" is not a company id.`, undefined, {
|
|
1056
|
+
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_COMPANY_INVALID */.zr.NOTIFICATION_COMPANY_INVALID,
|
|
1057
|
+
messageVariables: {
|
|
1058
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
1059
|
+
value: shown(value)
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
/** Sends `batches` (each already checked) as one step: the run's budget is taken for all of them first; a test run sends nothing. */ async function sendAll(node, state, batches, skipKey, recipients, send) {
|
|
1064
|
+
countMessages(state, batches.length);
|
|
1065
|
+
if (state.mode === 'dry_run') return {
|
|
1066
|
+
output: {
|
|
1067
|
+
skipped: true,
|
|
1068
|
+
reasonKey: skipKey,
|
|
1069
|
+
count: batches.length,
|
|
1070
|
+
recipients
|
|
1071
|
+
},
|
|
1072
|
+
skipped: true
|
|
1073
|
+
};
|
|
1074
|
+
let status = 'sent';
|
|
1075
|
+
for (const batch of batches)status = await deliver(node, state, ()=>send(batch));
|
|
1076
|
+
return status;
|
|
1077
|
+
}
|
|
1078
|
+
const nobody = (extra = {})=>({
|
|
1079
|
+
output: {
|
|
1080
|
+
skipped: true,
|
|
1081
|
+
reasonKey: message_keys/* .FLOW_SKIP_MESSAGES.NO_RECIPIENTS */.CB.NO_RECIPIENTS,
|
|
1082
|
+
recipients: 0,
|
|
1083
|
+
...extra
|
|
1084
|
+
},
|
|
1085
|
+
skipped: true
|
|
1086
|
+
});
|
|
1087
|
+
/** Title, message, type, data and push of a notification, read in the current scope (the item's, in many mode). */ async function notificationContent(node, cfg, env, state) {
|
|
1088
|
+
const ctx = evaluationContext(state, env);
|
|
1089
|
+
const title = asText(await env.ruleEngine.resolveExpression(cfg.title, ctx)).trim().slice(0, flow_types/* .FLOW_LIMITS.MAX_NOTIFICATION_TITLE_LENGTH */.Or.MAX_NOTIFICATION_TITLE_LENGTH);
|
|
1090
|
+
if (!title) throw new flow_errors/* .FlowHttpError */.hk(400, `The title of "${(0,flow_types/* .nodeLabel */.dA)(node)}" is empty.`, undefined, {
|
|
1091
|
+
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_TITLE_EMPTY */.zr.NOTIFICATION_TITLE_EMPTY,
|
|
1092
|
+
messageVariables: {
|
|
1093
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node)
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
const message = cfg.message === undefined ? '' : asText(await env.ruleEngine.resolveExpression(cfg.message, ctx));
|
|
1097
|
+
const data = cfg.data && Object.keys(cfg.data).length ? await mapValues(cfg.data, env, state) : null;
|
|
1098
|
+
return {
|
|
1099
|
+
title,
|
|
1100
|
+
message: message || null,
|
|
1101
|
+
type: cfg.type ?? 'info',
|
|
1102
|
+
data,
|
|
1103
|
+
realtime: cfg.realtime !== false
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
/** One notification to the users `userIds` names, or null when that is nobody. */ async function userNotification(node, cfg, env, state) {
|
|
1107
|
+
const ctx = evaluationContext(state, env);
|
|
1108
|
+
const userIds = notificationRecipients(node, cfg.userIds === undefined ? undefined : await env.ruleEngine.resolveExpression(cfg.userIds, ctx));
|
|
1109
|
+
if (!userIds.length) return null;
|
|
1110
|
+
assertRecipientCount(node, userIds.length, flow_types/* .FLOW_LIMITS.MAX_NOTIFICATION_RECIPIENTS */.Or.MAX_NOTIFICATION_RECIPIENTS);
|
|
1111
|
+
const content = await notificationContent(node, cfg, env, state);
|
|
1112
|
+
const companyId = notificationCompany(node, cfg.companyId === undefined ? undefined : await env.ruleEngine.resolveExpression(cfg.companyId, ctx), cfg.companyId !== undefined, state);
|
|
1113
|
+
return {
|
|
1114
|
+
...content,
|
|
1115
|
+
userIds,
|
|
1116
|
+
companyId
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
/** One notification per company that has members, each shown under its own company. */ async function companyNotifications(node, cfg, env, state) {
|
|
1120
|
+
const companyMembers = env.companyMembers;
|
|
1121
|
+
if (!companyMembers) {
|
|
1122
|
+
throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_COMPANY_UNAVAILABLE */.zr.NOTIFICATION_COMPANY_UNAVAILABLE, {
|
|
1123
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node)
|
|
1124
|
+
}, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" notifies companies, which needs the company feature.`);
|
|
1125
|
+
}
|
|
1126
|
+
const ctx = evaluationContext(state, env);
|
|
1127
|
+
const resolve = async (expression)=>expression === undefined ? undefined : env.ruleEngine.resolveExpression(expression, ctx);
|
|
1128
|
+
const companyIds = idsOf(node, await resolve(cfg.companyIds), message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_COMPANY_INVALID */.zr.NOTIFICATION_COMPANY_INVALID, (value)=>`"${(0,flow_types/* .nodeLabel */.dA)(node)}": "${value}" is not a company id.`);
|
|
1129
|
+
const branchIds = idsOf(node, await resolve(cfg.branchIds), message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_BRANCH_INVALID */.zr.NOTIFICATION_BRANCH_INVALID, (value)=>`"${(0,flow_types/* .nodeLabel */.dA)(node)}": "${value}" is not a branch id.`);
|
|
1130
|
+
if (!companyIds.length) return [];
|
|
1131
|
+
const content = await notificationContent(node, cfg, env, state);
|
|
1132
|
+
const batches = [];
|
|
1133
|
+
let total = 0;
|
|
1134
|
+
for (const companyId of companyIds){
|
|
1135
|
+
const userIds = await companyMembers(companyId, branchIds.length ? branchIds : undefined);
|
|
1136
|
+
total += userIds.length;
|
|
1137
|
+
assertRecipientCount(node, total, flow_types/* .FLOW_LIMITS.MAX_COMPANY_NOTIFICATION_RECIPIENTS */.Or.MAX_COMPANY_NOTIFICATION_RECIPIENTS);
|
|
1138
|
+
if (userIds.length) batches.push({
|
|
1139
|
+
...content,
|
|
1140
|
+
userIds,
|
|
1141
|
+
companyId
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
return batches;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* one: `{ status, recipients }`. many / company: `{ status, count, recipients }` (+ `skipped` items
|
|
1148
|
+
* with nobody to notify, in many mode). `status` is `sent`, or `queued` inside a transaction (sent once
|
|
1149
|
+
* it commits). Every message is checked before the first goes out; nobody at all skips the step, and
|
|
1150
|
+
* a test run checks everything but sends nothing.
|
|
1151
|
+
*/ async function runSendNotification(node, cfg, env, state) {
|
|
1152
|
+
const notifications = env.notifications;
|
|
1153
|
+
if (!notifications) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_UNAVAILABLE */.zr.NOTIFICATION_UNAVAILABLE, {
|
|
1154
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node)
|
|
1155
|
+
}, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" needs the notification module, which this app does not run.`);
|
|
1156
|
+
const target = cfg.target ?? 'one';
|
|
1157
|
+
let batches;
|
|
1158
|
+
let skippedItems = 0;
|
|
1159
|
+
if (target === 'company') {
|
|
1160
|
+
batches = await companyNotifications(node, cfg, env, state);
|
|
1161
|
+
} else if (target === 'many') {
|
|
1162
|
+
const items = await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN, env, state);
|
|
1163
|
+
const prepared = await eachItem(state, items, ()=>userNotification(node, cfg, env, state));
|
|
1164
|
+
batches = prepared.filter((batch)=>batch !== null);
|
|
1165
|
+
skippedItems = prepared.length - batches.length;
|
|
1166
|
+
} else {
|
|
1167
|
+
batches = [
|
|
1168
|
+
await userNotification(node, cfg, env, state)
|
|
1169
|
+
].filter((batch)=>batch !== null);
|
|
1170
|
+
}
|
|
1171
|
+
const counts = target === 'one' ? {} : {
|
|
1172
|
+
count: batches.length,
|
|
1173
|
+
...target === 'many' ? {
|
|
1174
|
+
skipped: skippedItems
|
|
1175
|
+
} : {}
|
|
1176
|
+
};
|
|
1177
|
+
if (!batches.length) return nobody(counts);
|
|
1178
|
+
const recipients = batches.reduce((sum, batch)=>sum + batch.userIds.length, 0);
|
|
1179
|
+
const sent = await sendAll(node, state, batches, message_keys/* .FLOW_SKIP_MESSAGES.NOTIFICATION */.CB.NOTIFICATION, recipients, async (batch)=>{
|
|
1180
|
+
try {
|
|
1181
|
+
await notifications.sendToMany(batch);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
throw sendFailure(message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_FAILED */.zr.NOTIFICATION_FAILED, node, error);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
if (typeof sent !== 'string') return sent;
|
|
1187
|
+
return {
|
|
1188
|
+
output: {
|
|
1189
|
+
status: sent,
|
|
1190
|
+
recipients,
|
|
1191
|
+
...counts
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
/** One email, read in the current scope (the item's, in many mode), or null when `to` has no address. */ async function prepareEmail(node, cfg, email, env, state) {
|
|
1196
|
+
const ctx = evaluationContext(state, env);
|
|
1197
|
+
const addresses = async (expression)=>expression === undefined ? [] : emailAddresses(node, await env.ruleEngine.resolveExpression(expression, ctx));
|
|
1198
|
+
const to = await addresses(cfg.to);
|
|
1199
|
+
if (!to.length) return null;
|
|
1200
|
+
// Someone already on `to` gets the email once.
|
|
1201
|
+
const listed = new Set(to.map((address)=>address.toLowerCase()));
|
|
1202
|
+
const notListed = (list)=>{
|
|
1203
|
+
const kept = list.filter((address)=>!listed.has(address.toLowerCase()));
|
|
1204
|
+
kept.forEach((address)=>listed.add(address.toLowerCase()));
|
|
1205
|
+
return kept;
|
|
1206
|
+
};
|
|
1207
|
+
const cc = notListed(await addresses(cfg.cc));
|
|
1208
|
+
const bcc = notListed(await addresses(cfg.bcc));
|
|
1209
|
+
const recipients = to.length + cc.length + bcc.length;
|
|
1210
|
+
assertRecipientCount(node, recipients, flow_types/* .FLOW_LIMITS.MAX_EMAIL_RECIPIENTS */.Or.MAX_EMAIL_RECIPIENTS);
|
|
1211
|
+
const replyTo = await addresses(cfg.replyTo);
|
|
1212
|
+
if (replyTo.length > 1) {
|
|
1213
|
+
throw new flow_errors/* .FlowHttpError */.hk(400, `"${replyTo.join(', ')}" is not a single email address.`, undefined, {
|
|
1214
|
+
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_ADDRESS_INVALID */.zr.EMAIL_ADDRESS_INVALID,
|
|
1215
|
+
messageVariables: {
|
|
1216
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node),
|
|
1217
|
+
address: shown(replyTo.join(', '))
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
const base = {
|
|
1222
|
+
to,
|
|
1223
|
+
...cc.length ? {
|
|
1224
|
+
cc
|
|
1225
|
+
} : {},
|
|
1226
|
+
...bcc.length ? {
|
|
1227
|
+
bcc
|
|
1228
|
+
} : {},
|
|
1229
|
+
...replyTo.length ? {
|
|
1230
|
+
replyTo: replyTo[0]
|
|
1231
|
+
} : {},
|
|
1232
|
+
...cfg.emailConfigId ? {
|
|
1233
|
+
emailConfigId: cfg.emailConfigId
|
|
1234
|
+
} : {}
|
|
1235
|
+
};
|
|
1236
|
+
const user = state.user;
|
|
1237
|
+
if (cfg.mode === 'template') {
|
|
1238
|
+
const variables = await mapValues(cfg.variables, env, state);
|
|
1239
|
+
const templateSlug = (cfg.templateSlug ?? '').trim();
|
|
1240
|
+
return {
|
|
1241
|
+
recipients,
|
|
1242
|
+
send: ()=>email.sendTemplate({
|
|
1243
|
+
...base,
|
|
1244
|
+
templateSlug,
|
|
1245
|
+
variables
|
|
1246
|
+
}, user)
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
// A line break in a subject would start a new mail header.
|
|
1250
|
+
const subject = asText(cfg.subject === undefined ? '' : await env.ruleEngine.resolveExpression(cfg.subject, ctx)).replace(/[\r\n]+/g, ' ').trim().slice(0, flow_types/* .FLOW_LIMITS.MAX_EMAIL_SUBJECT_LENGTH */.Or.MAX_EMAIL_SUBJECT_LENGTH);
|
|
1251
|
+
if (!subject) throw new flow_errors/* .FlowHttpError */.hk(400, `The subject of "${(0,flow_types/* .nodeLabel */.dA)(node)}" is empty.`, undefined, {
|
|
1252
|
+
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_SUBJECT_EMPTY */.zr.EMAIL_SUBJECT_EMPTY,
|
|
1253
|
+
messageVariables: {
|
|
1254
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node)
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
const html = cfg.format === 'html';
|
|
1258
|
+
const body = cfg.body === undefined ? '' : asText(html ? await env.ruleEngine.resolveHtml(cfg.body, ctx) : await env.ruleEngine.resolveExpression(cfg.body, ctx));
|
|
1259
|
+
if (!body.trim()) throw new flow_errors/* .FlowHttpError */.hk(400, `The message of "${(0,flow_types/* .nodeLabel */.dA)(node)}" is empty.`, undefined, {
|
|
1260
|
+
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_BODY_EMPTY */.zr.EMAIL_BODY_EMPTY,
|
|
1261
|
+
messageVariables: {
|
|
1262
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node)
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
return {
|
|
1266
|
+
recipients,
|
|
1267
|
+
send: ()=>email.send({
|
|
1268
|
+
...base,
|
|
1269
|
+
subject,
|
|
1270
|
+
...html ? {
|
|
1271
|
+
html: body
|
|
1272
|
+
} : {
|
|
1273
|
+
text: body
|
|
1274
|
+
}
|
|
1275
|
+
}, user)
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* one: `{ status, recipients, messageId }`. many: `{ status, count, recipients, skipped, messageIds }`
|
|
1280
|
+
* (one id per email sent, in order). `status` is `sent`, or `queued` inside a transaction (sent once it
|
|
1281
|
+
* commits, no ids yet). Every email is checked before the first goes out; no address at all skips the
|
|
1282
|
+
* step, and a test run checks everything but sends nothing.
|
|
1283
|
+
*/ async function runSendEmail(node, cfg, env, state) {
|
|
1284
|
+
const email = env.email;
|
|
1285
|
+
if (!email) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_UNAVAILABLE */.zr.EMAIL_UNAVAILABLE, {
|
|
1286
|
+
node: (0,flow_types/* .nodeLabel */.dA)(node)
|
|
1287
|
+
}, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" needs the email module, which this app does not run.`);
|
|
1288
|
+
const many = cfg.target === 'many';
|
|
1289
|
+
let emails;
|
|
1290
|
+
let skippedItems = 0;
|
|
1291
|
+
if (many) {
|
|
1292
|
+
const items = await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN, env, state);
|
|
1293
|
+
const prepared = await eachItem(state, items, ()=>prepareEmail(node, cfg, email, env, state));
|
|
1294
|
+
emails = prepared.filter((item)=>item !== null);
|
|
1295
|
+
skippedItems = prepared.length - emails.length;
|
|
1296
|
+
} else {
|
|
1297
|
+
emails = [
|
|
1298
|
+
await prepareEmail(node, cfg, email, env, state)
|
|
1299
|
+
].filter((prepared)=>prepared !== null);
|
|
1300
|
+
}
|
|
1301
|
+
if (!emails.length) return nobody(many ? {
|
|
1302
|
+
count: 0,
|
|
1303
|
+
skipped: skippedItems
|
|
1304
|
+
} : {});
|
|
1305
|
+
const recipients = emails.reduce((sum, prepared)=>sum + prepared.recipients, 0);
|
|
1306
|
+
const messageIds = [];
|
|
1307
|
+
const sent = await sendAll(node, state, emails, message_keys/* .FLOW_SKIP_MESSAGES.EMAIL */.CB.EMAIL, recipients, async (prepared)=>{
|
|
1308
|
+
let result;
|
|
1309
|
+
try {
|
|
1310
|
+
result = await prepared.send();
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
throw sendFailure(message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_FAILED */.zr.EMAIL_FAILED, node, error);
|
|
1313
|
+
}
|
|
1314
|
+
if (!result?.success) throw sendFailure(message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_FAILED */.zr.EMAIL_FAILED, node, result?.error || 'The email was not accepted.');
|
|
1315
|
+
messageIds.push(result.messageId ?? null);
|
|
1316
|
+
});
|
|
1317
|
+
if (typeof sent !== 'string') return sent;
|
|
1318
|
+
if (!many) return {
|
|
1319
|
+
output: {
|
|
1320
|
+
status: sent,
|
|
1321
|
+
recipients,
|
|
1322
|
+
messageId: messageIds[0] ?? null
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
return {
|
|
1326
|
+
output: {
|
|
1327
|
+
status: sent,
|
|
1328
|
+
count: emails.length,
|
|
1329
|
+
recipients,
|
|
1330
|
+
skipped: skippedItems,
|
|
1331
|
+
messageIds
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
900
1335
|
/** Runs one node. Ports/branching/looping are the executor's job; this only does the node's own work. */ async function runNode(node, env, state) {
|
|
901
1336
|
const ctx = evaluationContext(state, env);
|
|
902
1337
|
const c = node.config;
|
|
@@ -911,6 +1346,8 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
911
1346
|
return runCode(node, c, state);
|
|
912
1347
|
case 'permission_check':
|
|
913
1348
|
return runPermissionCheck(c, env, state);
|
|
1349
|
+
case 'company_branch_check':
|
|
1350
|
+
return runCompanyBranchCheck(c, env, state);
|
|
914
1351
|
case 'set':
|
|
915
1352
|
{
|
|
916
1353
|
const cfg = c;
|
|
@@ -967,7 +1404,6 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
967
1404
|
case 'entity_get':
|
|
968
1405
|
{
|
|
969
1406
|
const cfg = c;
|
|
970
|
-
await authorize(env, state, cfg.entityCode, 'read');
|
|
971
1407
|
const id = asId(await env.ruleEngine.resolveExpression(cfg.id, ctx));
|
|
972
1408
|
const service = await env.entityService(cfg.entityCode, state.qr);
|
|
973
1409
|
try {
|
|
@@ -984,7 +1420,6 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
984
1420
|
case 'entity_query':
|
|
985
1421
|
{
|
|
986
1422
|
const cfg = c;
|
|
987
|
-
await authorize(env, state, cfg.entityCode, 'read');
|
|
988
1423
|
const service = await env.entityService(cfg.entityCode, state.qr);
|
|
989
1424
|
const filter = await mapFilter(node, cfg.filter, env, state, 'skip');
|
|
990
1425
|
const search = cfg.search ? await env.ruleEngine.resolveExpression(cfg.search, ctx) : undefined;
|
|
@@ -1061,6 +1496,7 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
1061
1496
|
k,
|
|
1062
1497
|
String(v ?? '')
|
|
1063
1498
|
]));
|
|
1499
|
+
if (cfg.forwardAuth === true && state.authorization && !Object.keys(headers).some((name)=>name.toLowerCase() === 'authorization')) headers['authorization'] = state.authorization;
|
|
1064
1500
|
const body = cfg.body && Object.keys(cfg.body).length ? await mapValues(cfg.body, env, state) : undefined;
|
|
1065
1501
|
const remaining = state.deadline - Date.now();
|
|
1066
1502
|
if (remaining <= 0) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.OUT_OF_TIME */.zr.OUT_OF_TIME);
|
|
@@ -1099,6 +1535,10 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
1099
1535
|
}
|
|
1100
1536
|
};
|
|
1101
1537
|
}
|
|
1538
|
+
case 'send_notification':
|
|
1539
|
+
return runSendNotification(node, c, env, state);
|
|
1540
|
+
case 'send_email':
|
|
1541
|
+
return runSendEmail(node, c, env, state);
|
|
1102
1542
|
case 'respond':
|
|
1103
1543
|
{
|
|
1104
1544
|
const cfg = c;
|
|
@@ -1382,11 +1822,11 @@ function successResult(state, startedAt) {
|
|
|
1382
1822
|
/* import */ var _config_entity_builder_constants_js__rspack_import_8 = __webpack_require__(6576);
|
|
1383
1823
|
/* import */ var _config_message_keys_js__rspack_import_7 = __webpack_require__(5732);
|
|
1384
1824
|
/* import */ var _interfaces_generic_record_interface_js__rspack_import_1 = __webpack_require__(3372);
|
|
1385
|
-
/* import */ var _rule_engine_index_js__rspack_import_2 = __webpack_require__(
|
|
1825
|
+
/* import */ var _rule_engine_index_js__rspack_import_2 = __webpack_require__(8552);
|
|
1386
1826
|
/* import */ var _rule_engine_rule_ip_js__rspack_import_3 = __webpack_require__(809);
|
|
1387
1827
|
/* import */ var _rule_engine_rule_permission_js__rspack_import_4 = __webpack_require__(5191);
|
|
1388
|
-
/* import */ var
|
|
1389
|
-
/* import */ var
|
|
1828
|
+
/* import */ var _flow_types_js__rspack_import_5 = __webpack_require__(6256);
|
|
1829
|
+
/* import */ var _flow_api_key_js__rspack_import_6 = __webpack_require__(4816);
|
|
1390
1830
|
|
|
1391
1831
|
|
|
1392
1832
|
|
|
@@ -1530,7 +1970,14 @@ const DURATION_ARG = {
|
|
|
1530
1970
|
case 'lookup':
|
|
1531
1971
|
for (const item of Object.values(expression.filter ?? {})){
|
|
1532
1972
|
const value = (0,_interfaces_generic_record_interface_js__rspack_import_1/* .isRecordFilterCondition */.t1)(item) ? item.value : item;
|
|
1533
|
-
|
|
1973
|
+
for (const operand of (0,_interfaces_generic_record_interface_js__rspack_import_1/* .isFilterRange */.iA)(value) ? [
|
|
1974
|
+
value.from,
|
|
1975
|
+
value.to
|
|
1976
|
+
] : [
|
|
1977
|
+
value
|
|
1978
|
+
]){
|
|
1979
|
+
if (isExpression(operand)) collectExpressionRefs(operand, out);
|
|
1980
|
+
}
|
|
1534
1981
|
}
|
|
1535
1982
|
break;
|
|
1536
1983
|
}
|
|
@@ -1651,7 +2098,7 @@ function functionProblem(value, depth, options) {
|
|
|
1651
2098
|
return null;
|
|
1652
2099
|
}
|
|
1653
2100
|
function patternProblem(pattern) {
|
|
1654
|
-
switch((0,
|
|
2101
|
+
switch((0,_flusys_nestjs_shared_utils__rspack_import_0.safeRegexProblem)(pattern)){
|
|
1655
2102
|
case 'too_long':
|
|
1656
2103
|
return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.REGEX_TOO_LONG */.IP.REGEX_TOO_LONG, {
|
|
1657
2104
|
max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_REGEX_PATTERN_LENGTH */.bw.MAX_REGEX_PATTERN_LENGTH
|
|
@@ -1694,11 +2141,16 @@ function lookupProblem(value, depth, options) {
|
|
|
1694
2141
|
});
|
|
1695
2142
|
return expressionProblem(item, depth + 1, options);
|
|
1696
2143
|
}
|
|
1697
|
-
|
|
2144
|
+
const kind = _interfaces_generic_record_interface_js__rspack_import_1/* .FILTER_OP_VALUE_KIND */.w9[item.op];
|
|
2145
|
+
if (kind === 'none') return null;
|
|
1698
2146
|
if (item.value === undefined) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.FILTER_VALUE_REQUIRED */.IP.FILTER_VALUE_REQUIRED, {
|
|
1699
2147
|
op: item.op
|
|
1700
2148
|
});
|
|
1701
|
-
return expressionProblem(item.value, depth + 1, options);
|
|
2149
|
+
if (kind !== 'range') return expressionProblem(item.value, depth + 1, options);
|
|
2150
|
+
if (!(0,_interfaces_generic_record_interface_js__rspack_import_1/* .isFilterRange */.iA)(item.value)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.FILTER_RANGE_INVALID */.IP.FILTER_RANGE_INVALID, {
|
|
2151
|
+
op: item.op
|
|
2152
|
+
});
|
|
2153
|
+
return expressionProblem(item.value.from, depth + 1, options) ?? expressionProblem(item.value.to, depth + 1, options);
|
|
1702
2154
|
}
|
|
1703
2155
|
function firstProblem(items, depth, options) {
|
|
1704
2156
|
for (const item of items){
|
|
@@ -1744,39 +2196,29 @@ function conditionProblem(node, options) {
|
|
|
1744
2196
|
/**
|
|
1745
2197
|
* Save-time check of a whole flow: structure (Request steps with distinct URLs, valid ids/ports, no cycles),
|
|
1746
2198
|
* every node's configuration, every reference to another node, and the combination of
|
|
1747
|
-
* settings (
|
|
2199
|
+
* settings (auth / transaction). Pure: no I/O, so the same result is what the
|
|
1748
2200
|
* designer shows and what the server enforces.
|
|
1749
2201
|
*/ function validateFlow(flow, context = {}) {
|
|
1750
2202
|
const errors = [];
|
|
1751
2203
|
const warnings = [];
|
|
1752
2204
|
const err = (message)=>errors.push(message);
|
|
1753
|
-
if (!
|
|
1754
|
-
if (!_flow_types_js__rspack_import_6/* .FLOW_RUN_AS.includes */.gl.includes(flow.runAs)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.RUN_AS_INVALID */.qj.RUN_AS_INVALID, {
|
|
1755
|
-
runAs: String(flow.runAs)
|
|
1756
|
-
}));
|
|
1757
|
-
if (!_flow_types_js__rspack_import_6/* .FLOW_AUTH_MODES.includes */.fO.includes(flow.authMode)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.AUTH_MODE_INVALID */.qj.AUTH_MODE_INVALID, {
|
|
1758
|
-
authMode: String(flow.authMode)
|
|
1759
|
-
}));
|
|
1760
|
-
if (flow.runAs === 'caller' && flow.authMode !== 'jwt' && flow.authMode !== 'permission' && flow.authMode !== 'internal') {
|
|
1761
|
-
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.RUN_AS_CALLER_NEEDS_AUTH */.qj.RUN_AS_CALLER_NEEDS_AUTH));
|
|
1762
|
-
}
|
|
1763
|
-
validateInputSchema(flow, err);
|
|
2205
|
+
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));
|
|
1764
2206
|
const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
|
|
1765
2207
|
const edges = Array.isArray(flow.edges) ? flow.edges : [];
|
|
1766
2208
|
if (!nodes.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NO_NODES */.qj.NO_NODES));
|
|
1767
|
-
if (nodes.length >
|
|
1768
|
-
max:
|
|
2209
|
+
if (nodes.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_NODES */.qj.TOO_MANY_NODES, {
|
|
2210
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES
|
|
1769
2211
|
}));
|
|
1770
|
-
if (edges.length >
|
|
1771
|
-
max:
|
|
2212
|
+
if (edges.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_EDGES */.qj.TOO_MANY_EDGES, {
|
|
2213
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES
|
|
1772
2214
|
}));
|
|
1773
|
-
if (errors.length && (!nodes.length || nodes.length >
|
|
2215
|
+
if (errors.length && (!nodes.length || nodes.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES || edges.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES)) return {
|
|
1774
2216
|
errors,
|
|
1775
2217
|
warnings
|
|
1776
2218
|
};
|
|
1777
2219
|
const byId = new Map();
|
|
1778
2220
|
for (const node of nodes){
|
|
1779
|
-
if (!
|
|
2221
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_NODE_ID_PATTERN.test */.z6.test(node?.id ?? '')) {
|
|
1780
2222
|
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, {
|
|
1781
2223
|
id: node?.id
|
|
1782
2224
|
}));
|
|
@@ -1786,7 +2228,7 @@ function conditionProblem(node, options) {
|
|
|
1786
2228
|
id: node.id
|
|
1787
2229
|
}));
|
|
1788
2230
|
byId.set(node.id, node);
|
|
1789
|
-
if (!
|
|
2231
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_NODE_TYPES.includes */.q2.includes(node.type)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_TYPE_UNKNOWN */.qj.NODE_TYPE_UNKNOWN, {
|
|
1790
2232
|
id: node.id,
|
|
1791
2233
|
type: node.type
|
|
1792
2234
|
}));
|
|
@@ -1816,7 +2258,7 @@ function conditionProblem(node, options) {
|
|
|
1816
2258
|
id: edge.from
|
|
1817
2259
|
}));
|
|
1818
2260
|
if (to.type === 'trigger') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_INTO_TRIGGER */.qj.EDGE_INTO_TRIGGER));
|
|
1819
|
-
if (!(0,
|
|
2261
|
+
if (!(0,_flow_types_js__rspack_import_5/* .portsOf */.a5)(from).includes(edge.port)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_NO_PORT */.qj.EDGE_NO_PORT, {
|
|
1820
2262
|
id: from.id,
|
|
1821
2263
|
port: edge.port
|
|
1822
2264
|
}));
|
|
@@ -1845,36 +2287,86 @@ function conditionProblem(node, options) {
|
|
|
1845
2287
|
])) : null;
|
|
1846
2288
|
for (const node of nodes){
|
|
1847
2289
|
if (reachable && !reachable.has(node.id)) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_UNREACHABLE */.qj.NODE_UNREACHABLE, {
|
|
1848
|
-
node: (0,
|
|
2290
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
|
|
1849
2291
|
}));
|
|
1850
2292
|
}
|
|
1851
2293
|
checkNodeReferences(nodes, byId, flow, err, warnings);
|
|
1852
2294
|
if (!cyclic && reachable) checkReferenceTiming(nodes, edges, byId, reachable, warnings);
|
|
1853
2295
|
if (!cyclic) checkLoopDepth(nodes, outgoing, err);
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2296
|
+
// Transaction and access are a Request step's own: each is checked against the steps its runs reach.
|
|
2297
|
+
const runs = (0,_flow_types_js__rspack_import_5/* .flowEndpoints */.Xr)(flow).map((endpoint)=>({
|
|
2298
|
+
endpoint,
|
|
2299
|
+
label: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(byId.get(endpoint.nodeId) ?? {
|
|
2300
|
+
id: endpoint.nodeId,
|
|
2301
|
+
name: ''
|
|
2302
|
+
}),
|
|
2303
|
+
steps: [
|
|
2304
|
+
...reach(endpoint.nodeId, outgoing)
|
|
2305
|
+
].map((id)=>byId.get(id)).filter((node)=>!!node)
|
|
2306
|
+
}));
|
|
2307
|
+
checkEndpointRuns(runs, warnings);
|
|
2308
|
+
checkTransactionUse(flow, runs, outgoing, context, warnings);
|
|
1864
2309
|
return {
|
|
1865
2310
|
errors,
|
|
1866
2311
|
warnings
|
|
1867
2312
|
};
|
|
1868
2313
|
}
|
|
2314
|
+
/** The entities a step reads or writes: its own, its child inserts' and those its lookups read. */ function entitiesOf(node) {
|
|
2315
|
+
const codes = collectLookupEntities(node.config);
|
|
2316
|
+
if (node.type.startsWith('entity_') && typeof node.config?.['entityCode'] === 'string') codes.add(node.config['entityCode']);
|
|
2317
|
+
const children = node.type === 'entity_insert' && Array.isArray(node.config?.['children']) ? node.config['children'] : [];
|
|
2318
|
+
for (const child of children)if (typeof child?.entityCode === 'string') codes.add(child.entityCode);
|
|
2319
|
+
return [
|
|
2320
|
+
...codes
|
|
2321
|
+
];
|
|
2322
|
+
}
|
|
2323
|
+
/** What depends on a Request step's access and transaction, for the steps its runs reach. A finding shared by several Request steps is reported once. */ function checkEndpointRuns(runs, warnings) {
|
|
2324
|
+
const reported = new Set();
|
|
2325
|
+
const warnOnce = (message)=>{
|
|
2326
|
+
const key = JSON.stringify(message);
|
|
2327
|
+
if (reported.has(key)) return;
|
|
2328
|
+
reported.add(key);
|
|
2329
|
+
warnings.push(message);
|
|
2330
|
+
};
|
|
2331
|
+
const keyless = {
|
|
2332
|
+
endpoints: [],
|
|
2333
|
+
entities: new Set()
|
|
2334
|
+
};
|
|
2335
|
+
for (const { endpoint, label, steps } of runs){
|
|
2336
|
+
if (endpoint.isTransactional) {
|
|
2337
|
+
if (steps.some((n)=>n.type === 'http_request')) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRANSACTIONAL_HTTP */.qj.TRANSACTIONAL_HTTP, {
|
|
2338
|
+
node: label
|
|
2339
|
+
}));
|
|
2340
|
+
// Inside the run's transaction a message is only sent once it commits: a failed send is logged, it cannot take the error path.
|
|
2341
|
+
for (const n of steps)if ((0,_flow_types_js__rspack_import_5/* .isMessageNode */.SE)(n.type) && n.onError === 'continue') warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.MESSAGE_AFTER_COMMIT */.qj.MESSAGE_AFTER_COMMIT, {
|
|
2342
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(n)
|
|
2343
|
+
}));
|
|
2344
|
+
}
|
|
2345
|
+
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));
|
|
2346
|
+
// 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
|
+
if (!_flow_types_js__rspack_import_5/* .KEYLESS_AUTH_MODES.has */.d7.has(endpoint.authMode)) continue;
|
|
2348
|
+
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));
|
|
2349
|
+
const entities = steps.flatMap(entitiesOf);
|
|
2350
|
+
if (!entities.length) continue;
|
|
2351
|
+
keyless.endpoints.push(endpoint.path ? `/${endpoint.path}` : '/');
|
|
2352
|
+
for (const code of entities)keyless.entities.add(code);
|
|
2353
|
+
}
|
|
2354
|
+
if (keyless.endpoints.length) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.KEYLESS_ENTITY */.qj.KEYLESS_ENTITY, {
|
|
2355
|
+
endpoints: keyless.endpoints.join(', '),
|
|
2356
|
+
entities: [
|
|
2357
|
+
...keyless.entities
|
|
2358
|
+
].join(', ')
|
|
2359
|
+
}));
|
|
2360
|
+
}
|
|
1869
2361
|
/** At least one Request step; at most one on the flow's own URL, the others on distinct paths. */ function checkTriggers(triggers, err) {
|
|
1870
2362
|
if (!triggers.length) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_MISSING */.qj.TRIGGER_MISSING));
|
|
1871
|
-
const main = triggers.filter((trigger)=>!(0,
|
|
2363
|
+
const main = triggers.filter((trigger)=>!(0,_flow_types_js__rspack_import_5/* .triggerPath */.um)(trigger));
|
|
1872
2364
|
if (main.length > 1) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_MAIN_DUPLICATE */.qj.TRIGGER_MAIN_DUPLICATE, {
|
|
1873
2365
|
count: main.length
|
|
1874
2366
|
}));
|
|
1875
2367
|
const paths = new Set();
|
|
1876
2368
|
for (const trigger of triggers){
|
|
1877
|
-
const path = (0,
|
|
2369
|
+
const path = (0,_flow_types_js__rspack_import_5/* .triggerPath */.um)(trigger);
|
|
1878
2370
|
if (!path) continue;
|
|
1879
2371
|
if (paths.has(path)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_DUPLICATE */.qj.TRIGGER_PATH_DUPLICATE, {
|
|
1880
2372
|
path
|
|
@@ -1883,23 +2375,24 @@ function conditionProblem(node, options) {
|
|
|
1883
2375
|
}
|
|
1884
2376
|
}
|
|
1885
2377
|
/**
|
|
1886
|
-
* What the runtime would always refuse, caught on save: an API-key flow
|
|
1887
|
-
*
|
|
2378
|
+
* What the runtime would always refuse, caught on save: an API-key URL (a flow has no key to send),
|
|
2379
|
+
* a required input left out, a chain that comes back to this flow or nests deeper
|
|
1888
2380
|
* than the limit. An inactive flow and an input the called flow does not declare are warnings.
|
|
1889
|
-
*/ function checkCalledFlow(
|
|
2381
|
+
*/ function checkCalledFlow(node, flow, known, err, warn) {
|
|
2382
|
+
const { flowSlug: slug, endpoint, input, inputList } = node.config;
|
|
2383
|
+
const at = {
|
|
2384
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
|
|
2385
|
+
};
|
|
1890
2386
|
const target = known.get(slug);
|
|
1891
2387
|
const vars = {
|
|
1892
2388
|
...at,
|
|
1893
2389
|
flow: slug
|
|
1894
2390
|
};
|
|
1895
2391
|
if (!target.isActive) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INACTIVE */.qj.NODE_FLOW_INACTIVE, vars));
|
|
1896
|
-
if (target.authMode === 'api_key') {
|
|
1897
|
-
// A function runs as whoever calls it, so it fails only when a flow running as the caller does.
|
|
1898
|
-
if (flow.authMode === 'internal') warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_API_KEY */.qj.NODE_FLOW_API_KEY, vars));
|
|
1899
|
-
else if (flow.runAs === 'caller') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_API_KEY */.qj.NODE_FLOW_API_KEY, vars));
|
|
1900
|
-
}
|
|
1901
2392
|
const path = typeof endpoint === 'string' && endpoint ? endpoint : null;
|
|
1902
2393
|
const entry = target.endpoints.find((e)=>e.path === path);
|
|
2394
|
+
// A flow has no key to send, so an API-key URL is never callable from one.
|
|
2395
|
+
if (entry?.authMode === 'api_key') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_API_KEY */.qj.NODE_FLOW_API_KEY, vars));
|
|
1903
2396
|
if (!entry) {
|
|
1904
2397
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(path ? _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_ENDPOINT_UNKNOWN */.qj.NODE_FLOW_ENDPOINT_UNKNOWN : _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_ENDPOINT_REQUIRED */.qj.NODE_FLOW_ENDPOINT_REQUIRED, {
|
|
1905
2398
|
...vars,
|
|
@@ -1920,11 +2413,40 @@ function conditionProblem(node, options) {
|
|
|
1920
2413
|
...loop
|
|
1921
2414
|
].join(' > ')
|
|
1922
2415
|
}));
|
|
1923
|
-
if (1 + callDepth(slug, known, new Set()) >
|
|
2416
|
+
if (1 + callDepth(slug, known, new Set()) > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_TOO_DEEP */.qj.NODE_FLOW_TOO_DEEP, {
|
|
1924
2417
|
...at,
|
|
1925
|
-
max:
|
|
2418
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH
|
|
1926
2419
|
}));
|
|
1927
2420
|
}
|
|
2421
|
+
/**
|
|
2422
|
+
* What the Call flow steps of `caller` whose call chain reaches `slug` (that flow itself, or one
|
|
2423
|
+
* that calls it, however deep) would hit against the flows in `known`: the save errors of those
|
|
2424
|
+
* steps, and the flow they run being off (a warning on save, a failure at run).
|
|
2425
|
+
*/ function callProblems(caller, slug, known) {
|
|
2426
|
+
const problems = [];
|
|
2427
|
+
for (const node of Array.isArray(caller.nodes) ? caller.nodes : []){
|
|
2428
|
+
const c = node?.config ?? {};
|
|
2429
|
+
if (node?.type !== 'call_flow' || typeof c.flowSlug !== 'string') continue;
|
|
2430
|
+
if (c.flowSlug !== slug && !callPathTo(c.flowSlug, slug, known, new Set())) continue;
|
|
2431
|
+
const at = {
|
|
2432
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
|
|
2433
|
+
};
|
|
2434
|
+
const target = known.get(c.flowSlug);
|
|
2435
|
+
if (!target) {
|
|
2436
|
+
problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_UNKNOWN */.qj.NODE_FLOW_UNKNOWN, {
|
|
2437
|
+
...at,
|
|
2438
|
+
flow: c.flowSlug
|
|
2439
|
+
}));
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
if (!target.isActive) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INACTIVE */.qj.NODE_FLOW_INACTIVE, {
|
|
2443
|
+
...at,
|
|
2444
|
+
flow: c.flowSlug
|
|
2445
|
+
}));
|
|
2446
|
+
checkCalledFlow(node, caller, known, (m)=>problems.push(m), []);
|
|
2447
|
+
}
|
|
2448
|
+
return problems;
|
|
2449
|
+
}
|
|
1928
2450
|
/** The named inputs sent to a flow whose body is an object: every required one given, the rest declared. */ function checkCalledInputs(input, schema, vars, err, warn) {
|
|
1929
2451
|
const given = input && typeof input === 'object' && !Array.isArray(input) ? Object.keys(input) : [];
|
|
1930
2452
|
if (schema.length) {
|
|
@@ -1959,39 +2481,33 @@ function conditionProblem(node, options) {
|
|
|
1959
2481
|
return null;
|
|
1960
2482
|
}
|
|
1961
2483
|
/** How many calls deep the longest chain starting inside `slug` goes, counted only as far as the limit; a chain that loops is cut where it repeats. */ function callDepth(slug, known, running) {
|
|
1962
|
-
if (running.has(slug) || running.size >
|
|
2484
|
+
if (running.has(slug) || running.size > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH) return 0;
|
|
1963
2485
|
running.add(slug);
|
|
1964
2486
|
let deepest = 0;
|
|
1965
2487
|
for (const next of new Set(known.get(slug)?.calls ?? []))deepest = Math.max(deepest, 1 + callDepth(next, known, running));
|
|
1966
2488
|
running.delete(slug);
|
|
1967
2489
|
return deepest;
|
|
1968
2490
|
}
|
|
1969
|
-
function validateInputSchema(flow, err) {
|
|
1970
|
-
if (flow.bodyType !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_BODY_TYPES.includes */.Y3.includes(flow.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, {
|
|
1971
|
-
type: String(flow.bodyType)
|
|
1972
|
-
}));
|
|
1973
|
-
validateInputFields(flow.inputSchema ?? [], '', 1, err);
|
|
1974
|
-
}
|
|
1975
2491
|
/** One level of the request body: the top-level fields, or the fields of an object / list item at `prefix`. */ function validateInputFields(fields, prefix, depth, err) {
|
|
1976
|
-
if (fields.length >
|
|
1977
|
-
max:
|
|
2492
|
+
if (fields.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_INPUT_FIELDS */.Or.MAX_INPUT_FIELDS) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_TOO_MANY */.qj.INPUT_TOO_MANY, {
|
|
2493
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_INPUT_FIELDS */.Or.MAX_INPUT_FIELDS
|
|
1978
2494
|
}));
|
|
1979
2495
|
const seen = new Set();
|
|
1980
2496
|
for (const field of fields){
|
|
1981
2497
|
const name = `${prefix}${field?.name ?? ''}`;
|
|
1982
|
-
if (!
|
|
2498
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_INPUT_NAME_PATTERN.test */.fZ.test(field?.name ?? '')) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_NAME_INVALID */.qj.INPUT_NAME_INVALID, {
|
|
1983
2499
|
name
|
|
1984
2500
|
}));
|
|
1985
2501
|
else if (seen.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_DUPLICATE */.qj.INPUT_NAME_DUPLICATE, {
|
|
1986
2502
|
name
|
|
1987
2503
|
}));
|
|
1988
2504
|
seen.add(field?.name);
|
|
1989
|
-
if (!
|
|
2505
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_INPUT_TYPES.includes */.Q7.includes(field?.type)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_TYPE_UNKNOWN */.qj.INPUT_TYPE_UNKNOWN, {
|
|
1990
2506
|
name,
|
|
1991
2507
|
type: field?.type
|
|
1992
2508
|
}));
|
|
1993
2509
|
const itemType = field?.type === 'array' ? field.itemType : undefined;
|
|
1994
|
-
if (itemType !== undefined && (itemType === 'array' || !
|
|
2510
|
+
if (itemType !== undefined && (itemType === 'array' || !_flow_types_js__rspack_import_5/* .FLOW_INPUT_TYPES.includes */.Q7.includes(itemType))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_ITEM_TYPE_UNKNOWN */.qj.INPUT_ITEM_TYPE_UNKNOWN, {
|
|
1995
2511
|
name,
|
|
1996
2512
|
type: itemType
|
|
1997
2513
|
}));
|
|
@@ -2000,10 +2516,10 @@ function validateInputSchema(flow, err) {
|
|
|
2000
2516
|
}));
|
|
2001
2517
|
const holdsFields = field?.type === 'object' || itemType === 'object';
|
|
2002
2518
|
if (!holdsFields || !Array.isArray(field.fields) || !field.fields.length) continue;
|
|
2003
|
-
if (depth >=
|
|
2519
|
+
if (depth >= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_INPUT_DEPTH */.Or.MAX_INPUT_DEPTH) {
|
|
2004
2520
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_TOO_DEEP */.qj.INPUT_TOO_DEEP, {
|
|
2005
2521
|
name,
|
|
2006
|
-
max:
|
|
2522
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_INPUT_DEPTH */.Or.MAX_INPUT_DEPTH
|
|
2007
2523
|
}));
|
|
2008
2524
|
continue;
|
|
2009
2525
|
}
|
|
@@ -2012,7 +2528,7 @@ function validateInputSchema(flow, err) {
|
|
|
2012
2528
|
}
|
|
2013
2529
|
function validateNodeConfig(node, flow, context, err, warn) {
|
|
2014
2530
|
const at = {
|
|
2015
|
-
node: (0,
|
|
2531
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
|
|
2016
2532
|
};
|
|
2017
2533
|
const c = node.config ?? {};
|
|
2018
2534
|
const shape = {
|
|
@@ -2100,18 +2616,59 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2100
2616
|
response: {
|
|
2101
2617
|
list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.RESPONSE_VALUES */.bc.RESPONSE_VALUES,
|
|
2102
2618
|
item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.RESPONSE_VALUE */.bc.RESPONSE_VALUE
|
|
2619
|
+
},
|
|
2620
|
+
data: {
|
|
2621
|
+
list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.DATA_VALUES */.bc.DATA_VALUES,
|
|
2622
|
+
item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.DATA_VALUE */.bc.DATA_VALUE
|
|
2623
|
+
},
|
|
2624
|
+
template: {
|
|
2625
|
+
list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.TEMPLATE_VALUES */.bc.TEMPLATE_VALUES,
|
|
2626
|
+
item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.TEMPLATE_VALUE */.bc.TEMPLATE_VALUE
|
|
2103
2627
|
}
|
|
2104
2628
|
};
|
|
2629
|
+
const optionalExpr = (value, what)=>{
|
|
2630
|
+
if (value !== undefined) expr(value, what);
|
|
2631
|
+
};
|
|
2632
|
+
/** An AND / OR rule of permission codes: one that names none needs a permission, a malformed one is `invalid`. */ const permissionRule = (value, invalid)=>{
|
|
2633
|
+
const problem = (0,_flusys_nestjs_shared_utils__rspack_import_0.permissionLogicProblem)(value);
|
|
2634
|
+
if (problem === 'empty') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_REQUIRED */.qj.NODE_PERMISSION_REQUIRED, at));
|
|
2635
|
+
else if (problem === 'too_many') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .RULE_ENGINE_MESSAGES.TOO_MANY_PERMISSION_CODES */.x3.TOO_MANY_PERMISSION_CODES, {
|
|
2636
|
+
max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_PERMISSION_CODES */.bw.MAX_PERMISSION_CODES
|
|
2637
|
+
}));
|
|
2638
|
+
else if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(invalid, at));
|
|
2639
|
+
};
|
|
2640
|
+
const serviceRunning = (service, unavailable)=>{
|
|
2641
|
+
if (context.messageServices && !context.messageServices[service]) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(unavailable, at));
|
|
2642
|
+
};
|
|
2105
2643
|
switch(node.type){
|
|
2106
2644
|
case 'trigger':
|
|
2107
2645
|
{
|
|
2108
|
-
if (c.path !== undefined && c.path !== '' && (typeof c.path !== 'string' || !
|
|
2109
|
-
|
|
2110
|
-
if (
|
|
2111
|
-
if (c.
|
|
2646
|
+
if (c.path !== undefined && c.path !== '' && (typeof c.path !== 'string' || !_flow_types_js__rspack_import_5/* .FLOW_ENDPOINT_PATH_PATTERN.test */.YO.test(c.path))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_INVALID */.qj.TRIGGER_PATH_INVALID, at));
|
|
2647
|
+
if (c.authMode !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_AUTH_MODES.includes */.fO.includes(c.authMode)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_AUTH_INVALID */.qj.TRIGGER_AUTH_INVALID, at));
|
|
2648
|
+
if (c.permissions !== undefined) permissionRule(c.permissions, _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_AUTH_INVALID */.qj.TRIGGER_AUTH_INVALID);
|
|
2649
|
+
if (c.isTransactional !== undefined && typeof c.isTransactional !== 'boolean') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_TRANSACTION_INVALID */.qj.TRIGGER_TRANSACTION_INVALID, at));
|
|
2650
|
+
if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MIN_TIMEOUT_MS */.Or.MIN_TIMEOUT_MS && c.timeoutMs <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_TIMEOUT_MS */.Or.MAX_TIMEOUT_MS)) {
|
|
2651
|
+
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_TIMEOUT_RANGE */.qj.TRIGGER_TIMEOUT_RANGE, {
|
|
2652
|
+
...at,
|
|
2653
|
+
min: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MIN_TIMEOUT_MS */.Or.MIN_TIMEOUT_MS / 1000,
|
|
2654
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_TIMEOUT_MS */.Or.MAX_TIMEOUT_MS / 1000
|
|
2655
|
+
}));
|
|
2656
|
+
}
|
|
2657
|
+
if (c.rateLimitPerMinute !== undefined && !(Number.isInteger(c.rateLimitPerMinute) && c.rateLimitPerMinute >= 0 && c.rateLimitPerMinute <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_RATE_LIMIT_PER_MINUTE */.Or.MAX_RATE_LIMIT_PER_MINUTE)) {
|
|
2658
|
+
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_RATE_LIMIT_RANGE */.qj.TRIGGER_RATE_LIMIT_RANGE, {
|
|
2659
|
+
...at,
|
|
2660
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_RATE_LIMIT_PER_MINUTE */.Or.MAX_RATE_LIMIT_PER_MINUTE
|
|
2661
|
+
}));
|
|
2662
|
+
}
|
|
2663
|
+
// Without a key an API-key URL could never be called; any other access keeps none.
|
|
2664
|
+
if (c.authMode === 'api_key' && c.apiKey === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_API_KEY_REQUIRED */.qj.TRIGGER_API_KEY_REQUIRED, at));
|
|
2665
|
+
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
|
+
// Field errors are named after the URL (`<path>: <field>`) once a flow has several.
|
|
2667
|
+
const path = (0,_flow_types_js__rspack_import_5/* .triggerPath */.um)(node);
|
|
2668
|
+
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, {
|
|
2112
2669
|
type: String(c.bodyType)
|
|
2113
2670
|
}));
|
|
2114
|
-
validateInputFields(Array.isArray(c.inputSchema) ? c.inputSchema : [], `${path}:
|
|
2671
|
+
validateInputFields(Array.isArray(c.inputSchema) ? c.inputSchema : [], path ? `${path}: ` : '', 1, err);
|
|
2115
2672
|
break;
|
|
2116
2673
|
}
|
|
2117
2674
|
case 'validate':
|
|
@@ -2123,16 +2680,39 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2123
2680
|
break;
|
|
2124
2681
|
case 'permission_check':
|
|
2125
2682
|
{
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2683
|
+
if (c.permissions === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_REQUIRED */.qj.NODE_PERMISSION_REQUIRED, at));
|
|
2684
|
+
else permissionRule(c.permissions, _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID);
|
|
2685
|
+
if (c.subject !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_ACCESS_SUBJECTS.includes */.iM.includes(c.subject)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2686
|
+
if (c.subject === 'user') {
|
|
2687
|
+
if (c.userId === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_ACCESS_USER_REQUIRED */.qj.NODE_ACCESS_USER_REQUIRED, at));
|
|
2688
|
+
else expr(c.userId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_USER */.bc.THE_USER));
|
|
2689
|
+
}
|
|
2690
|
+
if (c.companyId !== undefined) expr(c.companyId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_COMPANY */.bc.THE_COMPANY));
|
|
2691
|
+
if (c.branchId !== undefined) expr(c.branchId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_BRANCH */.bc.THE_BRANCH));
|
|
2692
|
+
if (c.onDenied !== undefined && c.onDenied !== 'reject' && c.onDenied !== 'branch') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2693
|
+
if (c.message !== undefined && typeof c.message !== 'string') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2694
|
+
if (c.statusCode !== undefined && !(Number.isInteger(c.statusCode) && c.statusCode >= 400 && c.statusCode <= 499)) {
|
|
2695
|
+
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_4XX */.qj.NODE_STATUS_4XX, at));
|
|
2696
|
+
}
|
|
2697
|
+
break;
|
|
2698
|
+
}
|
|
2699
|
+
case 'company_branch_check':
|
|
2700
|
+
{
|
|
2701
|
+
if (c.subject !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_ACCESS_SUBJECTS.includes */.iM.includes(c.subject)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2702
|
+
if (c.branchMatch !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_BRANCH_MATCHES.includes */.Et.includes(c.branchMatch)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2703
|
+
if (c.subject === 'user') {
|
|
2704
|
+
if (c.userId === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_ACCESS_USER_REQUIRED */.qj.NODE_ACCESS_USER_REQUIRED, at));
|
|
2705
|
+
else expr(c.userId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_USER */.bc.THE_USER));
|
|
2706
|
+
}
|
|
2707
|
+
// Another user has no current company.
|
|
2708
|
+
if (c.branchCompany !== undefined && (!_flow_types_js__rspack_import_5/* .FLOW_BRANCH_COMPANIES.includes */.sk.includes(c.branchCompany) || c.branchCompany === 'current' && c.subject === 'user')) {
|
|
2709
|
+
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2132
2710
|
}
|
|
2133
|
-
if (c.
|
|
2711
|
+
if (c.companyIds !== undefined) expr(c.companyIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
|
|
2712
|
+
if (c.branchIds !== undefined) expr(c.branchIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
|
|
2134
2713
|
if (c.onDenied !== undefined && c.onDenied !== 'reject' && c.onDenied !== 'branch') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2135
|
-
|
|
2714
|
+
// Without an id to check there is nothing to allow or deny.
|
|
2715
|
+
if (c.onDenied === 'branch' && c.companyIds === undefined && c.branchIds === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_ACCESS_TARGET_REQUIRED */.qj.NODE_ACCESS_TARGET_REQUIRED, at));
|
|
2136
2716
|
if (c.message !== undefined && typeof c.message !== 'string') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
|
|
2137
2717
|
if (c.statusCode !== undefined && !(Number.isInteger(c.statusCode) && c.statusCode >= 400 && c.statusCode <= 499)) {
|
|
2138
2718
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_4XX */.qj.NODE_STATUS_4XX, at));
|
|
@@ -2143,7 +2723,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2143
2723
|
exprMap(c.values, KIND.variable);
|
|
2144
2724
|
if (!c.values || !Object.keys(c.values).length) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_SETS_NOTHING */.qj.NODE_SETS_NOTHING, at));
|
|
2145
2725
|
for (const key of Object.keys(c.values ?? {})){
|
|
2146
|
-
if (!
|
|
2726
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_INPUT_NAME_PATTERN.test */.fZ.test(key)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_VARIABLE_INVALID */.qj.NODE_VARIABLE_INVALID, {
|
|
2147
2727
|
...at,
|
|
2148
2728
|
name: key
|
|
2149
2729
|
}));
|
|
@@ -2174,10 +2754,10 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2174
2754
|
case 'foreach':
|
|
2175
2755
|
expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
|
|
2176
2756
|
if (c.collect !== undefined) expr(c.collect, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_COLLECTED_VALUE */.bc.THE_COLLECTED_VALUE));
|
|
2177
|
-
if (c.maxItems !== undefined && !(Number.isInteger(c.maxItems) && c.maxItems >= 1 && c.maxItems <=
|
|
2757
|
+
if (c.maxItems !== undefined && !(Number.isInteger(c.maxItems) && c.maxItems >= 1 && c.maxItems <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_FOREACH_ITEMS */.Or.MAX_FOREACH_ITEMS)) {
|
|
2178
2758
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_MAX_ITEMS_RANGE */.qj.NODE_MAX_ITEMS_RANGE, {
|
|
2179
2759
|
...at,
|
|
2180
|
-
max:
|
|
2760
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_FOREACH_ITEMS */.Or.MAX_FOREACH_ITEMS
|
|
2181
2761
|
}));
|
|
2182
2762
|
}
|
|
2183
2763
|
break;
|
|
@@ -2202,7 +2782,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2202
2782
|
break;
|
|
2203
2783
|
case 'entity_update':
|
|
2204
2784
|
entity(c.entityCode);
|
|
2205
|
-
writeTargetConfig(c,
|
|
2785
|
+
writeTargetConfig(c, _flow_types_js__rspack_import_5/* .RECORD_WRITE_TARGETS */.Dn, [
|
|
2206
2786
|
'error',
|
|
2207
2787
|
'skip',
|
|
2208
2788
|
'insert'
|
|
@@ -2216,7 +2796,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2216
2796
|
break;
|
|
2217
2797
|
case 'entity_delete':
|
|
2218
2798
|
entity(c.entityCode);
|
|
2219
|
-
writeTargetConfig(c,
|
|
2799
|
+
writeTargetConfig(c, _flow_types_js__rspack_import_5/* .RECORD_WRITE_TARGETS */.Dn, [
|
|
2220
2800
|
'error',
|
|
2221
2801
|
'skip'
|
|
2222
2802
|
], {
|
|
@@ -2234,22 +2814,26 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2234
2814
|
entity(c.entityCode);
|
|
2235
2815
|
filterMap(c.filter, KIND.filter);
|
|
2236
2816
|
if (c.search !== undefined) expr(c.search, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_SEARCH_TEXT */.bc.THE_SEARCH_TEXT));
|
|
2237
|
-
if (c.
|
|
2817
|
+
if (c.sort !== undefined) {
|
|
2818
|
+
const sort = c.sort;
|
|
2819
|
+
if (typeof sort?.field !== 'string' || !COLUMN_CODE.test(sort.field) || sort.direction !== undefined && sort.direction !== 'ASC' && sort.direction !== 'DESC') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_SORT_INVALID */.qj.NODE_SORT_INVALID, at));
|
|
2820
|
+
}
|
|
2821
|
+
if (c.limit !== undefined && !(Number.isInteger(c.limit) && c.limit >= 1 && c.limit <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT)) {
|
|
2238
2822
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_LIMIT_RANGE */.qj.NODE_LIMIT_RANGE, {
|
|
2239
2823
|
...at,
|
|
2240
|
-
max:
|
|
2824
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT
|
|
2241
2825
|
}));
|
|
2242
2826
|
}
|
|
2243
2827
|
break;
|
|
2244
2828
|
case 'call_flow':
|
|
2245
|
-
if (typeof c.flowSlug !== 'string' || !
|
|
2829
|
+
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));
|
|
2246
2830
|
else if (c.flowSlug === flow.slug) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CALLS_ITSELF */.qj.NODE_CALLS_ITSELF, at));
|
|
2247
2831
|
else if (context.knownFlows && !context.knownFlows.has(c.flowSlug)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_UNKNOWN */.qj.NODE_FLOW_UNKNOWN, {
|
|
2248
2832
|
...at,
|
|
2249
2833
|
flow: c.flowSlug
|
|
2250
2834
|
}));
|
|
2251
|
-
else if (context.knownFlows) checkCalledFlow(
|
|
2252
|
-
if (c.endpoint !== undefined && c.endpoint !== '' && (typeof c.endpoint !== 'string' || !
|
|
2835
|
+
else if (context.knownFlows) checkCalledFlow(node, flow, context.knownFlows, err, warn);
|
|
2836
|
+
if (c.endpoint !== undefined && c.endpoint !== '' && (typeof c.endpoint !== 'string' || !_flow_types_js__rspack_import_5/* .FLOW_ENDPOINT_PATH_PATTERN.test */.YO.test(c.endpoint))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_INVALID */.qj.TRIGGER_PATH_INVALID, at));
|
|
2253
2837
|
exprMap(c.input, KIND.input);
|
|
2254
2838
|
if (c.inputList !== undefined) expr(c.inputList, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_INPUT_LIST */.bc.THE_INPUT_LIST));
|
|
2255
2839
|
break;
|
|
@@ -2265,10 +2849,11 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2265
2849
|
expr(c.url, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_URL */.bc.THE_URL));
|
|
2266
2850
|
exprMap(c.headers, KIND.header);
|
|
2267
2851
|
exprMap(c.body, KIND.body);
|
|
2268
|
-
if (c.
|
|
2852
|
+
if (c.forwardAuth === true) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FORWARDS_TOKEN */.qj.NODE_FORWARDS_TOKEN, at));
|
|
2853
|
+
if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >= 100 && c.timeoutMs <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_HTTP_TIMEOUT_MS */.Or.MAX_HTTP_TIMEOUT_MS)) {
|
|
2269
2854
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_TIMEOUT_RANGE */.qj.NODE_TIMEOUT_RANGE, {
|
|
2270
2855
|
...at,
|
|
2271
|
-
max:
|
|
2856
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_HTTP_TIMEOUT_MS */.Or.MAX_HTTP_TIMEOUT_MS
|
|
2272
2857
|
}));
|
|
2273
2858
|
}
|
|
2274
2859
|
break;
|
|
@@ -2277,7 +2862,50 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2277
2862
|
if (typeof c.eventName !== 'string' || !/^[a-z][a-z0-9_.-]{1,59}$/.test(c.eventName)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EVENT_NAME_INVALID */.qj.NODE_EVENT_NAME_INVALID, at));
|
|
2278
2863
|
exprMap(c.payload, KIND.payload);
|
|
2279
2864
|
break;
|
|
2280
|
-
case '
|
|
2865
|
+
case 'send_notification':
|
|
2866
|
+
{
|
|
2867
|
+
serviceRunning('notification', _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_UNAVAILABLE */.qj.NODE_NOTIFICATION_UNAVAILABLE);
|
|
2868
|
+
const target = c.target ?? 'one';
|
|
2869
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_NOTIFICATION_TARGETS.includes */.as.includes(target)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_MESSAGE_TARGET_INVALID */.qj.NODE_MESSAGE_TARGET_INVALID, at));
|
|
2870
|
+
if (target === 'many') expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
|
|
2871
|
+
if (target === 'company') {
|
|
2872
|
+
if (context.companyMembers === false) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_COMPANY_UNAVAILABLE */.qj.NODE_NOTIFICATION_COMPANY_UNAVAILABLE, at));
|
|
2873
|
+
expr(c.companyIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_COMPANIES */.bc.THE_COMPANIES));
|
|
2874
|
+
optionalExpr(c.branchIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_BRANCHES */.bc.THE_BRANCHES));
|
|
2875
|
+
} else {
|
|
2876
|
+
expr(c.userIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RECIPIENTS */.bc.THE_RECIPIENTS));
|
|
2877
|
+
optionalExpr(c.companyId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_COMPANY */.bc.THE_COMPANY));
|
|
2878
|
+
}
|
|
2879
|
+
expr(c.title, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_TITLE */.bc.THE_TITLE));
|
|
2880
|
+
optionalExpr(c.message, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_MESSAGE */.bc.THE_MESSAGE));
|
|
2881
|
+
exprMap(c.data, KIND.data);
|
|
2882
|
+
if (c.type !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_NOTIFICATION_TYPES.includes */.LG.includes(c.type)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_OPTION_INVALID */.qj.NODE_NOTIFICATION_OPTION_INVALID, at));
|
|
2883
|
+
if (c.realtime !== undefined && typeof c.realtime !== 'boolean') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_OPTION_INVALID */.qj.NODE_NOTIFICATION_OPTION_INVALID, at));
|
|
2884
|
+
break;
|
|
2885
|
+
}
|
|
2886
|
+
case 'send_email':
|
|
2887
|
+
{
|
|
2888
|
+
serviceRunning('email', _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_UNAVAILABLE */.qj.NODE_EMAIL_UNAVAILABLE);
|
|
2889
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_EMAIL_TARGETS.includes */.B9.includes(c.target ?? 'one')) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_MESSAGE_TARGET_INVALID */.qj.NODE_MESSAGE_TARGET_INVALID, at));
|
|
2890
|
+
if (c.target === 'many') expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
|
|
2891
|
+
expr(c.to, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RECIPIENTS */.bc.THE_RECIPIENTS));
|
|
2892
|
+
optionalExpr(c.cc, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_CC */.bc.THE_CC));
|
|
2893
|
+
optionalExpr(c.bcc, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_BCC */.bc.THE_BCC));
|
|
2894
|
+
optionalExpr(c.replyTo, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_REPLY_TO */.bc.THE_REPLY_TO));
|
|
2895
|
+
if (c.mode !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_EMAIL_MODES.includes */.v1.includes(c.mode)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_OPTION_INVALID */.qj.NODE_EMAIL_OPTION_INVALID, at));
|
|
2896
|
+
if (c.format !== undefined && !_flow_types_js__rspack_import_5/* .FLOW_EMAIL_FORMATS.includes */.FU.includes(c.format)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_OPTION_INVALID */.qj.NODE_EMAIL_OPTION_INVALID, at));
|
|
2897
|
+
if (c.emailConfigId !== undefined && c.emailConfigId !== '' && (typeof c.emailConfigId !== 'string' || !_flow_types_js__rspack_import_5/* .FLOW_ID_PATTERN.test */.E2.test(c.emailConfigId))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_OPTION_INVALID */.qj.NODE_EMAIL_OPTION_INVALID, at));
|
|
2898
|
+
if (c.mode === 'template') {
|
|
2899
|
+
const slug = typeof c.templateSlug === 'string' ? c.templateSlug.trim() : '';
|
|
2900
|
+
if (!slug || slug.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_EMAIL_TEMPLATE_SLUG_LENGTH */.Or.MAX_EMAIL_TEMPLATE_SLUG_LENGTH) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_TEMPLATE_REQUIRED */.qj.NODE_EMAIL_TEMPLATE_REQUIRED, at));
|
|
2901
|
+
exprMap(c.variables, KIND.template);
|
|
2902
|
+
} else {
|
|
2903
|
+
expr(c.subject, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_SUBJECT */.bc.THE_SUBJECT));
|
|
2904
|
+
expr(c.body, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_BODY */.bc.THE_BODY));
|
|
2905
|
+
}
|
|
2906
|
+
break;
|
|
2907
|
+
}
|
|
2908
|
+
case 'respond':
|
|
2281
2909
|
{
|
|
2282
2910
|
const status = c.statusCode;
|
|
2283
2911
|
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));
|
|
@@ -2290,15 +2918,15 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2290
2918
|
break;
|
|
2291
2919
|
case 'code':
|
|
2292
2920
|
if (typeof c.code !== 'string' || !c.code.trim()) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_REQUIRED */.qj.NODE_CODE_REQUIRED, at));
|
|
2293
|
-
else if (c.code.length >
|
|
2921
|
+
else if (c.code.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CODE_LENGTH */.Or.MAX_CODE_LENGTH) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_TOO_LONG */.qj.NODE_CODE_TOO_LONG, {
|
|
2294
2922
|
...at,
|
|
2295
|
-
max:
|
|
2923
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CODE_LENGTH */.Or.MAX_CODE_LENGTH
|
|
2296
2924
|
}));
|
|
2297
|
-
if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >=
|
|
2925
|
+
if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MIN_CODE_TIMEOUT_MS */.Or.MIN_CODE_TIMEOUT_MS && c.timeoutMs <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS)) {
|
|
2298
2926
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_TIMEOUT_RANGE */.qj.NODE_CODE_TIMEOUT_RANGE, {
|
|
2299
2927
|
...at,
|
|
2300
|
-
min:
|
|
2301
|
-
max:
|
|
2928
|
+
min: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MIN_CODE_TIMEOUT_MS */.Or.MIN_CODE_TIMEOUT_MS,
|
|
2929
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS
|
|
2302
2930
|
}));
|
|
2303
2931
|
}
|
|
2304
2932
|
break;
|
|
@@ -2317,10 +2945,10 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2317
2945
|
if (target === 'filter') {
|
|
2318
2946
|
filterMap(c.filter);
|
|
2319
2947
|
if (!c.filter || typeof c.filter !== 'object' || !Object.keys(c.filter).length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FILTER_REQUIRED */.qj.NODE_FILTER_REQUIRED, at));
|
|
2320
|
-
if (c.limit !== undefined && !(Number.isInteger(c.limit) && c.limit >= 1 && c.limit <=
|
|
2948
|
+
if (c.limit !== undefined && !(Number.isInteger(c.limit) && c.limit >= 1 && c.limit <= _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS)) {
|
|
2321
2949
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_LIMIT_RANGE */.qj.NODE_LIMIT_RANGE, {
|
|
2322
2950
|
...at,
|
|
2323
|
-
max:
|
|
2951
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS
|
|
2324
2952
|
}));
|
|
2325
2953
|
}
|
|
2326
2954
|
}
|
|
@@ -2328,9 +2956,9 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2328
2956
|
function childInserts(value, { at, entity, expr, err }) {
|
|
2329
2957
|
if (value === undefined) return;
|
|
2330
2958
|
if (!Array.isArray(value)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHILDREN_INVALID */.qj.NODE_CHILDREN_INVALID, at));
|
|
2331
|
-
if (value.length >
|
|
2959
|
+
if (value.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CHILD_INSERTS */.Or.MAX_CHILD_INSERTS) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHILDREN_TOO_MANY */.qj.NODE_CHILDREN_TOO_MANY, {
|
|
2332
2960
|
...at,
|
|
2333
|
-
max:
|
|
2961
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_CHILD_INSERTS */.Or.MAX_CHILD_INSERTS
|
|
2334
2962
|
}));
|
|
2335
2963
|
const names = new Set();
|
|
2336
2964
|
for (const child of value){
|
|
@@ -2339,7 +2967,7 @@ function childInserts(value, { at, entity, expr, err }) {
|
|
|
2339
2967
|
continue;
|
|
2340
2968
|
}
|
|
2341
2969
|
const name = String(child.as ?? '');
|
|
2342
|
-
if (!
|
|
2970
|
+
if (!_flow_types_js__rspack_import_5/* .FLOW_INPUT_NAME_PATTERN.test */.fZ.test(name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHILD_NAME_INVALID */.qj.NODE_CHILD_NAME_INVALID, {
|
|
2343
2971
|
...at,
|
|
2344
2972
|
name
|
|
2345
2973
|
}));
|
|
@@ -2373,7 +3001,8 @@ function childInserts(value, { at, entity, expr, err }) {
|
|
|
2373
3001
|
/**
|
|
2374
3002
|
* How many loop levels each part of a node's config runs inside, beyond the loops around the node:
|
|
2375
3003
|
* a foreach's `collect` runs in its own loop; a many / filter write's per-record expressions in one
|
|
2376
|
-
* of the node's own; child records one deeper than their parent record
|
|
3004
|
+
* of the node's own; child records one deeper than their parent record; a many message step's values
|
|
3005
|
+
* (all but its list) one per item.
|
|
2377
3006
|
*/ function configScopes(node) {
|
|
2378
3007
|
const config = node.config ?? {};
|
|
2379
3008
|
if (node.type === 'foreach') {
|
|
@@ -2391,6 +3020,21 @@ function childInserts(value, { at, entity, expr, err }) {
|
|
|
2391
3020
|
}
|
|
2392
3021
|
];
|
|
2393
3022
|
}
|
|
3023
|
+
if ((0,_flow_types_js__rspack_import_5/* .isMessageNode */.SE)(node.type) && config['target'] === 'many') {
|
|
3024
|
+
const { items, ...perItem } = config;
|
|
3025
|
+
return [
|
|
3026
|
+
{
|
|
3027
|
+
value: items,
|
|
3028
|
+
extra: 0,
|
|
3029
|
+
ownLoop: false
|
|
3030
|
+
},
|
|
3031
|
+
{
|
|
3032
|
+
value: perItem,
|
|
3033
|
+
extra: 1,
|
|
3034
|
+
ownLoop: false
|
|
3035
|
+
}
|
|
3036
|
+
];
|
|
3037
|
+
}
|
|
2394
3038
|
if (!WRITE_NODE_TYPES.has(node.type)) return [
|
|
2395
3039
|
{
|
|
2396
3040
|
value: config,
|
|
@@ -2430,9 +3074,9 @@ function childInserts(value, { at, entity, expr, err }) {
|
|
|
2430
3074
|
}
|
|
2431
3075
|
function validateChecks(c, at, rule, err) {
|
|
2432
3076
|
if (!Array.isArray(c.checks) || !c.checks.length) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_REQUIRED */.qj.NODE_CHECK_REQUIRED, at));
|
|
2433
|
-
if (c.checks.length >
|
|
3077
|
+
if (c.checks.length > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_VALIDATE_CHECKS */.Or.MAX_VALIDATE_CHECKS) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_TOO_MANY_CHECKS */.qj.NODE_TOO_MANY_CHECKS, {
|
|
2434
3078
|
...at,
|
|
2435
|
-
max:
|
|
3079
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_VALIDATE_CHECKS */.Or.MAX_VALIDATE_CHECKS
|
|
2436
3080
|
}));
|
|
2437
3081
|
const ids = new Set();
|
|
2438
3082
|
for (const check of c.checks){
|
|
@@ -2461,7 +3105,7 @@ function validateChecks(c, at, rule, err) {
|
|
|
2461
3105
|
}
|
|
2462
3106
|
}
|
|
2463
3107
|
/** Every `context.<node>` must name a node of this flow; `input.<name>` should be an input some Request step declares. */ function checkNodeReferences(nodes, byId, flow, err, warnings) {
|
|
2464
|
-
const endpoints = (0,
|
|
3108
|
+
const endpoints = (0,_flow_types_js__rspack_import_5/* .flowEndpoints */.Xr)(flow);
|
|
2465
3109
|
const declaredInputs = new Set(endpoints.flatMap((endpoint)=>endpoint.inputSchema.map((f)=>f.name)));
|
|
2466
3110
|
// A list body is read by index, and an object body without fields takes anything: neither has names to check.
|
|
2467
3111
|
const namesKnown = endpoints.length > 0 && endpoints.every((endpoint)=>endpoint.bodyType !== 'list' && endpoint.inputSchema.length > 0);
|
|
@@ -2476,21 +3120,21 @@ function validateChecks(c, at, rule, err) {
|
|
|
2476
3120
|
const [scope, second] = ref.split('.');
|
|
2477
3121
|
if (!REF_SCOPES.has(scope)) {
|
|
2478
3122
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_BAD_SCOPE */.qj.REF_BAD_SCOPE, {
|
|
2479
|
-
node: (0,
|
|
3123
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node),
|
|
2480
3124
|
ref
|
|
2481
3125
|
}));
|
|
2482
3126
|
} else if (scope === 'context' && second && !byId.has(second)) {
|
|
2483
3127
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_NODE_MISSING */.qj.REF_NODE_MISSING, {
|
|
2484
|
-
node: (0,
|
|
3128
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node),
|
|
2485
3129
|
target: second
|
|
2486
3130
|
}));
|
|
2487
3131
|
} else if (scope === 'context' && second === node.id) {
|
|
2488
3132
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_SELF */.qj.REF_SELF, {
|
|
2489
|
-
node: (0,
|
|
3133
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node)
|
|
2490
3134
|
}));
|
|
2491
3135
|
} else if (scope === 'input' && second && namesKnown && !declaredInputs.has(second)) {
|
|
2492
3136
|
warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_INPUT_UNDECLARED */.qj.REF_INPUT_UNDECLARED, {
|
|
2493
|
-
node: (0,
|
|
3137
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(node),
|
|
2494
3138
|
input: second
|
|
2495
3139
|
}));
|
|
2496
3140
|
}
|
|
@@ -2505,15 +3149,28 @@ function refsOf(value, refs = [], depth = 0) {
|
|
|
2505
3149
|
return refs;
|
|
2506
3150
|
}
|
|
2507
3151
|
/**
|
|
2508
|
-
* Writes that do not share a transaction, and Save progress steps that would not commit:
|
|
2509
|
-
* without the transaction setting, inside a loop (once per item), or in a flow
|
|
2510
|
-
* (inside its caller's transaction it is skipped).
|
|
2511
|
-
*/ function checkTransactionUse(flow,
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
3152
|
+
* Writes that do not share a transaction, and Save progress steps that would not commit: reached
|
|
3153
|
+
* from a Request step without the transaction setting, inside a loop (once per item), or in a flow
|
|
3154
|
+
* other flows call (inside its caller's transaction it is skipped).
|
|
3155
|
+
*/ function checkTransactionUse(flow, runs, outgoing, context, warnings) {
|
|
3156
|
+
for (const { endpoint, label, steps } of runs){
|
|
3157
|
+
if (endpoint.isTransactional) continue;
|
|
3158
|
+
const writes = steps.filter((n)=>WRITE_NODE_TYPES.has(n.type)).length;
|
|
3159
|
+
if (writes >= 2) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.WRITES_NOT_TRANSACTIONAL */.qj.WRITES_NOT_TRANSACTIONAL, {
|
|
3160
|
+
count: writes,
|
|
3161
|
+
node: label
|
|
3162
|
+
}));
|
|
3163
|
+
for (const commit of steps)if (commit.type === 'commit') warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.COMMIT_NOT_TRANSACTIONAL */.qj.COMMIT_NOT_TRANSACTIONAL, {
|
|
3164
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(commit),
|
|
3165
|
+
endpoint: label
|
|
3166
|
+
}));
|
|
3167
|
+
}
|
|
3168
|
+
const live = [
|
|
3169
|
+
...new Map(runs.flatMap((run)=>run.steps).map((n)=>[
|
|
3170
|
+
n.id,
|
|
3171
|
+
n
|
|
3172
|
+
])).values()
|
|
3173
|
+
];
|
|
2517
3174
|
const commits = live.filter((n)=>n.type === 'commit');
|
|
2518
3175
|
if (!commits.length) return;
|
|
2519
3176
|
const loops = live.filter((n)=>n.type === 'foreach').map((n)=>({
|
|
@@ -2525,13 +3182,12 @@ function refsOf(value, refs = [], depth = 0) {
|
|
|
2525
3182
|
].some((f)=>f.calls.includes(flow.slug));
|
|
2526
3183
|
for (const commit of commits){
|
|
2527
3184
|
const at = {
|
|
2528
|
-
node: (0,
|
|
3185
|
+
node: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(commit)
|
|
2529
3186
|
};
|
|
2530
|
-
if (!flow.isTransactional) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.COMMIT_NOT_TRANSACTIONAL */.qj.COMMIT_NOT_TRANSACTIONAL, at));
|
|
2531
3187
|
const loop = loops.find((l)=>l.body.has(commit.id));
|
|
2532
3188
|
if (loop) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.COMMIT_IN_LOOP */.qj.COMMIT_IN_LOOP, {
|
|
2533
3189
|
...at,
|
|
2534
|
-
loop: (0,
|
|
3190
|
+
loop: (0,_flow_types_js__rspack_import_5/* .nodeLabel */.dA)(loop.node)
|
|
2535
3191
|
}));
|
|
2536
3192
|
if (called) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.COMMIT_IN_CALLED_FLOW */.qj.COMMIT_IN_CALLED_FLOW, at));
|
|
2537
3193
|
}
|
|
@@ -2698,14 +3354,15 @@ function reach(start, outgoing) {
|
|
|
2698
3354
|
nesting.set(id, max);
|
|
2699
3355
|
return max;
|
|
2700
3356
|
};
|
|
2701
|
-
if (nodes.some((n)=>n.type === 'trigger' && nestingFrom(n.id) >
|
|
2702
|
-
max:
|
|
3357
|
+
if (nodes.some((n)=>n.type === 'trigger' && nestingFrom(n.id) > _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_LOOP_DEPTH */.Or.MAX_LOOP_DEPTH)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.LOOP_TOO_DEEP */.qj.LOOP_TOO_DEEP, {
|
|
3358
|
+
max: _flow_types_js__rspack_import_5/* .FLOW_LIMITS.MAX_LOOP_DEPTH */.Or.MAX_LOOP_DEPTH
|
|
2703
3359
|
}));
|
|
2704
3360
|
}
|
|
2705
3361
|
|
|
2706
3362
|
__webpack_require__.d(__webpack_exports__, {
|
|
2707
|
-
|
|
2708
|
-
|
|
3363
|
+
nC: () => (validateFlow),
|
|
3364
|
+
r7: () => (callProblems),
|
|
3365
|
+
u9: () => (usesPermissionFunction)
|
|
2709
3366
|
});
|
|
2710
3367
|
|
|
2711
3368
|
|
|
@@ -2908,6 +3565,19 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2908
3565
|
});
|
|
2909
3566
|
|
|
2910
3567
|
|
|
3568
|
+
},
|
|
3569
|
+
8220(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
3570
|
+
const permissionAction = (code)=>({
|
|
3571
|
+
type: 'action',
|
|
3572
|
+
actionId: code
|
|
3573
|
+
});
|
|
3574
|
+
|
|
3575
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
3576
|
+
}, {
|
|
3577
|
+
s: permissionAction
|
|
3578
|
+
});
|
|
3579
|
+
|
|
3580
|
+
|
|
2911
3581
|
},
|
|
2912
3582
|
8542(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
2913
3583
|
/* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
|
|
@@ -2989,203 +3659,14 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2989
3659
|
});
|
|
2990
3660
|
|
|
2991
3661
|
|
|
2992
|
-
},
|
|
2993
|
-
6256(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
2994
|
-
const FLOW_NODE_TYPES = [
|
|
2995
|
-
'trigger',
|
|
2996
|
-
'validate',
|
|
2997
|
-
'permission_check',
|
|
2998
|
-
'set',
|
|
2999
|
-
'condition',
|
|
3000
|
-
'switch',
|
|
3001
|
-
'foreach',
|
|
3002
|
-
'entity_insert',
|
|
3003
|
-
'entity_update',
|
|
3004
|
-
'entity_delete',
|
|
3005
|
-
'entity_get',
|
|
3006
|
-
'entity_query',
|
|
3007
|
-
'commit',
|
|
3008
|
-
'call_flow',
|
|
3009
|
-
'http_request',
|
|
3010
|
-
'publish_event',
|
|
3011
|
-
'respond',
|
|
3012
|
-
'code'
|
|
3013
|
-
];
|
|
3014
|
-
/** A failure of these always ends the run: checks can never be carried past, and Save progress cannot run in a savepoint. */ const NON_CONTINUABLE_NODE_TYPES = new Set([
|
|
3015
|
-
'validate',
|
|
3016
|
-
'permission_check',
|
|
3017
|
-
'commit'
|
|
3018
|
-
]);
|
|
3019
|
-
const FLOW_AUTH_MODES = [
|
|
3020
|
-
'public',
|
|
3021
|
-
'api_key',
|
|
3022
|
-
'jwt',
|
|
3023
|
-
'permission',
|
|
3024
|
-
'internal'
|
|
3025
|
-
];
|
|
3026
|
-
const FLOW_RUN_AS = [
|
|
3027
|
-
'caller',
|
|
3028
|
-
'system'
|
|
3029
|
-
];
|
|
3030
|
-
/** object: the body is one object and the fields are its keys (`input.<name>`). list: the body is a list and the fields describe each item (`input.0.<name>`). */ const FLOW_BODY_TYPES = [
|
|
3031
|
-
'object',
|
|
3032
|
-
'list'
|
|
3033
|
-
];
|
|
3034
|
-
const FLOW_INPUT_TYPES = [
|
|
3035
|
-
'string',
|
|
3036
|
-
'text',
|
|
3037
|
-
'number',
|
|
3038
|
-
'integer',
|
|
3039
|
-
'boolean',
|
|
3040
|
-
'date',
|
|
3041
|
-
'datetime',
|
|
3042
|
-
'email',
|
|
3043
|
-
'uuid',
|
|
3044
|
-
'object',
|
|
3045
|
-
'array',
|
|
3046
|
-
'choice'
|
|
3047
|
-
];
|
|
3048
|
-
/** How a step is named in messages: its name, or its id when it was left unnamed. */ const nodeLabel = (node)=>node.name || node.id;
|
|
3049
|
-
/**
|
|
3050
|
-
* Which records a write node touches. one (default): a single record. many: one per item of `items`,
|
|
3051
|
-
* whose per-record expressions read the item as `loop.item` (a loop around the node becomes
|
|
3052
|
-
* `loop.parent`). filter: every record matching `filter`, read as `loop.item`.
|
|
3053
|
-
*/ const RECORD_WRITE_TARGETS = [
|
|
3054
|
-
'one',
|
|
3055
|
-
'many',
|
|
3056
|
-
'filter'
|
|
3057
|
-
];
|
|
3058
|
-
const FLOW_LIMITS = {
|
|
3059
|
-
MAX_NODES: 100,
|
|
3060
|
-
MAX_EDGES: 300,
|
|
3061
|
-
/** Room for a full foreach (MAX_FOREACH_ITEMS) with a few steps per item. */ MAX_STEPS_PER_RUN: 5000,
|
|
3062
|
-
MAX_FOREACH_ITEMS: 1000,
|
|
3063
|
-
MAX_LOOP_DEPTH: 3,
|
|
3064
|
-
MAX_CALL_DEPTH: 5,
|
|
3065
|
-
MAX_HTTP_CALLS: 10,
|
|
3066
|
-
MAX_HTTP_TIMEOUT_MS: 15_000,
|
|
3067
|
-
MAX_HTTP_RESPONSE_BYTES: 1_000_000,
|
|
3068
|
-
MAX_QUERY_LIMIT: 200,
|
|
3069
|
-
MAX_WRITE_ITEMS: 1000,
|
|
3070
|
-
DEFAULT_WRITE_FILTER_LIMIT: 100,
|
|
3071
|
-
MAX_CHILD_INSERTS: 10,
|
|
3072
|
-
MAX_WRITES_PER_RUN: 5000,
|
|
3073
|
-
DEFAULT_TIMEOUT_MS: 30_000,
|
|
3074
|
-
MIN_TIMEOUT_MS: 1000,
|
|
3075
|
-
MAX_TIMEOUT_MS: 120_000,
|
|
3076
|
-
TRACE_PREVIEW_CHARS: 2000,
|
|
3077
|
-
MAX_INPUT_FIELDS: 50,
|
|
3078
|
-
/** How deep object/list-of-object fields may nest inside the request body. */ MAX_INPUT_DEPTH: 4,
|
|
3079
|
-
MAX_VALIDATE_CHECKS: 50,
|
|
3080
|
-
MAX_CODE_LENGTH: 20_000,
|
|
3081
|
-
DEFAULT_CODE_TIMEOUT_MS: 1000,
|
|
3082
|
-
MIN_CODE_TIMEOUT_MS: 10,
|
|
3083
|
-
MAX_CODE_TIMEOUT_MS: 5000,
|
|
3084
|
-
CODE_MEMORY_LIMIT_BYTES: 32 * 1024 * 1024,
|
|
3085
|
-
CODE_STACK_SIZE_BYTES: 512 * 1024,
|
|
3086
|
-
MAX_CODE_OUTPUT_BYTES: 1_000_000,
|
|
3087
|
-
MAX_CODE_LOG_LINES: 50,
|
|
3088
|
-
MAX_CODE_LOG_LINE_CHARS: 500
|
|
3089
|
-
};
|
|
3090
|
-
const FLOW_NODE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,39}$/;
|
|
3091
|
-
const FLOW_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,62}$/;
|
|
3092
|
-
const FLOW_INPUT_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,59}$/;
|
|
3093
|
-
const FLOW_ENDPOINT_PATH_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
|
|
3094
|
-
/** A Request step's path, or null when it answers on the flow's own URL. */ function triggerPath(node) {
|
|
3095
|
-
const path = node.config?.path;
|
|
3096
|
-
return typeof path === 'string' && path.trim() ? path.trim() : null;
|
|
3097
|
-
}
|
|
3098
|
-
/** Every Request step of a flow as a URL. */ function flowEndpoints(flow) {
|
|
3099
|
-
return (flow.nodes ?? []).filter((node)=>node?.type === 'trigger').map((node)=>{
|
|
3100
|
-
const path = triggerPath(node);
|
|
3101
|
-
if (!path) return {
|
|
3102
|
-
nodeId: node.id,
|
|
3103
|
-
path,
|
|
3104
|
-
bodyType: flow.bodyType ?? 'object',
|
|
3105
|
-
inputSchema: flow.inputSchema ?? []
|
|
3106
|
-
};
|
|
3107
|
-
const config = node.config;
|
|
3108
|
-
return {
|
|
3109
|
-
nodeId: node.id,
|
|
3110
|
-
path,
|
|
3111
|
-
bodyType: config.bodyType ?? 'object',
|
|
3112
|
-
inputSchema: Array.isArray(config.inputSchema) ? config.inputSchema : []
|
|
3113
|
-
};
|
|
3114
|
-
});
|
|
3115
|
-
}
|
|
3116
|
-
/** The URL a call came in on: `path` of a Request step, or null / empty for the flow's own URL. */ function findEndpoint(flow, path) {
|
|
3117
|
-
const wanted = path?.trim() || null;
|
|
3118
|
-
return flowEndpoints(flow).find((endpoint)=>endpoint.path === wanted);
|
|
3119
|
-
}
|
|
3120
|
-
/** Output ports a node of this type can have (a `respond` node ends the flow: none). */ function portsOf(node) {
|
|
3121
|
-
switch(node.type){
|
|
3122
|
-
case 'condition':
|
|
3123
|
-
return [
|
|
3124
|
-
'true',
|
|
3125
|
-
'false'
|
|
3126
|
-
];
|
|
3127
|
-
case 'switch':
|
|
3128
|
-
return [
|
|
3129
|
-
...(node.config.cases ?? []).map((c)=>c.id),
|
|
3130
|
-
'default'
|
|
3131
|
-
];
|
|
3132
|
-
case 'foreach':
|
|
3133
|
-
return [
|
|
3134
|
-
'each',
|
|
3135
|
-
'done'
|
|
3136
|
-
];
|
|
3137
|
-
case 'permission_check':
|
|
3138
|
-
return node.config.onDenied === 'branch' ? [
|
|
3139
|
-
'allowed',
|
|
3140
|
-
'denied'
|
|
3141
|
-
] : [
|
|
3142
|
-
'out'
|
|
3143
|
-
];
|
|
3144
|
-
case 'respond':
|
|
3145
|
-
return [];
|
|
3146
|
-
case 'trigger':
|
|
3147
|
-
case 'validate':
|
|
3148
|
-
case 'set':
|
|
3149
|
-
case 'commit':
|
|
3150
|
-
return [
|
|
3151
|
-
'out'
|
|
3152
|
-
];
|
|
3153
|
-
default:
|
|
3154
|
-
return [
|
|
3155
|
-
'out',
|
|
3156
|
-
'error'
|
|
3157
|
-
];
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
3162
|
-
Xr: () => (flowEndpoints),
|
|
3163
|
-
a5: () => (portsOf),
|
|
3164
|
-
um: () => (triggerPath),
|
|
3165
|
-
vS: () => (findEndpoint)
|
|
3166
|
-
}, {
|
|
3167
|
-
At: NON_CONTINUABLE_NODE_TYPES,
|
|
3168
|
-
Dn: RECORD_WRITE_TARGETS,
|
|
3169
|
-
Or: FLOW_LIMITS,
|
|
3170
|
-
Q7: FLOW_INPUT_TYPES,
|
|
3171
|
-
Y3: FLOW_BODY_TYPES,
|
|
3172
|
-
YD: FLOW_SLUG_PATTERN,
|
|
3173
|
-
YO: FLOW_ENDPOINT_PATH_PATTERN,
|
|
3174
|
-
dA: nodeLabel,
|
|
3175
|
-
fO: FLOW_AUTH_MODES,
|
|
3176
|
-
fZ: FLOW_INPUT_NAME_PATTERN,
|
|
3177
|
-
gl: FLOW_RUN_AS,
|
|
3178
|
-
q2: FLOW_NODE_TYPES,
|
|
3179
|
-
z6: FLOW_NODE_ID_PATTERN
|
|
3180
|
-
});
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
3662
|
},
|
|
3184
3663
|
2678(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
3185
3664
|
const SENSITIVE_KEY = /pass(word)?|secret|token|authorization|api[-_]?key|cookie|credential|private[-_]?key/i;
|
|
3186
3665
|
/** Copy of `value` with the content of sensitive-looking keys replaced, safe to store in a log. */ function redactSensitive(value, depth = 0) {
|
|
3187
3666
|
if (depth > 8) return '[deep]';
|
|
3188
3667
|
if (Array.isArray(value)) return value.slice(0, 100).map((v)=>redactSensitive(v, depth + 1));
|
|
3668
|
+
// Dates (and anything else with its own JSON form) have no own keys: walked as objects they would log as `{}`.
|
|
3669
|
+
if (value && typeof value.toJSON === 'function') return redactSensitive(value.toJSON(), depth + 1);
|
|
3189
3670
|
if (value && typeof value === 'object') {
|
|
3190
3671
|
const out = {};
|
|
3191
3672
|
for (const [key, item] of Object.entries(value)){
|
|
@@ -3214,6 +3695,24 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3214
3695
|
});
|
|
3215
3696
|
|
|
3216
3697
|
|
|
3698
|
+
},
|
|
3699
|
+
9609(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
3700
|
+
/** What a version holds and a draft edits: everything about a flow but its identity. */ const FLOW_VERSIONED_FIELDS = [
|
|
3701
|
+
'name',
|
|
3702
|
+
'slug',
|
|
3703
|
+
'description',
|
|
3704
|
+
'isActive',
|
|
3705
|
+
'retainExecutions',
|
|
3706
|
+
'nodes',
|
|
3707
|
+
'edges'
|
|
3708
|
+
];
|
|
3709
|
+
|
|
3710
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
3711
|
+
}, {
|
|
3712
|
+
v: FLOW_VERSIONED_FIELDS
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3715
|
+
|
|
3217
3716
|
},
|
|
3218
3717
|
8323(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
3219
3718
|
/* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
|
|
@@ -3415,6 +3914,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
3415
3914
|
}
|
|
3416
3915
|
|
|
3417
3916
|
|
|
3917
|
+
const VARIABLE_ENV_PREFIX = 'ENTITY_BUILDER_VAR_';
|
|
3418
3918
|
class EntityBuilderConfigService {
|
|
3419
3919
|
options;
|
|
3420
3920
|
constructor(options){
|
|
@@ -3435,6 +3935,20 @@ class EntityBuilderConfigService {
|
|
|
3435
3935
|
getConfig() {
|
|
3436
3936
|
return this.options.config;
|
|
3437
3937
|
}
|
|
3938
|
+
/** `designer.readOnly`, else the `ENTITY_BUILDER_READ_ONLY=true` env variable (production). */ isDesignerReadOnly() {
|
|
3939
|
+
return this.getConfig()?.designer?.readOnly ?? process.env['ENTITY_BUILDER_READ_ONLY'] === 'true';
|
|
3940
|
+
}
|
|
3941
|
+
/** 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
|
+
const fromEnv = Object.entries(process.env).filter((entry)=>entry[0].startsWith(VARIABLE_ENV_PREFIX) && !!entry[1]).map(([key, value])=>[
|
|
3943
|
+
key.slice(VARIABLE_ENV_PREFIX.length),
|
|
3944
|
+
value
|
|
3945
|
+
]);
|
|
3946
|
+
const variables = {
|
|
3947
|
+
...Object.fromEntries(fromEnv),
|
|
3948
|
+
...this.getConfig()?.bundles?.variables
|
|
3949
|
+
};
|
|
3950
|
+
return Object.fromEntries(Object.entries(variables).filter((entry)=>_config_entity_builder_constants_js__rspack_import_1/* .BUNDLE_VARIABLE_NAME_PATTERN.test */.JI.test(entry[0]) && typeof entry[1] === 'string'));
|
|
3951
|
+
}
|
|
3438
3952
|
}
|
|
3439
3953
|
EntityBuilderConfigService = _ts_decorate([
|
|
3440
3954
|
(0,_nestjs_common__rspack_import_0.Injectable)(),
|
|
@@ -3840,54 +4354,24 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3840
4354
|
|
|
3841
4355
|
|
|
3842
4356
|
},
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
__webpack_require__
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
var
|
|
3852
|
-
|
|
3853
|
-
var
|
|
3854
|
-
|
|
3855
|
-
var
|
|
3856
|
-
|
|
3857
|
-
var
|
|
3858
|
-
|
|
3859
|
-
var
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
const API_KEY_PREFIX_LENGTH = 11; // "fk_" + 8 hex
|
|
3863
|
-
/** A new key: `fk_<8 hex>_<32 hex>`. Shown once; only its hash is stored. */ function generateApiKey() {
|
|
3864
|
-
const key = `fk_${(0,external_node_crypto_.randomBytes)(4).toString('hex')}_${(0,external_node_crypto_.randomBytes)(16).toString('hex')}`;
|
|
3865
|
-
return {
|
|
3866
|
-
key,
|
|
3867
|
-
prefix: apiKeyPrefix(key),
|
|
3868
|
-
hash: hashApiKey(key)
|
|
3869
|
-
};
|
|
3870
|
-
}
|
|
3871
|
-
const hashApiKey = (key)=>(0,external_node_crypto_.createHash)('sha256').update(key).digest('hex');
|
|
3872
|
-
const apiKeyPrefix = (key)=>key.slice(0, API_KEY_PREFIX_LENGTH);
|
|
3873
|
-
function looksLikeApiKey(key) {
|
|
3874
|
-
return typeof key === 'string' && /^fk_[0-9a-f]{8}_[0-9a-f]{32}$/.test(key);
|
|
3875
|
-
}
|
|
3876
|
-
/** Constant-time comparison of a presented key against a stored hash. */ function apiKeyMatches(presented, storedHash) {
|
|
3877
|
-
const a = Buffer.from(hashApiKey(presented), 'hex');
|
|
3878
|
-
const b = Buffer.from(storedHash, 'hex');
|
|
3879
|
-
return a.length === b.length && (0,external_node_crypto_.timingSafeEqual)(a, b);
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/entities/flow-definition.entity.ts
|
|
3883
|
-
var flow_definition_entity = __webpack_require__(3770);
|
|
3884
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/rule-values.ts
|
|
3885
|
-
var rule_values = __webpack_require__(6622);
|
|
3886
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-datasource.provider.ts
|
|
3887
|
-
var entity_builder_datasource_provider = __webpack_require__(424);
|
|
3888
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/flow-definition.service.ts
|
|
3889
|
-
var flow_definition_service = __webpack_require__(5229);
|
|
3890
|
-
;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/flow-api-key.service.ts
|
|
4357
|
+
5229(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4358
|
+
/* import */ var _flusys_nestjs_shared__rspack_import_0 = __webpack_require__(3422);
|
|
4359
|
+
/* import */ var _flusys_nestjs_shared_classes__rspack_import_1 = __webpack_require__(6937);
|
|
4360
|
+
/* import */ var _flusys_nestjs_shared_interfaces__rspack_import_2 = __webpack_require__(5103);
|
|
4361
|
+
/* import */ var _flusys_nestjs_shared_modules__rspack_import_3 = __webpack_require__(1236);
|
|
4362
|
+
/* import */ var _nestjs_common__rspack_import_4 = __webpack_require__(6157);
|
|
4363
|
+
/* import */ var typeorm__rspack_import_5 = __webpack_require__(7158);
|
|
4364
|
+
/* import */ var _config_event_actions_js__rspack_import_16 = __webpack_require__(982);
|
|
4365
|
+
/* import */ var _config_message_keys_js__rspack_import_14 = __webpack_require__(5732);
|
|
4366
|
+
/* import */ var _entities_entity_definition_entity_js__rspack_import_6 = __webpack_require__(3189);
|
|
4367
|
+
/* import */ var _entities_flow_definition_entity_js__rspack_import_7 = __webpack_require__(3770);
|
|
4368
|
+
/* import */ var _entities_flow_version_entity_js__rspack_import_8 = __webpack_require__(2399);
|
|
4369
|
+
/* import */ var _flow_engine_flow_api_key_js__rspack_import_9 = __webpack_require__(4816);
|
|
4370
|
+
/* import */ var _flow_engine_flow_permission_logic_js__rspack_import_13 = __webpack_require__(8220);
|
|
4371
|
+
/* import */ var _flow_engine_flow_graph_validator_js__rspack_import_10 = __webpack_require__(3186);
|
|
4372
|
+
/* import */ var _flow_engine_flow_types_js__rspack_import_11 = __webpack_require__(6256);
|
|
4373
|
+
/* import */ var _interfaces_flow_definition_interface_js__rspack_import_15 = __webpack_require__(9609);
|
|
4374
|
+
/* import */ var _entity_builder_datasource_provider_js__rspack_import_12 = __webpack_require__(424);
|
|
3891
4375
|
function _ts_decorate(decorators, target, key, desc) {
|
|
3892
4376
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3893
4377
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -3920,194 +4404,6 @@ function _ts_param(paramIndex, decorator) {
|
|
|
3920
4404
|
|
|
3921
4405
|
|
|
3922
4406
|
|
|
3923
|
-
const TOUCH_INTERVAL_MS = 60_000;
|
|
3924
|
-
class FlowApiKeyService {
|
|
3925
|
-
provider;
|
|
3926
|
-
constructor(provider){
|
|
3927
|
-
this.provider = provider;
|
|
3928
|
-
}
|
|
3929
|
-
async repo() {
|
|
3930
|
-
return this.provider.getRepository(flow_api_key_entity/* .FlowApiKey */.S);
|
|
3931
|
-
}
|
|
3932
|
-
async assertFlowExists(flowId) {
|
|
3933
|
-
const flow = await (await this.provider.getRepository(flow_definition_entity/* .FlowDefinition */.G)).findOne({
|
|
3934
|
-
where: {
|
|
3935
|
-
id: flowId
|
|
3936
|
-
},
|
|
3937
|
-
select: {
|
|
3938
|
-
id: true
|
|
3939
|
-
}
|
|
3940
|
-
});
|
|
3941
|
-
if (!flow) throw (0,flow_definition_service/* .flowNotFound */.YW)();
|
|
3942
|
-
}
|
|
3943
|
-
async findKey(id) {
|
|
3944
|
-
const key = await (await this.repo()).findOne({
|
|
3945
|
-
where: {
|
|
3946
|
-
id
|
|
3947
|
-
}
|
|
3948
|
-
});
|
|
3949
|
-
if (!key) throw new common_.NotFoundException({
|
|
3950
|
-
message: 'API key not found.',
|
|
3951
|
-
messageKey: message_keys/* .FLOW_MESSAGES.API_KEY_NOT_FOUND */.wu.API_KEY_NOT_FOUND
|
|
3952
|
-
});
|
|
3953
|
-
await this.assertFlowExists(key.flowId);
|
|
3954
|
-
return key;
|
|
3955
|
-
}
|
|
3956
|
-
/** The plain key is returned here and never again. */ async create(flowId, name, expiresAt, user) {
|
|
3957
|
-
const expiry = expiresAt ? (0,rule_values/* .parseDateText */.hz)(expiresAt) : null;
|
|
3958
|
-
if (expiry !== null && !(expiry > Date.now())) {
|
|
3959
|
-
throw new common_.BadRequestException({
|
|
3960
|
-
message: 'The expiry date of an API key must be in the future.',
|
|
3961
|
-
messageKey: message_keys/* .FLOW_MESSAGES.API_KEY_EXPIRY_IN_PAST */.wu.API_KEY_EXPIRY_IN_PAST
|
|
3962
|
-
});
|
|
3963
|
-
}
|
|
3964
|
-
await this.assertFlowExists(flowId);
|
|
3965
|
-
const { key, prefix, hash } = generateApiKey();
|
|
3966
|
-
const repo = await this.repo();
|
|
3967
|
-
const saved = await repo.save(repo.create({
|
|
3968
|
-
flowId,
|
|
3969
|
-
name,
|
|
3970
|
-
keyPrefix: prefix,
|
|
3971
|
-
keyHash: hash,
|
|
3972
|
-
isActive: true,
|
|
3973
|
-
expiresAt: expiry === null ? null : new Date(expiry),
|
|
3974
|
-
lastUsedAt: null,
|
|
3975
|
-
createdById: user?.id ?? null
|
|
3976
|
-
}));
|
|
3977
|
-
return {
|
|
3978
|
-
key,
|
|
3979
|
-
info: this.info(saved)
|
|
3980
|
-
};
|
|
3981
|
-
}
|
|
3982
|
-
async list(flowId) {
|
|
3983
|
-
await this.assertFlowExists(flowId);
|
|
3984
|
-
const rows = await (await this.repo()).find({
|
|
3985
|
-
where: {
|
|
3986
|
-
flowId
|
|
3987
|
-
},
|
|
3988
|
-
order: {
|
|
3989
|
-
createdAt: 'DESC'
|
|
3990
|
-
}
|
|
3991
|
-
});
|
|
3992
|
-
return rows.map((r)=>this.info(r));
|
|
3993
|
-
}
|
|
3994
|
-
async revoke(id) {
|
|
3995
|
-
const key = await this.findKey(id);
|
|
3996
|
-
await (await this.repo()).update({
|
|
3997
|
-
id: key.id
|
|
3998
|
-
}, {
|
|
3999
|
-
isActive: false
|
|
4000
|
-
});
|
|
4001
|
-
}
|
|
4002
|
-
async remove(id) {
|
|
4003
|
-
const key = await this.findKey(id);
|
|
4004
|
-
await (await this.repo()).softDelete({
|
|
4005
|
-
id: key.id
|
|
4006
|
-
});
|
|
4007
|
-
}
|
|
4008
|
-
/**
|
|
4009
|
-
* Checks `presented` against the unrevoked keys of this flow. Only the flow access guard calls
|
|
4010
|
-
* this, and only while the flow's authMode is `api_key`; revoking works in any authMode.
|
|
4011
|
-
*/ async verify(flowId, presented) {
|
|
4012
|
-
if (!looksLikeApiKey(presented)) return 'invalid';
|
|
4013
|
-
const repo = await this.repo();
|
|
4014
|
-
const candidates = await repo.find({
|
|
4015
|
-
where: {
|
|
4016
|
-
flowId,
|
|
4017
|
-
keyPrefix: apiKeyPrefix(presented),
|
|
4018
|
-
isActive: true,
|
|
4019
|
-
deletedAt: (0,external_typeorm_.IsNull)()
|
|
4020
|
-
},
|
|
4021
|
-
select: {
|
|
4022
|
-
id: true,
|
|
4023
|
-
keyHash: true,
|
|
4024
|
-
expiresAt: true,
|
|
4025
|
-
lastUsedAt: true
|
|
4026
|
-
}
|
|
4027
|
-
});
|
|
4028
|
-
const match = candidates.find((c)=>apiKeyMatches(presented, c.keyHash));
|
|
4029
|
-
if (!match) return 'invalid';
|
|
4030
|
-
if (isExpired(match.expiresAt)) return 'expired';
|
|
4031
|
-
if (!match.lastUsedAt || Date.now() - match.lastUsedAt.getTime() > TOUCH_INTERVAL_MS) {
|
|
4032
|
-
await repo.update({
|
|
4033
|
-
id: match.id
|
|
4034
|
-
}, {
|
|
4035
|
-
lastUsedAt: new Date()
|
|
4036
|
-
});
|
|
4037
|
-
}
|
|
4038
|
-
return 'valid';
|
|
4039
|
-
}
|
|
4040
|
-
info(row) {
|
|
4041
|
-
const status = !row.isActive ? 'revoked' : isExpired(row.expiresAt) ? 'expired' : 'active';
|
|
4042
|
-
return {
|
|
4043
|
-
id: row.id,
|
|
4044
|
-
flowId: row.flowId,
|
|
4045
|
-
name: row.name,
|
|
4046
|
-
keyPrefix: row.keyPrefix,
|
|
4047
|
-
isActive: row.isActive,
|
|
4048
|
-
status,
|
|
4049
|
-
expiresAt: row.expiresAt,
|
|
4050
|
-
lastUsedAt: row.lastUsedAt,
|
|
4051
|
-
createdAt: row.createdAt
|
|
4052
|
-
};
|
|
4053
|
-
}
|
|
4054
|
-
}
|
|
4055
|
-
FlowApiKeyService = _ts_decorate([
|
|
4056
|
-
(0,common_.Injectable)({
|
|
4057
|
-
scope: common_.Scope.REQUEST
|
|
4058
|
-
}),
|
|
4059
|
-
_ts_param(0, (0,common_.Inject)(entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D)),
|
|
4060
|
-
_ts_metadata("design:type", Function),
|
|
4061
|
-
_ts_metadata("design:paramtypes", [
|
|
4062
|
-
typeof entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D
|
|
4063
|
-
])
|
|
4064
|
-
], FlowApiKeyService);
|
|
4065
|
-
function isExpired(expiresAt) {
|
|
4066
|
-
return !!expiresAt && expiresAt.getTime() <= Date.now();
|
|
4067
|
-
}
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
},
|
|
4071
|
-
5229(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4072
|
-
/* import */ var _flusys_nestjs_shared__rspack_import_0 = __webpack_require__(3422);
|
|
4073
|
-
/* import */ var _flusys_nestjs_shared_classes__rspack_import_1 = __webpack_require__(6937);
|
|
4074
|
-
/* import */ var _flusys_nestjs_shared_modules__rspack_import_2 = __webpack_require__(1236);
|
|
4075
|
-
/* import */ var _nestjs_common__rspack_import_3 = __webpack_require__(6157);
|
|
4076
|
-
/* import */ var typeorm__rspack_import_4 = __webpack_require__(7158);
|
|
4077
|
-
/* import */ var _config_event_actions_js__rspack_import_11 = __webpack_require__(982);
|
|
4078
|
-
/* import */ var _config_message_keys_js__rspack_import_10 = __webpack_require__(5732);
|
|
4079
|
-
/* import */ var _entities_entity_definition_entity_js__rspack_import_5 = __webpack_require__(3189);
|
|
4080
|
-
/* import */ var _entities_flow_definition_entity_js__rspack_import_6 = __webpack_require__(3770);
|
|
4081
|
-
/* import */ var _flow_engine_flow_graph_validator_js__rspack_import_7 = __webpack_require__(3186);
|
|
4082
|
-
/* import */ var _flow_engine_flow_types_js__rspack_import_8 = __webpack_require__(6256);
|
|
4083
|
-
/* import */ var _entity_builder_datasource_provider_js__rspack_import_9 = __webpack_require__(424);
|
|
4084
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
4085
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4086
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
4087
|
-
r = Reflect.decorate(decorators, target, key, desc);
|
|
4088
|
-
} else {
|
|
4089
|
-
for(var i = decorators.length - 1; i >= 0; i--){
|
|
4090
|
-
if (d = decorators[i]) {
|
|
4091
|
-
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
}
|
|
4095
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
4096
|
-
}
|
|
4097
|
-
function _ts_metadata(metadataKey, metadataValue) {
|
|
4098
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
4099
|
-
return Reflect.metadata(metadataKey, metadataValue);
|
|
4100
|
-
}
|
|
4101
|
-
}
|
|
4102
|
-
function _ts_param(paramIndex, decorator) {
|
|
4103
|
-
return function(target, key) {
|
|
4104
|
-
decorator(target, key, paramIndex);
|
|
4105
|
-
};
|
|
4106
|
-
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
4407
|
|
|
4112
4408
|
|
|
4113
4409
|
|
|
@@ -4117,21 +4413,28 @@ function _ts_param(paramIndex, decorator) {
|
|
|
4117
4413
|
|
|
4118
4414
|
|
|
4119
4415
|
const defaultFlowPermissionCode = (slug)=>`entity_builder.flow.${slug}.execute`;
|
|
4120
|
-
const
|
|
4416
|
+
/** The rule a `permission` URL asks callers to pass: its own, or the flow's execute permission. */ const flowPermissions = (endpoint, slug)=>endpoint.permissions ?? (0,_flow_engine_flow_permission_logic_js__rspack_import_13/* .permissionAction */.s)(defaultFlowPermissionCode(slug));
|
|
4417
|
+
const flowNotFound = ()=>new _nestjs_common__rspack_import_4.NotFoundException({
|
|
4121
4418
|
message: 'Flow not found.',
|
|
4122
|
-
messageKey:
|
|
4419
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NOT_FOUND */.wu.NOT_FOUND
|
|
4420
|
+
});
|
|
4421
|
+
/** A flow that has been published at least once: its columns are what callers run. */ const isPublished = (flow)=>flow.version > 0;
|
|
4422
|
+
/** The versioned part of a flow (or of a draft), in a fixed key order so two snapshots compare as text. */ function snapshotOf(source) {
|
|
4423
|
+
return Object.fromEntries(_interfaces_flow_definition_interface_js__rspack_import_15/* .FLOW_VERSIONED_FIELDS.filter */.v.filter((key)=>source[key] !== undefined).map((key)=>[
|
|
4424
|
+
key,
|
|
4425
|
+
source[key]
|
|
4426
|
+
]));
|
|
4427
|
+
}
|
|
4428
|
+
const sameSnapshot = (a, b)=>JSON.stringify(a) === JSON.stringify(b);
|
|
4429
|
+
/** What the designer edits: the saved draft, or the published flow when there is none. */ const workingCopy = (flow)=>({
|
|
4430
|
+
...snapshotOf(flow),
|
|
4431
|
+
...flow.draft
|
|
4123
4432
|
});
|
|
4124
|
-
/**
|
|
4433
|
+
/** What running a flow needs; everything about a run is on its Request steps. */ function toFlowSettings(dto) {
|
|
4125
4434
|
return {
|
|
4126
4435
|
slug: dto.slug,
|
|
4127
|
-
authMode: dto.authMode ?? 'jwt',
|
|
4128
|
-
runAs: dto.runAs ?? 'caller',
|
|
4129
|
-
isTransactional: dto.isTransactional ?? false,
|
|
4130
|
-
bodyType: dto.bodyType ?? 'object',
|
|
4131
|
-
inputSchema: dto.inputSchema ?? [],
|
|
4132
4436
|
nodes: dto.nodes ?? [],
|
|
4133
|
-
edges: dto.edges ?? []
|
|
4134
|
-
timeoutMs: dto.timeoutMs ?? _flow_engine_flow_types_js__rspack_import_8/* .FLOW_LIMITS.DEFAULT_TIMEOUT_MS */.Or.DEFAULT_TIMEOUT_MS
|
|
4437
|
+
edges: dto.edges ?? []
|
|
4135
4438
|
};
|
|
4136
4439
|
}
|
|
4137
4440
|
class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import_1.ApiService {
|
|
@@ -4140,11 +4443,14 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4140
4443
|
provider;
|
|
4141
4444
|
permissionCache;
|
|
4142
4445
|
actionRegistry;
|
|
4143
|
-
|
|
4144
|
-
|
|
4446
|
+
notifications;
|
|
4447
|
+
email;
|
|
4448
|
+
companyAccess;
|
|
4449
|
+
constructor(cacheManager, utilsService, provider, permissionCache, actionRegistry, notifications, email, companyAccess){
|
|
4450
|
+
super(_config_event_actions_js__rspack_import_16/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION, cacheManager, utilsService, FlowDefinitionService.name, true, _config_event_actions_js__rspack_import_16/* .ENTITY_BUILDER_EVENT_MODULE */.S, provider, _entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G), this.cacheManager = cacheManager, this.utilsService = utilsService, this.provider = provider, this.permissionCache = permissionCache, this.actionRegistry = actionRegistry, this.notifications = notifications, this.email = email, this.companyAccess = companyAccess;
|
|
4145
4451
|
}
|
|
4146
4452
|
async findOrFail(id) {
|
|
4147
|
-
const flow = await (await this.provider.getRepository(
|
|
4453
|
+
const flow = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
|
|
4148
4454
|
where: {
|
|
4149
4455
|
id
|
|
4150
4456
|
}
|
|
@@ -4152,28 +4458,28 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4152
4458
|
if (!flow) throw flowNotFound();
|
|
4153
4459
|
return flow;
|
|
4154
4460
|
}
|
|
4155
|
-
/** Validation with the real entity and flow lists. Returns errors and warnings without throwing. */ async validate(settings) {
|
|
4156
|
-
const entities = await (await this.provider.getRepository(
|
|
4461
|
+
/** Validation with the real entity and flow lists and the modules this app runs. Returns errors and warnings without throwing. */ async validate(settings) {
|
|
4462
|
+
const entities = await (await this.provider.getRepository(_entities_entity_definition_entity_js__rspack_import_6/* .EntityDefinition */.f)).find({
|
|
4157
4463
|
select: {
|
|
4158
4464
|
code: true
|
|
4159
4465
|
}
|
|
4160
4466
|
});
|
|
4161
|
-
const flows = await (await this.provider.getRepository(
|
|
4162
|
-
return (0,
|
|
4467
|
+
const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find();
|
|
4468
|
+
return (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .validateFlow */.nC)(settings, {
|
|
4163
4469
|
knownEntityCodes: new Set(entities.map((e)=>e.code)),
|
|
4164
4470
|
knownFlows: new Map(flows.map((flow)=>[
|
|
4165
4471
|
flow.slug,
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4472
|
+
callableOf(flow)
|
|
4473
|
+
])),
|
|
4474
|
+
messageServices: {
|
|
4475
|
+
notification: !!this.notifications,
|
|
4476
|
+
email: !!this.email
|
|
4477
|
+
},
|
|
4478
|
+
companyMembers: !!this.companyAccess
|
|
4173
4479
|
});
|
|
4174
4480
|
}
|
|
4175
4481
|
/** Names of the other flows whose `call_flow` nodes run `slug`. */ async callersOf(slug, excludeIds) {
|
|
4176
|
-
const flows = await (await this.provider.getRepository(
|
|
4482
|
+
const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
|
|
4177
4483
|
select: {
|
|
4178
4484
|
id: true,
|
|
4179
4485
|
name: true,
|
|
@@ -4185,20 +4491,83 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4185
4491
|
async assertNotCalled(slug, excludeIds) {
|
|
4186
4492
|
const callers = await this.callersOf(slug, excludeIds);
|
|
4187
4493
|
if (!callers.length) return;
|
|
4188
|
-
throw new
|
|
4494
|
+
throw new _nestjs_common__rspack_import_4.ConflictException({
|
|
4189
4495
|
message: `The flow '${slug}' is called by: ${callers.join(', ')}. Change those flows first.`,
|
|
4190
|
-
messageKey:
|
|
4496
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.CALLED_BY_OTHER_FLOWS */.wu.CALLED_BY_OTHER_FLOWS,
|
|
4191
4497
|
messageVariables: {
|
|
4192
4498
|
slug,
|
|
4193
4499
|
flows: callers.join(', ')
|
|
4194
4500
|
}
|
|
4195
4501
|
});
|
|
4196
4502
|
}
|
|
4503
|
+
/**
|
|
4504
|
+
* Other flows' Call flow steps keep working: the URL name they call is not renamed, the paths they
|
|
4505
|
+
* start at are not dropped, and nothing else they rely on changes under them - access, body,
|
|
4506
|
+
* required inputs, the flow staying on, the call chain staying loop-free and within depth.
|
|
4507
|
+
*/ async assertCallersKept(before, after, live) {
|
|
4508
|
+
if (after.slug !== before.slug) return this.assertNotCalled(before.slug, [
|
|
4509
|
+
before.id
|
|
4510
|
+
]);
|
|
4511
|
+
await this.assertEndpointsNotCalled(before, after);
|
|
4512
|
+
await this.assertCallsStillValid(before, {
|
|
4513
|
+
...after,
|
|
4514
|
+
...live
|
|
4515
|
+
});
|
|
4516
|
+
}
|
|
4517
|
+
/**
|
|
4518
|
+
* Checks each running flow that calls this one, directly or through other flows, against the flows
|
|
4519
|
+
* as they are and as this change leaves them, and refuses the change when it adds a problem. A
|
|
4520
|
+
* caller already broken for another reason does not block it; only what this change breaks does.
|
|
4521
|
+
*/ async assertCallsStillValid(before, after) {
|
|
4522
|
+
const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find();
|
|
4523
|
+
const running = flows.filter((flow)=>flow.id !== before.id && flow.isActive && isPublished(flow) && calledSlugs(flow.nodes).length);
|
|
4524
|
+
if (!running.length) return;
|
|
4525
|
+
const now = new Map(flows.map((flow)=>[
|
|
4526
|
+
flow.slug,
|
|
4527
|
+
callableOf(flow)
|
|
4528
|
+
]));
|
|
4529
|
+
const next = new Map(now).set(before.slug, callableOf(after));
|
|
4530
|
+
const broken = [];
|
|
4531
|
+
for (const caller of running){
|
|
4532
|
+
const settings = toFlowSettings(caller);
|
|
4533
|
+
// Counted, not a set: two steps with the same label can share a problem, and only the new one is added.
|
|
4534
|
+
const existing = new Map();
|
|
4535
|
+
for (const problem of (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .callProblems */.r7)(settings, before.slug, now)){
|
|
4536
|
+
const key = JSON.stringify(problem);
|
|
4537
|
+
existing.set(key, (existing.get(key) ?? 0) + 1);
|
|
4538
|
+
}
|
|
4539
|
+
const added = (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .callProblems */.r7)(settings, before.slug, next).filter((problem)=>{
|
|
4540
|
+
const key = JSON.stringify(problem);
|
|
4541
|
+
const left = existing.get(key) ?? 0;
|
|
4542
|
+
existing.set(key, left - 1);
|
|
4543
|
+
return left <= 0;
|
|
4544
|
+
});
|
|
4545
|
+
if (added.length) broken.push({
|
|
4546
|
+
flow: caller.name,
|
|
4547
|
+
problems: added
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
if (!broken.length) return;
|
|
4551
|
+
const names = broken.map((b)=>b.flow).join(', ');
|
|
4552
|
+
throw new _nestjs_common__rspack_import_4.ConflictException({
|
|
4553
|
+
message: `This change breaks flows that call '${before.slug}': ${names}. Change those flows first.`,
|
|
4554
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.CALLERS_BROKEN */.wu.CALLERS_BROKEN,
|
|
4555
|
+
messageVariables: {
|
|
4556
|
+
slug: before.slug,
|
|
4557
|
+
flows: names
|
|
4558
|
+
},
|
|
4559
|
+
errors: broken.flatMap((b)=>b.problems.map((problem)=>({
|
|
4560
|
+
field: 'flow',
|
|
4561
|
+
flow: b.flow,
|
|
4562
|
+
...problem
|
|
4563
|
+
})))
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4197
4566
|
/** A URL other flows' Call flow steps start at cannot be removed or renamed from under them. */ async assertEndpointsNotCalled(before, after) {
|
|
4198
|
-
const kept = new Set((0,
|
|
4199
|
-
const removed = (0,
|
|
4567
|
+
const kept = new Set((0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(after).map((e)=>e.path));
|
|
4568
|
+
const removed = (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(before).map((e)=>e.path).filter((path)=>!kept.has(path));
|
|
4200
4569
|
if (!removed.length) return;
|
|
4201
|
-
const flows = await (await this.provider.getRepository(
|
|
4570
|
+
const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
|
|
4202
4571
|
select: {
|
|
4203
4572
|
id: true,
|
|
4204
4573
|
name: true,
|
|
@@ -4209,9 +4578,9 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4209
4578
|
const callers = flows.filter((flow)=>flow.id !== before.id && calledTargets(flow.nodes).some((t)=>t.slug === before.slug && t.endpoint === path)).map((flow)=>flow.name);
|
|
4210
4579
|
if (!callers.length) continue;
|
|
4211
4580
|
const url = `api-flows/${before.slug}${path ? `/${path}` : ''}`;
|
|
4212
|
-
throw new
|
|
4581
|
+
throw new _nestjs_common__rspack_import_4.ConflictException({
|
|
4213
4582
|
message: `The URL ${url} is called by: ${callers.join(', ')}. Change those flows first.`,
|
|
4214
|
-
messageKey:
|
|
4583
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.ENDPOINT_CALLED_BY_OTHER_FLOWS */.wu.ENDPOINT_CALLED_BY_OTHER_FLOWS,
|
|
4215
4584
|
messageVariables: {
|
|
4216
4585
|
url,
|
|
4217
4586
|
flows: callers.join(', ')
|
|
@@ -4222,9 +4591,9 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4222
4591
|
async assertSaveable(settings, user, selfId) {
|
|
4223
4592
|
const { errors, warnings } = await this.validate(settings);
|
|
4224
4593
|
if (errors.length) {
|
|
4225
|
-
throw new
|
|
4594
|
+
throw new _nestjs_common__rspack_import_4.BadRequestException({
|
|
4226
4595
|
message: 'The flow has problems.',
|
|
4227
|
-
messageKey:
|
|
4596
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
|
|
4228
4597
|
errors: errors.map((error)=>({
|
|
4229
4598
|
field: 'flow',
|
|
4230
4599
|
...error
|
|
@@ -4232,16 +4601,16 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4232
4601
|
warnings
|
|
4233
4602
|
});
|
|
4234
4603
|
}
|
|
4235
|
-
const clash = await (await this.provider.getRepository(
|
|
4604
|
+
const clash = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
|
|
4236
4605
|
where: {
|
|
4237
4606
|
slug: settings.slug
|
|
4238
4607
|
},
|
|
4239
4608
|
withDeleted: true
|
|
4240
4609
|
});
|
|
4241
4610
|
if (clash && clash.id !== selfId) {
|
|
4242
|
-
throw new
|
|
4611
|
+
throw new _nestjs_common__rspack_import_4.ConflictException({
|
|
4243
4612
|
message: `The URL name '${settings.slug}' is already used by another flow.`,
|
|
4244
|
-
messageKey:
|
|
4613
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.SLUG_TAKEN */.wu.SLUG_TAKEN,
|
|
4245
4614
|
messageVariables: {
|
|
4246
4615
|
slug: settings.slug
|
|
4247
4616
|
}
|
|
@@ -4250,7 +4619,6 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4250
4619
|
await this.assertMayAuthor(settings, user);
|
|
4251
4620
|
}
|
|
4252
4621
|
/** The extra permissions a flow needs from whoever writes it: saving it, or test-running it as an unsaved draft. */ async assertMayAuthor(settings, user) {
|
|
4253
|
-
if (settings.runAs === 'system') await this.assertHasPermission(user, _flusys_nestjs_shared__rspack_import_0.FLOW_DEFINITION_PERMISSIONS.SYSTEM);
|
|
4254
4622
|
if (settings.nodes.some((node)=>node?.type === 'code')) await this.assertHasPermission(user, _flusys_nestjs_shared__rspack_import_0.FLOW_DEFINITION_PERMISSIONS.CODE);
|
|
4255
4623
|
}
|
|
4256
4624
|
async assertMayCreate(user) {
|
|
@@ -4258,13 +4626,13 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4258
4626
|
}
|
|
4259
4627
|
/**
|
|
4260
4628
|
* A test run needs what running the flow for real would: the author permissions for its
|
|
4261
|
-
*
|
|
4262
|
-
* real - a draft the permission to save it, a permission
|
|
4263
|
-
*/ async assertMayTest(settings, user, run) {
|
|
4629
|
+
* code nodes (a saved flow as much as a draft). A live run also needs what makes it
|
|
4630
|
+
* real - a draft the permission to save it, a permission URL its own execute permission.
|
|
4631
|
+
*/ async assertMayTest(settings, endpoint, user, run) {
|
|
4264
4632
|
await this.assertMayAuthor(settings, user);
|
|
4265
4633
|
if (run.mode !== 'live') return;
|
|
4266
4634
|
if (run.draft) await this.assertHasPermission(user, run.saved ? _flusys_nestjs_shared__rspack_import_0.FLOW_DEFINITION_PERMISSIONS.UPDATE : _flusys_nestjs_shared__rspack_import_0.FLOW_DEFINITION_PERMISSIONS.CREATE);
|
|
4267
|
-
if (
|
|
4635
|
+
if (endpoint?.authMode === 'permission') await this.permissionCache.assertPermissionLogic(user, flowPermissions(endpoint, settings.slug));
|
|
4268
4636
|
}
|
|
4269
4637
|
assertHasPermission(user, permission) {
|
|
4270
4638
|
return this.permissionCache.assertPermission(user, permission);
|
|
@@ -4274,37 +4642,171 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4274
4642
|
dto
|
|
4275
4643
|
]){
|
|
4276
4644
|
await this.assertSaveable(toFlowSettings(d), user);
|
|
4277
|
-
|
|
4278
|
-
d.
|
|
4279
|
-
d.bodyType ??= 'object';
|
|
4280
|
-
d.authMode ??= 'jwt';
|
|
4281
|
-
d.runAs ??= 'caller';
|
|
4645
|
+
d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_9/* .sealApiKeys */.ue)(d.nodes);
|
|
4646
|
+
d.version = 0;
|
|
4282
4647
|
}
|
|
4283
4648
|
}
|
|
4284
|
-
|
|
4649
|
+
/**
|
|
4650
|
+
* Checked against what the save produces: the draft of a published flow, the flow itself before its
|
|
4651
|
+
* first publish. An unpublished flow changes in place, so its callers are protected now, not at publish.
|
|
4652
|
+
*/ async beforeUpdateOperation(dto, user, _qr) {
|
|
4285
4653
|
for (const d of Array.isArray(dto) ? dto : [
|
|
4286
4654
|
dto
|
|
4287
4655
|
]){
|
|
4288
4656
|
const existing = await this.findOrFail(d.id);
|
|
4289
|
-
const
|
|
4290
|
-
...existing,
|
|
4657
|
+
const settings = toFlowSettings({
|
|
4658
|
+
...workingCopy(existing),
|
|
4291
4659
|
...stripUndefined(d)
|
|
4292
4660
|
});
|
|
4293
|
-
await this.assertSaveable(
|
|
4294
|
-
if (
|
|
4295
|
-
existing.
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
d.
|
|
4661
|
+
await this.assertSaveable(settings, user, existing.id);
|
|
4662
|
+
if (!isPublished(existing)) await this.assertCallersKept(existing, settings, {
|
|
4663
|
+
isActive: d.isActive ?? existing.isActive,
|
|
4664
|
+
version: existing.version
|
|
4665
|
+
});
|
|
4666
|
+
if (d.nodes) d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_9/* .sealApiKeys */.ue)(d.nodes);
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
/** A published flow keeps running as published: the edits go to its draft (none when they match what is live). */ async convertSingleDtoToEntity(dto, user) {
|
|
4670
|
+
const id = dto.id;
|
|
4671
|
+
if (!id) return super.convertSingleDtoToEntity(dto, user);
|
|
4672
|
+
const existing = await this.findOrFail(id);
|
|
4673
|
+
if (!isPublished(existing)) return super.convertSingleDtoToEntity(dto, user);
|
|
4674
|
+
const draft = snapshotOf({
|
|
4675
|
+
...workingCopy(existing),
|
|
4676
|
+
...stripUndefined(dto)
|
|
4677
|
+
});
|
|
4678
|
+
existing.draft = sameSnapshot(draft, snapshotOf(existing)) ? null : draft;
|
|
4679
|
+
return existing;
|
|
4680
|
+
}
|
|
4681
|
+
/**
|
|
4682
|
+
* Makes the draft (or, before the first publish, the flow as saved) what callers run, as the next
|
|
4683
|
+
* version. It is checked like a save, plus what only going live can break: other flows calling
|
|
4684
|
+
* this one by a URL name or path it no longer has.
|
|
4685
|
+
*/ async publish(id, note, user) {
|
|
4686
|
+
const existing = await this.findOrFail(id);
|
|
4687
|
+
const published = isPublished(existing);
|
|
4688
|
+
if (published && !existing.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
|
|
4689
|
+
message: 'There are no changes to publish.',
|
|
4690
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NOTHING_TO_PUBLISH */.wu.NOTHING_TO_PUBLISH
|
|
4691
|
+
});
|
|
4692
|
+
const next = workingCopy(existing);
|
|
4693
|
+
const settings = toFlowSettings(next);
|
|
4694
|
+
await this.assertSaveable(settings, user, existing.id);
|
|
4695
|
+
await this.assertCallersKept(existing, settings, {
|
|
4696
|
+
isActive: next.isActive ?? existing.isActive,
|
|
4697
|
+
version: existing.version + 1
|
|
4698
|
+
});
|
|
4699
|
+
const qr = await this.provider.getQueryRunner();
|
|
4700
|
+
try {
|
|
4701
|
+
await qr.connect();
|
|
4702
|
+
await qr.startTransaction();
|
|
4703
|
+
const versions = qr.manager.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p);
|
|
4704
|
+
Object.assign(existing, next, {
|
|
4705
|
+
draft: null,
|
|
4706
|
+
version: existing.version + 1,
|
|
4707
|
+
updatedById: user?.id ?? null
|
|
4708
|
+
});
|
|
4709
|
+
await qr.manager.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G).save(existing);
|
|
4710
|
+
await versions.save(versions.create({
|
|
4711
|
+
flowId: existing.id,
|
|
4712
|
+
version: existing.version,
|
|
4713
|
+
definition: next,
|
|
4714
|
+
note: note?.trim() || null,
|
|
4715
|
+
publishedAt: new Date(),
|
|
4716
|
+
publishedById: user?.id ?? null
|
|
4717
|
+
}));
|
|
4718
|
+
await qr.commitTransaction();
|
|
4719
|
+
} catch (error) {
|
|
4720
|
+
await qr.rollbackTransaction().catch(()=>undefined);
|
|
4721
|
+
throw error;
|
|
4722
|
+
} finally{
|
|
4723
|
+
await qr.release().catch(()=>undefined);
|
|
4299
4724
|
}
|
|
4725
|
+
await this.clearCaches(existing);
|
|
4726
|
+
await this.provisionPermissions([
|
|
4727
|
+
existing
|
|
4728
|
+
]);
|
|
4729
|
+
return existing;
|
|
4730
|
+
}
|
|
4731
|
+
/** Newest first, without the definitions (`getVersion` returns one in full). */ async listVersions(flowId) {
|
|
4732
|
+
await this.findOrFail(flowId);
|
|
4733
|
+
return (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p)).find({
|
|
4734
|
+
where: {
|
|
4735
|
+
flowId
|
|
4736
|
+
},
|
|
4737
|
+
order: {
|
|
4738
|
+
version: 'DESC'
|
|
4739
|
+
},
|
|
4740
|
+
select: {
|
|
4741
|
+
id: true,
|
|
4742
|
+
flowId: true,
|
|
4743
|
+
version: true,
|
|
4744
|
+
note: true,
|
|
4745
|
+
publishedAt: true,
|
|
4746
|
+
publishedById: true
|
|
4747
|
+
}
|
|
4748
|
+
});
|
|
4749
|
+
}
|
|
4750
|
+
async getVersion(flowId, version) {
|
|
4751
|
+
const row = await (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p)).findOne({
|
|
4752
|
+
where: {
|
|
4753
|
+
flowId,
|
|
4754
|
+
version
|
|
4755
|
+
}
|
|
4756
|
+
});
|
|
4757
|
+
if (!row) throw new _nestjs_common__rspack_import_4.NotFoundException({
|
|
4758
|
+
message: `Version ${version} of this flow was not found.`,
|
|
4759
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.VERSION_NOT_FOUND */.wu.VERSION_NOT_FOUND,
|
|
4760
|
+
messageVariables: {
|
|
4761
|
+
version
|
|
4762
|
+
}
|
|
4763
|
+
});
|
|
4764
|
+
return row;
|
|
4765
|
+
}
|
|
4766
|
+
/**
|
|
4767
|
+
* Puts an old version back into the draft; callers keep the published flow until it is published
|
|
4768
|
+
* again. Not checked like a save (the entities it names may have changed since), but the author
|
|
4769
|
+
* still needs the permissions its code steps call for.
|
|
4770
|
+
*/ async restoreVersion(flowId, version, user) {
|
|
4771
|
+
const flow = await this.findOrFail(flowId);
|
|
4772
|
+
const snapshot = snapshotOf((await this.getVersion(flowId, version)).definition);
|
|
4773
|
+
await this.assertMayAuthor(toFlowSettings(snapshot), user);
|
|
4774
|
+
flow.draft = sameSnapshot(snapshot, snapshotOf(flow)) ? null : snapshot;
|
|
4775
|
+
return this.saveDraft(flow, user);
|
|
4776
|
+
}
|
|
4777
|
+
async discardDraft(flowId, user) {
|
|
4778
|
+
const flow = await this.findOrFail(flowId);
|
|
4779
|
+
if (!flow.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
|
|
4780
|
+
message: 'This flow has no unpublished changes.',
|
|
4781
|
+
messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NO_DRAFT */.wu.NO_DRAFT
|
|
4782
|
+
});
|
|
4783
|
+
flow.draft = null;
|
|
4784
|
+
return this.saveDraft(flow, user);
|
|
4785
|
+
}
|
|
4786
|
+
async saveDraft(flow, user) {
|
|
4787
|
+
await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).update(flow.id, {
|
|
4788
|
+
draft: flow.draft,
|
|
4789
|
+
updatedById: user?.id ?? null
|
|
4790
|
+
});
|
|
4791
|
+
await this.clearCaches(flow);
|
|
4792
|
+
return flow;
|
|
4793
|
+
}
|
|
4794
|
+
async clearCaches(flow) {
|
|
4795
|
+
if (!this.isCacheable) return;
|
|
4796
|
+
await Promise.all([
|
|
4797
|
+
this.clearCacheForAll(),
|
|
4798
|
+
this.clearCacheForId([
|
|
4799
|
+
flow
|
|
4800
|
+
])
|
|
4801
|
+
]);
|
|
4300
4802
|
}
|
|
4301
4803
|
async beforeDeleteOperation(dto, user, _qr) {
|
|
4302
4804
|
const ids = Array.isArray(dto.id) ? dto.id : [
|
|
4303
4805
|
dto.id
|
|
4304
4806
|
];
|
|
4305
|
-
const flows = await (await this.provider.getRepository(
|
|
4807
|
+
const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
|
|
4306
4808
|
where: {
|
|
4307
|
-
id: (0,
|
|
4809
|
+
id: (0,typeorm__rspack_import_5.In)(ids)
|
|
4308
4810
|
},
|
|
4309
4811
|
withDeleted: true
|
|
4310
4812
|
});
|
|
@@ -4312,39 +4814,50 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4312
4814
|
if (dto.type === 'restore') return;
|
|
4313
4815
|
for (const flow of flows)await this.assertNotCalled(flow.slug, ids);
|
|
4314
4816
|
}
|
|
4315
|
-
async
|
|
4316
|
-
await this.provisionPermissions(entities);
|
|
4317
|
-
}
|
|
4318
|
-
async afterUpdateOperation(entities) {
|
|
4319
|
-
await this.provisionPermissions(entities);
|
|
4320
|
-
}
|
|
4321
|
-
async provisionPermissions(flows) {
|
|
4322
|
-
const needing = flows.filter((f)=>f.authMode === 'permission');
|
|
4323
|
-
if (!needing.length) return;
|
|
4817
|
+
/** 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) {
|
|
4324
4818
|
if (!this.actionRegistry) return;
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4819
|
+
const actions = new Map();
|
|
4820
|
+
for (const flow of flows){
|
|
4821
|
+
for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow)){
|
|
4822
|
+
if (endpoint.authMode !== 'permission' || endpoint.permissions) continue;
|
|
4823
|
+
const code = defaultFlowPermissionCode(flow.slug);
|
|
4824
|
+
if (!actions.has(code)) actions.set(code, endpoint.path ? `Run ${flow.name} (${endpoint.path})` : `Run ${flow.name}`);
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
if (actions.size) await this.actionRegistry.registerActions([
|
|
4828
|
+
...actions
|
|
4829
|
+
].map(([code, name])=>({
|
|
4830
|
+
code,
|
|
4831
|
+
name
|
|
4328
4832
|
})));
|
|
4329
4833
|
}
|
|
4330
4834
|
}
|
|
4331
4835
|
FlowDefinitionService = _ts_decorate([
|
|
4332
|
-
(0,
|
|
4333
|
-
scope:
|
|
4836
|
+
(0,_nestjs_common__rspack_import_4.Injectable)({
|
|
4837
|
+
scope: _nestjs_common__rspack_import_4.Scope.REQUEST
|
|
4334
4838
|
}),
|
|
4335
|
-
_ts_param(0, (0,
|
|
4336
|
-
_ts_param(1, (0,
|
|
4337
|
-
_ts_param(2, (0,
|
|
4338
|
-
_ts_param(3, (0,
|
|
4339
|
-
_ts_param(4, (0,
|
|
4340
|
-
_ts_param(4, (0,
|
|
4839
|
+
_ts_param(0, (0,_nestjs_common__rspack_import_4.Inject)('CACHE_INSTANCE')),
|
|
4840
|
+
_ts_param(1, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.UtilsService)),
|
|
4841
|
+
_ts_param(2, (0,_nestjs_common__rspack_import_4.Inject)(_entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D)),
|
|
4842
|
+
_ts_param(3, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService)),
|
|
4843
|
+
_ts_param(4, (0,_nestjs_common__rspack_import_4.Optional)()),
|
|
4844
|
+
_ts_param(4, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared__rspack_import_0.PERMISSION_ACTION_REGISTRY)),
|
|
4845
|
+
_ts_param(5, (0,_nestjs_common__rspack_import_4.Optional)()),
|
|
4846
|
+
_ts_param(5, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.NOTIFICATION_ADAPTER)),
|
|
4847
|
+
_ts_param(6, (0,_nestjs_common__rspack_import_4.Optional)()),
|
|
4848
|
+
_ts_param(6, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.EMAIL_ADAPTER)),
|
|
4849
|
+
_ts_param(7, (0,_nestjs_common__rspack_import_4.Optional)()),
|
|
4850
|
+
_ts_param(7, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.COMPANY_ACCESS_RESOLVER)),
|
|
4341
4851
|
_ts_metadata("design:type", Function),
|
|
4342
4852
|
_ts_metadata("design:paramtypes", [
|
|
4343
4853
|
typeof _flusys_nestjs_shared_classes__rspack_import_1.HybridCache === "undefined" ? Object : _flusys_nestjs_shared_classes__rspack_import_1.HybridCache,
|
|
4344
|
-
typeof
|
|
4345
|
-
typeof
|
|
4346
|
-
typeof
|
|
4347
|
-
typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry
|
|
4854
|
+
typeof _flusys_nestjs_shared_modules__rspack_import_3.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.UtilsService,
|
|
4855
|
+
typeof _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D,
|
|
4856
|
+
typeof _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService,
|
|
4857
|
+
typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry,
|
|
4858
|
+
typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter,
|
|
4859
|
+
typeof IEmailAdapter === "undefined" ? Object : IEmailAdapter,
|
|
4860
|
+
typeof ICompanyAccessResolver === "undefined" ? Object : ICompanyAccessResolver
|
|
4348
4861
|
])
|
|
4349
4862
|
], FlowDefinitionService);
|
|
4350
4863
|
/** 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) {
|
|
@@ -4356,6 +4869,13 @@ FlowDefinitionService = _ts_decorate([
|
|
|
4356
4869
|
};
|
|
4357
4870
|
});
|
|
4358
4871
|
}
|
|
4872
|
+
/** What a Call flow step checks about a flow: it runs only once published and switched on. */ function callableOf(flow) {
|
|
4873
|
+
return {
|
|
4874
|
+
isActive: flow.isActive && isPublished(flow),
|
|
4875
|
+
endpoints: (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow),
|
|
4876
|
+
calls: calledSlugs(flow.nodes)
|
|
4877
|
+
};
|
|
4878
|
+
}
|
|
4359
4879
|
/** Slugs the `call_flow` nodes of a flow run. */ function calledSlugs(nodes) {
|
|
4360
4880
|
return calledTargets(nodes).map((target)=>target.slug);
|
|
4361
4881
|
}
|
|
@@ -4365,10 +4885,14 @@ function stripUndefined(value) {
|
|
|
4365
4885
|
|
|
4366
4886
|
__webpack_require__.d(__webpack_exports__, {
|
|
4367
4887
|
RK: () => (toFlowSettings),
|
|
4368
|
-
Tk: () => (FlowDefinitionService)
|
|
4888
|
+
Tk: () => (FlowDefinitionService),
|
|
4889
|
+
eY: () => (snapshotOf)
|
|
4369
4890
|
}, {
|
|
4370
4891
|
Er: defaultFlowPermissionCode,
|
|
4371
|
-
|
|
4892
|
+
HF: isPublished,
|
|
4893
|
+
YW: flowNotFound,
|
|
4894
|
+
j4: flowPermissions,
|
|
4895
|
+
lw: workingCopy
|
|
4372
4896
|
});
|
|
4373
4897
|
|
|
4374
4898
|
|
|
@@ -4428,8 +4952,11 @@ class FlowExecutionService {
|
|
|
4428
4952
|
await repo.save(repo.create({
|
|
4429
4953
|
flowId: entry.flowId,
|
|
4430
4954
|
flowSlug: entry.flowSlug,
|
|
4955
|
+
flowVersion: entry.flowVersion,
|
|
4431
4956
|
status: entry.result.status,
|
|
4432
4957
|
trigger: entry.trigger,
|
|
4958
|
+
endpointNodeId: entry.endpoint?.nodeId ?? null,
|
|
4959
|
+
endpointPath: entry.endpoint?.path ?? null,
|
|
4433
4960
|
mode: entry.mode,
|
|
4434
4961
|
statusCode: entry.result.statusCode,
|
|
4435
4962
|
input: inputText.length > INPUT_LIMIT_CHARS ? {
|
|
@@ -4480,6 +5007,8 @@ class FlowExecutionService {
|
|
|
4480
5007
|
const where = {};
|
|
4481
5008
|
if (query.flowId) where.flowId = query.flowId;
|
|
4482
5009
|
if (query.status) where.status = query.status;
|
|
5010
|
+
if (query.trigger) where.trigger = query.trigger;
|
|
5011
|
+
if (query.endpointNodeId) where.endpointNodeId = query.endpointNodeId;
|
|
4483
5012
|
const [rows, total] = await repo.findAndCount({
|
|
4484
5013
|
where,
|
|
4485
5014
|
order: {
|
|
@@ -4492,8 +5021,11 @@ class FlowExecutionService {
|
|
|
4492
5021
|
id: true,
|
|
4493
5022
|
flowId: true,
|
|
4494
5023
|
flowSlug: true,
|
|
5024
|
+
flowVersion: true,
|
|
4495
5025
|
status: true,
|
|
4496
5026
|
trigger: true,
|
|
5027
|
+
endpointNodeId: true,
|
|
5028
|
+
endpointPath: true,
|
|
4497
5029
|
mode: true,
|
|
4498
5030
|
statusCode: true,
|
|
4499
5031
|
durationMs: true,
|
|
@@ -4538,7 +5070,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
4538
5070
|
|
|
4539
5071
|
|
|
4540
5072
|
},
|
|
4541
|
-
|
|
5073
|
+
3908(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4542
5074
|
|
|
4543
5075
|
// EXPORTS
|
|
4544
5076
|
__webpack_require__.d(__webpack_exports__, {
|
|
@@ -4548,10 +5080,14 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
4548
5080
|
|
|
4549
5081
|
// EXTERNAL MODULE: external "@flusys/nestjs-shared/classes"
|
|
4550
5082
|
var classes_ = __webpack_require__(6937);
|
|
4551
|
-
|
|
4552
|
-
|
|
5083
|
+
;// CONCATENATED MODULE: external "@flusys/nestjs-shared/exceptions"
|
|
5084
|
+
|
|
5085
|
+
// EXTERNAL MODULE: external "@flusys/nestjs-shared/interfaces"
|
|
5086
|
+
var interfaces_ = __webpack_require__(5103);
|
|
4553
5087
|
// EXTERNAL MODULE: external "@flusys/nestjs-shared/modules"
|
|
4554
5088
|
var modules_ = __webpack_require__(1236);
|
|
5089
|
+
// EXTERNAL MODULE: external "@flusys/nestjs-shared/utils"
|
|
5090
|
+
var utils_ = __webpack_require__(9216);
|
|
4555
5091
|
// EXTERNAL MODULE: external "@nestjs/common"
|
|
4556
5092
|
var common_ = __webpack_require__(6157);
|
|
4557
5093
|
// EXTERNAL MODULE: external "@nestjs/core"
|
|
@@ -4790,7 +5326,7 @@ const BLOCKED_REQUEST_HEADERS = new Set([
|
|
|
4790
5326
|
}
|
|
4791
5327
|
|
|
4792
5328
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/index.ts + 2 modules
|
|
4793
|
-
var rule_engine = __webpack_require__(
|
|
5329
|
+
var rule_engine = __webpack_require__(8552);
|
|
4794
5330
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-config.service.ts
|
|
4795
5331
|
var entity_builder_config_service = __webpack_require__(4281);
|
|
4796
5332
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-datasource.provider.ts
|
|
@@ -4843,6 +5379,8 @@ function _ts_param(paramIndex, decorator) {
|
|
|
4843
5379
|
|
|
4844
5380
|
|
|
4845
5381
|
|
|
5382
|
+
|
|
5383
|
+
|
|
4846
5384
|
|
|
4847
5385
|
|
|
4848
5386
|
|
|
@@ -4853,27 +5391,32 @@ const publishFlowEvent = (eventName, payload, user)=>(0,classes_.publishDomainEv
|
|
|
4853
5391
|
metadata: payload,
|
|
4854
5392
|
user
|
|
4855
5393
|
});
|
|
4856
|
-
/** The run's time budget, whatever a
|
|
5394
|
+
/** The run's time budget, whatever a Request step asks for. */ const flowTimeout = (timeoutMs)=>typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) ? Math.min(Math.max(timeoutMs, flow_types/* .FLOW_LIMITS.MIN_TIMEOUT_MS */.Or.MIN_TIMEOUT_MS), flow_types/* .FLOW_LIMITS.MAX_TIMEOUT_MS */.Or.MAX_TIMEOUT_MS) : flow_types/* .FLOW_LIMITS.DEFAULT_TIMEOUT_MS */.Or.DEFAULT_TIMEOUT_MS;
|
|
4857
5395
|
class FlowExecutorService {
|
|
4858
5396
|
provider;
|
|
4859
5397
|
ruleEngine;
|
|
4860
5398
|
moduleRef;
|
|
4861
5399
|
permissionCache;
|
|
4862
5400
|
config;
|
|
5401
|
+
companyAccess;
|
|
5402
|
+
notifications;
|
|
5403
|
+
email;
|
|
5404
|
+
branchHierarchy;
|
|
4863
5405
|
logger = new common_.Logger(FlowExecutorService.name);
|
|
4864
|
-
constructor(provider, ruleEngine, moduleRef, permissionCache, config){
|
|
5406
|
+
constructor(provider, ruleEngine, moduleRef, permissionCache, config, companyAccess, notifications, email, branchHierarchy){
|
|
4865
5407
|
this.provider = provider;
|
|
4866
5408
|
this.ruleEngine = ruleEngine;
|
|
4867
5409
|
this.moduleRef = moduleRef;
|
|
4868
5410
|
this.permissionCache = permissionCache;
|
|
4869
5411
|
this.config = config;
|
|
5412
|
+
this.companyAccess = companyAccess;
|
|
5413
|
+
this.notifications = notifications;
|
|
5414
|
+
this.email = email;
|
|
5415
|
+
this.branchHierarchy = branchHierarchy;
|
|
4870
5416
|
}
|
|
4871
|
-
async run(
|
|
5417
|
+
async run(flow, options) {
|
|
4872
5418
|
const startedAt = Date.now();
|
|
4873
|
-
const
|
|
4874
|
-
...definition,
|
|
4875
|
-
timeoutMs: flowTimeout(definition.timeoutMs)
|
|
4876
|
-
};
|
|
5419
|
+
const start = (0,flow_types/* .startEndpoint */.IE)(flow, options.startAt);
|
|
4877
5420
|
const state = {
|
|
4878
5421
|
startAt: options.startAt,
|
|
4879
5422
|
input: options.input,
|
|
@@ -4881,14 +5424,14 @@ class FlowExecutorService {
|
|
|
4881
5424
|
context: {},
|
|
4882
5425
|
request: options.request,
|
|
4883
5426
|
user: options.user,
|
|
5427
|
+
authorization: options.authorization,
|
|
4884
5428
|
permissions: this.ruleEngine.createPermissionResolver(options.user),
|
|
4885
5429
|
mode: options.mode,
|
|
4886
|
-
runAs: flow.runAs,
|
|
4887
|
-
systemAuthorized: flow.runAs === 'system' && options.systemAuthorized === true,
|
|
4888
5430
|
steps: 0,
|
|
4889
5431
|
httpCalls: 0,
|
|
5432
|
+
messages: 0,
|
|
4890
5433
|
writes: 0,
|
|
4891
|
-
deadline: startedAt +
|
|
5434
|
+
deadline: startedAt + flowTimeout(start?.timeoutMs),
|
|
4892
5435
|
callStack: [
|
|
4893
5436
|
flow.slug
|
|
4894
5437
|
],
|
|
@@ -4896,15 +5439,7 @@ class FlowExecutorService {
|
|
|
4896
5439
|
trace: []
|
|
4897
5440
|
};
|
|
4898
5441
|
try {
|
|
4899
|
-
|
|
4900
|
-
throw new flow_errors/* .FlowHttpError */.hk(403, 'This flow is not allowed to run.', undefined, {
|
|
4901
|
-
messageKey: message_keys/* .FLOW_ERROR_MESSAGES.RUN_AS_NOT_ALLOWED */.zr.RUN_AS_NOT_ALLOWED,
|
|
4902
|
-
messageVariables: {
|
|
4903
|
-
runAs: String(flow.runAs)
|
|
4904
|
-
}
|
|
4905
|
-
});
|
|
4906
|
-
}
|
|
4907
|
-
await this.execute(flow, state, this.createEnv());
|
|
5442
|
+
await this.execute(flow, start?.isTransactional === true, state, this.createEnv());
|
|
4908
5443
|
return {
|
|
4909
5444
|
...(0,flow_engine/* .successResult */.JK)(state, startedAt),
|
|
4910
5445
|
...state.savedUpTo ? {
|
|
@@ -4934,10 +5469,10 @@ class FlowExecutorService {
|
|
|
4934
5469
|
}
|
|
4935
5470
|
}
|
|
4936
5471
|
/**
|
|
4937
|
-
* Walks `flow`. A flow called from inside a transaction joins it; otherwise a
|
|
4938
|
-
*
|
|
4939
|
-
*/ async execute(flow, state, env) {
|
|
4940
|
-
if (state.qr || !(
|
|
5472
|
+
* Walks `flow`. A flow called from inside a transaction joins it; otherwise a run whose Request step
|
|
5473
|
+
* is transactional (or any test run) gets its own, and its held-back events are published after the commit.
|
|
5474
|
+
*/ async execute(flow, transactional, state, env) {
|
|
5475
|
+
if (state.qr || !(transactional || state.mode === 'dry_run')) {
|
|
4941
5476
|
await new flow_engine/* .FlowEngine */.$I(flow, env).walk(state);
|
|
4942
5477
|
return;
|
|
4943
5478
|
}
|
|
@@ -5016,7 +5551,6 @@ class FlowExecutorService {
|
|
|
5016
5551
|
max: flow_types/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH
|
|
5017
5552
|
});
|
|
5018
5553
|
const flow = await this.findCallable(slug, parent.qr);
|
|
5019
|
-
await this.assertMayCall(flow, parent, env);
|
|
5020
5554
|
const settings = (0,flow_definition_service/* .toFlowSettings */.RK)(flow);
|
|
5021
5555
|
const start = (0,flow_types/* .findEndpoint */.vS)(settings, endpoint);
|
|
5022
5556
|
if (!start) {
|
|
@@ -5025,6 +5559,7 @@ class FlowExecutorService {
|
|
|
5025
5559
|
endpoint: endpoint ?? ''
|
|
5026
5560
|
}, `Flow '${slug}' has no URL '${endpoint ?? ''}'.`);
|
|
5027
5561
|
}
|
|
5562
|
+
await this.assertMayCall(flow, start, parent, env);
|
|
5028
5563
|
const checked = (0,flow_input_validator/* .validateFlowInput */.v)(start.inputSchema, input, start.bodyType);
|
|
5029
5564
|
if (checked.errors.length) {
|
|
5030
5565
|
throw new flow_errors/* .FlowHttpError */.hk(400, 'Validation failed', checked.errors, {
|
|
@@ -5041,15 +5576,14 @@ class FlowExecutorService {
|
|
|
5041
5576
|
context: {},
|
|
5042
5577
|
request: parent.request,
|
|
5043
5578
|
user: parent.user,
|
|
5579
|
+
authorization: parent.authorization,
|
|
5044
5580
|
permissions: parent.permissions,
|
|
5045
5581
|
mode: parent.mode,
|
|
5046
|
-
runAs: flow.authMode === 'internal' ? parent.runAs : settings.runAs,
|
|
5047
|
-
// A function inherits the caller's grant; any other called flow is a saved flow whose own run-as was checked when it was saved.
|
|
5048
|
-
systemAuthorized: flow.authMode === 'internal' ? parent.systemAuthorized === true : settings.runAs === 'system',
|
|
5049
5582
|
steps: parent.steps,
|
|
5050
5583
|
httpCalls: parent.httpCalls,
|
|
5584
|
+
messages: parent.messages,
|
|
5051
5585
|
writes: parent.writes,
|
|
5052
|
-
deadline: Math.min(parent.deadline, Date.now() + flowTimeout(
|
|
5586
|
+
deadline: Math.min(parent.deadline, Date.now() + flowTimeout(start.timeoutMs)),
|
|
5053
5587
|
qr: parent.qr,
|
|
5054
5588
|
callStack: [
|
|
5055
5589
|
...parent.callStack,
|
|
@@ -5064,13 +5598,14 @@ class FlowExecutorService {
|
|
|
5064
5598
|
if (child.pendingEvents === parent.pendingEvents) parent.pendingEvents.length = queued;
|
|
5065
5599
|
};
|
|
5066
5600
|
try {
|
|
5067
|
-
await this.execute(settings, child, env);
|
|
5601
|
+
await this.execute(settings, start.isTransactional, child, env);
|
|
5068
5602
|
} catch (error) {
|
|
5069
5603
|
dropQueued();
|
|
5070
5604
|
throw calledFlowFailure(flow, error);
|
|
5071
5605
|
} finally{
|
|
5072
5606
|
parent.steps = child.steps;
|
|
5073
5607
|
parent.httpCalls = child.httpCalls;
|
|
5608
|
+
parent.messages = child.messages;
|
|
5074
5609
|
parent.writes = child.writes;
|
|
5075
5610
|
}
|
|
5076
5611
|
if (child.response) {
|
|
@@ -5089,7 +5624,8 @@ class FlowExecutorService {
|
|
|
5089
5624
|
where: {
|
|
5090
5625
|
slug,
|
|
5091
5626
|
isActive: true,
|
|
5092
|
-
deletedAt: (0,external_typeorm_.IsNull)()
|
|
5627
|
+
deletedAt: (0,external_typeorm_.IsNull)(),
|
|
5628
|
+
version: (0,external_typeorm_.MoreThan)(0)
|
|
5093
5629
|
}
|
|
5094
5630
|
});
|
|
5095
5631
|
if (!flow) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_NOT_FOUND */.zr.CALLED_FLOW_NOT_FOUND, {
|
|
@@ -5098,21 +5634,23 @@ class FlowExecutorService {
|
|
|
5098
5634
|
return flow;
|
|
5099
5635
|
}
|
|
5100
5636
|
/**
|
|
5101
|
-
*
|
|
5102
|
-
*
|
|
5103
|
-
*
|
|
5104
|
-
*/ async assertMayCall(flow, parent, env) {
|
|
5105
|
-
|
|
5106
|
-
if (
|
|
5637
|
+
* The called URL's own access, checked against the run's user: any flow may call a function or a
|
|
5638
|
+
* public URL; a signed-in one needs a user, a permission one the user holding it. An API-key URL
|
|
5639
|
+
* is never called from a flow, which has no key to send.
|
|
5640
|
+
*/ async assertMayCall(flow, endpoint, parent, env) {
|
|
5641
|
+
const mode = endpoint.authMode;
|
|
5642
|
+
if (mode === 'public' || mode === 'internal') return;
|
|
5643
|
+
if (mode === 'api_key' || !parent.user) {
|
|
5107
5644
|
throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_NOT_ALLOWED */.zr.CALLED_FLOW_NOT_ALLOWED, {
|
|
5108
5645
|
flow: flow.slug,
|
|
5109
|
-
authMode:
|
|
5110
|
-
}, `Flow '${flow.slug}' (${
|
|
5646
|
+
authMode: mode
|
|
5647
|
+
}, `Flow '${flow.slug}' (${mode}) cannot be called by this caller.`);
|
|
5111
5648
|
}
|
|
5112
|
-
if (
|
|
5649
|
+
if (mode === 'permission') await env.assertPermissions((0,flow_definition_service/* .flowPermissions */.j4)(endpoint, flow.slug), parent.user);
|
|
5113
5650
|
}
|
|
5114
5651
|
createEnv() {
|
|
5115
5652
|
const entityServices = new Map();
|
|
5653
|
+
/** One lookup per user for the whole run, however many steps ask. */ const access = new Map();
|
|
5116
5654
|
let permissions;
|
|
5117
5655
|
const env = {
|
|
5118
5656
|
ruleEngine: this.ruleEngine,
|
|
@@ -5135,29 +5673,48 @@ class FlowExecutorService {
|
|
|
5135
5673
|
atomic: (state, work)=>this.atomic(state, work),
|
|
5136
5674
|
commit: (state)=>this.commitProgress(state),
|
|
5137
5675
|
http: flowHttpRequest,
|
|
5676
|
+
notifications: this.notifications,
|
|
5677
|
+
email: this.email,
|
|
5678
|
+
...this.companyAccess ? {
|
|
5679
|
+
companyMembers: (companyId, branchIds)=>this.companyAccess.getCompanyMembers(companyId, branchIds)
|
|
5680
|
+
} : {},
|
|
5681
|
+
...this.branchHierarchy ? {
|
|
5682
|
+
branchTree: (branchIds)=>this.branchHierarchy.getDescendantIds(branchIds, {
|
|
5683
|
+
includeSelf: true
|
|
5684
|
+
})
|
|
5685
|
+
} : {},
|
|
5686
|
+
companyAccess: (userId)=>{
|
|
5687
|
+
if (!access.has(userId)) access.set(userId, this.companyAccess ? this.companyAccess.getUserAccess(userId) : Promise.resolve({
|
|
5688
|
+
companies: [],
|
|
5689
|
+
branches: []
|
|
5690
|
+
}));
|
|
5691
|
+
return access.get(userId);
|
|
5692
|
+
},
|
|
5138
5693
|
publish: (eventName, payload, user)=>publishFlowEvent(eventName, payload, user),
|
|
5139
|
-
|
|
5694
|
+
assertPermissions: async (logic, user)=>{
|
|
5140
5695
|
if (!user) throw new common_.UnauthorizedException({
|
|
5141
5696
|
message: 'Authentication required',
|
|
5142
5697
|
messageKey: constants_.ERROR_MESSAGES.UNAUTHORIZED
|
|
5143
5698
|
});
|
|
5144
|
-
if (!this.permissionCache.isCacheConfigured()) throw new
|
|
5699
|
+
if (!this.permissionCache.isCacheConfigured()) throw new __rspack_external__flusys_nestjs_shared_exceptions_3edf22e9.PermissionSystemUnavailableException();
|
|
5145
5700
|
permissions ??= this.permissionCache.getPermissions({
|
|
5146
5701
|
userId: user.id,
|
|
5147
5702
|
companyId: user.companyId,
|
|
5148
5703
|
branchId: user.branchId
|
|
5149
5704
|
});
|
|
5150
|
-
const
|
|
5151
|
-
if (!
|
|
5152
|
-
code
|
|
5153
|
-
], 'AND');
|
|
5705
|
+
const result = (0,utils_.evaluatePermissionLogic)(logic, await permissions);
|
|
5706
|
+
if (!result.passed) throw new __rspack_external__flusys_nestjs_shared_exceptions_3edf22e9.InsufficientPermissionsException(result.missingPermissions, result.operator);
|
|
5154
5707
|
}
|
|
5155
5708
|
};
|
|
5156
5709
|
return env;
|
|
5157
5710
|
}
|
|
5158
|
-
/** After commit, so listeners see committed data. A failing publish never turns a completed run into an error. */ async publishPendingEvents(events, user) {
|
|
5711
|
+
/** 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) {
|
|
5159
5712
|
for (const event of events){
|
|
5160
5713
|
try {
|
|
5714
|
+
if ('delivery' in event) {
|
|
5715
|
+
await event.send();
|
|
5716
|
+
continue;
|
|
5717
|
+
}
|
|
5161
5718
|
if ('eventName' in event) {
|
|
5162
5719
|
await publishFlowEvent(event.eventName, event.payload, user);
|
|
5163
5720
|
continue;
|
|
@@ -5176,6 +5733,10 @@ class FlowExecutorService {
|
|
|
5176
5733
|
user
|
|
5177
5734
|
});
|
|
5178
5735
|
} catch (error) {
|
|
5736
|
+
if ('delivery' in event) {
|
|
5737
|
+
this.logger.error(`Could not send the message of "${event.delivery}" after the commit: ${error.message}`);
|
|
5738
|
+
continue;
|
|
5739
|
+
}
|
|
5179
5740
|
const name = 'eventName' in event ? event.eventName : `${event.entityCode}.${event.action}`;
|
|
5180
5741
|
this.logger.error(`Could not publish ${name}: ${error.message}`);
|
|
5181
5742
|
}
|
|
@@ -5189,13 +5750,25 @@ FlowExecutorService = _ts_decorate([
|
|
|
5189
5750
|
_ts_param(2, (0,common_.Inject)(core_.ModuleRef)),
|
|
5190
5751
|
_ts_param(3, (0,common_.Inject)(modules_.SharedPermissionCacheService)),
|
|
5191
5752
|
_ts_param(4, (0,common_.Inject)(entity_builder_config_service/* .EntityBuilderConfigService */.E)),
|
|
5753
|
+
_ts_param(5, (0,common_.Optional)()),
|
|
5754
|
+
_ts_param(5, (0,common_.Inject)(interfaces_.COMPANY_ACCESS_RESOLVER)),
|
|
5755
|
+
_ts_param(6, (0,common_.Optional)()),
|
|
5756
|
+
_ts_param(6, (0,common_.Inject)(interfaces_.NOTIFICATION_ADAPTER)),
|
|
5757
|
+
_ts_param(7, (0,common_.Optional)()),
|
|
5758
|
+
_ts_param(7, (0,common_.Inject)(interfaces_.EMAIL_ADAPTER)),
|
|
5759
|
+
_ts_param(8, (0,common_.Optional)()),
|
|
5760
|
+
_ts_param(8, (0,common_.Inject)(interfaces_.BRANCH_HIERARCHY_RESOLVER)),
|
|
5192
5761
|
_ts_metadata("design:type", Function),
|
|
5193
5762
|
_ts_metadata("design:paramtypes", [
|
|
5194
5763
|
typeof entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D,
|
|
5195
5764
|
typeof rule_engine/* .RuleEngineService */.iI === "undefined" ? Object : rule_engine/* .RuleEngineService */.iI,
|
|
5196
5765
|
typeof core_.ModuleRef === "undefined" ? Object : core_.ModuleRef,
|
|
5197
5766
|
typeof modules_.SharedPermissionCacheService === "undefined" ? Object : modules_.SharedPermissionCacheService,
|
|
5198
|
-
typeof entity_builder_config_service/* .EntityBuilderConfigService */.E === "undefined" ? Object : entity_builder_config_service/* .EntityBuilderConfigService */.E
|
|
5767
|
+
typeof entity_builder_config_service/* .EntityBuilderConfigService */.E === "undefined" ? Object : entity_builder_config_service/* .EntityBuilderConfigService */.E,
|
|
5768
|
+
typeof ICompanyAccessResolver === "undefined" ? Object : ICompanyAccessResolver,
|
|
5769
|
+
typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter,
|
|
5770
|
+
typeof IEmailAdapter === "undefined" ? Object : IEmailAdapter,
|
|
5771
|
+
typeof IBranchHierarchyResolver === "undefined" ? Object : IBranchHierarchyResolver
|
|
5199
5772
|
])
|
|
5200
5773
|
], FlowExecutorService);
|
|
5201
5774
|
/** A rejection or a limit inside the called flow is passed on as it is; anything else fails the calling node with the reason. */ function calledFlowFailure(flow, error) {
|
|
@@ -5238,7 +5811,7 @@ FlowExecutorService = _ts_decorate([
|
|
|
5238
5811
|
/* import */ var _entity_builder_datasource_provider_js__rspack_import_5 = __webpack_require__(424);
|
|
5239
5812
|
/* import */ var _flow_definition_service_js__rspack_import_6 = __webpack_require__(5229);
|
|
5240
5813
|
/* import */ var _flow_execution_service_js__rspack_import_7 = __webpack_require__(7810);
|
|
5241
|
-
/* import */ var _flow_executor_service_js__rspack_import_8 = __webpack_require__(
|
|
5814
|
+
/* import */ var _flow_executor_service_js__rspack_import_8 = __webpack_require__(3908);
|
|
5242
5815
|
function _ts_decorate(decorators, target, key, desc) {
|
|
5243
5816
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5244
5817
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -5308,6 +5881,11 @@ const MAX_QUERY_VALUE_LENGTH = 2000;
|
|
|
5308
5881
|
receivedAt: new Date().toISOString()
|
|
5309
5882
|
};
|
|
5310
5883
|
}
|
|
5884
|
+
/** The caller's own `Authorization` header, kept for Request steps that forward it: only for a signed-in (verified) caller. */ function callerAuthorization(caller, user) {
|
|
5885
|
+
if (!user) return undefined;
|
|
5886
|
+
const value = Object.entries(caller.headers).find(([key])=>key.toLowerCase() === 'authorization')?.[1];
|
|
5887
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
5888
|
+
}
|
|
5311
5889
|
/** A test run's request: the designer's own, with the headers / query / IP the author chose to simulate on top. */ function simulatedCaller(caller, simulated) {
|
|
5312
5890
|
if (!simulated) return caller;
|
|
5313
5891
|
const headers = {
|
|
@@ -5347,13 +5925,13 @@ class FlowRuntimeService {
|
|
|
5347
5925
|
this.executions = executions;
|
|
5348
5926
|
this.definitions = definitions;
|
|
5349
5927
|
}
|
|
5350
|
-
/** A
|
|
5928
|
+
/** A flow never published is not live yet: it looks the same as an unknown one. */ async findActive(slug) {
|
|
5351
5929
|
return (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_2/* .FlowDefinition */.G)).findOne({
|
|
5352
5930
|
where: {
|
|
5353
5931
|
slug,
|
|
5354
5932
|
isActive: true,
|
|
5355
5933
|
deletedAt: (0,typeorm__rspack_import_1.IsNull)(),
|
|
5356
|
-
|
|
5934
|
+
version: (0,typeorm__rspack_import_1.MoreThan)(0)
|
|
5357
5935
|
}
|
|
5358
5936
|
});
|
|
5359
5937
|
}
|
|
@@ -5362,25 +5940,23 @@ class FlowRuntimeService {
|
|
|
5362
5940
|
const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
|
|
5363
5941
|
if (checked.errors.length) {
|
|
5364
5942
|
const result = inputRejected(checked.errors);
|
|
5365
|
-
await this.logWebhook(flow, body, result, startedAt, user, caller);
|
|
5943
|
+
await this.logWebhook(flow, endpoint, body, result, startedAt, user, caller);
|
|
5366
5944
|
return result;
|
|
5367
5945
|
}
|
|
5368
|
-
// A saved flow: saving it as `runAs: system` needed the system permission.
|
|
5369
5946
|
const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow), {
|
|
5370
5947
|
mode: 'live',
|
|
5371
5948
|
user,
|
|
5372
5949
|
input: checked.values,
|
|
5373
5950
|
startAt: endpoint.nodeId,
|
|
5374
5951
|
request: requestInfo(caller),
|
|
5375
|
-
|
|
5376
|
-
|
|
5952
|
+
authorization: callerAuthorization(caller, user),
|
|
5953
|
+
exposeDetails: false
|
|
5377
5954
|
});
|
|
5378
|
-
await this.logWebhook(flow, checked.values, result, startedAt, user, caller);
|
|
5955
|
+
await this.logWebhook(flow, endpoint, checked.values, result, startedAt, user, caller);
|
|
5379
5956
|
return result;
|
|
5380
5957
|
}
|
|
5381
5958
|
/** The designer's "Test run": a saved flow or an unsaved draft. */ async testRun(dto, user, caller) {
|
|
5382
5959
|
let settings;
|
|
5383
|
-
let permissionCode;
|
|
5384
5960
|
let flow = null;
|
|
5385
5961
|
if (dto.flowId) {
|
|
5386
5962
|
flow = await this.definitions.findOrFail(dto.flowId);
|
|
@@ -5388,10 +5964,8 @@ class FlowRuntimeService {
|
|
|
5388
5964
|
if (dto.definition) {
|
|
5389
5965
|
// The designer's current draft, even for a saved flow: `flow` above is kept only to attribute the run.
|
|
5390
5966
|
settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(dto.definition);
|
|
5391
|
-
permissionCode = dto.definition.permissionCode;
|
|
5392
5967
|
} else if (flow) {
|
|
5393
5968
|
settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow);
|
|
5394
|
-
permissionCode = flow.permissionCode;
|
|
5395
5969
|
} else {
|
|
5396
5970
|
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
5397
5971
|
message: 'Send a flowId or a definition to test.',
|
|
@@ -5399,15 +5973,22 @@ class FlowRuntimeService {
|
|
|
5399
5973
|
});
|
|
5400
5974
|
}
|
|
5401
5975
|
const mode = dto.mode ?? 'dry_run';
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
permissionCode
|
|
5406
|
-
}, user, {
|
|
5976
|
+
const endpoint = (0,_flow_engine_flow_types_js__rspack_import_4/* .findEndpoint */.vS)(settings, dto.endpoint);
|
|
5977
|
+
// Checked before anything runs.
|
|
5978
|
+
await this.definitions.assertMayTest(settings, endpoint, user, {
|
|
5407
5979
|
mode,
|
|
5408
5980
|
draft: !!dto.definition,
|
|
5409
5981
|
saved: !!flow
|
|
5410
5982
|
});
|
|
5983
|
+
if (!endpoint) {
|
|
5984
|
+
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
5985
|
+
message: `This flow has no URL '${dto.endpoint ?? ''}'.`,
|
|
5986
|
+
messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.ENDPOINT_NOT_FOUND */.wu.ENDPOINT_NOT_FOUND,
|
|
5987
|
+
messageVariables: {
|
|
5988
|
+
endpoint: dto.endpoint ?? ''
|
|
5989
|
+
}
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5411
5992
|
const validation = await this.definitions.validate(settings);
|
|
5412
5993
|
if (validation.errors.length) {
|
|
5413
5994
|
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
@@ -5419,16 +6000,6 @@ class FlowRuntimeService {
|
|
|
5419
6000
|
}))
|
|
5420
6001
|
});
|
|
5421
6002
|
}
|
|
5422
|
-
const endpoint = (0,_flow_engine_flow_types_js__rspack_import_4/* .findEndpoint */.vS)(settings, dto.endpoint);
|
|
5423
|
-
if (!endpoint) {
|
|
5424
|
-
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
5425
|
-
message: `This flow has no URL '${dto.endpoint ?? ''}'.`,
|
|
5426
|
-
messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.ENDPOINT_NOT_FOUND */.wu.ENDPOINT_NOT_FOUND,
|
|
5427
|
-
messageVariables: {
|
|
5428
|
-
endpoint: dto.endpoint ?? ''
|
|
5429
|
-
}
|
|
5430
|
-
});
|
|
5431
|
-
}
|
|
5432
6003
|
const startedAt = new Date();
|
|
5433
6004
|
const simulated = simulatedCaller(caller, dto.request);
|
|
5434
6005
|
const body = dto.input ?? (endpoint.bodyType === 'list' ? [] : {});
|
|
@@ -5439,13 +6010,15 @@ class FlowRuntimeService {
|
|
|
5439
6010
|
input: checked.values,
|
|
5440
6011
|
startAt: endpoint.nodeId,
|
|
5441
6012
|
request: requestInfo(simulated),
|
|
5442
|
-
|
|
5443
|
-
|
|
6013
|
+
authorization: callerAuthorization(caller, user),
|
|
6014
|
+
exposeDetails: true
|
|
5444
6015
|
});
|
|
5445
6016
|
await this.executions.record({
|
|
5446
6017
|
flowId: flow?.id ?? null,
|
|
5447
6018
|
flowSlug: settings.slug,
|
|
6019
|
+
flowVersion: dto.definition ? null : flow?.version ?? null,
|
|
5448
6020
|
trigger: 'test',
|
|
6021
|
+
endpoint,
|
|
5449
6022
|
mode,
|
|
5450
6023
|
input: body,
|
|
5451
6024
|
result,
|
|
@@ -5459,11 +6032,13 @@ class FlowRuntimeService {
|
|
|
5459
6032
|
warnings: validation.warnings
|
|
5460
6033
|
};
|
|
5461
6034
|
}
|
|
5462
|
-
async logWebhook(flow, input, result, startedAt, user, caller) {
|
|
6035
|
+
async logWebhook(flow, endpoint, input, result, startedAt, user, caller) {
|
|
5463
6036
|
await this.executions.record({
|
|
5464
6037
|
flowId: flow.id,
|
|
5465
6038
|
flowSlug: flow.slug,
|
|
6039
|
+
flowVersion: flow.version,
|
|
5466
6040
|
trigger: 'webhook',
|
|
6041
|
+
endpoint,
|
|
5467
6042
|
mode: 'live',
|
|
5468
6043
|
input,
|
|
5469
6044
|
result,
|
|
@@ -5607,9 +6182,9 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
5607
6182
|
/* import */ var _flusys_nestjs_shared_utils__rspack_import_1 = __webpack_require__(9216);
|
|
5608
6183
|
/* import */ var _nestjs_common__rspack_import_2 = __webpack_require__(6157);
|
|
5609
6184
|
/* import */ var _config_event_actions_js__rspack_import_10 = __webpack_require__(982);
|
|
5610
|
-
/* import */ var
|
|
6185
|
+
/* import */ var _config_message_keys_js__rspack_import_8 = __webpack_require__(5732);
|
|
5611
6186
|
/* import */ var _enums_entity_status_enum_js__rspack_import_9 = __webpack_require__(1313);
|
|
5612
|
-
/* import */ var
|
|
6187
|
+
/* import */ var _enums_field_type_enum_js__rspack_import_7 = __webpack_require__(9614);
|
|
5613
6188
|
/* import */ var _interfaces_generic_record_interface_js__rspack_import_3 = __webpack_require__(3372);
|
|
5614
6189
|
/* import */ var _database_error_mapper_js__rspack_import_4 = __webpack_require__(8323);
|
|
5615
6190
|
/* import */ var _field_value_validator_js__rspack_import_5 = __webpack_require__(3525);
|
|
@@ -5641,6 +6216,18 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5641
6216
|
lt: '<',
|
|
5642
6217
|
lte: '<='
|
|
5643
6218
|
};
|
|
6219
|
+
/** The LIKE pattern of each text-match operator, built from the already escaped text. */ const TEXT_PATTERN = {
|
|
6220
|
+
contains: (t)=>`%${t}%`,
|
|
6221
|
+
not_contains: (t)=>`%${t}%`,
|
|
6222
|
+
starts_with: (t)=>`${t}%`,
|
|
6223
|
+
ends_with: (t)=>`%${t}`
|
|
6224
|
+
};
|
|
6225
|
+
const TEXT_FIELD_TYPES = new Set([
|
|
6226
|
+
_enums_field_type_enum_js__rspack_import_7/* .FieldType.TEXT */.P.TEXT,
|
|
6227
|
+
_enums_field_type_enum_js__rspack_import_7/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
|
|
6228
|
+
_enums_field_type_enum_js__rspack_import_7/* .FieldType.EMAIL */.P.EMAIL,
|
|
6229
|
+
_enums_field_type_enum_js__rspack_import_7/* .FieldType.SINGLE_SELECT */.P.SINGLE_SELECT
|
|
6230
|
+
]);
|
|
5644
6231
|
/**
|
|
5645
6232
|
* Generic CRUD for one dynamically-defined entity, mirroring ApiService's shape
|
|
5646
6233
|
* (transactional writes, lifecycle event publishing) but
|
|
@@ -5692,7 +6279,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5692
6279
|
if (errors.length === 0) return;
|
|
5693
6280
|
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
5694
6281
|
message: 'Validation failed',
|
|
5695
|
-
messageKey:
|
|
6282
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.VALIDATION_FAILED */.j4.VALIDATION_FAILED,
|
|
5696
6283
|
errors
|
|
5697
6284
|
});
|
|
5698
6285
|
}
|
|
@@ -5701,14 +6288,14 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5701
6288
|
const errors = [];
|
|
5702
6289
|
for (const field of this.fields){
|
|
5703
6290
|
const value = values[field.code];
|
|
5704
|
-
if (field.fieldType !==
|
|
6291
|
+
if (field.fieldType !== _enums_field_type_enum_js__rspack_import_7/* .FieldType.RELATION */.P.RELATION || !field.relationTargetEntityId || typeof value !== 'string') continue;
|
|
5705
6292
|
const related = await this.resolveRelated(field.relationTargetEntityId, queryRunner);
|
|
5706
6293
|
const found = await related.exists(value, {
|
|
5707
6294
|
queryRunner
|
|
5708
6295
|
});
|
|
5709
6296
|
if (!found) errors.push({
|
|
5710
6297
|
field: field.code,
|
|
5711
|
-
...(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(
|
|
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, {
|
|
5712
6299
|
label: field.label
|
|
5713
6300
|
})
|
|
5714
6301
|
});
|
|
@@ -5718,7 +6305,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5718
6305
|
notFound(id) {
|
|
5719
6306
|
return new _nestjs_common__rspack_import_2.NotFoundException({
|
|
5720
6307
|
message: `Record '${id}' not found on entity '${this.entityCode}'.`,
|
|
5721
|
-
messageKey:
|
|
6308
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.NOT_FOUND */.j4.NOT_FOUND,
|
|
5722
6309
|
messageVariables: {
|
|
5723
6310
|
id,
|
|
5724
6311
|
entity: this.entityCode
|
|
@@ -5746,7 +6333,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5746
6333
|
if (this.definition.status === _enums_entity_status_enum_js__rspack_import_9/* .EntityStatus.DEPRECATED */.c.DEPRECATED) {
|
|
5747
6334
|
throw new _nestjs_common__rspack_import_2.ForbiddenException({
|
|
5748
6335
|
message: `Entity '${this.definition.code}' has been deprecated and no longer accepts writes.`,
|
|
5749
|
-
messageKey:
|
|
6336
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .ENTITY_DEFINITION_MESSAGES.DEPRECATED */.pL.DEPRECATED,
|
|
5750
6337
|
messageVariables: {
|
|
5751
6338
|
code: this.definition.code
|
|
5752
6339
|
}
|
|
@@ -5774,7 +6361,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5774
6361
|
if (SYSTEM_COLUMNS.has(key)) {
|
|
5775
6362
|
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
5776
6363
|
message: `'${key}' is a system column and cannot be set directly.`,
|
|
5777
|
-
messageKey:
|
|
6364
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.SYSTEM_COLUMN_READONLY */.j4.SYSTEM_COLUMN_READONLY,
|
|
5778
6365
|
messageVariables: {
|
|
5779
6366
|
key
|
|
5780
6367
|
}
|
|
@@ -5783,7 +6370,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5783
6370
|
if (!this.activeFieldCodes.has(key)) {
|
|
5784
6371
|
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
5785
6372
|
message: `'${key}' is not a field on entity '${this.entityCode}'.`,
|
|
5786
|
-
messageKey:
|
|
6373
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.UNKNOWN_FIELD */.j4.UNKNOWN_FIELD,
|
|
5787
6374
|
messageVariables: {
|
|
5788
6375
|
key,
|
|
5789
6376
|
entity: this.entityCode
|
|
@@ -5856,7 +6443,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5856
6443
|
if (!dto.id) {
|
|
5857
6444
|
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
5858
6445
|
message: 'id is required to update a record.',
|
|
5859
|
-
messageKey:
|
|
6446
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.ID_REQUIRED */.j4.ID_REQUIRED
|
|
5860
6447
|
});
|
|
5861
6448
|
}
|
|
5862
6449
|
const { id: _id, ...rest } = dto;
|
|
@@ -5865,6 +6452,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5865
6452
|
/** Soft-deletes the record, or deletes it for good when the entity has soft delete turned off. */ async delete(id, user, options = {}) {
|
|
5866
6453
|
this.ensureActive();
|
|
5867
6454
|
const repository = await this.ensureRepository();
|
|
6455
|
+
const soft = this.definition.isSoftDeleteEnabled;
|
|
5868
6456
|
await this.withTransaction(options, async (queryRunner)=>{
|
|
5869
6457
|
const existing = await queryRunner.manager.findOne(repository.target, {
|
|
5870
6458
|
where: {
|
|
@@ -5872,7 +6460,6 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5872
6460
|
}
|
|
5873
6461
|
});
|
|
5874
6462
|
if (!existing) throw this.notFound(id);
|
|
5875
|
-
const soft = this.definition.isSoftDeleteEnabled;
|
|
5876
6463
|
if (soft) {
|
|
5877
6464
|
await queryRunner.manager.softDelete(repository.target, [
|
|
5878
6465
|
id
|
|
@@ -5898,6 +6485,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5898
6485
|
]
|
|
5899
6486
|
};
|
|
5900
6487
|
}, user);
|
|
6488
|
+
return soft ? 'deleted' : 'purged';
|
|
5901
6489
|
}
|
|
5902
6490
|
async findById(id, options = {}) {
|
|
5903
6491
|
const repository = await this.repositoryFor(options.queryRunner);
|
|
@@ -5944,55 +6532,177 @@ const FILTER_COMPARISON_SQL = {
|
|
|
5944
6532
|
* getFilterQuery/getSortQuery (raw client-supplied keys concatenated into SQL) is
|
|
5945
6533
|
* structurally impossible here since an unknown key is rejected outright. Operators map
|
|
5946
6534
|
* to a fixed SQL table and every value is a bound parameter. */ applyFilter(query, filter) {
|
|
6535
|
+
const postgres = query.connection.options.type === 'postgres';
|
|
5947
6536
|
Object.entries(filter).forEach(([key, value], index)=>{
|
|
5948
6537
|
this.assertKnownColumn(key);
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
if (value !== null && typeof value === 'object') {
|
|
5953
|
-
const op = value.op;
|
|
5954
|
-
if (typeof op === 'string' && !('type' in value) && !_interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_OPS.includes */.ox.includes(op)) throw this.unknownOperator(key, op);
|
|
5955
|
-
throw this.invalidFilterValue(key, 'eq');
|
|
5956
|
-
}
|
|
5957
|
-
query.andWhere(`${column} = :${param}`, {
|
|
5958
|
-
[param]: value
|
|
5959
|
-
});
|
|
5960
|
-
return;
|
|
6538
|
+
if (!(0,_interfaces_generic_record_interface_js__rspack_import_3/* .isRecordFilterCondition */.t1)(value) && value !== null && typeof value === 'object') {
|
|
6539
|
+
const op = value.op;
|
|
6540
|
+
if (typeof op === 'string' && !('type' in value) && !_interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_OPS.includes */.ox.includes(op)) throw this.unknownOperator(key, op);
|
|
5961
6541
|
}
|
|
5962
|
-
const { op, value: operand } = value
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
6542
|
+
const { op, value: operand } = (0,_interfaces_generic_record_interface_js__rspack_import_3/* .isRecordFilterCondition */.t1)(value) ? value : {
|
|
6543
|
+
op: 'eq',
|
|
6544
|
+
value
|
|
6545
|
+
};
|
|
6546
|
+
this.assertOperatorFits(key, op);
|
|
6547
|
+
const [sql, params] = this.conditionSql(`${this.entityCode}.${key}`, key, op, operand, `filter_${index}`, postgres);
|
|
6548
|
+
query.andWhere(sql, params);
|
|
6549
|
+
});
|
|
6550
|
+
return query;
|
|
6551
|
+
}
|
|
6552
|
+
/** One filter as SQL: the column is a checked field code and every value is a bound parameter. */ conditionSql(column, key, op, operand, param, postgres) {
|
|
6553
|
+
const multiSelect = this.fieldTypeOf(key) === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT;
|
|
6554
|
+
switch(_interfaces_generic_record_interface_js__rspack_import_3/* .FILTER_OP_VALUE_KIND */.w9[op]){
|
|
6555
|
+
case 'none':
|
|
6556
|
+
if (op === 'is_null') return [
|
|
6557
|
+
`${column} IS NULL`,
|
|
6558
|
+
{}
|
|
6559
|
+
];
|
|
6560
|
+
if (op === 'not_null') return [
|
|
6561
|
+
`${column} IS NOT NULL`,
|
|
6562
|
+
{}
|
|
6563
|
+
];
|
|
6564
|
+
if (!multiSelect) return [
|
|
6565
|
+
op === 'is_empty' ? `(${column} IS NULL OR ${column} = '')` : `(${column} IS NOT NULL AND ${column} <> '')`,
|
|
6566
|
+
{}
|
|
6567
|
+
];
|
|
6568
|
+
if (postgres) return [
|
|
6569
|
+
op === 'is_empty' ? `(${column} IS NULL OR ${column} = '[]'::jsonb)` : `(${column} IS NOT NULL AND ${column} <> '[]'::jsonb)`,
|
|
6570
|
+
{}
|
|
6571
|
+
];
|
|
6572
|
+
return [
|
|
6573
|
+
op === 'is_empty' ? `(${column} IS NULL OR JSON_LENGTH(${column}) = 0)` : `JSON_LENGTH(${column}) > 0`,
|
|
6574
|
+
{}
|
|
6575
|
+
];
|
|
6576
|
+
case 'list':
|
|
6577
|
+
{
|
|
6578
|
+
const values = this.listOperand(key, op, operand);
|
|
6579
|
+
if (op === 'in') return values.length ? [
|
|
6580
|
+
`${column} IN (:...${param})`,
|
|
6581
|
+
{
|
|
6582
|
+
[param]: values
|
|
5976
6583
|
}
|
|
5977
|
-
|
|
6584
|
+
] : [
|
|
6585
|
+
'1 = 0',
|
|
6586
|
+
{}
|
|
6587
|
+
];
|
|
6588
|
+
if (op === 'not_in') return values.length ? [
|
|
6589
|
+
`${column} NOT IN (:...${param})`,
|
|
6590
|
+
{
|
|
6591
|
+
[param]: values
|
|
6592
|
+
}
|
|
6593
|
+
] : [
|
|
6594
|
+
'1 = 1',
|
|
6595
|
+
{}
|
|
6596
|
+
];
|
|
6597
|
+
const holds = (list, name)=>[
|
|
6598
|
+
postgres ? `${column} @> CAST(:${name} AS jsonb)` : `JSON_CONTAINS(${column}, CAST(:${name} AS JSON))`,
|
|
6599
|
+
{
|
|
6600
|
+
[name]: JSON.stringify(list)
|
|
6601
|
+
}
|
|
6602
|
+
];
|
|
6603
|
+
if (op === 'has_all') return values.length ? holds(values, param) : [
|
|
6604
|
+
'1 = 1',
|
|
6605
|
+
{}
|
|
6606
|
+
];
|
|
6607
|
+
if (!values.length) return [
|
|
6608
|
+
op === 'has_any' ? '1 = 0' : '1 = 1',
|
|
6609
|
+
{}
|
|
6610
|
+
];
|
|
6611
|
+
const each = values.map((v, i)=>holds([
|
|
6612
|
+
v
|
|
6613
|
+
], `${param}_${i}`));
|
|
6614
|
+
const any = `(${each.map(([sql])=>sql).join(' OR ')})`;
|
|
6615
|
+
return [
|
|
6616
|
+
op === 'has_any' ? any : `NOT ${any}`,
|
|
6617
|
+
Object.assign({}, ...each.map(([, params])=>params))
|
|
6618
|
+
];
|
|
6619
|
+
}
|
|
6620
|
+
case 'range':
|
|
6621
|
+
{
|
|
6622
|
+
if (!Array.isArray(operand) || operand.length !== 2 || !operand.every(isFilterScalar)) throw this.invalidFilterValue(key, op);
|
|
6623
|
+
const [from, to] = [
|
|
6624
|
+
`${param}_from`,
|
|
6625
|
+
`${param}_to`
|
|
6626
|
+
];
|
|
6627
|
+
return [
|
|
6628
|
+
`${column} ${op === 'between' ? 'BETWEEN' : 'NOT BETWEEN'} :${from} AND :${to}`,
|
|
6629
|
+
{
|
|
6630
|
+
[from]: operand[0],
|
|
6631
|
+
[to]: operand[1]
|
|
6632
|
+
}
|
|
6633
|
+
];
|
|
6634
|
+
}
|
|
6635
|
+
case 'single':
|
|
6636
|
+
{
|
|
6637
|
+
if (operand === null && (op === 'eq' || op === 'ne')) return [
|
|
6638
|
+
`${column} IS ${op === 'eq' ? '' : 'NOT '}NULL`,
|
|
6639
|
+
{}
|
|
6640
|
+
];
|
|
6641
|
+
if (operand === null || operand === undefined || !isFilterScalar(operand)) throw this.invalidFilterValue(key, op);
|
|
6642
|
+
if (op === 'ieq') return [
|
|
6643
|
+
`LOWER(${column}) = LOWER(:${param})`,
|
|
6644
|
+
{
|
|
6645
|
+
[param]: String(operand)
|
|
6646
|
+
}
|
|
6647
|
+
];
|
|
6648
|
+
const pattern = TEXT_PATTERN[op];
|
|
6649
|
+
if (!pattern) return [
|
|
6650
|
+
`${column} ${FILTER_COMPARISON_SQL[op]} :${param}`,
|
|
6651
|
+
{
|
|
6652
|
+
[param]: operand
|
|
6653
|
+
}
|
|
6654
|
+
];
|
|
6655
|
+
const text = String(operand);
|
|
6656
|
+
if (!text.trim()) throw this.invalidFilterValue(key, op);
|
|
6657
|
+
const like = `${op === 'not_contains' ? 'NOT ' : ''}${postgres ? 'ILIKE' : 'LIKE'}`;
|
|
6658
|
+
return [
|
|
6659
|
+
`${column} ${like} :${param}`,
|
|
6660
|
+
{
|
|
6661
|
+
[param]: pattern(text.replace(/[\\%_]/g, '\\$&'))
|
|
6662
|
+
}
|
|
6663
|
+
];
|
|
6664
|
+
}
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
/** A list of scalars; one scalar (e.g. a single value read from input) counts as a one-item list. */ listOperand(key, op, operand) {
|
|
6668
|
+
const list = isFilterScalar(operand) ? [
|
|
6669
|
+
operand
|
|
6670
|
+
] : operand;
|
|
6671
|
+
if (!Array.isArray(list) || !list.every(isFilterScalar)) throw this.invalidFilterValue(key, op);
|
|
6672
|
+
if (list.length > _interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES) {
|
|
6673
|
+
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
6674
|
+
message: `The '${key}' filter lists more than ${_interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES} values.`,
|
|
6675
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_TOO_MANY_VALUES */.j4.FILTER_TOO_MANY_VALUES,
|
|
6676
|
+
messageVariables: {
|
|
6677
|
+
key,
|
|
6678
|
+
max: _interfaces_generic_record_interface_js__rspack_import_3/* .RECORD_FILTER_LIMITS.MAX_IN_VALUES */.M5.MAX_IN_VALUES
|
|
5978
6679
|
}
|
|
5979
|
-
if (!operand.length) query.andWhere('1 = 0');
|
|
5980
|
-
else query.andWhere(`${column} IN (:...${param})`, {
|
|
5981
|
-
[param]: operand
|
|
5982
|
-
});
|
|
5983
|
-
return;
|
|
5984
|
-
}
|
|
5985
|
-
if (operand === null || operand === undefined || !isFilterScalar(operand)) throw this.invalidFilterValue(key, op);
|
|
5986
|
-
query.andWhere(`${column} ${FILTER_COMPARISON_SQL[op]} :${param}`, {
|
|
5987
|
-
[param]: operand
|
|
5988
6680
|
});
|
|
5989
|
-
}
|
|
5990
|
-
return
|
|
6681
|
+
}
|
|
6682
|
+
return list;
|
|
6683
|
+
}
|
|
6684
|
+
fieldTypeOf(key) {
|
|
6685
|
+
return this.fields.find((f)=>f.code === key && !f.deprecatedAt && !f.isSystemField)?.fieldType;
|
|
6686
|
+
}
|
|
6687
|
+
/** 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
|
+
const type = this.fieldTypeOf(key);
|
|
6689
|
+
const textField = type !== undefined && TEXT_FIELD_TYPES.has(type);
|
|
6690
|
+
const fits = _interfaces_generic_record_interface_js__rspack_import_3/* .TEXT_FILTER_OPS.includes */.wk.includes(op) ? textField : _interfaces_generic_record_interface_js__rspack_import_3/* .LIST_FIELD_FILTER_OPS.includes */.yV.includes(op) ? type === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT : _interfaces_generic_record_interface_js__rspack_import_3/* .EMPTY_FILTER_OPS.includes */.Oh.includes(op) ? textField || type === _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT : op === 'is_null' || op === 'not_null' || type !== _enums_field_type_enum_js__rspack_import_7/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT && type !== _enums_field_type_enum_js__rspack_import_7/* .FieldType.JSON */.P.JSON;
|
|
6691
|
+
if (!fits) {
|
|
6692
|
+
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
6693
|
+
message: `The '${op}' filter cannot be used on '${key}'.`,
|
|
6694
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_OPERATOR_NOT_FOR_FIELD */.j4.FILTER_OPERATOR_NOT_FOR_FIELD,
|
|
6695
|
+
messageVariables: {
|
|
6696
|
+
key,
|
|
6697
|
+
op
|
|
6698
|
+
}
|
|
6699
|
+
});
|
|
6700
|
+
}
|
|
5991
6701
|
}
|
|
5992
6702
|
unknownOperator(key, op) {
|
|
5993
6703
|
return new _nestjs_common__rspack_import_2.BadRequestException({
|
|
5994
6704
|
message: `'${op}' is not a filter operator.`,
|
|
5995
|
-
messageKey:
|
|
6705
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_OPERATOR_UNKNOWN */.j4.FILTER_OPERATOR_UNKNOWN,
|
|
5996
6706
|
messageVariables: {
|
|
5997
6707
|
key,
|
|
5998
6708
|
op,
|
|
@@ -6002,8 +6712,8 @@ const FILTER_COMPARISON_SQL = {
|
|
|
6002
6712
|
}
|
|
6003
6713
|
invalidFilterValue(key, op) {
|
|
6004
6714
|
return new _nestjs_common__rspack_import_2.BadRequestException({
|
|
6005
|
-
message:
|
|
6006
|
-
messageKey:
|
|
6715
|
+
message: `The '${key}' filter has an invalid value for '${op}'.`,
|
|
6716
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.FILTER_VALUE_INVALID */.j4.FILTER_VALUE_INVALID,
|
|
6007
6717
|
messageVariables: {
|
|
6008
6718
|
key,
|
|
6009
6719
|
op
|
|
@@ -6032,7 +6742,7 @@ const FILTER_COMPARISON_SQL = {
|
|
|
6032
6742
|
if (key !== 'id' && !this.presentSystemColumns.has(key) && !this.activeFieldCodes.has(key)) {
|
|
6033
6743
|
throw new _nestjs_common__rspack_import_2.BadRequestException({
|
|
6034
6744
|
message: `'${key}' is not a field on entity '${this.entityCode}'.`,
|
|
6035
|
-
messageKey:
|
|
6745
|
+
messageKey: _config_message_keys_js__rspack_import_8/* .GENERIC_RECORD_MESSAGES.UNKNOWN_FIELD */.j4.UNKNOWN_FIELD,
|
|
6036
6746
|
messageVariables: {
|
|
6037
6747
|
key,
|
|
6038
6748
|
entity: this.entityCode
|
|
@@ -6100,181 +6810,6 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
6100
6810
|
});
|
|
6101
6811
|
|
|
6102
6812
|
|
|
6103
|
-
},
|
|
6104
|
-
5313(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
6105
|
-
/* import */ var node_crypto__rspack_import_0 = __webpack_require__(2561);
|
|
6106
|
-
/* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
|
|
6107
|
-
/* import */ var _enums_field_type_enum_js__rspack_import_2 = __webpack_require__(9614);
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
/** Columns every dynamic table may carry (the `Identity` contract). Everything else is a designed field. */ const SYSTEM_COLUMN_NAMES = [
|
|
6112
|
-
'id',
|
|
6113
|
-
'created_at',
|
|
6114
|
-
'updated_at',
|
|
6115
|
-
'deleted_at',
|
|
6116
|
-
'created_by_id',
|
|
6117
|
-
'updated_by_id',
|
|
6118
|
-
'deleted_by_id'
|
|
6119
|
-
];
|
|
6120
|
-
/** Field types stored as unbounded text or JSON: an index or unique constraint on them fails (MySQL) or breaks on large values (PostgreSQL). */ const NON_INDEXABLE_FIELD_TYPES = [
|
|
6121
|
-
_enums_field_type_enum_js__rspack_import_2/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
|
|
6122
|
-
_enums_field_type_enum_js__rspack_import_2/* .FieldType.JSON */.P.JSON,
|
|
6123
|
-
_enums_field_type_enum_js__rspack_import_2/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT
|
|
6124
|
-
];
|
|
6125
|
-
const isIndexableFieldType = (fieldType)=>!NON_INDEXABLE_FIELD_TYPES.includes(fieldType);
|
|
6126
|
-
const uuidColumn = (name, driver, options = {})=>new typeorm__rspack_import_1.TableColumn({
|
|
6127
|
-
name,
|
|
6128
|
-
...driver === 'postgres' ? {
|
|
6129
|
-
type: 'uuid'
|
|
6130
|
-
} : {
|
|
6131
|
-
type: 'char',
|
|
6132
|
-
length: '36'
|
|
6133
|
-
},
|
|
6134
|
-
...options
|
|
6135
|
-
});
|
|
6136
|
-
function buildSystemColumns(flags, driver) {
|
|
6137
|
-
const columns = [
|
|
6138
|
-
uuidColumn('id', driver, {
|
|
6139
|
-
isPrimary: true,
|
|
6140
|
-
isGenerated: true,
|
|
6141
|
-
generationStrategy: 'uuid'
|
|
6142
|
-
}),
|
|
6143
|
-
new typeorm__rspack_import_1.TableColumn({
|
|
6144
|
-
name: 'created_at',
|
|
6145
|
-
type: 'timestamp',
|
|
6146
|
-
isNullable: false,
|
|
6147
|
-
default: 'CURRENT_TIMESTAMP'
|
|
6148
|
-
}),
|
|
6149
|
-
new typeorm__rspack_import_1.TableColumn({
|
|
6150
|
-
name: 'updated_at',
|
|
6151
|
-
type: 'timestamp',
|
|
6152
|
-
isNullable: false,
|
|
6153
|
-
default: 'CURRENT_TIMESTAMP'
|
|
6154
|
-
})
|
|
6155
|
-
];
|
|
6156
|
-
if (flags.isSoftDeleteEnabled) columns.push(softDeleteColumn());
|
|
6157
|
-
if (flags.isAuditEnabled) columns.push(...auditColumns(driver));
|
|
6158
|
-
return columns;
|
|
6159
|
-
}
|
|
6160
|
-
const softDeleteColumn = ()=>new typeorm__rspack_import_1.TableColumn({
|
|
6161
|
-
name: 'deleted_at',
|
|
6162
|
-
type: 'timestamp',
|
|
6163
|
-
isNullable: true
|
|
6164
|
-
});
|
|
6165
|
-
const auditColumns = (driver)=>[
|
|
6166
|
-
uuidColumn('created_by_id', driver, {
|
|
6167
|
-
isNullable: true
|
|
6168
|
-
}),
|
|
6169
|
-
uuidColumn('updated_by_id', driver, {
|
|
6170
|
-
isNullable: true
|
|
6171
|
-
}),
|
|
6172
|
-
uuidColumn('deleted_by_id', driver, {
|
|
6173
|
-
isNullable: true
|
|
6174
|
-
})
|
|
6175
|
-
];
|
|
6176
|
-
const sameColumns = (columnNames, expected)=>columnNames.length === expected.length && expected.every((c)=>columnNames.includes(c));
|
|
6177
|
-
const NAME_HASH_LENGTH = 8;
|
|
6178
|
-
/**
|
|
6179
|
-
* Deterministic index/constraint name within the driver's identifier limit. The hash of
|
|
6180
|
-
* `table.column` keeps names distinct when the readable part is cut short, and when two
|
|
6181
|
-
* table/column pairs would read the same (`eb_a_b` + `c` vs `eb_a` + `b_c`).
|
|
6182
|
-
*/ function indexName(prefix, table, column, maxLength) {
|
|
6183
|
-
const hash = (0,node_crypto__rspack_import_0.createHash)('sha256').update(`${table}.${column}`).digest('hex').slice(0, NAME_HASH_LENGTH);
|
|
6184
|
-
const room = maxLength - prefix.length - hash.length - 2;
|
|
6185
|
-
return `${prefix}_${`${table}_${column}`.slice(0, room)}_${hash}`;
|
|
6186
|
-
}
|
|
6187
|
-
const plainIndex = (table, column, driver, adapter)=>new typeorm__rspack_import_1.TableIndex({
|
|
6188
|
-
name: indexName('idx', table, column, adapter.maxIdentifierLength(driver)),
|
|
6189
|
-
columnNames: [
|
|
6190
|
-
column
|
|
6191
|
-
]
|
|
6192
|
-
});
|
|
6193
|
-
const tableUnique = (table, column, driver, adapter)=>new typeorm__rspack_import_1.TableUnique({
|
|
6194
|
-
name: indexName('uq', table, column, adapter.maxIdentifierLength(driver)),
|
|
6195
|
-
columnNames: [
|
|
6196
|
-
column
|
|
6197
|
-
]
|
|
6198
|
-
});
|
|
6199
|
-
/** MySQL has no unique constraints (TypeORM refuses them), only unique indexes. */ async function addUnique(qr, table, column, driver, adapter) {
|
|
6200
|
-
const name = indexName('uq', table.name, column, adapter.maxIdentifierLength(driver));
|
|
6201
|
-
if (driver === 'mysql') {
|
|
6202
|
-
await qr.createIndex(table, new typeorm__rspack_import_1.TableIndex({
|
|
6203
|
-
name,
|
|
6204
|
-
columnNames: [
|
|
6205
|
-
column
|
|
6206
|
-
],
|
|
6207
|
-
isUnique: true
|
|
6208
|
-
}));
|
|
6209
|
-
return;
|
|
6210
|
-
}
|
|
6211
|
-
await qr.createUniqueConstraint(table, new typeorm__rspack_import_1.TableUnique({
|
|
6212
|
-
name,
|
|
6213
|
-
columnNames: [
|
|
6214
|
-
column
|
|
6215
|
-
]
|
|
6216
|
-
}));
|
|
6217
|
-
}
|
|
6218
|
-
/** The stored column type, with a `uuid` stored before MySQL support became char(36) resolved to what MySQL accepts. */ function columnType(field, driver, adapter) {
|
|
6219
|
-
const type = field.dbType || adapter.mapFieldType(field.fieldType, driver);
|
|
6220
|
-
return driver === 'mysql' && type === 'uuid' ? 'char' : type;
|
|
6221
|
-
}
|
|
6222
|
-
/**
|
|
6223
|
-
* Column for a field. `enforceNotNull` controls whether the DB column is NOT NULL: true when
|
|
6224
|
-
* the table is brand new (no rows can violate it); false when adding to a table that already
|
|
6225
|
-
* has rows (the NOT NULL is applied afterwards, once the column has been backfilled).
|
|
6226
|
-
*/ function buildFieldColumn(field, driver, adapter, options) {
|
|
6227
|
-
const sizing = adapter.getColumnSizing(field.fieldType, driver);
|
|
6228
|
-
return new typeorm__rspack_import_1.TableColumn({
|
|
6229
|
-
name: field.code,
|
|
6230
|
-
type: columnType(field, driver, adapter),
|
|
6231
|
-
length: sizing.length,
|
|
6232
|
-
precision: sizing.precision ?? undefined,
|
|
6233
|
-
scale: sizing.scale,
|
|
6234
|
-
isNullable: options.enforceNotNull ? field.nullable : true,
|
|
6235
|
-
isUnique: options.withUnique ? field.unique : false
|
|
6236
|
-
});
|
|
6237
|
-
}
|
|
6238
|
-
/** The SQL type text for a field type on a driver, e.g. `varchar(255)`, `numeric(18,4)`, `char(36)`. */ function sqlTypeFor(fieldType, driver, adapter) {
|
|
6239
|
-
const base = adapter.mapFieldType(fieldType, driver);
|
|
6240
|
-
const sizing = adapter.getColumnSizing(fieldType, driver);
|
|
6241
|
-
if (sizing.length) return `${base}(${sizing.length})`;
|
|
6242
|
-
if (sizing.precision !== undefined) return `${base}(${sizing.precision},${sizing.scale ?? 0})`;
|
|
6243
|
-
return base;
|
|
6244
|
-
}
|
|
6245
|
-
/** Coarse type family, so `character varying` (pg) and `varchar` (mysql) compare equal. */ function typeFamily(type) {
|
|
6246
|
-
const t = type.toLowerCase();
|
|
6247
|
-
if (t === 'uuid') return 'uuid';
|
|
6248
|
-
if (t.includes('json')) return 'json';
|
|
6249
|
-
if (t.includes('timestamp') || t === 'datetime') return 'timestamp';
|
|
6250
|
-
if (t === 'date') return 'date';
|
|
6251
|
-
if (t.includes('bool') || t === 'tinyint') return 'boolean';
|
|
6252
|
-
if (t.includes('int') && !t.includes('point')) return 'integer';
|
|
6253
|
-
if (t.includes('numeric') || t.includes('decimal')) return 'numeric';
|
|
6254
|
-
if (t.includes('char') || t === 'varchar') return 'varchar';
|
|
6255
|
-
if (t.includes('text')) return 'text';
|
|
6256
|
-
return t;
|
|
6257
|
-
}
|
|
6258
|
-
|
|
6259
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
6260
|
-
$6: () => (addUnique),
|
|
6261
|
-
Rm: () => (buildSystemColumns),
|
|
6262
|
-
cg: () => (sqlTypeFor),
|
|
6263
|
-
fu: () => (buildFieldColumn),
|
|
6264
|
-
jp: () => (typeFamily),
|
|
6265
|
-
mo: () => (indexName)
|
|
6266
|
-
}, {
|
|
6267
|
-
G7: sameColumns,
|
|
6268
|
-
HK: auditColumns,
|
|
6269
|
-
OG: softDeleteColumn,
|
|
6270
|
-
d4: isIndexableFieldType,
|
|
6271
|
-
gW: NON_INDEXABLE_FIELD_TYPES,
|
|
6272
|
-
mq: SYSTEM_COLUMN_NAMES,
|
|
6273
|
-
uy: tableUnique,
|
|
6274
|
-
v7: plainIndex
|
|
6275
|
-
});
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
6813
|
},
|
|
6279
6814
|
7259(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
6280
6815
|
/* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
|