@flusys/nestjs-entity-builder 9.1.0-rc.1 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +52 -23
  2. package/config/entity-builder.constants.d.ts +2 -0
  3. package/config/message-keys.d.ts +102 -13
  4. package/controllers/definition-bundle.controller.d.ts +15 -0
  5. package/controllers/flow-definition.controller.d.ts +7 -1
  6. package/controllers/flow-runtime.controller.d.ts +16 -8
  7. package/controllers/index.d.ts +1 -1
  8. package/dtos/definition-bundle.dto.d.ts +57 -0
  9. package/dtos/flow.dto.d.ts +13 -20
  10. package/dtos/index.d.ts +1 -0
  11. package/entities/flow-definition.entity.d.ts +3 -9
  12. package/entities/flow-execution.entity.d.ts +3 -0
  13. package/entities/flow-version.entity.d.ts +10 -0
  14. package/entities/index.d.ts +3 -3
  15. package/fesm/21.js +105 -15
  16. package/fesm/458.js +695 -0
  17. package/fesm/{489.js → 582.js} +22 -30
  18. package/fesm/{996.js → 677.js} +1562 -1064
  19. package/fesm/794.js +135 -240
  20. package/fesm/{719.js → 870.js} +2065 -324
  21. package/fesm/{362.js → 897.js} +572 -8
  22. package/fesm/{606.js → 946.js} +621 -296
  23. package/fesm/config/index.js +15 -2
  24. package/fesm/controllers/index.js +9 -9
  25. package/fesm/docs/index.js +7 -4
  26. package/fesm/dtos/index.js +451 -256
  27. package/fesm/entities/index.js +58 -3
  28. package/fesm/guards/index.js +106 -22
  29. package/fesm/index.js +429 -937
  30. package/fesm/interfaces/index.js +19 -2
  31. package/fesm/modules/index.js +10 -627
  32. package/fesm/rule-engine/index.js +12 -3
  33. package/fesm/services/index.js +101 -29
  34. package/flow-engine/flow-api-key.d.ts +3 -7
  35. package/flow-engine/flow-graph.validator.d.ts +5 -4
  36. package/flow-engine/flow-permission-logic.d.ts +2 -0
  37. package/flow-engine/flow-run.types.d.ts +19 -9
  38. package/flow-engine/flow.types.d.ts +115 -11
  39. package/guards/designer-writable.interceptor.d.ts +12 -0
  40. package/guards/index.d.ts +1 -0
  41. package/interfaces/definition-bundle.interface.d.ts +111 -0
  42. package/interfaces/entity-builder-module.interface.d.ts +6 -0
  43. package/interfaces/flow-definition.interface.d.ts +13 -9
  44. package/interfaces/index.d.ts +1 -0
  45. package/package.json +5 -5
  46. package/rule-engine/rule-engine.service.d.ts +1 -0
  47. package/rule-engine/rule-permission.d.ts +3 -7
  48. package/services/definition-bundle.diff.d.ts +62 -0
  49. package/services/definition-bundle.file.d.ts +7 -0
  50. package/services/definition-bundle.service.d.ts +32 -0
  51. package/services/entity-builder-config.service.d.ts +2 -0
  52. package/services/entity-flow-scaffold.d.ts +4 -3
  53. package/services/flow-definition.service.d.ts +26 -12
  54. package/services/flow-execution.service.d.ts +7 -0
  55. package/services/flow-executor.service.d.ts +9 -8
  56. package/services/flow-runtime.service.d.ts +2 -1
  57. package/services/generic-entity.service.d.ts +1 -1
  58. package/services/index.d.ts +3 -1
  59. package/services/schema-evolution.service.d.ts +3 -1
  60. package/services/schema-sync.service.d.ts +6 -2
  61. package/controllers/flow-api-key.controller.d.ts +0 -14
  62. package/entities/flow-api-key.entity.d.ts +0 -10
  63. package/services/flow-api-key.service.d.ts +0 -31
@@ -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 = 996;
12
- export const __rspack_esm_ids = [996];
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,8 +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
107
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/interfaces/generic-record.interface.ts
86
108
  var generic_record_interface = __webpack_require__(3372);
87
109
  ;// CONCATENATED MODULE: external "node:os"
@@ -302,7 +324,7 @@ const timeoutError = (job)=>new flow_errors/* .FlowCodeError */.yL(message_keys/
302
324
  const unavailableError = (node, detail)=>new flow_errors/* .FlowCodeError */.yL(message_keys/* .FLOW_ERROR_MESSAGES.CODE_UNAVAILABLE */.zr.CODE_UNAVAILABLE, {
303
325
  node
304
326
  }, `The code sandbox could not run the code: ${detail}`);
305
- let CodeSandboxPool = class CodeSandboxPool {
327
+ let flow_code_sandbox_CodeSandboxPool = class CodeSandboxPool {
306
328
  idle = [];
307
329
  running = new Map();
308
330
  queue = [];
@@ -432,14 +454,12 @@ let pool;
432
454
  detail: error.message
433
455
  }, error.message);
434
456
  }
435
- pool ??= new CodeSandboxPool();
457
+ pool ??= new flow_code_sandbox_CodeSandboxPool();
436
458
  return pool.run(code, input, timeoutMs, node);
437
459
  }
438
460
 
439
461
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-graph.validator.ts
440
462
  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
463
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/rule-values.ts
444
464
  var rule_values = __webpack_require__(6622);
445
465
  ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-node-handlers.ts
@@ -452,15 +472,7 @@ var rule_values = __webpack_require__(6622);
452
472
 
453
473
 
454
474
 
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) {
475
+ /** With `env`, `lookup` expressions read through the flow (its transaction). */ function evaluationContext(state, env) {
464
476
  const base = {
465
477
  input: state.input,
466
478
  vars: state.vars,
@@ -478,7 +490,6 @@ const PERMISSION_ACTION = {
478
490
  function lookupResolver(env, state) {
479
491
  return {
480
492
  find: async (entityCode, query)=>{
481
- await authorize(env, state, entityCode, 'read');
482
493
  const service = await env.entityService(entityCode, state.qr);
483
494
  const { data, total } = await service.getAll(undefined, {
484
495
  filter: query.filter,
@@ -556,7 +567,7 @@ async function runValidate(cfg, env, state) {
556
567
  passed: true
557
568
  }
558
569
  };
559
- const status = cfg.statusCode ?? (failed.every((check)=>(0,flow_graph_validator/* .usesPermissionFunction */.u)(check.rule)) ? 403 : 400);
570
+ const status = cfg.statusCode ?? (failed.every((check)=>(0,flow_graph_validator/* .usesPermissionFunction */.u9)(check.rule)) ? 403 : 400);
560
571
  const errors = failed.map((check)=>({
561
572
  field: check.field || check.id,
562
573
  message: check.message
@@ -569,21 +580,17 @@ async function runValidate(cfg, env, state) {
569
580
  }
570
581
  });
571
582
  }
572
- /** `{ allowed, missing }` - `missing` lists the codes the caller does not hold. */ async function runPermissionCheck(cfg, env, state) {
573
- const codes = (0,rule_permission/* .parsePermissionCodes */.PS)(cfg.codes);
574
- let branch;
575
- if (cfg.branch === null) branch = null;
576
- else if (cfg.branch !== undefined) branch = (0,rule_permission/* .toBranchArgument */.lH)([
577
- await env.ruleEngine.resolveExpression(cfg.branch, evaluationContext(state, env))
578
- ], 0);
583
+ /** `{ allowed, missing }` - whether the caller passes the AND / OR rule; `missing` lists the codes it names that the caller does not hold. */ async function runPermissionCheck(cfg, env, state) {
579
584
  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 = {
585
+ const granted = state.user ? await permissions.granted(undefined) : [];
586
+ const missing = (0,utils_.permissionLogicCodes)(cfg.permissions).filter((code)=>!(0,utils_.matchesPermission)(granted, code));
587
+ const allowed = (0,utils_.evaluatePermissionLogic)(cfg.permissions, granted).passed;
588
+ return deniedResult(cfg, {
584
589
  allowed,
585
590
  missing
586
- };
591
+ }, allowed);
592
+ }
593
+ /** A denied check: `onDenied: branch` follows the `denied` port, otherwise the run is answered `statusCode` (403). */ function deniedResult(cfg, output, allowed) {
587
594
  if (cfg.onDenied === 'branch') return {
588
595
  output,
589
596
  port: allowed ? 'allowed' : 'denied'
@@ -599,6 +606,68 @@ async function runValidate(cfg, env, state) {
599
606
  output
600
607
  };
601
608
  }
609
+ /** One id, a list of ids, or nothing (undefined / null / empty text), each id once. */ function idList(value) {
610
+ const items = Array.isArray(value) ? value : [
611
+ value
612
+ ];
613
+ return [
614
+ ...new Set(items.filter((item)=>item !== undefined && item !== null && item !== '').map(String))
615
+ ];
616
+ }
617
+ /** One id from an expression's value; anything but non-empty text is none. */ function oneId(value) {
618
+ return typeof value === 'string' && value ? value : null;
619
+ }
620
+ /**
621
+ * `{ userId, currentCompanyId, currentBranchId, companies, branches }` - what the user (the signed-in
622
+ * caller, or the one `userId` names) may work in; none without a user or the company feature. With
623
+ * `companyIds` / `branchIds` it also answers `allowed` and `missing`: every id asked for must be within
624
+ * reach (`branchMatch`, among the branches of `branchCompany`), and a target that resolves to nothing
625
+ * is a denial, never a pass.
626
+ */ async function runCompanyBranchCheck(cfg, env, state) {
627
+ const ctx = evaluationContext(state, env);
628
+ const resolve = async (expression)=>expression === undefined ? undefined : env.ruleEngine.resolveExpression(expression, ctx);
629
+ const asCaller = cfg.subject !== 'user';
630
+ const userId = asCaller ? state.user?.id ?? null : oneId(await resolve(cfg.userId));
631
+ const { companies, branches } = userId ? await env.companyAccess(userId) : {
632
+ companies: [],
633
+ branches: []
634
+ };
635
+ const currentCompanyId = asCaller ? state.user?.companyId ?? null : null;
636
+ const currentBranchId = asCaller ? state.user?.branchId ?? null : null;
637
+ const output = {
638
+ userId,
639
+ currentCompanyId,
640
+ currentBranchId,
641
+ companies,
642
+ branches
643
+ };
644
+ if (cfg.companyIds === undefined && cfg.branchIds === undefined) return {
645
+ output
646
+ };
647
+ const wanted = async (expression)=>expression === undefined ? null : idList(await resolve(expression));
648
+ const wantedCompanies = await wanted(cfg.companyIds);
649
+ const wantedBranches = await wanted(cfg.branchIds);
650
+ const granted = branches.filter((branch)=>cfg.branchCompany !== 'current' || branch.companyId === currentCompanyId).map((branch)=>branch.id);
651
+ const reachable = await reachableBranches(cfg.branchMatch ?? 'direct', granted, wantedBranches, env);
652
+ const missing = {
653
+ companyIds: (wantedCompanies ?? []).filter((id)=>!companies.some((company)=>company.id === id)),
654
+ branchIds: (wantedBranches ?? []).filter((id)=>!reachable.has(id))
655
+ };
656
+ const nothingToCheck = wantedCompanies?.length === 0 || wantedBranches?.length === 0;
657
+ const allowed = !nothingToCheck && !missing.companyIds.length && !missing.branchIds.length;
658
+ return deniedResult(cfg, {
659
+ ...output,
660
+ allowed,
661
+ missing
662
+ }, allowed);
663
+ }
664
+ /** The branches a check passes: the granted ones (direct), and with `within` every branch under them too. */ async function reachableBranches(match, granted, wanted, env) {
665
+ if (match !== 'within' || !wanted?.length || !granted.length || !env.branchTree || wanted.every((id)=>granted.includes(id))) return new Set(granted);
666
+ return new Set([
667
+ ...granted,
668
+ ...await env.branchTree(granted)
669
+ ]);
670
+ }
602
671
  const USES_PERMISSIONS = /\b(permissions|hasPermission|hasAnyPermission|hasAllPermissions)\b/;
603
672
  /** 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
673
  const user = state.user;
@@ -641,16 +710,6 @@ function asId(value) {
641
710
  });
642
711
  return value;
643
712
  }
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
713
  function writeOptions(state) {
655
714
  return state.qr ? {
656
715
  queryRunner: state.qr,
@@ -727,15 +786,16 @@ async function updateRecord(entityCode, service, values, state) {
727
786
  }
728
787
  /** @returns false when the record does not exist and `skip` allows that. */ async function deleteRecord(entityCode, service, id, skipMissing, state) {
729
788
  countWrite(state);
789
+ let action;
730
790
  try {
731
- await service.delete(id, state.user, writeOptions(state));
791
+ action = await service.delete(id, state.user, writeOptions(state));
732
792
  } catch (error) {
733
793
  if (skipMissing && error instanceof common_.NotFoundException) return false;
734
794
  throw error;
735
795
  }
736
796
  if (state.qr) state.pendingEvents.push({
737
797
  entityCode,
738
- action: 'deleted',
798
+ action,
739
799
  row: {
740
800
  id
741
801
  }
@@ -780,8 +840,6 @@ async function insertChildren(node, child, parentId, env, state) {
780
840
  * A write of several records (many, or any children) is all-or-nothing.
781
841
  */ async function runInsert(node, cfg, env, state) {
782
842
  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
843
  const service = await env.entityService(cfg.entityCode, state.qr);
786
844
  const insertOne = async ()=>{
787
845
  const saved = await insertRecord(cfg.entityCode, service, await mapFieldValues(cfg.fields, env, state), state);
@@ -835,8 +893,6 @@ async function insertChildren(node, child, parentId, env, state) {
835
893
  }
836
894
  /** 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
895
  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
896
  const service = await env.entityService(cfg.entityCode, state.qr);
841
897
  if (target === 'one') return {
842
898
  output: (await updateOrCreate(cfg, service, undefined, env, state)).row
@@ -867,7 +923,6 @@ async function insertChildren(node, child, parentId, env, state) {
867
923
  /** 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
924
  const target = writeTarget(cfg);
869
925
  const skipMissing = cfg.onNotFound === 'skip';
870
- await authorize(env, state, cfg.entityCode, 'delete');
871
926
  const service = await env.entityService(cfg.entityCode, state.qr);
872
927
  const idOf = async (item)=>asId(cfg.id === undefined ? itemId(item) : await env.ruleEngine.resolveExpression(cfg.id, evaluationContext(state, env)));
873
928
  if (target === 'one') {
@@ -897,6 +952,377 @@ async function insertChildren(node, child, parentId, env, state) {
897
952
  })
898
953
  };
899
954
  }
955
+ /** 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@,;<>"]+$/;
956
+ const MAX_EMAIL_LENGTH = 254;
957
+ const shown = (value)=>String(typeof value === 'object' && value !== null ? JSON.stringify(value) : value).slice(0, 100);
958
+ /** A value as message text: nothing for null / undefined, JSON for an object. */ function asText(value) {
959
+ if (value === null || value === undefined) return '';
960
+ return typeof value === 'object' ? JSON.stringify(value) : String(value);
961
+ }
962
+ /** 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) {
963
+ 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, {
964
+ max: flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN
965
+ });
966
+ state.messages += count;
967
+ }
968
+ function assertRecipientCount(node, count, max) {
969
+ if (count > max) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.TOO_MANY_RECIPIENTS */.zr.TOO_MANY_RECIPIENTS, {
970
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
971
+ count,
972
+ max
973
+ });
974
+ }
975
+ /** Whatever the module threw, or its `success: false`, fails the step with the reason (a limit stays a limit). */ function sendFailure(key, node, error) {
976
+ if (error instanceof flow_errors/* .FlowKeyedError */.Hp || error instanceof flow_errors/* .FlowHttpError */.hk) return error;
977
+ const detail = error instanceof Error ? error.message : String(error);
978
+ return new flow_errors/* .FlowKeyedError */.Hp(key, {
979
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
980
+ detail
981
+ }, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" could not send: ${detail}`);
982
+ }
983
+ /** Inside a transaction the message waits for the commit, so a run that rolls back sends nothing. */ async function deliver(node, state, send) {
984
+ if (state.qr) {
985
+ state.pendingEvents.push({
986
+ delivery: (0,flow_types/* .nodeLabel */.dA)(node),
987
+ send
988
+ });
989
+ return 'queued';
990
+ }
991
+ await send();
992
+ return 'sent';
993
+ }
994
+ /** 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) {
995
+ const items = (Array.isArray(value) ? value : [
996
+ value
997
+ ]).filter((item)=>item !== undefined && item !== null && item !== '');
998
+ for (const item of items){
999
+ if (typeof item !== 'string' || !flow_types/* .FLOW_ID_PATTERN.test */.E2.test(item)) {
1000
+ throw new flow_errors/* .FlowHttpError */.hk(400, text(shown(item)), undefined, {
1001
+ messageKey: key,
1002
+ messageVariables: {
1003
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
1004
+ value: shown(item)
1005
+ }
1006
+ });
1007
+ }
1008
+ }
1009
+ return [
1010
+ ...new Set(items)
1011
+ ];
1012
+ }
1013
+ 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.`);
1014
+ /** Addresses: one, a list, or comma / semicolon separated text; each once (case aside), in the order given. */ function emailAddresses(node, value) {
1015
+ const items = (Array.isArray(value) ? value : [
1016
+ value
1017
+ ]).filter((item)=>item !== undefined && item !== null);
1018
+ const seen = new Map();
1019
+ for (const item of items){
1020
+ const parts = typeof item === 'string' ? item.split(/[,;]/) : [
1021
+ item
1022
+ ];
1023
+ for (const part of parts){
1024
+ const address = typeof part === 'string' ? part.trim() : part;
1025
+ if (address === '') continue;
1026
+ if (typeof address !== 'string' || address.length > MAX_EMAIL_LENGTH || !EMAIL_PATTERN.test(address)) {
1027
+ throw new flow_errors/* .FlowHttpError */.hk(400, `"${shown(address)}" is not a valid email address.`, undefined, {
1028
+ messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_ADDRESS_INVALID */.zr.EMAIL_ADDRESS_INVALID,
1029
+ messageVariables: {
1030
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
1031
+ address: shown(address)
1032
+ }
1033
+ });
1034
+ }
1035
+ if (!seen.has(address.toLowerCase())) seen.set(address.toLowerCase(), address);
1036
+ }
1037
+ }
1038
+ return [
1039
+ ...seen.values()
1040
+ ];
1041
+ }
1042
+ /** The company a notification shows under: the one given, else the caller's current one. */ function notificationCompany(node, value, given, state) {
1043
+ if (!given) return state.user?.companyId ?? null;
1044
+ if (value === undefined || value === null || value === '') return null;
1045
+ if (typeof value === 'string' && flow_types/* .FLOW_ID_PATTERN.test */.E2.test(value)) return value;
1046
+ throw new flow_errors/* .FlowHttpError */.hk(400, `"${(0,flow_types/* .nodeLabel */.dA)(node)}": "${shown(value)}" is not a company id.`, undefined, {
1047
+ messageKey: message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_COMPANY_INVALID */.zr.NOTIFICATION_COMPANY_INVALID,
1048
+ messageVariables: {
1049
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
1050
+ value: shown(value)
1051
+ }
1052
+ });
1053
+ }
1054
+ /** 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) {
1055
+ countMessages(state, batches.length);
1056
+ if (state.mode === 'dry_run') return {
1057
+ output: {
1058
+ skipped: true,
1059
+ reasonKey: skipKey,
1060
+ count: batches.length,
1061
+ recipients
1062
+ },
1063
+ skipped: true
1064
+ };
1065
+ let status = 'sent';
1066
+ for (const batch of batches)status = await deliver(node, state, ()=>send(batch));
1067
+ return status;
1068
+ }
1069
+ const nobody = (extra = {})=>({
1070
+ output: {
1071
+ skipped: true,
1072
+ reasonKey: message_keys/* .FLOW_SKIP_MESSAGES.NO_RECIPIENTS */.CB.NO_RECIPIENTS,
1073
+ recipients: 0,
1074
+ ...extra
1075
+ },
1076
+ skipped: true
1077
+ });
1078
+ /** 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) {
1079
+ const ctx = evaluationContext(state, env);
1080
+ 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);
1081
+ if (!title) throw new flow_errors/* .FlowHttpError */.hk(400, `The title of "${(0,flow_types/* .nodeLabel */.dA)(node)}" is empty.`, undefined, {
1082
+ messageKey: message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_TITLE_EMPTY */.zr.NOTIFICATION_TITLE_EMPTY,
1083
+ messageVariables: {
1084
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
1085
+ }
1086
+ });
1087
+ const message = cfg.message === undefined ? '' : asText(await env.ruleEngine.resolveExpression(cfg.message, ctx));
1088
+ const data = cfg.data && Object.keys(cfg.data).length ? await mapValues(cfg.data, env, state) : null;
1089
+ return {
1090
+ title,
1091
+ message: message || null,
1092
+ type: cfg.type ?? 'info',
1093
+ data,
1094
+ realtime: cfg.realtime !== false
1095
+ };
1096
+ }
1097
+ /** One notification to the users `userIds` names, or null when that is nobody. */ async function userNotification(node, cfg, env, state) {
1098
+ const ctx = evaluationContext(state, env);
1099
+ const userIds = notificationRecipients(node, cfg.userIds === undefined ? undefined : await env.ruleEngine.resolveExpression(cfg.userIds, ctx));
1100
+ if (!userIds.length) return null;
1101
+ assertRecipientCount(node, userIds.length, flow_types/* .FLOW_LIMITS.MAX_NOTIFICATION_RECIPIENTS */.Or.MAX_NOTIFICATION_RECIPIENTS);
1102
+ const content = await notificationContent(node, cfg, env, state);
1103
+ const companyId = notificationCompany(node, cfg.companyId === undefined ? undefined : await env.ruleEngine.resolveExpression(cfg.companyId, ctx), cfg.companyId !== undefined, state);
1104
+ return {
1105
+ ...content,
1106
+ userIds,
1107
+ companyId
1108
+ };
1109
+ }
1110
+ /** One notification per company that has members, each shown under its own company. */ async function companyNotifications(node, cfg, env, state) {
1111
+ const companyMembers = env.companyMembers;
1112
+ if (!companyMembers) {
1113
+ throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_COMPANY_UNAVAILABLE */.zr.NOTIFICATION_COMPANY_UNAVAILABLE, {
1114
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
1115
+ }, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" notifies companies, which needs the company feature.`);
1116
+ }
1117
+ const ctx = evaluationContext(state, env);
1118
+ const resolve = async (expression)=>expression === undefined ? undefined : env.ruleEngine.resolveExpression(expression, ctx);
1119
+ 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.`);
1120
+ 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.`);
1121
+ if (!companyIds.length) return [];
1122
+ const content = await notificationContent(node, cfg, env, state);
1123
+ const batches = [];
1124
+ let total = 0;
1125
+ for (const companyId of companyIds){
1126
+ const userIds = await companyMembers(companyId, branchIds.length ? branchIds : undefined);
1127
+ total += userIds.length;
1128
+ assertRecipientCount(node, total, flow_types/* .FLOW_LIMITS.MAX_COMPANY_NOTIFICATION_RECIPIENTS */.Or.MAX_COMPANY_NOTIFICATION_RECIPIENTS);
1129
+ if (userIds.length) batches.push({
1130
+ ...content,
1131
+ userIds,
1132
+ companyId
1133
+ });
1134
+ }
1135
+ return batches;
1136
+ }
1137
+ /**
1138
+ * one: `{ status, recipients }`. many / company: `{ status, count, recipients }` (+ `skipped` items
1139
+ * with nobody to notify, in many mode). `status` is `sent`, or `queued` inside a transaction (sent once
1140
+ * it commits). Every message is checked before the first goes out; nobody at all skips the step, and
1141
+ * a test run checks everything but sends nothing.
1142
+ */ async function runSendNotification(node, cfg, env, state) {
1143
+ const notifications = env.notifications;
1144
+ if (!notifications) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_UNAVAILABLE */.zr.NOTIFICATION_UNAVAILABLE, {
1145
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
1146
+ }, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" needs the notification module, which this app does not run.`);
1147
+ const target = cfg.target ?? 'one';
1148
+ let batches;
1149
+ let skippedItems = 0;
1150
+ if (target === 'company') {
1151
+ batches = await companyNotifications(node, cfg, env, state);
1152
+ } else if (target === 'many') {
1153
+ const items = await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN, env, state);
1154
+ const prepared = await eachItem(state, items, ()=>userNotification(node, cfg, env, state));
1155
+ batches = prepared.filter((batch)=>batch !== null);
1156
+ skippedItems = prepared.length - batches.length;
1157
+ } else {
1158
+ batches = [
1159
+ await userNotification(node, cfg, env, state)
1160
+ ].filter((batch)=>batch !== null);
1161
+ }
1162
+ const counts = target === 'one' ? {} : {
1163
+ count: batches.length,
1164
+ ...target === 'many' ? {
1165
+ skipped: skippedItems
1166
+ } : {}
1167
+ };
1168
+ if (!batches.length) return nobody(counts);
1169
+ const recipients = batches.reduce((sum, batch)=>sum + batch.userIds.length, 0);
1170
+ const sent = await sendAll(node, state, batches, message_keys/* .FLOW_SKIP_MESSAGES.NOTIFICATION */.CB.NOTIFICATION, recipients, async (batch)=>{
1171
+ try {
1172
+ await notifications.sendToMany(batch);
1173
+ } catch (error) {
1174
+ throw sendFailure(message_keys/* .FLOW_ERROR_MESSAGES.NOTIFICATION_FAILED */.zr.NOTIFICATION_FAILED, node, error);
1175
+ }
1176
+ });
1177
+ if (typeof sent !== 'string') return sent;
1178
+ return {
1179
+ output: {
1180
+ status: sent,
1181
+ recipients,
1182
+ ...counts
1183
+ }
1184
+ };
1185
+ }
1186
+ /** 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) {
1187
+ const ctx = evaluationContext(state, env);
1188
+ const addresses = async (expression)=>expression === undefined ? [] : emailAddresses(node, await env.ruleEngine.resolveExpression(expression, ctx));
1189
+ const to = await addresses(cfg.to);
1190
+ if (!to.length) return null;
1191
+ // Someone already on `to` gets the email once.
1192
+ const listed = new Set(to.map((address)=>address.toLowerCase()));
1193
+ const notListed = (list)=>{
1194
+ const kept = list.filter((address)=>!listed.has(address.toLowerCase()));
1195
+ kept.forEach((address)=>listed.add(address.toLowerCase()));
1196
+ return kept;
1197
+ };
1198
+ const cc = notListed(await addresses(cfg.cc));
1199
+ const bcc = notListed(await addresses(cfg.bcc));
1200
+ const recipients = to.length + cc.length + bcc.length;
1201
+ assertRecipientCount(node, recipients, flow_types/* .FLOW_LIMITS.MAX_EMAIL_RECIPIENTS */.Or.MAX_EMAIL_RECIPIENTS);
1202
+ const replyTo = await addresses(cfg.replyTo);
1203
+ if (replyTo.length > 1) {
1204
+ throw new flow_errors/* .FlowHttpError */.hk(400, `"${replyTo.join(', ')}" is not a single email address.`, undefined, {
1205
+ messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_ADDRESS_INVALID */.zr.EMAIL_ADDRESS_INVALID,
1206
+ messageVariables: {
1207
+ node: (0,flow_types/* .nodeLabel */.dA)(node),
1208
+ address: shown(replyTo.join(', '))
1209
+ }
1210
+ });
1211
+ }
1212
+ const base = {
1213
+ to,
1214
+ ...cc.length ? {
1215
+ cc
1216
+ } : {},
1217
+ ...bcc.length ? {
1218
+ bcc
1219
+ } : {},
1220
+ ...replyTo.length ? {
1221
+ replyTo: replyTo[0]
1222
+ } : {},
1223
+ ...cfg.emailConfigId ? {
1224
+ emailConfigId: cfg.emailConfigId
1225
+ } : {}
1226
+ };
1227
+ const user = state.user;
1228
+ if (cfg.mode === 'template') {
1229
+ const variables = await mapValues(cfg.variables, env, state);
1230
+ const templateSlug = (cfg.templateSlug ?? '').trim();
1231
+ return {
1232
+ recipients,
1233
+ send: ()=>email.sendTemplate({
1234
+ ...base,
1235
+ templateSlug,
1236
+ variables
1237
+ }, user)
1238
+ };
1239
+ }
1240
+ // A line break in a subject would start a new mail header.
1241
+ 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);
1242
+ if (!subject) throw new flow_errors/* .FlowHttpError */.hk(400, `The subject of "${(0,flow_types/* .nodeLabel */.dA)(node)}" is empty.`, undefined, {
1243
+ messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_SUBJECT_EMPTY */.zr.EMAIL_SUBJECT_EMPTY,
1244
+ messageVariables: {
1245
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
1246
+ }
1247
+ });
1248
+ const html = cfg.format === 'html';
1249
+ const body = cfg.body === undefined ? '' : asText(html ? await env.ruleEngine.resolveHtml(cfg.body, ctx) : await env.ruleEngine.resolveExpression(cfg.body, ctx));
1250
+ if (!body.trim()) throw new flow_errors/* .FlowHttpError */.hk(400, `The message of "${(0,flow_types/* .nodeLabel */.dA)(node)}" is empty.`, undefined, {
1251
+ messageKey: message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_BODY_EMPTY */.zr.EMAIL_BODY_EMPTY,
1252
+ messageVariables: {
1253
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
1254
+ }
1255
+ });
1256
+ return {
1257
+ recipients,
1258
+ send: ()=>email.send({
1259
+ ...base,
1260
+ subject,
1261
+ ...html ? {
1262
+ html: body
1263
+ } : {
1264
+ text: body
1265
+ }
1266
+ }, user)
1267
+ };
1268
+ }
1269
+ /**
1270
+ * one: `{ status, recipients, messageId }`. many: `{ status, count, recipients, skipped, messageIds }`
1271
+ * (one id per email sent, in order). `status` is `sent`, or `queued` inside a transaction (sent once it
1272
+ * commits, no ids yet). Every email is checked before the first goes out; no address at all skips the
1273
+ * step, and a test run checks everything but sends nothing.
1274
+ */ async function runSendEmail(node, cfg, env, state) {
1275
+ const email = env.email;
1276
+ if (!email) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_UNAVAILABLE */.zr.EMAIL_UNAVAILABLE, {
1277
+ node: (0,flow_types/* .nodeLabel */.dA)(node)
1278
+ }, `"${(0,flow_types/* .nodeLabel */.dA)(node)}" needs the email module, which this app does not run.`);
1279
+ const many = cfg.target === 'many';
1280
+ let emails;
1281
+ let skippedItems = 0;
1282
+ if (many) {
1283
+ const items = await resolveList(node, cfg.items, flow_types/* .FLOW_LIMITS.MAX_MESSAGES_PER_RUN */.Or.MAX_MESSAGES_PER_RUN, env, state);
1284
+ const prepared = await eachItem(state, items, ()=>prepareEmail(node, cfg, email, env, state));
1285
+ emails = prepared.filter((item)=>item !== null);
1286
+ skippedItems = prepared.length - emails.length;
1287
+ } else {
1288
+ emails = [
1289
+ await prepareEmail(node, cfg, email, env, state)
1290
+ ].filter((prepared)=>prepared !== null);
1291
+ }
1292
+ if (!emails.length) return nobody(many ? {
1293
+ count: 0,
1294
+ skipped: skippedItems
1295
+ } : {});
1296
+ const recipients = emails.reduce((sum, prepared)=>sum + prepared.recipients, 0);
1297
+ const messageIds = [];
1298
+ const sent = await sendAll(node, state, emails, message_keys/* .FLOW_SKIP_MESSAGES.EMAIL */.CB.EMAIL, recipients, async (prepared)=>{
1299
+ let result;
1300
+ try {
1301
+ result = await prepared.send();
1302
+ } catch (error) {
1303
+ throw sendFailure(message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_FAILED */.zr.EMAIL_FAILED, node, error);
1304
+ }
1305
+ if (!result?.success) throw sendFailure(message_keys/* .FLOW_ERROR_MESSAGES.EMAIL_FAILED */.zr.EMAIL_FAILED, node, result?.error || 'The email was not accepted.');
1306
+ messageIds.push(result.messageId ?? null);
1307
+ });
1308
+ if (typeof sent !== 'string') return sent;
1309
+ if (!many) return {
1310
+ output: {
1311
+ status: sent,
1312
+ recipients,
1313
+ messageId: messageIds[0] ?? null
1314
+ }
1315
+ };
1316
+ return {
1317
+ output: {
1318
+ status: sent,
1319
+ count: emails.length,
1320
+ recipients,
1321
+ skipped: skippedItems,
1322
+ messageIds
1323
+ }
1324
+ };
1325
+ }
900
1326
  /** 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
1327
  const ctx = evaluationContext(state, env);
902
1328
  const c = node.config;
@@ -911,6 +1337,8 @@ async function insertChildren(node, child, parentId, env, state) {
911
1337
  return runCode(node, c, state);
912
1338
  case 'permission_check':
913
1339
  return runPermissionCheck(c, env, state);
1340
+ case 'company_branch_check':
1341
+ return runCompanyBranchCheck(c, env, state);
914
1342
  case 'set':
915
1343
  {
916
1344
  const cfg = c;
@@ -967,7 +1395,6 @@ async function insertChildren(node, child, parentId, env, state) {
967
1395
  case 'entity_get':
968
1396
  {
969
1397
  const cfg = c;
970
- await authorize(env, state, cfg.entityCode, 'read');
971
1398
  const id = asId(await env.ruleEngine.resolveExpression(cfg.id, ctx));
972
1399
  const service = await env.entityService(cfg.entityCode, state.qr);
973
1400
  try {
@@ -984,7 +1411,6 @@ async function insertChildren(node, child, parentId, env, state) {
984
1411
  case 'entity_query':
985
1412
  {
986
1413
  const cfg = c;
987
- await authorize(env, state, cfg.entityCode, 'read');
988
1414
  const service = await env.entityService(cfg.entityCode, state.qr);
989
1415
  const filter = await mapFilter(node, cfg.filter, env, state, 'skip');
990
1416
  const search = cfg.search ? await env.ruleEngine.resolveExpression(cfg.search, ctx) : undefined;
@@ -1034,7 +1460,7 @@ async function insertChildren(node, child, parentId, env, state) {
1034
1460
  const cfg = c;
1035
1461
  const input = cfg.inputList !== undefined ? await env.ruleEngine.resolveExpression(cfg.inputList, evaluationContext(state, env)) : await mapValues(cfg.input, env, state);
1036
1462
  return {
1037
- output: await env.callFlow(cfg.flowSlug, input, state)
1463
+ output: await env.callFlow(cfg.flowSlug, input, state, cfg.endpoint || undefined)
1038
1464
  };
1039
1465
  }
1040
1466
  case 'http_request':
@@ -1061,6 +1487,7 @@ async function insertChildren(node, child, parentId, env, state) {
1061
1487
  k,
1062
1488
  String(v ?? '')
1063
1489
  ]));
1490
+ if (cfg.forwardAuth === true && state.authorization && !Object.keys(headers).some((name)=>name.toLowerCase() === 'authorization')) headers['authorization'] = state.authorization;
1064
1491
  const body = cfg.body && Object.keys(cfg.body).length ? await mapValues(cfg.body, env, state) : undefined;
1065
1492
  const remaining = state.deadline - Date.now();
1066
1493
  if (remaining <= 0) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.OUT_OF_TIME */.zr.OUT_OF_TIME);
@@ -1099,6 +1526,10 @@ async function insertChildren(node, child, parentId, env, state) {
1099
1526
  }
1100
1527
  };
1101
1528
  }
1529
+ case 'send_notification':
1530
+ return runSendNotification(node, c, env, state);
1531
+ case 'send_email':
1532
+ return runSendEmail(node, c, env, state);
1102
1533
  case 'respond':
1103
1534
  {
1104
1535
  const cfg = c;
@@ -1128,7 +1559,8 @@ async function insertChildren(node, child, parentId, env, state) {
1128
1559
 
1129
1560
  const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
1130
1561
  /**
1131
- * Walks a flow graph from its trigger: runs a node, follows the output port it chose, repeats.
1562
+ * Walks a flow graph from the Request step the call came in on (`state.startAt`; left out, the one
1563
+ * on the flow's own URL): runs a node, follows the output port it chose, repeats.
1132
1564
  * `foreach` runs its `each` branch once per item and then continues on `done`; inside a nested
1133
1565
  * loop the outer loop's scope stays reachable as `loop.parent`. A `respond` node
1134
1566
  * ends the whole run. Limits (steps, time) are checked before every node.
@@ -1147,7 +1579,7 @@ const errorMessage = (error)=>error instanceof Error ? error.message : String(er
1147
1579
  /** Runs to completion or throws (FlowNodeError wraps whatever a node threw). */ async walk(state) {
1148
1580
  const trigger = [
1149
1581
  ...this.nodes.values()
1150
- ].find((n)=>n.type === 'trigger');
1582
+ ].find((n)=>n.type === 'trigger' && (state.startAt ? n.id === state.startAt : !(0,flow_types/* .triggerPath */.um)(n)));
1151
1583
  if (!trigger) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.NO_TRIGGER */.zr.NO_TRIGGER);
1152
1584
  await this.run(trigger.id, state);
1153
1585
  }
@@ -1378,14 +1810,16 @@ function successResult(state, startedAt) {
1378
1810
  },
1379
1811
  3186(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1380
1812
  /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
1381
- /* import */ var _config_entity_builder_constants_js__rspack_import_8 = __webpack_require__(6576);
1382
- /* import */ var _config_message_keys_js__rspack_import_7 = __webpack_require__(5732);
1813
+ /* import */ var _config_entity_builder_constants_js__rspack_import_9 = __webpack_require__(6576);
1814
+ /* import */ var _config_message_keys_js__rspack_import_8 = __webpack_require__(5732);
1383
1815
  /* import */ var _interfaces_generic_record_interface_js__rspack_import_1 = __webpack_require__(3372);
1384
- /* import */ var _rule_engine_index_js__rspack_import_2 = __webpack_require__(5920);
1816
+ /* import */ var _rule_engine_index_js__rspack_import_2 = __webpack_require__(8429);
1385
1817
  /* import */ var _rule_engine_rule_ip_js__rspack_import_3 = __webpack_require__(809);
1386
1818
  /* import */ var _rule_engine_rule_permission_js__rspack_import_4 = __webpack_require__(5191);
1387
1819
  /* import */ var _rule_engine_rule_regex_js__rspack_import_5 = __webpack_require__(6308);
1388
1820
  /* import */ var _flow_types_js__rspack_import_6 = __webpack_require__(6256);
1821
+ /* import */ var _flow_api_key_js__rspack_import_7 = __webpack_require__(4816);
1822
+
1389
1823
 
1390
1824
 
1391
1825
 
@@ -1566,37 +2000,37 @@ function isExpression(value) {
1566
2000
  return !!value && typeof value === 'object' && typeof value.type === 'string';
1567
2001
  }
1568
2002
  /** Expression shape check with the engine's limits (no evaluation). */ function expressionProblem(value, depth = 0, options = {}) {
1569
- if (!isExpression(value)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.NOT_AN_EXPRESSION */.IP.NOT_AN_EXPRESSION);
1570
- if (depth > _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_GROUP_DEPTH */.bw.MAX_GROUP_DEPTH) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.TOO_DEEP */.IP.TOO_DEEP);
2003
+ if (!isExpression(value)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.NOT_AN_EXPRESSION */.IP.NOT_AN_EXPRESSION);
2004
+ if (depth > _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_GROUP_DEPTH */.bw.MAX_GROUP_DEPTH) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.TOO_DEEP */.IP.TOO_DEEP);
1571
2005
  switch(value.type){
1572
2006
  case 'literal':
1573
2007
  return null;
1574
2008
  case 'field_ref':
1575
- return typeof value.ref === 'string' && value.ref.length > 0 ? null : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.EMPTY_REFERENCE */.IP.EMPTY_REFERENCE);
2009
+ return typeof value.ref === 'string' && value.ref.length > 0 ? null : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.EMPTY_REFERENCE */.IP.EMPTY_REFERENCE);
1576
2010
  case 'template':
1577
- if (typeof value.template !== 'string') return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.NO_TEMPLATE_TEXT */.IP.NO_TEMPLATE_TEXT);
1578
- return value.template.length <= _rule_engine_index_js__rspack_import_2/* .RULE_TEMPLATE_MAX_LENGTH */.ym ? null : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.TEMPLATE_TOO_LONG */.IP.TEMPLATE_TOO_LONG);
2011
+ if (typeof value.template !== 'string') return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.NO_TEMPLATE_TEXT */.IP.NO_TEMPLATE_TEXT);
2012
+ return value.template.length <= _rule_engine_index_js__rspack_import_2/* .RULE_TEMPLATE_MAX_LENGTH */.ym ? null : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.TEMPLATE_TOO_LONG */.IP.TEMPLATE_TOO_LONG);
1579
2013
  case 'arithmetic':
1580
- return Array.isArray(value.operands) ? firstProblem(value.operands, depth, options) : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.NO_OPERANDS */.IP.NO_OPERANDS);
2014
+ return Array.isArray(value.operands) ? firstProblem(value.operands, depth, options) : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.NO_OPERANDS */.IP.NO_OPERANDS);
1581
2015
  case 'concat':
1582
- return Array.isArray(value.parts) ? firstProblem(value.parts, depth, options) : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.NO_PARTS */.IP.NO_PARTS);
2016
+ return Array.isArray(value.parts) ? firstProblem(value.parts, depth, options) : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.NO_PARTS */.IP.NO_PARTS);
1583
2017
  case 'function':
1584
2018
  return functionProblem(value, depth, options);
1585
2019
  case 'lookup':
1586
2020
  return lookupProblem(value, depth, options);
1587
2021
  default:
1588
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.UNKNOWN_EXPRESSION_TYPE */.IP.UNKNOWN_EXPRESSION_TYPE, {
2022
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.UNKNOWN_EXPRESSION_TYPE */.IP.UNKNOWN_EXPRESSION_TYPE, {
1589
2023
  type: value.type
1590
2024
  });
1591
2025
  }
1592
2026
  }
1593
2027
  function functionProblem(value, depth, options) {
1594
- if (!Array.isArray(value.args)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.NO_ARGUMENTS */.IP.NO_ARGUMENTS);
2028
+ if (!Array.isArray(value.args)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.NO_ARGUMENTS */.IP.NO_ARGUMENTS);
1595
2029
  const arity = FUNCTION_ARITY[value.name];
1596
- if (!arity) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.UNKNOWN_FUNCTION */.IP.UNKNOWN_FUNCTION, {
2030
+ if (!arity) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.UNKNOWN_FUNCTION */.IP.UNKNOWN_FUNCTION, {
1597
2031
  name: String(value.name)
1598
2032
  });
1599
- if (value.args.length < arity[0] || value.args.length > arity[1]) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.WRONG_ARGUMENT_COUNT */.IP.WRONG_ARGUMENT_COUNT, {
2033
+ if (value.args.length < arity[0] || value.args.length > arity[1]) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.WRONG_ARGUMENT_COUNT */.IP.WRONG_ARGUMENT_COUNT, {
1600
2034
  name: value.name,
1601
2035
  min: arity[0],
1602
2036
  max: arity[1]
@@ -1604,7 +2038,7 @@ function functionProblem(value, depth, options) {
1604
2038
  const unitAt = DURATION_ARG[value.name];
1605
2039
  const unit = unitAt === undefined ? undefined : value.args[unitAt];
1606
2040
  if (unit?.type === 'literal' && !_rule_engine_index_js__rspack_import_2/* .RULE_DURATION_UNITS.includes */.qO.includes(unit.value)) {
1607
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.DURATION_UNIT_INVALID */.IP.DURATION_UNIT_INVALID, {
2041
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.DURATION_UNIT_INVALID */.IP.DURATION_UNIT_INVALID, {
1608
2042
  units: _rule_engine_index_js__rspack_import_2/* .RULE_DURATION_UNITS.join */.qO.join(', ')
1609
2043
  });
1610
2044
  }
@@ -1627,18 +2061,18 @@ function functionProblem(value, depth, options) {
1627
2061
  try {
1628
2062
  parsed = (0,_rule_engine_rule_permission_js__rspack_import_4/* .parsePermissionCodes */.PS)(codes.value);
1629
2063
  } catch {
1630
- return (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, {
2064
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .RULE_ENGINE_MESSAGES.TOO_MANY_PERMISSION_CODES */.x3.TOO_MANY_PERMISSION_CODES, {
1631
2065
  max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_PERMISSION_CODES */.bw.MAX_PERMISSION_CODES
1632
2066
  });
1633
2067
  }
1634
- return parsed.length ? null : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.PERMISSION_CODE_REQUIRED */.IP.PERMISSION_CODE_REQUIRED, {
2068
+ return parsed.length ? null : (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.PERMISSION_CODE_REQUIRED */.IP.PERMISSION_CODE_REQUIRED, {
1635
2069
  name: value.name
1636
2070
  });
1637
2071
  }
1638
2072
  if (value.name === 'IP_IN_RANGE') {
1639
2073
  const ranges = literal(1);
1640
2074
  const invalid = ranges.ok ? (0,_rule_engine_rule_ip_js__rspack_import_3/* .invalidIpRange */.I8)(ranges.value) : null;
1641
- return invalid ? (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.IP_RANGE_INVALID */.IP.IP_RANGE_INVALID, {
2075
+ return invalid ? (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.IP_RANGE_INVALID */.IP.IP_RANGE_INVALID, {
1642
2076
  range: invalid
1643
2077
  }) : null;
1644
2078
  }
@@ -1652,13 +2086,13 @@ function functionProblem(value, depth, options) {
1652
2086
  function patternProblem(pattern) {
1653
2087
  switch((0,_rule_engine_rule_regex_js__rspack_import_5/* .rulePatternProblem */._V)(pattern)){
1654
2088
  case 'too_long':
1655
- 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, {
2089
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.REGEX_TOO_LONG */.IP.REGEX_TOO_LONG, {
1656
2090
  max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_REGEX_PATTERN_LENGTH */.bw.MAX_REGEX_PATTERN_LENGTH
1657
2091
  });
1658
2092
  case 'invalid':
1659
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.REGEX_INVALID */.IP.REGEX_INVALID);
2093
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.REGEX_INVALID */.IP.REGEX_INVALID);
1660
2094
  case 'too_complex':
1661
- return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.REGEX_TOO_COMPLEX */.IP.REGEX_TOO_COMPLEX, {
2095
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.REGEX_TOO_COMPLEX */.IP.REGEX_TOO_COMPLEX, {
1662
2096
  max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_REGEX_REPEAT */.bw.MAX_REGEX_REPEAT
1663
2097
  });
1664
2098
  default:
@@ -1666,20 +2100,20 @@ function patternProblem(pattern) {
1666
2100
  }
1667
2101
  }
1668
2102
  function lookupProblem(value, depth, options) {
1669
- if (typeof value.entity !== 'string' || !_config_entity_builder_constants_js__rspack_import_8/* .ENTITY_CODE_PATTERN.test */.vs.test(value.entity)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_ENTITY_INVALID */.IP.LOOKUP_ENTITY_INVALID);
1670
- if (options.knownEntityCodes && !options.knownEntityCodes.has(value.entity)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_ENTITY_UNKNOWN */.IP.LOOKUP_ENTITY_UNKNOWN, {
2103
+ if (typeof value.entity !== 'string' || !_config_entity_builder_constants_js__rspack_import_9/* .ENTITY_CODE_PATTERN.test */.vs.test(value.entity)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_ENTITY_INVALID */.IP.LOOKUP_ENTITY_INVALID);
2104
+ if (options.knownEntityCodes && !options.knownEntityCodes.has(value.entity)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_ENTITY_UNKNOWN */.IP.LOOKUP_ENTITY_UNKNOWN, {
1671
2105
  entity: value.entity
1672
2106
  });
1673
2107
  if (![
1674
2108
  'exists',
1675
2109
  'count',
1676
2110
  'first'
1677
- ].includes(value.mode)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_MODE_INVALID */.IP.LOOKUP_MODE_INVALID);
1678
- if (value.field !== undefined && (typeof value.field !== 'string' || !COLUMN_CODE.test(value.field))) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_FIELD_INVALID */.IP.LOOKUP_FIELD_INVALID);
1679
- if (value.sort !== undefined && (typeof value.sort?.field !== 'string' || !COLUMN_CODE.test(value.sort.field))) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_FIELD_INVALID */.IP.LOOKUP_FIELD_INVALID);
1680
- if (!value.filter || typeof value.filter !== 'object' || Array.isArray(value.filter)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_FILTER_INVALID */.IP.LOOKUP_FILTER_INVALID);
2111
+ ].includes(value.mode)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_MODE_INVALID */.IP.LOOKUP_MODE_INVALID);
2112
+ if (value.field !== undefined && (typeof value.field !== 'string' || !COLUMN_CODE.test(value.field))) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_FIELD_INVALID */.IP.LOOKUP_FIELD_INVALID);
2113
+ if (value.sort !== undefined && (typeof value.sort?.field !== 'string' || !COLUMN_CODE.test(value.sort.field))) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_FIELD_INVALID */.IP.LOOKUP_FIELD_INVALID);
2114
+ if (!value.filter || typeof value.filter !== 'object' || Array.isArray(value.filter)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_FILTER_INVALID */.IP.LOOKUP_FILTER_INVALID);
1681
2115
  for (const [key, item] of Object.entries(value.filter)){
1682
- if (!COLUMN_CODE.test(key)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.LOOKUP_FILTER_INVALID */.IP.LOOKUP_FILTER_INVALID);
2116
+ if (!COLUMN_CODE.test(key)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.LOOKUP_FILTER_INVALID */.IP.LOOKUP_FILTER_INVALID);
1683
2117
  const problem = filterValueProblem(item, depth, options);
1684
2118
  if (problem) return problem;
1685
2119
  }
@@ -1688,13 +2122,13 @@ function lookupProblem(value, depth, options) {
1688
2122
  /** A filter entry: an expression (eq) or `{ op, value? }` with an expression value. */ function filterValueProblem(item, depth, options) {
1689
2123
  if (!(0,_interfaces_generic_record_interface_js__rspack_import_1/* .isRecordFilterCondition */.t1)(item)) {
1690
2124
  const op = item && typeof item === 'object' && !Array.isArray(item) && !('type' in item) ? item.op : undefined;
1691
- if (typeof op === 'string' && !_interfaces_generic_record_interface_js__rspack_import_1/* .RECORD_FILTER_OPS.includes */.ox.includes(op)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.FILTER_OPERATOR_UNKNOWN */.IP.FILTER_OPERATOR_UNKNOWN, {
2125
+ if (typeof op === 'string' && !_interfaces_generic_record_interface_js__rspack_import_1/* .RECORD_FILTER_OPS.includes */.ox.includes(op)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.FILTER_OPERATOR_UNKNOWN */.IP.FILTER_OPERATOR_UNKNOWN, {
1692
2126
  op
1693
2127
  });
1694
2128
  return expressionProblem(item, depth + 1, options);
1695
2129
  }
1696
2130
  if (item.op === 'is_null' || item.op === 'not_null') return null;
1697
- 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, {
2131
+ if (item.value === undefined) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.FILTER_VALUE_REQUIRED */.IP.FILTER_VALUE_REQUIRED, {
1698
2132
  op: item.op
1699
2133
  });
1700
2134
  return expressionProblem(item.value, depth + 1, options);
@@ -1708,8 +2142,8 @@ function firstProblem(items, depth, options) {
1708
2142
  }
1709
2143
  function conditionProblem(node, options) {
1710
2144
  const hasLeft = node?.left !== undefined;
1711
- if (!node || !hasLeft && (typeof node.field !== 'string' || !node.field) || typeof node.comparison !== 'string') return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.CONDITION_INCOMPLETE */.IP.CONDITION_INCOMPLETE);
1712
- if (!_rule_engine_index_js__rspack_import_2/* .RULE_COMPARISON_OPERATORS.includes */.dJ.includes(node.comparison)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.UNKNOWN_COMPARISON */.IP.UNKNOWN_COMPARISON, {
2145
+ if (!node || !hasLeft && (typeof node.field !== 'string' || !node.field) || typeof node.comparison !== 'string') return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.CONDITION_INCOMPLETE */.IP.CONDITION_INCOMPLETE);
2146
+ if (!_rule_engine_index_js__rspack_import_2/* .RULE_COMPARISON_OPERATORS.includes */.dJ.includes(node.comparison)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.UNKNOWN_COMPARISON */.IP.UNKNOWN_COMPARISON, {
1713
2147
  comparison: node.comparison
1714
2148
  });
1715
2149
  if (hasLeft) {
@@ -1725,15 +2159,15 @@ function conditionProblem(node, options) {
1725
2159
  return null;
1726
2160
  }
1727
2161
  /** Rule group shape check with the engine's limits (no evaluation). */ function groupProblem(group, depth = 0, options = {}) {
1728
- if (!group || typeof group !== 'object' || !Array.isArray(group.conditions)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.NOT_A_RULE */.IP.NOT_A_RULE);
2162
+ if (!group || typeof group !== 'object' || !Array.isArray(group.conditions)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.NOT_A_RULE */.IP.NOT_A_RULE);
1729
2163
  const g = group;
1730
- if (depth > _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_GROUP_DEPTH */.bw.MAX_GROUP_DEPTH) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.TOO_DEEP */.IP.TOO_DEEP);
1731
- if (g.conditions.length > _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_CONDITIONS_PER_GROUP */.bw.MAX_CONDITIONS_PER_GROUP) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.TOO_MANY_CONDITIONS */.IP.TOO_MANY_CONDITIONS);
2164
+ if (depth > _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_GROUP_DEPTH */.bw.MAX_GROUP_DEPTH) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.TOO_DEEP */.IP.TOO_DEEP);
2165
+ if (g.conditions.length > _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_CONDITIONS_PER_GROUP */.bw.MAX_CONDITIONS_PER_GROUP) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.TOO_MANY_CONDITIONS */.IP.TOO_MANY_CONDITIONS);
1732
2166
  if (![
1733
2167
  'AND',
1734
2168
  'OR',
1735
2169
  'NOT'
1736
- ].includes(g.operator)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SHAPE_MESSAGES.UNKNOWN_OPERATOR */.IP.UNKNOWN_OPERATOR);
2170
+ ].includes(g.operator)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SHAPE_MESSAGES.UNKNOWN_OPERATOR */.IP.UNKNOWN_OPERATOR);
1737
2171
  for (const node of g.conditions){
1738
2172
  const problem = node && (0,_rule_engine_index_js__rspack_import_2/* .isRuleGroup */.GD)(node) ? groupProblem(node, depth + 1, options) : conditionProblem(node, options);
1739
2173
  if (problem) return problem;
@@ -1741,32 +2175,22 @@ function conditionProblem(node, options) {
1741
2175
  return null;
1742
2176
  }
1743
2177
  /**
1744
- * Save-time check of a whole flow: structure (one trigger, valid ids/ports, no cycles),
2178
+ * Save-time check of a whole flow: structure (Request steps with distinct URLs, valid ids/ports, no cycles),
1745
2179
  * every node's configuration, every reference to another node, and the combination of
1746
- * settings (run-as / auth / transaction). Pure: no I/O, so the same result is what the
2180
+ * settings (auth / transaction). Pure: no I/O, so the same result is what the
1747
2181
  * designer shows and what the server enforces.
1748
2182
  */ function validateFlow(flow, context = {}) {
1749
2183
  const errors = [];
1750
2184
  const warnings = [];
1751
2185
  const err = (message)=>errors.push(message);
1752
- if (!_flow_types_js__rspack_import_6/* .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));
1753
- 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, {
1754
- runAs: String(flow.runAs)
1755
- }));
1756
- 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, {
1757
- authMode: String(flow.authMode)
1758
- }));
1759
- if (flow.runAs === 'caller' && flow.authMode !== 'jwt' && flow.authMode !== 'permission' && flow.authMode !== 'internal') {
1760
- 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));
1761
- }
1762
- validateInputSchema(flow, err);
2186
+ if (!_flow_types_js__rspack_import_6/* .FLOW_SLUG_PATTERN.test */.YD.test(flow.slug ?? '')) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.SLUG_INVALID */.qj.SLUG_INVALID));
1763
2187
  const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
1764
2188
  const edges = Array.isArray(flow.edges) ? flow.edges : [];
1765
- 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));
1766
- if (nodes.length > _flow_types_js__rspack_import_6/* .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, {
2189
+ if (!nodes.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NO_NODES */.qj.NO_NODES));
2190
+ if (nodes.length > _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_NODES */.qj.TOO_MANY_NODES, {
1767
2191
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES
1768
2192
  }));
1769
- if (edges.length > _flow_types_js__rspack_import_6/* .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, {
2193
+ if (edges.length > _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TOO_MANY_EDGES */.qj.TOO_MANY_EDGES, {
1770
2194
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES
1771
2195
  }));
1772
2196
  if (errors.length && (!nodes.length || nodes.length > _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_NODES */.Or.MAX_NODES || edges.length > _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_EDGES */.Or.MAX_EDGES)) return {
@@ -1776,48 +2200,46 @@ function conditionProblem(node, options) {
1776
2200
  const byId = new Map();
1777
2201
  for (const node of nodes){
1778
2202
  if (!_flow_types_js__rspack_import_6/* .FLOW_NODE_ID_PATTERN.test */.z6.test(node?.id ?? '')) {
1779
- 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, {
2203
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_ID_INVALID */.qj.NODE_ID_INVALID, {
1780
2204
  id: node?.id
1781
2205
  }));
1782
2206
  continue;
1783
2207
  }
1784
- if (byId.has(node.id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_ID_DUPLICATE */.qj.NODE_ID_DUPLICATE, {
2208
+ if (byId.has(node.id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_ID_DUPLICATE */.qj.NODE_ID_DUPLICATE, {
1785
2209
  id: node.id
1786
2210
  }));
1787
2211
  byId.set(node.id, node);
1788
- if (!_flow_types_js__rspack_import_6/* .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, {
2212
+ if (!_flow_types_js__rspack_import_6/* .FLOW_NODE_TYPES.includes */.q2.includes(node.type)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_TYPE_UNKNOWN */.qj.NODE_TYPE_UNKNOWN, {
1789
2213
  id: node.id,
1790
2214
  type: node.type
1791
2215
  }));
1792
2216
  }
1793
2217
  const triggers = nodes.filter((n)=>n.type === 'trigger');
1794
- if (triggers.length !== 1) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_COUNT */.qj.TRIGGER_COUNT, {
1795
- count: triggers.length
1796
- }));
2218
+ checkTriggers(triggers, err);
1797
2219
  for (const node of nodes){
1798
2220
  if (byId.get(node.id) === node) validateNodeConfig(node, flow, context, err, warnings);
1799
2221
  }
1800
2222
  const outgoing = new Map();
1801
2223
  const edgeIds = new Set();
1802
2224
  for (const edge of edges){
1803
- if (edgeIds.has(edge.id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_ID_DUPLICATE */.qj.EDGE_ID_DUPLICATE, {
2225
+ if (edgeIds.has(edge.id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.EDGE_ID_DUPLICATE */.qj.EDGE_ID_DUPLICATE, {
1804
2226
  id: edge.id
1805
2227
  }));
1806
2228
  edgeIds.add(edge.id);
1807
2229
  const from = byId.get(edge.from);
1808
2230
  const to = byId.get(edge.to);
1809
2231
  if (!from || !to) {
1810
- err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_DANGLING */.qj.EDGE_DANGLING, {
2232
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.EDGE_DANGLING */.qj.EDGE_DANGLING, {
1811
2233
  from: edge.from,
1812
2234
  to: edge.to
1813
2235
  }));
1814
2236
  continue;
1815
2237
  }
1816
- if (edge.from === edge.to) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_SELF */.qj.EDGE_SELF, {
2238
+ if (edge.from === edge.to) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.EDGE_SELF */.qj.EDGE_SELF, {
1817
2239
  id: edge.from
1818
2240
  }));
1819
- 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));
1820
- if (!(0,_flow_types_js__rspack_import_6/* .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, {
2241
+ if (to.type === 'trigger') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.EDGE_INTO_TRIGGER */.qj.EDGE_INTO_TRIGGER));
2242
+ if (!(0,_flow_types_js__rspack_import_6/* .portsOf */.a5)(from).includes(edge.port)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.EDGE_NO_PORT */.qj.EDGE_NO_PORT, {
1821
2243
  id: from.id,
1822
2244
  port: edge.port
1823
2245
  }));
@@ -1833,87 +2255,191 @@ function conditionProblem(node, options) {
1833
2255
  const seenPairs = new Set();
1834
2256
  for (const edge of edges){
1835
2257
  const key = `${edge.from}|${edge.port}|${edge.to}`;
1836
- if (seenPairs.has(key)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.EDGE_TWICE */.qj.EDGE_TWICE, {
2258
+ if (seenPairs.has(key)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.EDGE_TWICE */.qj.EDGE_TWICE, {
1837
2259
  from: edge.from,
1838
2260
  to: edge.to
1839
2261
  }));
1840
2262
  seenPairs.add(key);
1841
2263
  }
1842
2264
  const cyclic = hasCycle(nodes, outgoing);
1843
- if (cyclic) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.CYCLE */.qj.CYCLE));
1844
- const reachable = triggers.length === 1 ? reach(triggers[0].id, outgoing) : null;
2265
+ if (cyclic) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.CYCLE */.qj.CYCLE));
2266
+ const reachable = triggers.length ? new Set(triggers.flatMap((trigger)=>[
2267
+ ...reach(trigger.id, outgoing)
2268
+ ])) : null;
1845
2269
  for (const node of nodes){
1846
- 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, {
2270
+ if (reachable && !reachable.has(node.id)) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_UNREACHABLE */.qj.NODE_UNREACHABLE, {
1847
2271
  node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node)
1848
2272
  }));
1849
2273
  }
1850
2274
  checkNodeReferences(nodes, byId, flow, err, warnings);
1851
2275
  if (!cyclic && reachable) checkReferenceTiming(nodes, edges, byId, reachable, warnings);
1852
2276
  if (!cyclic) checkLoopDepth(nodes, outgoing, err);
1853
- if (flow.isTransactional && nodes.some((n)=>n.type === 'http_request')) {
1854
- 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));
1855
- }
1856
- checkTransactionUse(flow, nodes, outgoing, reachable, context, warnings);
1857
- if (flow.runAs === 'system' && flow.authMode === 'public' && nodes.some((n)=>n.type.startsWith('entity_') || collectLookupEntities(n.config).size > 0)) {
1858
- warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.PUBLIC_SYSTEM_ENTITY */.qj.PUBLIC_SYSTEM_ENTITY));
1859
- }
1860
- if ((flow.authMode === 'public' || flow.authMode === 'api_key') && nodes.some((n)=>n.type === 'permission_check' || usesPermissionFunction(n.config))) {
1861
- warnings.push((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));
1862
- }
2277
+ // Transaction and access are a Request step's own: each is checked against the steps its runs reach.
2278
+ const runs = (0,_flow_types_js__rspack_import_6/* .flowEndpoints */.Xr)(flow).map((endpoint)=>({
2279
+ endpoint,
2280
+ label: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(byId.get(endpoint.nodeId) ?? {
2281
+ id: endpoint.nodeId,
2282
+ name: ''
2283
+ }),
2284
+ steps: [
2285
+ ...reach(endpoint.nodeId, outgoing)
2286
+ ].map((id)=>byId.get(id)).filter((node)=>!!node)
2287
+ }));
2288
+ checkEndpointRuns(runs, warnings);
2289
+ checkTransactionUse(flow, runs, outgoing, context, warnings);
1863
2290
  return {
1864
2291
  errors,
1865
2292
  warnings
1866
2293
  };
1867
2294
  }
2295
+ /** The entities a step reads or writes: its own, its child inserts' and those its lookups read. */ function entitiesOf(node) {
2296
+ const codes = collectLookupEntities(node.config);
2297
+ if (node.type.startsWith('entity_') && typeof node.config?.['entityCode'] === 'string') codes.add(node.config['entityCode']);
2298
+ const children = node.type === 'entity_insert' && Array.isArray(node.config?.['children']) ? node.config['children'] : [];
2299
+ for (const child of children)if (typeof child?.entityCode === 'string') codes.add(child.entityCode);
2300
+ return [
2301
+ ...codes
2302
+ ];
2303
+ }
2304
+ /** 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) {
2305
+ const reported = new Set();
2306
+ const warnOnce = (message)=>{
2307
+ const key = JSON.stringify(message);
2308
+ if (reported.has(key)) return;
2309
+ reported.add(key);
2310
+ warnings.push(message);
2311
+ };
2312
+ const keyless = {
2313
+ endpoints: [],
2314
+ entities: new Set()
2315
+ };
2316
+ for (const { endpoint, label, steps } of runs){
2317
+ if (endpoint.isTransactional) {
2318
+ 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_8/* .FLOW_VALIDATION_MESSAGES.TRANSACTIONAL_HTTP */.qj.TRANSACTIONAL_HTTP, {
2319
+ node: label
2320
+ }));
2321
+ // Inside the run's transaction a message is only sent once it commits: a failed send is logged, it cannot take the error path.
2322
+ for (const n of steps)if ((0,_flow_types_js__rspack_import_6/* .isMessageNode */.SE)(n.type) && n.onError === 'continue') warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.MESSAGE_AFTER_COMMIT */.qj.MESSAGE_AFTER_COMMIT, {
2323
+ node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(n)
2324
+ }));
2325
+ }
2326
+ if (endpoint.authMode === 'public' && steps.some((n)=>(0,_flow_types_js__rspack_import_6/* .isMessageNode */.SE)(n.type))) warnOnce((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.PUBLIC_MESSAGE */.qj.PUBLIC_MESSAGE));
2327
+ // 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.
2328
+ if (!_flow_types_js__rspack_import_6/* .KEYLESS_AUTH_MODES.has */.d7.has(endpoint.authMode)) continue;
2329
+ 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_8/* .FLOW_VALIDATION_MESSAGES.PERMISSION_WITHOUT_USER */.qj.PERMISSION_WITHOUT_USER));
2330
+ const entities = steps.flatMap(entitiesOf);
2331
+ if (!entities.length) continue;
2332
+ keyless.endpoints.push(endpoint.path ? `/${endpoint.path}` : '/');
2333
+ for (const code of entities)keyless.entities.add(code);
2334
+ }
2335
+ if (keyless.endpoints.length) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.KEYLESS_ENTITY */.qj.KEYLESS_ENTITY, {
2336
+ endpoints: keyless.endpoints.join(', '),
2337
+ entities: [
2338
+ ...keyless.entities
2339
+ ].join(', ')
2340
+ }));
2341
+ }
2342
+ /** At least one Request step; at most one on the flow's own URL, the others on distinct paths. */ function checkTriggers(triggers, err) {
2343
+ if (!triggers.length) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_MISSING */.qj.TRIGGER_MISSING));
2344
+ const main = triggers.filter((trigger)=>!(0,_flow_types_js__rspack_import_6/* .triggerPath */.um)(trigger));
2345
+ if (main.length > 1) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_MAIN_DUPLICATE */.qj.TRIGGER_MAIN_DUPLICATE, {
2346
+ count: main.length
2347
+ }));
2348
+ const paths = new Set();
2349
+ for (const trigger of triggers){
2350
+ const path = (0,_flow_types_js__rspack_import_6/* .triggerPath */.um)(trigger);
2351
+ if (!path) continue;
2352
+ if (paths.has(path)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_DUPLICATE */.qj.TRIGGER_PATH_DUPLICATE, {
2353
+ path
2354
+ }));
2355
+ paths.add(path);
2356
+ }
2357
+ }
1868
2358
  /**
1869
- * What the runtime would always refuse, caught on save: an API-key flow called by a flow running
1870
- * as the caller, a required input left out, a chain that comes back to this flow or nests deeper
2359
+ * What the runtime would always refuse, caught on save: an API-key URL (a flow has no key to send),
2360
+ * a required input left out, a chain that comes back to this flow or nests deeper
1871
2361
  * than the limit. An inactive flow and an input the called flow does not declare are warnings.
1872
- */ function checkCalledFlow(slug, input, inputList, flow, known, at, err, warn) {
2362
+ */ function checkCalledFlow(node, flow, known, err, warn) {
2363
+ const { flowSlug: slug, endpoint, input, inputList } = node.config;
2364
+ const at = {
2365
+ node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node)
2366
+ };
1873
2367
  const target = known.get(slug);
1874
2368
  const vars = {
1875
2369
  ...at,
1876
2370
  flow: slug
1877
2371
  };
1878
- 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));
1879
- if (target.authMode === 'api_key') {
1880
- // A function runs as whoever calls it, so it fails only when a flow running as the caller does.
1881
- 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));
1882
- 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));
1883
- }
1884
- if (target.bodyType === 'list') {
1885
- if (inputList === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_LIST_REQUIRED */.qj.NODE_FLOW_INPUT_LIST_REQUIRED, vars));
2372
+ if (!target.isActive) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INACTIVE */.qj.NODE_FLOW_INACTIVE, vars));
2373
+ const path = typeof endpoint === 'string' && endpoint ? endpoint : null;
2374
+ const entry = target.endpoints.find((e)=>e.path === path);
2375
+ // A flow has no key to send, so an API-key URL is never callable from one.
2376
+ if (entry?.authMode === 'api_key') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_API_KEY */.qj.NODE_FLOW_API_KEY, vars));
2377
+ if (!entry) {
2378
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(path ? _config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_ENDPOINT_UNKNOWN */.qj.NODE_FLOW_ENDPOINT_UNKNOWN : _config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_ENDPOINT_REQUIRED */.qj.NODE_FLOW_ENDPOINT_REQUIRED, {
2379
+ ...vars,
2380
+ endpoint: path ?? ''
2381
+ }));
2382
+ } else if (entry.bodyType === 'list') {
2383
+ if (inputList === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_LIST_REQUIRED */.qj.NODE_FLOW_INPUT_LIST_REQUIRED, vars));
1886
2384
  } else if (inputList !== undefined) {
1887
- err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_LIST_UNEXPECTED */.qj.NODE_FLOW_INPUT_LIST_UNEXPECTED, vars));
2385
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_LIST_UNEXPECTED */.qj.NODE_FLOW_INPUT_LIST_UNEXPECTED, vars));
1888
2386
  } else {
1889
- checkCalledInputs(input, target, vars, err, warn);
2387
+ checkCalledInputs(input, entry.inputSchema, vars, err, warn);
1890
2388
  }
1891
2389
  const loop = callPathTo(slug, flow.slug, known, new Set());
1892
- if (loop) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_LOOP */.qj.NODE_FLOW_LOOP, {
2390
+ if (loop) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_LOOP */.qj.NODE_FLOW_LOOP, {
1893
2391
  ...at,
1894
2392
  chain: [
1895
2393
  flow.slug,
1896
2394
  ...loop
1897
2395
  ].join(' > ')
1898
2396
  }));
1899
- if (1 + callDepth(slug, known, new Set()) > _flow_types_js__rspack_import_6/* .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, {
2397
+ if (1 + callDepth(slug, known, new Set()) > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_TOO_DEEP */.qj.NODE_FLOW_TOO_DEEP, {
1900
2398
  ...at,
1901
2399
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH
1902
2400
  }));
1903
2401
  }
1904
- /** The named inputs sent to a flow whose body is an object: every required one given, the rest declared. */ function checkCalledInputs(input, target, vars, err, warn) {
2402
+ /**
2403
+ * What the Call flow steps of `caller` whose call chain reaches `slug` (that flow itself, or one
2404
+ * that calls it, however deep) would hit against the flows in `known`: the save errors of those
2405
+ * steps, and the flow they run being off (a warning on save, a failure at run).
2406
+ */ function callProblems(caller, slug, known) {
2407
+ const problems = [];
2408
+ for (const node of Array.isArray(caller.nodes) ? caller.nodes : []){
2409
+ const c = node?.config ?? {};
2410
+ if (node?.type !== 'call_flow' || typeof c.flowSlug !== 'string') continue;
2411
+ if (c.flowSlug !== slug && !callPathTo(c.flowSlug, slug, known, new Set())) continue;
2412
+ const at = {
2413
+ node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node)
2414
+ };
2415
+ const target = known.get(c.flowSlug);
2416
+ if (!target) {
2417
+ problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_UNKNOWN */.qj.NODE_FLOW_UNKNOWN, {
2418
+ ...at,
2419
+ flow: c.flowSlug
2420
+ }));
2421
+ continue;
2422
+ }
2423
+ if (!target.isActive) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INACTIVE */.qj.NODE_FLOW_INACTIVE, {
2424
+ ...at,
2425
+ flow: c.flowSlug
2426
+ }));
2427
+ checkCalledFlow(node, caller, known, (m)=>problems.push(m), []);
2428
+ }
2429
+ return problems;
2430
+ }
2431
+ /** 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) {
1905
2432
  const given = input && typeof input === 'object' && !Array.isArray(input) ? Object.keys(input) : [];
1906
- const schema = target.inputSchema ?? [];
1907
2433
  if (schema.length) {
1908
2434
  const declared = new Set(schema.map((field)=>field.name));
1909
- for (const name of given)if (!declared.has(name)) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_UNDECLARED */.qj.NODE_FLOW_INPUT_UNDECLARED, {
2435
+ for (const name of given)if (!declared.has(name)) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_UNDECLARED */.qj.NODE_FLOW_INPUT_UNDECLARED, {
1910
2436
  ...vars,
1911
2437
  input: name
1912
2438
  }));
1913
2439
  }
1914
2440
  for (const field of schema){
1915
2441
  if (field.required && (field.default === undefined || field.default === null) && !given.includes(field.name)) {
1916
- err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_MISSING */.qj.NODE_FLOW_INPUT_MISSING, {
2442
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_MISSING */.qj.NODE_FLOW_INPUT_MISSING, {
1917
2443
  ...vars,
1918
2444
  input: field.name
1919
2445
  }));
@@ -1943,42 +2469,36 @@ function conditionProblem(node, options) {
1943
2469
  running.delete(slug);
1944
2470
  return deepest;
1945
2471
  }
1946
- function validateInputSchema(flow, err) {
1947
- 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, {
1948
- type: String(flow.bodyType)
1949
- }));
1950
- validateInputFields(flow.inputSchema ?? [], '', 1, err);
1951
- }
1952
2472
  /** 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) {
1953
- if (fields.length > _flow_types_js__rspack_import_6/* .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, {
2473
+ if (fields.length > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.INPUT_TOO_MANY */.qj.INPUT_TOO_MANY, {
1954
2474
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_INPUT_FIELDS */.Or.MAX_INPUT_FIELDS
1955
2475
  }));
1956
2476
  const seen = new Set();
1957
2477
  for (const field of fields){
1958
2478
  const name = `${prefix}${field?.name ?? ''}`;
1959
- if (!_flow_types_js__rspack_import_6/* .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, {
2479
+ if (!_flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.INPUT_NAME_INVALID */.qj.INPUT_NAME_INVALID, {
1960
2480
  name
1961
2481
  }));
1962
- 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, {
2482
+ else if (seen.has(field.name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.INPUT_NAME_DUPLICATE */.qj.INPUT_NAME_DUPLICATE, {
1963
2483
  name
1964
2484
  }));
1965
2485
  seen.add(field?.name);
1966
- if (!_flow_types_js__rspack_import_6/* .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, {
2486
+ if (!_flow_types_js__rspack_import_6/* .FLOW_INPUT_TYPES.includes */.Q7.includes(field?.type)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.INPUT_TYPE_UNKNOWN */.qj.INPUT_TYPE_UNKNOWN, {
1967
2487
  name,
1968
2488
  type: field?.type
1969
2489
  }));
1970
2490
  const itemType = field?.type === 'array' ? field.itemType : undefined;
1971
- if (itemType !== undefined && (itemType === 'array' || !_flow_types_js__rspack_import_6/* .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, {
2491
+ if (itemType !== undefined && (itemType === 'array' || !_flow_types_js__rspack_import_6/* .FLOW_INPUT_TYPES.includes */.Q7.includes(itemType))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.INPUT_ITEM_TYPE_UNKNOWN */.qj.INPUT_ITEM_TYPE_UNKNOWN, {
1972
2492
  name,
1973
2493
  type: itemType
1974
2494
  }));
1975
- if ((field?.type === 'choice' || itemType === 'choice') && !field.options?.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.INPUT_CHOICE_NO_OPTIONS */.qj.INPUT_CHOICE_NO_OPTIONS, {
2495
+ if ((field?.type === 'choice' || itemType === 'choice') && !field.options?.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.INPUT_CHOICE_NO_OPTIONS */.qj.INPUT_CHOICE_NO_OPTIONS, {
1976
2496
  name
1977
2497
  }));
1978
2498
  const holdsFields = field?.type === 'object' || itemType === 'object';
1979
2499
  if (!holdsFields || !Array.isArray(field.fields) || !field.fields.length) continue;
1980
2500
  if (depth >= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_INPUT_DEPTH */.Or.MAX_INPUT_DEPTH) {
1981
- 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, {
2501
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.INPUT_TOO_DEEP */.qj.INPUT_TOO_DEEP, {
1982
2502
  name,
1983
2503
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_INPUT_DEPTH */.Or.MAX_INPUT_DEPTH
1984
2504
  }));
@@ -1997,7 +2517,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
1997
2517
  };
1998
2518
  const expr = (value, what)=>{
1999
2519
  const problem = expressionProblem(value, 0, shape);
2000
- if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EXPRESSION_PROBLEM */.qj.NODE_EXPRESSION_PROBLEM, {
2520
+ if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_EXPRESSION_PROBLEM */.qj.NODE_EXPRESSION_PROBLEM, {
2001
2521
  ...at,
2002
2522
  what,
2003
2523
  problem
@@ -2005,7 +2525,7 @@ function validateNodeConfig(node, flow, context, err, warn) {
2005
2525
  };
2006
2526
  const exprMap = (value, kind)=>{
2007
2527
  if (value === undefined) return;
2008
- if (!value || typeof value !== 'object' || 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_PAIRS_INVALID */.qj.NODE_PAIRS_INVALID, {
2528
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PAIRS_INVALID */.qj.NODE_PAIRS_INVALID, {
2009
2529
  ...at,
2010
2530
  what: (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(kind.list)
2011
2531
  }));
@@ -2015,13 +2535,13 @@ function validateNodeConfig(node, flow, context, err, warn) {
2015
2535
  };
2016
2536
  const filterMap = (value, kind)=>{
2017
2537
  if (value === undefined) return;
2018
- if (!value || typeof value !== 'object' || 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_PAIRS_INVALID */.qj.NODE_PAIRS_INVALID, {
2538
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PAIRS_INVALID */.qj.NODE_PAIRS_INVALID, {
2019
2539
  ...at,
2020
2540
  what: (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(kind.list)
2021
2541
  }));
2022
2542
  for (const [key, item] of Object.entries(value)){
2023
2543
  const problem = filterValueProblem(item, -1, shape);
2024
- if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_EXPRESSION_PROBLEM */.qj.NODE_EXPRESSION_PROBLEM, {
2544
+ if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_EXPRESSION_PROBLEM */.qj.NODE_EXPRESSION_PROBLEM, {
2025
2545
  ...at,
2026
2546
  what: (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(kind.item, {
2027
2547
  name: key
@@ -2032,106 +2552,172 @@ function validateNodeConfig(node, flow, context, err, warn) {
2032
2552
  };
2033
2553
  const rule = (value, what)=>{
2034
2554
  const problem = groupProblem(value, 0, shape);
2035
- if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_RULE_PROBLEM */.qj.NODE_RULE_PROBLEM, {
2555
+ if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_RULE_PROBLEM */.qj.NODE_RULE_PROBLEM, {
2036
2556
  ...at,
2037
2557
  what,
2038
2558
  problem
2039
2559
  }));
2040
2560
  };
2041
2561
  const entity = (value)=>{
2042
- if (typeof value !== 'string' || !_config_entity_builder_constants_js__rspack_import_8/* .ENTITY_CODE_PATTERN.test */.vs.test(value)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHOOSE_ENTITY */.qj.NODE_CHOOSE_ENTITY, at));
2043
- if (context.knownEntityCodes && !context.knownEntityCodes.has(value)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_ENTITY_UNKNOWN */.qj.NODE_ENTITY_UNKNOWN, {
2562
+ if (typeof value !== 'string' || !_config_entity_builder_constants_js__rspack_import_9/* .ENTITY_CODE_PATTERN.test */.vs.test(value)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHOOSE_ENTITY */.qj.NODE_CHOOSE_ENTITY, at));
2563
+ if (context.knownEntityCodes && !context.knownEntityCodes.has(value)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_ENTITY_UNKNOWN */.qj.NODE_ENTITY_UNKNOWN, {
2044
2564
  ...at,
2045
2565
  entity: value
2046
2566
  }));
2047
2567
  };
2048
2568
  const KIND = {
2049
2569
  variable: {
2050
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.VARIABLES */.bc.VARIABLES,
2051
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.VARIABLE */.bc.VARIABLE
2570
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.VARIABLES */.bc.VARIABLES,
2571
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.VARIABLE */.bc.VARIABLE
2052
2572
  },
2053
2573
  field: {
2054
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.FIELDS */.bc.FIELDS,
2055
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.FIELD */.bc.FIELD
2574
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.FIELDS */.bc.FIELDS,
2575
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.FIELD */.bc.FIELD
2056
2576
  },
2057
2577
  filter: {
2058
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.FILTERS */.bc.FILTERS,
2059
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.FILTER */.bc.FILTER
2578
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.FILTERS */.bc.FILTERS,
2579
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.FILTER */.bc.FILTER
2060
2580
  },
2061
2581
  input: {
2062
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.INPUTS */.bc.INPUTS,
2063
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.INPUT */.bc.INPUT
2582
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.INPUTS */.bc.INPUTS,
2583
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.INPUT */.bc.INPUT
2064
2584
  },
2065
2585
  header: {
2066
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.HEADERS */.bc.HEADERS,
2067
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.HEADER */.bc.HEADER
2586
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.HEADERS */.bc.HEADERS,
2587
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.HEADER */.bc.HEADER
2068
2588
  },
2069
2589
  body: {
2070
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.BODY_VALUES */.bc.BODY_VALUES,
2071
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.BODY_VALUE */.bc.BODY_VALUE
2590
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.BODY_VALUES */.bc.BODY_VALUES,
2591
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.BODY_VALUE */.bc.BODY_VALUE
2072
2592
  },
2073
2593
  payload: {
2074
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.PAYLOAD_VALUES */.bc.PAYLOAD_VALUES,
2075
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.PAYLOAD_VALUE */.bc.PAYLOAD_VALUE
2594
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.PAYLOAD_VALUES */.bc.PAYLOAD_VALUES,
2595
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.PAYLOAD_VALUE */.bc.PAYLOAD_VALUE
2076
2596
  },
2077
2597
  response: {
2078
- list: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.RESPONSE_VALUES */.bc.RESPONSE_VALUES,
2079
- item: _config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.RESPONSE_VALUE */.bc.RESPONSE_VALUE
2598
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.RESPONSE_VALUES */.bc.RESPONSE_VALUES,
2599
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.RESPONSE_VALUE */.bc.RESPONSE_VALUE
2600
+ },
2601
+ data: {
2602
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.DATA_VALUES */.bc.DATA_VALUES,
2603
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.DATA_VALUE */.bc.DATA_VALUE
2604
+ },
2605
+ template: {
2606
+ list: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.TEMPLATE_VALUES */.bc.TEMPLATE_VALUES,
2607
+ item: _config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.TEMPLATE_VALUE */.bc.TEMPLATE_VALUE
2080
2608
  }
2081
2609
  };
2610
+ const optionalExpr = (value, what)=>{
2611
+ if (value !== undefined) expr(value, what);
2612
+ };
2613
+ /** An AND / OR rule of permission codes: one that names none needs a permission, a malformed one is `invalid`. */ const permissionRule = (value, invalid)=>{
2614
+ const problem = (0,_flusys_nestjs_shared_utils__rspack_import_0.permissionLogicProblem)(value);
2615
+ if (problem === 'empty') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_REQUIRED */.qj.NODE_PERMISSION_REQUIRED, at));
2616
+ else if (problem === 'too_many') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .RULE_ENGINE_MESSAGES.TOO_MANY_PERMISSION_CODES */.x3.TOO_MANY_PERMISSION_CODES, {
2617
+ max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_PERMISSION_CODES */.bw.MAX_PERMISSION_CODES
2618
+ }));
2619
+ else if (problem) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(invalid, at));
2620
+ };
2621
+ const serviceRunning = (service, unavailable)=>{
2622
+ if (context.messageServices && !context.messageServices[service]) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(unavailable, at));
2623
+ };
2082
2624
  switch(node.type){
2083
2625
  case 'trigger':
2084
- break;
2626
+ {
2627
+ if (c.path !== undefined && c.path !== '' && (typeof c.path !== 'string' || !_flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_INVALID */.qj.TRIGGER_PATH_INVALID, at));
2628
+ if (c.authMode !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_AUTH_MODES.includes */.fO.includes(c.authMode)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_AUTH_INVALID */.qj.TRIGGER_AUTH_INVALID, at));
2629
+ if (c.permissions !== undefined) permissionRule(c.permissions, _config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_AUTH_INVALID */.qj.TRIGGER_AUTH_INVALID);
2630
+ if (c.isTransactional !== undefined && typeof c.isTransactional !== 'boolean') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_TRANSACTION_INVALID */.qj.TRIGGER_TRANSACTION_INVALID, at));
2631
+ if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MIN_TIMEOUT_MS */.Or.MIN_TIMEOUT_MS && c.timeoutMs <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_TIMEOUT_MS */.Or.MAX_TIMEOUT_MS)) {
2632
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_TIMEOUT_RANGE */.qj.TRIGGER_TIMEOUT_RANGE, {
2633
+ ...at,
2634
+ min: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MIN_TIMEOUT_MS */.Or.MIN_TIMEOUT_MS / 1000,
2635
+ max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_TIMEOUT_MS */.Or.MAX_TIMEOUT_MS / 1000
2636
+ }));
2637
+ }
2638
+ if (c.rateLimitPerMinute !== undefined && !(Number.isInteger(c.rateLimitPerMinute) && c.rateLimitPerMinute >= 0 && c.rateLimitPerMinute <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_RATE_LIMIT_PER_MINUTE */.Or.MAX_RATE_LIMIT_PER_MINUTE)) {
2639
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_RATE_LIMIT_RANGE */.qj.TRIGGER_RATE_LIMIT_RANGE, {
2640
+ ...at,
2641
+ max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_RATE_LIMIT_PER_MINUTE */.Or.MAX_RATE_LIMIT_PER_MINUTE
2642
+ }));
2643
+ }
2644
+ // Without a key an API-key URL could never be called; any other access keeps none.
2645
+ if (c.authMode === 'api_key' && c.apiKey === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_API_KEY_REQUIRED */.qj.TRIGGER_API_KEY_REQUIRED, at));
2646
+ else if (c.apiKey !== undefined && !(0,_flow_api_key_js__rspack_import_7/* .looksLikeApiKey */.Jb)(c.apiKey) && !(0,_flow_types_js__rspack_import_6/* .isStoredApiKey */.xA)(c.apiKey)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_API_KEY_INVALID */.qj.TRIGGER_API_KEY_INVALID, at));
2647
+ // Field errors are named after the URL (`<path>: <field>`) once a flow has several.
2648
+ const path = (0,_flow_types_js__rspack_import_6/* .triggerPath */.um)(node);
2649
+ if (c.bodyType !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_BODY_TYPES.includes */.Y3.includes(c.bodyType)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.BODY_TYPE_UNKNOWN */.qj.BODY_TYPE_UNKNOWN, {
2650
+ type: String(c.bodyType)
2651
+ }));
2652
+ validateInputFields(Array.isArray(c.inputSchema) ? c.inputSchema : [], path ? `${path}: ` : '', 1, err);
2653
+ break;
2654
+ }
2085
2655
  case 'validate':
2086
2656
  validateChecks(c, at, rule, err);
2087
- if (c.mode !== undefined && c.mode !== 'all' && c.mode !== 'first') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_MODE_INVALID */.qj.NODE_MODE_INVALID, at));
2657
+ if (c.mode !== undefined && c.mode !== 'all' && c.mode !== 'first') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_MODE_INVALID */.qj.NODE_MODE_INVALID, at));
2088
2658
  if (c.statusCode !== undefined && !(Number.isInteger(c.statusCode) && c.statusCode >= 400 && c.statusCode <= 499)) {
2089
- 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));
2659
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_4XX */.qj.NODE_STATUS_4XX, at));
2090
2660
  }
2091
2661
  break;
2092
2662
  case 'permission_check':
2093
2663
  {
2094
- try {
2095
- if (!(0,_rule_engine_rule_permission_js__rspack_import_4/* .parsePermissionCodes */.PS)(c.codes).length) 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));
2096
- } catch {
2097
- 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, {
2098
- max: _rule_engine_index_js__rspack_import_2/* .RULE_ENGINE_LIMITS.MAX_PERMISSION_CODES */.bw.MAX_PERMISSION_CODES
2099
- }));
2664
+ if (c.permissions === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_REQUIRED */.qj.NODE_PERMISSION_REQUIRED, at));
2665
+ else permissionRule(c.permissions, _config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID);
2666
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2667
+ if (c.message !== undefined && typeof c.message !== 'string') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2668
+ if (c.statusCode !== undefined && !(Number.isInteger(c.statusCode) && c.statusCode >= 400 && c.statusCode <= 499)) {
2669
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_4XX */.qj.NODE_STATUS_4XX, at));
2670
+ }
2671
+ break;
2672
+ }
2673
+ case 'company_branch_check':
2674
+ {
2675
+ if (c.subject !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_ACCESS_SUBJECTS.includes */.iM.includes(c.subject)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2676
+ if (c.branchMatch !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_BRANCH_MATCHES.includes */.Et.includes(c.branchMatch)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2677
+ if (c.subject === 'user') {
2678
+ if (c.userId === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_ACCESS_USER_REQUIRED */.qj.NODE_ACCESS_USER_REQUIRED, at));
2679
+ else expr(c.userId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_USER */.bc.THE_USER));
2680
+ }
2681
+ // Another user has no current company.
2682
+ if (c.branchCompany !== undefined && (!_flow_types_js__rspack_import_6/* .FLOW_BRANCH_COMPANIES.includes */.sk.includes(c.branchCompany) || c.branchCompany === 'current' && c.subject === 'user')) {
2683
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2100
2684
  }
2101
- if (c.match !== undefined && c.match !== 'any' && c.match !== 'all') 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));
2102
- 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));
2103
- if (c.branch !== undefined && c.branch !== null) expr(c.branch, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
2104
- 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));
2685
+ if (c.companyIds !== undefined) expr(c.companyIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
2686
+ if (c.branchIds !== undefined) expr(c.branchIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
2687
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2688
+ // Without an id to check there is nothing to allow or deny.
2689
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_ACCESS_TARGET_REQUIRED */.qj.NODE_ACCESS_TARGET_REQUIRED, at));
2690
+ if (c.message !== undefined && typeof c.message !== 'string') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PERMISSION_OPTION_INVALID */.qj.NODE_PERMISSION_OPTION_INVALID, at));
2105
2691
  if (c.statusCode !== undefined && !(Number.isInteger(c.statusCode) && c.statusCode >= 400 && c.statusCode <= 499)) {
2106
- 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));
2692
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_4XX */.qj.NODE_STATUS_4XX, at));
2107
2693
  }
2108
2694
  break;
2109
2695
  }
2110
2696
  case 'set':
2111
2697
  exprMap(c.values, KIND.variable);
2112
- 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));
2698
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_SETS_NOTHING */.qj.NODE_SETS_NOTHING, at));
2113
2699
  for (const key of Object.keys(c.values ?? {})){
2114
- if (!_flow_types_js__rspack_import_6/* .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, {
2700
+ if (!_flow_types_js__rspack_import_6/* .FLOW_INPUT_NAME_PATTERN.test */.fZ.test(key)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_VARIABLE_INVALID */.qj.NODE_VARIABLE_INVALID, {
2115
2701
  ...at,
2116
2702
  name: key
2117
2703
  }));
2118
2704
  }
2119
2705
  break;
2120
2706
  case 'condition':
2121
- rule(c.rule, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RULE */.bc.THE_RULE));
2707
+ rule(c.rule, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RULE */.bc.THE_RULE));
2122
2708
  break;
2123
2709
  case 'switch':
2124
2710
  {
2125
- expr(c.value, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
2711
+ expr(c.value, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_VALUE */.bc.THE_VALUE));
2126
2712
  const cases = Array.isArray(c.cases) ? c.cases : [];
2127
- if (!cases.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CASE_REQUIRED */.qj.NODE_CASE_REQUIRED, at));
2713
+ if (!cases.length) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CASE_REQUIRED */.qj.NODE_CASE_REQUIRED, at));
2128
2714
  const ids = new Set();
2129
2715
  for (const item of cases){
2130
- if (typeof item.id !== 'string' || !/^[A-Za-z][A-Za-z0-9_]{0,29}$/.test(item.id) || item.id === 'default') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CASE_INVALID */.qj.NODE_CASE_INVALID, {
2716
+ if (typeof item.id !== 'string' || !/^[A-Za-z][A-Za-z0-9_]{0,29}$/.test(item.id) || item.id === 'default') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CASE_INVALID */.qj.NODE_CASE_INVALID, {
2131
2717
  ...at,
2132
2718
  name: String(item.id)
2133
2719
  }));
2134
- else if (ids.has(item.id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CASE_DUPLICATE */.qj.NODE_CASE_DUPLICATE, {
2720
+ else if (ids.has(item.id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CASE_DUPLICATE */.qj.NODE_CASE_DUPLICATE, {
2135
2721
  ...at,
2136
2722
  name: item.id
2137
2723
  }));
@@ -2140,10 +2726,10 @@ function validateNodeConfig(node, flow, context, err, warn) {
2140
2726
  break;
2141
2727
  }
2142
2728
  case 'foreach':
2143
- 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));
2144
- 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));
2729
+ expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
2730
+ if (c.collect !== undefined) expr(c.collect, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_COLLECTED_VALUE */.bc.THE_COLLECTED_VALUE));
2145
2731
  if (c.maxItems !== undefined && !(Number.isInteger(c.maxItems) && c.maxItems >= 1 && c.maxItems <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_FOREACH_ITEMS */.Or.MAX_FOREACH_ITEMS)) {
2146
- 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, {
2732
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_MAX_ITEMS_RANGE */.qj.NODE_MAX_ITEMS_RANGE, {
2147
2733
  ...at,
2148
2734
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_FOREACH_ITEMS */.Or.MAX_FOREACH_ITEMS
2149
2735
  }));
@@ -2196,29 +2782,34 @@ function validateNodeConfig(node, flow, context, err, warn) {
2196
2782
  break;
2197
2783
  case 'entity_get':
2198
2784
  entity(c.entityCode);
2199
- expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
2785
+ expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
2200
2786
  break;
2201
2787
  case 'entity_query':
2202
2788
  entity(c.entityCode);
2203
2789
  filterMap(c.filter, KIND.filter);
2204
- 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));
2790
+ if (c.search !== undefined) expr(c.search, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_SEARCH_TEXT */.bc.THE_SEARCH_TEXT));
2791
+ if (c.sort !== undefined) {
2792
+ const sort = c.sort;
2793
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_SORT_INVALID */.qj.NODE_SORT_INVALID, at));
2794
+ }
2205
2795
  if (c.limit !== undefined && !(Number.isInteger(c.limit) && c.limit >= 1 && c.limit <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT)) {
2206
- 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, {
2796
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_LIMIT_RANGE */.qj.NODE_LIMIT_RANGE, {
2207
2797
  ...at,
2208
2798
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT
2209
2799
  }));
2210
2800
  }
2211
2801
  break;
2212
2802
  case 'call_flow':
2213
- if (typeof c.flowSlug !== 'string' || !_flow_types_js__rspack_import_6/* .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));
2214
- 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));
2215
- 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, {
2803
+ if (typeof c.flowSlug !== 'string' || !_flow_types_js__rspack_import_6/* .FLOW_SLUG_PATTERN.test */.YD.test(c.flowSlug)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHOOSE_FLOW */.qj.NODE_CHOOSE_FLOW, at));
2804
+ else if (c.flowSlug === flow.slug) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CALLS_ITSELF */.qj.NODE_CALLS_ITSELF, at));
2805
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_UNKNOWN */.qj.NODE_FLOW_UNKNOWN, {
2216
2806
  ...at,
2217
2807
  flow: c.flowSlug
2218
2808
  }));
2219
- else if (context.knownFlows) checkCalledFlow(c.flowSlug, c.input, c.inputList, flow, context.knownFlows, at, err, warn);
2809
+ else if (context.knownFlows) checkCalledFlow(node, flow, context.knownFlows, err, warn);
2810
+ if (c.endpoint !== undefined && c.endpoint !== '' && (typeof c.endpoint !== 'string' || !_flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_INVALID */.qj.TRIGGER_PATH_INVALID, at));
2220
2811
  exprMap(c.input, KIND.input);
2221
- 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));
2812
+ if (c.inputList !== undefined) expr(c.inputList, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_INPUT_LIST */.bc.THE_INPUT_LIST));
2222
2813
  break;
2223
2814
  case 'http_request':
2224
2815
  {
@@ -2228,12 +2819,13 @@ function validateNodeConfig(node, flow, context, err, warn) {
2228
2819
  'PUT',
2229
2820
  'PATCH',
2230
2821
  'DELETE'
2231
- ].includes(String(c.method))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHOOSE_METHOD */.qj.NODE_CHOOSE_METHOD, at));
2232
- 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));
2822
+ ].includes(String(c.method))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHOOSE_METHOD */.qj.NODE_CHOOSE_METHOD, at));
2823
+ expr(c.url, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_URL */.bc.THE_URL));
2233
2824
  exprMap(c.headers, KIND.header);
2234
2825
  exprMap(c.body, KIND.body);
2826
+ if (c.forwardAuth === true) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_FORWARDS_TOKEN */.qj.NODE_FORWARDS_TOKEN, at));
2235
2827
  if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >= 100 && c.timeoutMs <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_HTTP_TIMEOUT_MS */.Or.MAX_HTTP_TIMEOUT_MS)) {
2236
- 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, {
2828
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_TIMEOUT_RANGE */.qj.NODE_TIMEOUT_RANGE, {
2237
2829
  ...at,
2238
2830
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_HTTP_TIMEOUT_MS */.Or.MAX_HTTP_TIMEOUT_MS
2239
2831
  }));
@@ -2241,28 +2833,71 @@ function validateNodeConfig(node, flow, context, err, warn) {
2241
2833
  break;
2242
2834
  }
2243
2835
  case 'publish_event':
2244
- 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));
2836
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_EVENT_NAME_INVALID */.qj.NODE_EVENT_NAME_INVALID, at));
2245
2837
  exprMap(c.payload, KIND.payload);
2246
2838
  break;
2247
- case 'respond':
2839
+ case 'send_notification':
2248
2840
  {
2249
- const status = c.statusCode;
2250
- 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));
2251
- exprMap(c.body, KIND.response);
2252
- if (c.bodyExpression !== undefined) expr(c.bodyExpression, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RESPONSE */.bc.THE_RESPONSE));
2841
+ serviceRunning('notification', _config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_UNAVAILABLE */.qj.NODE_NOTIFICATION_UNAVAILABLE);
2842
+ const target = c.target ?? 'one';
2843
+ if (!_flow_types_js__rspack_import_6/* .FLOW_NOTIFICATION_TARGETS.includes */.as.includes(target)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_MESSAGE_TARGET_INVALID */.qj.NODE_MESSAGE_TARGET_INVALID, at));
2844
+ if (target === 'many') expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
2845
+ if (target === 'company') {
2846
+ if (context.companyMembers === false) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_COMPANY_UNAVAILABLE */.qj.NODE_NOTIFICATION_COMPANY_UNAVAILABLE, at));
2847
+ expr(c.companyIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_COMPANIES */.bc.THE_COMPANIES));
2848
+ optionalExpr(c.branchIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_BRANCHES */.bc.THE_BRANCHES));
2849
+ } else {
2850
+ expr(c.userIds, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RECIPIENTS */.bc.THE_RECIPIENTS));
2851
+ optionalExpr(c.companyId, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_COMPANY */.bc.THE_COMPANY));
2852
+ }
2853
+ expr(c.title, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_TITLE */.bc.THE_TITLE));
2854
+ optionalExpr(c.message, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_MESSAGE */.bc.THE_MESSAGE));
2855
+ exprMap(c.data, KIND.data);
2856
+ if (c.type !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_NOTIFICATION_TYPES.includes */.LG.includes(c.type)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_OPTION_INVALID */.qj.NODE_NOTIFICATION_OPTION_INVALID, at));
2857
+ if (c.realtime !== undefined && typeof c.realtime !== 'boolean') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_NOTIFICATION_OPTION_INVALID */.qj.NODE_NOTIFICATION_OPTION_INVALID, at));
2253
2858
  break;
2254
2859
  }
2255
- case 'commit':
2256
- if (node.onError === 'continue') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_COMMIT_CONTINUE */.qj.NODE_COMMIT_CONTINUE, at));
2860
+ case 'send_email':
2861
+ {
2862
+ serviceRunning('email', _config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_UNAVAILABLE */.qj.NODE_EMAIL_UNAVAILABLE);
2863
+ if (!_flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.NODE_MESSAGE_TARGET_INVALID */.qj.NODE_MESSAGE_TARGET_INVALID, at));
2864
+ if (c.target === 'many') expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
2865
+ expr(c.to, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RECIPIENTS */.bc.THE_RECIPIENTS));
2866
+ optionalExpr(c.cc, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_CC */.bc.THE_CC));
2867
+ optionalExpr(c.bcc, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_BCC */.bc.THE_BCC));
2868
+ optionalExpr(c.replyTo, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_REPLY_TO */.bc.THE_REPLY_TO));
2869
+ if (c.mode !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_EMAIL_MODES.includes */.v1.includes(c.mode)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_OPTION_INVALID */.qj.NODE_EMAIL_OPTION_INVALID, at));
2870
+ if (c.format !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_EMAIL_FORMATS.includes */.FU.includes(c.format)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_OPTION_INVALID */.qj.NODE_EMAIL_OPTION_INVALID, at));
2871
+ if (c.emailConfigId !== undefined && c.emailConfigId !== '' && (typeof c.emailConfigId !== 'string' || !_flow_types_js__rspack_import_6/* .FLOW_ID_PATTERN.test */.E2.test(c.emailConfigId))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_OPTION_INVALID */.qj.NODE_EMAIL_OPTION_INVALID, at));
2872
+ if (c.mode === 'template') {
2873
+ const slug = typeof c.templateSlug === 'string' ? c.templateSlug.trim() : '';
2874
+ if (!slug || slug.length > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.NODE_EMAIL_TEMPLATE_REQUIRED */.qj.NODE_EMAIL_TEMPLATE_REQUIRED, at));
2875
+ exprMap(c.variables, KIND.template);
2876
+ } else {
2877
+ expr(c.subject, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_SUBJECT */.bc.THE_SUBJECT));
2878
+ expr(c.body, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_BODY */.bc.THE_BODY));
2879
+ }
2880
+ break;
2881
+ }
2882
+ case 'respond':
2883
+ {
2884
+ const status = c.statusCode;
2885
+ if (!(Number.isInteger(status) && status >= 200 && status <= 599)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_STATUS_2XX_5XX */.qj.NODE_STATUS_2XX_5XX, at));
2886
+ exprMap(c.body, KIND.response);
2887
+ if (c.bodyExpression !== undefined) expr(c.bodyExpression, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RESPONSE */.bc.THE_RESPONSE));
2888
+ break;
2889
+ }
2890
+ case 'commit':
2891
+ if (node.onError === 'continue') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_COMMIT_CONTINUE */.qj.NODE_COMMIT_CONTINUE, at));
2257
2892
  break;
2258
2893
  case 'code':
2259
- 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));
2260
- else if (c.code.length > _flow_types_js__rspack_import_6/* .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, {
2894
+ if (typeof c.code !== 'string' || !c.code.trim()) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_REQUIRED */.qj.NODE_CODE_REQUIRED, at));
2895
+ else if (c.code.length > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_TOO_LONG */.qj.NODE_CODE_TOO_LONG, {
2261
2896
  ...at,
2262
2897
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_CODE_LENGTH */.Or.MAX_CODE_LENGTH
2263
2898
  }));
2264
2899
  if (c.timeoutMs !== undefined && !(Number.isInteger(c.timeoutMs) && c.timeoutMs >= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MIN_CODE_TIMEOUT_MS */.Or.MIN_CODE_TIMEOUT_MS && c.timeoutMs <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS)) {
2265
- 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, {
2900
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CODE_TIMEOUT_RANGE */.qj.NODE_CODE_TIMEOUT_RANGE, {
2266
2901
  ...at,
2267
2902
  min: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MIN_CODE_TIMEOUT_MS */.Or.MIN_CODE_TIMEOUT_MS,
2268
2903
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_CODE_TIMEOUT_MS */.Or.MAX_CODE_TIMEOUT_MS
@@ -2273,19 +2908,19 @@ function validateNodeConfig(node, flow, context, err, warn) {
2273
2908
  }
2274
2909
  /** `target` and what it needs: one - an id; many - a list (the id is optional, the item's own id by default); filter - a filter and a limit. */ function writeTargetConfig(c, targets, notFound, { at, expr, filterMap, err }) {
2275
2910
  const target = c.target ?? 'one';
2276
- if (typeof target !== 'string' || !targets.includes(target)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_WRITE_TARGET_INVALID */.qj.NODE_WRITE_TARGET_INVALID, at));
2277
- if (c.onNotFound !== undefined && !notFound.includes(c.onNotFound)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_NOT_FOUND_INVALID */.qj.NODE_NOT_FOUND_INVALID, at));
2911
+ if (typeof target !== 'string' || !targets.includes(target)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_WRITE_TARGET_INVALID */.qj.NODE_WRITE_TARGET_INVALID, at));
2912
+ if (c.onNotFound !== undefined && !notFound.includes(c.onNotFound)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_NOT_FOUND_INVALID */.qj.NODE_NOT_FOUND_INVALID, at));
2278
2913
  const needsId = notFound.length > 0;
2279
- if (target === 'one' && needsId) expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
2914
+ if (target === 'one' && needsId) expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
2280
2915
  if (target === 'many') {
2281
- 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));
2282
- if (needsId && c.id !== undefined) expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
2916
+ expr(c.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_LIST */.bc.THE_LIST));
2917
+ if (needsId && c.id !== undefined) expr(c.id, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_RECORD_ID */.bc.THE_RECORD_ID));
2283
2918
  }
2284
2919
  if (target === 'filter') {
2285
2920
  filterMap(c.filter);
2286
- 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));
2921
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_FILTER_REQUIRED */.qj.NODE_FILTER_REQUIRED, at));
2287
2922
  if (c.limit !== undefined && !(Number.isInteger(c.limit) && c.limit >= 1 && c.limit <= _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS)) {
2288
- 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, {
2923
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_LIMIT_RANGE */.qj.NODE_LIMIT_RANGE, {
2289
2924
  ...at,
2290
2925
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_WRITE_ITEMS */.Or.MAX_WRITE_ITEMS
2291
2926
  }));
@@ -2294,45 +2929,45 @@ function validateNodeConfig(node, flow, context, err, warn) {
2294
2929
  }
2295
2930
  function childInserts(value, { at, entity, expr, err }) {
2296
2931
  if (value === undefined) return;
2297
- 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));
2298
- if (value.length > _flow_types_js__rspack_import_6/* .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, {
2932
+ if (!Array.isArray(value)) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHILDREN_INVALID */.qj.NODE_CHILDREN_INVALID, at));
2933
+ if (value.length > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHILDREN_TOO_MANY */.qj.NODE_CHILDREN_TOO_MANY, {
2299
2934
  ...at,
2300
2935
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_CHILD_INSERTS */.Or.MAX_CHILD_INSERTS
2301
2936
  }));
2302
2937
  const names = new Set();
2303
2938
  for (const child of value){
2304
2939
  if (!child || typeof child !== 'object') {
2305
- 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));
2940
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHILDREN_INVALID */.qj.NODE_CHILDREN_INVALID, at));
2306
2941
  continue;
2307
2942
  }
2308
2943
  const name = String(child.as ?? '');
2309
- if (!_flow_types_js__rspack_import_6/* .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, {
2944
+ if (!_flow_types_js__rspack_import_6/* .FLOW_INPUT_NAME_PATTERN.test */.fZ.test(name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHILD_NAME_INVALID */.qj.NODE_CHILD_NAME_INVALID, {
2310
2945
  ...at,
2311
2946
  name
2312
2947
  }));
2313
- else if (names.has(name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHILD_NAME_DUPLICATE */.qj.NODE_CHILD_NAME_DUPLICATE, {
2948
+ else if (names.has(name)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHILD_NAME_DUPLICATE */.qj.NODE_CHILD_NAME_DUPLICATE, {
2314
2949
  ...at,
2315
2950
  name
2316
2951
  }));
2317
2952
  names.add(name);
2318
2953
  entity(child.entityCode);
2319
- if (typeof child.parentField !== 'string' || !COLUMN_CODE.test(child.parentField)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHILD_PARENT_FIELD_REQUIRED */.qj.NODE_CHILD_PARENT_FIELD_REQUIRED, {
2954
+ if (typeof child.parentField !== 'string' || !COLUMN_CODE.test(child.parentField)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHILD_PARENT_FIELD_REQUIRED */.qj.NODE_CHILD_PARENT_FIELD_REQUIRED, {
2320
2955
  ...at,
2321
2956
  name
2322
2957
  }));
2323
- expr(child.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.CHILD_LIST */.bc.CHILD_LIST, {
2958
+ expr(child.items, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.CHILD_LIST */.bc.CHILD_LIST, {
2324
2959
  name
2325
2960
  }));
2326
2961
  const fields = child.fields;
2327
2962
  if (fields === undefined) continue;
2328
2963
  if (!fields || typeof fields !== 'object' || Array.isArray(fields)) {
2329
- err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_PAIRS_INVALID */.qj.NODE_PAIRS_INVALID, {
2964
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_PAIRS_INVALID */.qj.NODE_PAIRS_INVALID, {
2330
2965
  ...at,
2331
- what: (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.FIELDS */.bc.FIELDS)
2966
+ what: (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.FIELDS */.bc.FIELDS)
2332
2967
  }));
2333
2968
  continue;
2334
2969
  }
2335
- for (const [key, item] of Object.entries(fields))expr(item, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.CHILD_FIELD */.bc.CHILD_FIELD, {
2970
+ for (const [key, item] of Object.entries(fields))expr(item, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.CHILD_FIELD */.bc.CHILD_FIELD, {
2336
2971
  name: `${name}.${key}`
2337
2972
  }));
2338
2973
  }
@@ -2340,7 +2975,8 @@ function childInserts(value, { at, entity, expr, err }) {
2340
2975
  /**
2341
2976
  * How many loop levels each part of a node's config runs inside, beyond the loops around the node:
2342
2977
  * a foreach's `collect` runs in its own loop; a many / filter write's per-record expressions in one
2343
- * of the node's own; child records one deeper than their parent record.
2978
+ * of the node's own; child records one deeper than their parent record; a many message step's values
2979
+ * (all but its list) one per item.
2344
2980
  */ function configScopes(node) {
2345
2981
  const config = node.config ?? {};
2346
2982
  if (node.type === 'foreach') {
@@ -2358,6 +2994,21 @@ function childInserts(value, { at, entity, expr, err }) {
2358
2994
  }
2359
2995
  ];
2360
2996
  }
2997
+ if ((0,_flow_types_js__rspack_import_6/* .isMessageNode */.SE)(node.type) && config['target'] === 'many') {
2998
+ const { items, ...perItem } = config;
2999
+ return [
3000
+ {
3001
+ value: items,
3002
+ extra: 0,
3003
+ ownLoop: false
3004
+ },
3005
+ {
3006
+ value: perItem,
3007
+ extra: 1,
3008
+ ownLoop: false
3009
+ }
3010
+ ];
3011
+ }
2361
3012
  if (!WRITE_NODE_TYPES.has(node.type)) return [
2362
3013
  {
2363
3014
  value: config,
@@ -2396,39 +3047,42 @@ function childInserts(value, { at, entity, expr, err }) {
2396
3047
  return scopes;
2397
3048
  }
2398
3049
  function validateChecks(c, at, rule, err) {
2399
- 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));
2400
- if (c.checks.length > _flow_types_js__rspack_import_6/* .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, {
3050
+ 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_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_REQUIRED */.qj.NODE_CHECK_REQUIRED, at));
3051
+ if (c.checks.length > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.NODE_TOO_MANY_CHECKS */.qj.NODE_TOO_MANY_CHECKS, {
2401
3052
  ...at,
2402
3053
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_VALIDATE_CHECKS */.Or.MAX_VALIDATE_CHECKS
2403
3054
  }));
2404
3055
  const ids = new Set();
2405
3056
  for (const check of c.checks){
2406
3057
  const id = check?.id;
2407
- if (typeof id !== 'string' || !CHECK_ID.test(id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_ID_INVALID */.qj.NODE_CHECK_ID_INVALID, {
3058
+ if (typeof id !== 'string' || !CHECK_ID.test(id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_ID_INVALID */.qj.NODE_CHECK_ID_INVALID, {
2408
3059
  ...at,
2409
3060
  name: String(id)
2410
3061
  }));
2411
- else if (ids.has(id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_ID_DUPLICATE */.qj.NODE_CHECK_ID_DUPLICATE, {
3062
+ else if (ids.has(id)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_ID_DUPLICATE */.qj.NODE_CHECK_ID_DUPLICATE, {
2412
3063
  ...at,
2413
3064
  name: id
2414
3065
  }));
2415
3066
  else ids.add(id);
2416
3067
  const name = String(id);
2417
- rule(check?.rule, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_CHECK */.bc.THE_CHECK, {
3068
+ rule(check?.rule, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_SUBJECT_MESSAGES.THE_CHECK */.bc.THE_CHECK, {
2418
3069
  name
2419
3070
  }));
2420
- if (typeof check?.message !== 'string' || !check.message.trim()) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_MESSAGE_REQUIRED */.qj.NODE_CHECK_MESSAGE_REQUIRED, {
3071
+ if (typeof check?.message !== 'string' || !check.message.trim()) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_MESSAGE_REQUIRED */.qj.NODE_CHECK_MESSAGE_REQUIRED, {
2421
3072
  ...at,
2422
3073
  name
2423
3074
  }));
2424
- if (check?.field !== undefined && (typeof check.field !== 'string' || !check.field.trim() || check.field.length > 100)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_FIELD_INVALID */.qj.NODE_CHECK_FIELD_INVALID, {
3075
+ if (check?.field !== undefined && (typeof check.field !== 'string' || !check.field.trim() || check.field.length > 100)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.NODE_CHECK_FIELD_INVALID */.qj.NODE_CHECK_FIELD_INVALID, {
2425
3076
  ...at,
2426
3077
  name
2427
3078
  }));
2428
3079
  }
2429
3080
  }
2430
- /** Every `context.<node>` must name a node of this flow; `input.<name>` should be a declared input. */ function checkNodeReferences(nodes, byId, flow, err, warnings) {
2431
- const declaredInputs = new Set((flow.inputSchema ?? []).map((f)=>f.name));
3081
+ /** 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) {
3082
+ const endpoints = (0,_flow_types_js__rspack_import_6/* .flowEndpoints */.Xr)(flow);
3083
+ const declaredInputs = new Set(endpoints.flatMap((endpoint)=>endpoint.inputSchema.map((f)=>f.name)));
3084
+ // A list body is read by index, and an object body without fields takes anything: neither has names to check.
3085
+ const namesKnown = endpoints.length > 0 && endpoints.every((endpoint)=>endpoint.bodyType !== 'list' && endpoint.inputSchema.length > 0);
2432
3086
  for (const node of nodes){
2433
3087
  let refs;
2434
3088
  try {
@@ -2439,21 +3093,21 @@ function validateChecks(c, at, rule, err) {
2439
3093
  for (const ref of refs){
2440
3094
  const [scope, second] = ref.split('.');
2441
3095
  if (!REF_SCOPES.has(scope)) {
2442
- 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, {
3096
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_BAD_SCOPE */.qj.REF_BAD_SCOPE, {
2443
3097
  node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node),
2444
3098
  ref
2445
3099
  }));
2446
3100
  } else if (scope === 'context' && second && !byId.has(second)) {
2447
- 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, {
3101
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_NODE_MISSING */.qj.REF_NODE_MISSING, {
2448
3102
  node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node),
2449
3103
  target: second
2450
3104
  }));
2451
3105
  } else if (scope === 'context' && second === node.id) {
2452
- 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, {
3106
+ err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_SELF */.qj.REF_SELF, {
2453
3107
  node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node)
2454
3108
  }));
2455
- } else if (scope === 'input' && second && flow.bodyType !== 'list' && declaredInputs.size > 0 && !declaredInputs.has(second)) {
2456
- 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, {
3109
+ } else if (scope === 'input' && second && namesKnown && !declaredInputs.has(second)) {
3110
+ warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_INPUT_UNDECLARED */.qj.REF_INPUT_UNDECLARED, {
2457
3111
  node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node),
2458
3112
  input: second
2459
3113
  }));
@@ -2469,15 +3123,28 @@ function refsOf(value, refs = [], depth = 0) {
2469
3123
  return refs;
2470
3124
  }
2471
3125
  /**
2472
- * Writes that do not share a transaction, and Save progress steps that would not commit: in a flow
2473
- * without the transaction setting, inside a loop (once per item), or in a flow other flows call
2474
- * (inside its caller's transaction it is skipped).
2475
- */ function checkTransactionUse(flow, nodes, outgoing, reachable, context, warnings) {
2476
- const live = nodes.filter((n)=>!reachable || reachable.has(n.id));
2477
- const writes = live.filter((n)=>WRITE_NODE_TYPES.has(n.type)).length;
2478
- if (!flow.isTransactional && 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, {
2479
- count: writes
2480
- }));
3126
+ * Writes that do not share a transaction, and Save progress steps that would not commit: reached
3127
+ * from a Request step without the transaction setting, inside a loop (once per item), or in a flow
3128
+ * other flows call (inside its caller's transaction it is skipped).
3129
+ */ function checkTransactionUse(flow, runs, outgoing, context, warnings) {
3130
+ for (const { endpoint, label, steps } of runs){
3131
+ if (endpoint.isTransactional) continue;
3132
+ const writes = steps.filter((n)=>WRITE_NODE_TYPES.has(n.type)).length;
3133
+ if (writes >= 2) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.WRITES_NOT_TRANSACTIONAL */.qj.WRITES_NOT_TRANSACTIONAL, {
3134
+ count: writes,
3135
+ node: label
3136
+ }));
3137
+ 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_8/* .FLOW_VALIDATION_MESSAGES.COMMIT_NOT_TRANSACTIONAL */.qj.COMMIT_NOT_TRANSACTIONAL, {
3138
+ node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(commit),
3139
+ endpoint: label
3140
+ }));
3141
+ }
3142
+ const live = [
3143
+ ...new Map(runs.flatMap((run)=>run.steps).map((n)=>[
3144
+ n.id,
3145
+ n
3146
+ ])).values()
3147
+ ];
2481
3148
  const commits = live.filter((n)=>n.type === 'commit');
2482
3149
  if (!commits.length) return;
2483
3150
  const loops = live.filter((n)=>n.type === 'foreach').map((n)=>({
@@ -2491,13 +3158,12 @@ function refsOf(value, refs = [], depth = 0) {
2491
3158
  const at = {
2492
3159
  node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(commit)
2493
3160
  };
2494
- 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));
2495
3161
  const loop = loops.find((l)=>l.body.has(commit.id));
2496
- 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, {
3162
+ if (loop) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.COMMIT_IN_LOOP */.qj.COMMIT_IN_LOOP, {
2497
3163
  ...at,
2498
3164
  loop: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(loop.node)
2499
3165
  }));
2500
- 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));
3166
+ if (called) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.COMMIT_IN_CALLED_FLOW */.qj.COMMIT_IN_CALLED_FLOW, at));
2501
3167
  }
2502
3168
  }
2503
3169
  /** Every node a loop's `each` branch runs, nested loops included. */ function loopBody(loopId, outgoing) {
@@ -2600,14 +3266,14 @@ function refsOf(value, refs = [], depth = 0) {
2600
3266
  const [scope, target, field] = segs;
2601
3267
  if (scope === 'loop') {
2602
3268
  const parents = segs.slice(1).findIndex((s)=>s !== 'parent');
2603
- if (!depth) warn(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_LOOP_OUTSIDE */.qj.REF_LOOP_OUTSIDE, {});
2604
- else if ((parents < 0 ? segs.length - 1 : parents) >= depth) warn(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_LOOP_PARENT_MISSING */.qj.REF_LOOP_PARENT_MISSING, {});
3269
+ if (!depth) warn(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_LOOP_OUTSIDE */.qj.REF_LOOP_OUTSIDE, {});
3270
+ else if ((parents < 0 ? segs.length - 1 : parents) >= depth) warn(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_LOOP_PARENT_MISSING */.qj.REF_LOOP_PARENT_MISSING, {});
2605
3271
  }
2606
3272
  if (scope !== 'context' || !target || target === node.id || !byId.has(target)) continue;
2607
- if (!available.has(target)) warn(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_NODE_NOT_YET */.qj.REF_NODE_NOT_YET, {
3273
+ if (!available.has(target)) warn(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_NODE_NOT_YET */.qj.REF_NODE_NOT_YET, {
2608
3274
  target: nameOf(target)
2609
3275
  });
2610
- else if (field === 'results' && around.has(target)) warn(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_LOOP_RESULTS_EARLY */.qj.REF_LOOP_RESULTS_EARLY, {
3276
+ else if (field === 'results' && around.has(target)) warn(_config_message_keys_js__rspack_import_8/* .FLOW_VALIDATION_MESSAGES.REF_LOOP_RESULTS_EARLY */.qj.REF_LOOP_RESULTS_EARLY, {
2611
3277
  loop: nameOf(target)
2612
3278
  });
2613
3279
  }
@@ -2662,15 +3328,15 @@ function reach(start, outgoing) {
2662
3328
  nesting.set(id, max);
2663
3329
  return max;
2664
3330
  };
2665
- const start = nodes.find((n)=>n.type === 'trigger');
2666
- if (start && nestingFrom(start.id) > _flow_types_js__rspack_import_6/* .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, {
3331
+ if (nodes.some((n)=>n.type === 'trigger' && nestingFrom(n.id) > _flow_types_js__rspack_import_6/* .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_8/* .FLOW_VALIDATION_MESSAGES.LOOP_TOO_DEEP */.qj.LOOP_TOO_DEEP, {
2667
3332
  max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_LOOP_DEPTH */.Or.MAX_LOOP_DEPTH
2668
3333
  }));
2669
3334
  }
2670
3335
 
2671
3336
  __webpack_require__.d(__webpack_exports__, {
2672
- n: () => (validateFlow),
2673
- u: () => (usesPermissionFunction)
3337
+ nC: () => (validateFlow),
3338
+ r7: () => (callProblems),
3339
+ u9: () => (usesPermissionFunction)
2674
3340
  });
2675
3341
 
2676
3342
 
@@ -2873,6 +3539,19 @@ __webpack_require__.d(__webpack_exports__, {
2873
3539
  });
2874
3540
 
2875
3541
 
3542
+ },
3543
+ 8220(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3544
+ const permissionAction = (code)=>({
3545
+ type: 'action',
3546
+ actionId: code
3547
+ });
3548
+
3549
+ __webpack_require__.d(__webpack_exports__, {
3550
+ }, {
3551
+ s: permissionAction
3552
+ });
3553
+
3554
+
2876
3555
  },
2877
3556
  8542(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
2878
3557
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
@@ -2954,172 +3633,14 @@ __webpack_require__.d(__webpack_exports__, {
2954
3633
  });
2955
3634
 
2956
3635
 
2957
- },
2958
- 6256(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
2959
- const FLOW_NODE_TYPES = [
2960
- 'trigger',
2961
- 'validate',
2962
- 'permission_check',
2963
- 'set',
2964
- 'condition',
2965
- 'switch',
2966
- 'foreach',
2967
- 'entity_insert',
2968
- 'entity_update',
2969
- 'entity_delete',
2970
- 'entity_get',
2971
- 'entity_query',
2972
- 'commit',
2973
- 'call_flow',
2974
- 'http_request',
2975
- 'publish_event',
2976
- 'respond',
2977
- 'code'
2978
- ];
2979
- /** 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([
2980
- 'validate',
2981
- 'permission_check',
2982
- 'commit'
2983
- ]);
2984
- const FLOW_AUTH_MODES = [
2985
- 'public',
2986
- 'api_key',
2987
- 'jwt',
2988
- 'permission',
2989
- 'internal'
2990
- ];
2991
- const FLOW_RUN_AS = [
2992
- 'caller',
2993
- 'system'
2994
- ];
2995
- /** 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 = [
2996
- 'object',
2997
- 'list'
2998
- ];
2999
- const FLOW_INPUT_TYPES = [
3000
- 'string',
3001
- 'text',
3002
- 'number',
3003
- 'integer',
3004
- 'boolean',
3005
- 'date',
3006
- 'datetime',
3007
- 'email',
3008
- 'uuid',
3009
- 'object',
3010
- 'array',
3011
- 'choice'
3012
- ];
3013
- /** How a step is named in messages: its name, or its id when it was left unnamed. */ const nodeLabel = (node)=>node.name || node.id;
3014
- /**
3015
- * Which records a write node touches. one (default): a single record. many: one per item of `items`,
3016
- * whose per-record expressions read the item as `loop.item` (a loop around the node becomes
3017
- * `loop.parent`). filter: every record matching `filter`, read as `loop.item`.
3018
- */ const RECORD_WRITE_TARGETS = [
3019
- 'one',
3020
- 'many',
3021
- 'filter'
3022
- ];
3023
- const FLOW_LIMITS = {
3024
- MAX_NODES: 100,
3025
- MAX_EDGES: 300,
3026
- /** Room for a full foreach (MAX_FOREACH_ITEMS) with a few steps per item. */ MAX_STEPS_PER_RUN: 5000,
3027
- MAX_FOREACH_ITEMS: 1000,
3028
- MAX_LOOP_DEPTH: 3,
3029
- MAX_CALL_DEPTH: 5,
3030
- MAX_HTTP_CALLS: 10,
3031
- MAX_HTTP_TIMEOUT_MS: 15_000,
3032
- MAX_HTTP_RESPONSE_BYTES: 1_000_000,
3033
- MAX_QUERY_LIMIT: 200,
3034
- MAX_WRITE_ITEMS: 1000,
3035
- DEFAULT_WRITE_FILTER_LIMIT: 100,
3036
- MAX_CHILD_INSERTS: 10,
3037
- MAX_WRITES_PER_RUN: 5000,
3038
- DEFAULT_TIMEOUT_MS: 30_000,
3039
- MIN_TIMEOUT_MS: 1000,
3040
- MAX_TIMEOUT_MS: 120_000,
3041
- TRACE_PREVIEW_CHARS: 2000,
3042
- MAX_INPUT_FIELDS: 50,
3043
- /** How deep object/list-of-object fields may nest inside the request body. */ MAX_INPUT_DEPTH: 4,
3044
- MAX_VALIDATE_CHECKS: 50,
3045
- MAX_CODE_LENGTH: 20_000,
3046
- DEFAULT_CODE_TIMEOUT_MS: 1000,
3047
- MIN_CODE_TIMEOUT_MS: 10,
3048
- MAX_CODE_TIMEOUT_MS: 5000,
3049
- CODE_MEMORY_LIMIT_BYTES: 32 * 1024 * 1024,
3050
- CODE_STACK_SIZE_BYTES: 512 * 1024,
3051
- MAX_CODE_OUTPUT_BYTES: 1_000_000,
3052
- MAX_CODE_LOG_LINES: 50,
3053
- MAX_CODE_LOG_LINE_CHARS: 500
3054
- };
3055
- const FLOW_NODE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,39}$/;
3056
- const FLOW_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,62}$/;
3057
- const FLOW_INPUT_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,59}$/;
3058
- /** Output ports a node of this type can have (a `respond` node ends the flow: none). */ function portsOf(node) {
3059
- switch(node.type){
3060
- case 'condition':
3061
- return [
3062
- 'true',
3063
- 'false'
3064
- ];
3065
- case 'switch':
3066
- return [
3067
- ...(node.config.cases ?? []).map((c)=>c.id),
3068
- 'default'
3069
- ];
3070
- case 'foreach':
3071
- return [
3072
- 'each',
3073
- 'done'
3074
- ];
3075
- case 'permission_check':
3076
- return node.config.onDenied === 'branch' ? [
3077
- 'allowed',
3078
- 'denied'
3079
- ] : [
3080
- 'out'
3081
- ];
3082
- case 'respond':
3083
- return [];
3084
- case 'trigger':
3085
- case 'validate':
3086
- case 'set':
3087
- case 'commit':
3088
- return [
3089
- 'out'
3090
- ];
3091
- default:
3092
- return [
3093
- 'out',
3094
- 'error'
3095
- ];
3096
- }
3097
- }
3098
-
3099
- __webpack_require__.d(__webpack_exports__, {
3100
- a5: () => (portsOf)
3101
- }, {
3102
- At: NON_CONTINUABLE_NODE_TYPES,
3103
- Dn: RECORD_WRITE_TARGETS,
3104
- Or: FLOW_LIMITS,
3105
- Q7: FLOW_INPUT_TYPES,
3106
- Y3: FLOW_BODY_TYPES,
3107
- YD: FLOW_SLUG_PATTERN,
3108
- dA: nodeLabel,
3109
- fO: FLOW_AUTH_MODES,
3110
- fZ: FLOW_INPUT_NAME_PATTERN,
3111
- gl: FLOW_RUN_AS,
3112
- q2: FLOW_NODE_TYPES,
3113
- z6: FLOW_NODE_ID_PATTERN
3114
- });
3115
-
3116
-
3117
3636
  },
3118
3637
  2678(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3119
3638
  const SENSITIVE_KEY = /pass(word)?|secret|token|authorization|api[-_]?key|cookie|credential|private[-_]?key/i;
3120
3639
  /** Copy of `value` with the content of sensitive-looking keys replaced, safe to store in a log. */ function redactSensitive(value, depth = 0) {
3121
3640
  if (depth > 8) return '[deep]';
3122
3641
  if (Array.isArray(value)) return value.slice(0, 100).map((v)=>redactSensitive(v, depth + 1));
3642
+ // Dates (and anything else with its own JSON form) have no own keys: walked as objects they would log as `{}`.
3643
+ if (value && typeof value.toJSON === 'function') return redactSensitive(value.toJSON(), depth + 1);
3123
3644
  if (value && typeof value === 'object') {
3124
3645
  const out = {};
3125
3646
  for (const [key, item] of Object.entries(value)){
@@ -3148,6 +3669,24 @@ __webpack_require__.d(__webpack_exports__, {
3148
3669
  });
3149
3670
 
3150
3671
 
3672
+ },
3673
+ 9609(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3674
+ /** What a version holds and a draft edits: everything about a flow but its identity. */ const FLOW_VERSIONED_FIELDS = [
3675
+ 'name',
3676
+ 'slug',
3677
+ 'description',
3678
+ 'isActive',
3679
+ 'retainExecutions',
3680
+ 'nodes',
3681
+ 'edges'
3682
+ ];
3683
+
3684
+ __webpack_require__.d(__webpack_exports__, {
3685
+ }, {
3686
+ v: FLOW_VERSIONED_FIELDS
3687
+ });
3688
+
3689
+
3151
3690
  },
3152
3691
  8323(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3153
3692
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
@@ -3349,6 +3888,7 @@ function _ts_param(paramIndex, decorator) {
3349
3888
  }
3350
3889
 
3351
3890
 
3891
+ const VARIABLE_ENV_PREFIX = 'ENTITY_BUILDER_VAR_';
3352
3892
  class EntityBuilderConfigService {
3353
3893
  options;
3354
3894
  constructor(options){
@@ -3369,6 +3909,20 @@ class EntityBuilderConfigService {
3369
3909
  getConfig() {
3370
3910
  return this.options.config;
3371
3911
  }
3912
+ /** `designer.readOnly`, else the `ENTITY_BUILDER_READ_ONLY=true` env variable (production). */ isDesignerReadOnly() {
3913
+ return this.getConfig()?.designer?.readOnly ?? process.env['ENTITY_BUILDER_READ_ONLY'] === 'true';
3914
+ }
3915
+ /** 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() {
3916
+ const fromEnv = Object.entries(process.env).filter((entry)=>entry[0].startsWith(VARIABLE_ENV_PREFIX) && !!entry[1]).map(([key, value])=>[
3917
+ key.slice(VARIABLE_ENV_PREFIX.length),
3918
+ value
3919
+ ]);
3920
+ const variables = {
3921
+ ...Object.fromEntries(fromEnv),
3922
+ ...this.getConfig()?.bundles?.variables
3923
+ };
3924
+ 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'));
3925
+ }
3372
3926
  }
3373
3927
  EntityBuilderConfigService = _ts_decorate([
3374
3928
  (0,_nestjs_common__rspack_import_0.Injectable)(),
@@ -3774,54 +4328,24 @@ __webpack_require__.d(__webpack_exports__, {
3774
4328
 
3775
4329
 
3776
4330
  },
3777
- 8888(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3778
-
3779
- // EXPORTS
3780
- __webpack_require__.d(__webpack_exports__, {
3781
- T: () => (/* binding */ FlowApiKeyService)
3782
- });
3783
-
3784
- // EXTERNAL MODULE: external "@nestjs/common"
3785
- var common_ = __webpack_require__(6157);
3786
- // EXTERNAL MODULE: external "typeorm"
3787
- var external_typeorm_ = __webpack_require__(7158);
3788
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/config/message-keys.ts
3789
- var message_keys = __webpack_require__(5732);
3790
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/entities/flow-api-key.entity.ts
3791
- var flow_api_key_entity = __webpack_require__(201);
3792
- // EXTERNAL MODULE: external "node:crypto"
3793
- var external_node_crypto_ = __webpack_require__(2561);
3794
- ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow-api-key.ts
3795
-
3796
- const API_KEY_PREFIX_LENGTH = 11; // "fk_" + 8 hex
3797
- /** A new key: `fk_<8 hex>_<32 hex>`. Shown once; only its hash is stored. */ function generateApiKey() {
3798
- const key = `fk_${(0,external_node_crypto_.randomBytes)(4).toString('hex')}_${(0,external_node_crypto_.randomBytes)(16).toString('hex')}`;
3799
- return {
3800
- key,
3801
- prefix: apiKeyPrefix(key),
3802
- hash: hashApiKey(key)
3803
- };
3804
- }
3805
- const hashApiKey = (key)=>(0,external_node_crypto_.createHash)('sha256').update(key).digest('hex');
3806
- const apiKeyPrefix = (key)=>key.slice(0, API_KEY_PREFIX_LENGTH);
3807
- function looksLikeApiKey(key) {
3808
- return typeof key === 'string' && /^fk_[0-9a-f]{8}_[0-9a-f]{32}$/.test(key);
3809
- }
3810
- /** Constant-time comparison of a presented key against a stored hash. */ function apiKeyMatches(presented, storedHash) {
3811
- const a = Buffer.from(hashApiKey(presented), 'hex');
3812
- const b = Buffer.from(storedHash, 'hex');
3813
- return a.length === b.length && (0,external_node_crypto_.timingSafeEqual)(a, b);
3814
- }
3815
-
3816
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/entities/flow-definition.entity.ts
3817
- var flow_definition_entity = __webpack_require__(3770);
3818
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/rule-values.ts
3819
- var rule_values = __webpack_require__(6622);
3820
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-datasource.provider.ts
3821
- var entity_builder_datasource_provider = __webpack_require__(424);
3822
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/flow-definition.service.ts
3823
- var flow_definition_service = __webpack_require__(5229);
3824
- ;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/services/flow-api-key.service.ts
4331
+ 5229(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4332
+ /* import */ var _flusys_nestjs_shared__rspack_import_0 = __webpack_require__(3422);
4333
+ /* import */ var _flusys_nestjs_shared_classes__rspack_import_1 = __webpack_require__(6937);
4334
+ /* import */ var _flusys_nestjs_shared_interfaces__rspack_import_2 = __webpack_require__(5103);
4335
+ /* import */ var _flusys_nestjs_shared_modules__rspack_import_3 = __webpack_require__(1236);
4336
+ /* import */ var _nestjs_common__rspack_import_4 = __webpack_require__(6157);
4337
+ /* import */ var typeorm__rspack_import_5 = __webpack_require__(7158);
4338
+ /* import */ var _config_event_actions_js__rspack_import_16 = __webpack_require__(982);
4339
+ /* import */ var _config_message_keys_js__rspack_import_14 = __webpack_require__(5732);
4340
+ /* import */ var _entities_entity_definition_entity_js__rspack_import_6 = __webpack_require__(3189);
4341
+ /* import */ var _entities_flow_definition_entity_js__rspack_import_7 = __webpack_require__(3770);
4342
+ /* import */ var _entities_flow_version_entity_js__rspack_import_8 = __webpack_require__(2399);
4343
+ /* import */ var _flow_engine_flow_api_key_js__rspack_import_9 = __webpack_require__(4816);
4344
+ /* import */ var _flow_engine_flow_permission_logic_js__rspack_import_13 = __webpack_require__(8220);
4345
+ /* import */ var _flow_engine_flow_graph_validator_js__rspack_import_10 = __webpack_require__(3186);
4346
+ /* import */ var _flow_engine_flow_types_js__rspack_import_11 = __webpack_require__(6256);
4347
+ /* import */ var _interfaces_flow_definition_interface_js__rspack_import_15 = __webpack_require__(9609);
4348
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_12 = __webpack_require__(424);
3825
4349
  function _ts_decorate(decorators, target, key, desc) {
3826
4350
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3827
4351
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -3854,194 +4378,6 @@ function _ts_param(paramIndex, decorator) {
3854
4378
 
3855
4379
 
3856
4380
 
3857
- const TOUCH_INTERVAL_MS = 60_000;
3858
- class FlowApiKeyService {
3859
- provider;
3860
- constructor(provider){
3861
- this.provider = provider;
3862
- }
3863
- async repo() {
3864
- return this.provider.getRepository(flow_api_key_entity/* .FlowApiKey */.S);
3865
- }
3866
- async assertFlowExists(flowId) {
3867
- const flow = await (await this.provider.getRepository(flow_definition_entity/* .FlowDefinition */.G)).findOne({
3868
- where: {
3869
- id: flowId
3870
- },
3871
- select: {
3872
- id: true
3873
- }
3874
- });
3875
- if (!flow) throw (0,flow_definition_service/* .flowNotFound */.YW)();
3876
- }
3877
- async findKey(id) {
3878
- const key = await (await this.repo()).findOne({
3879
- where: {
3880
- id
3881
- }
3882
- });
3883
- if (!key) throw new common_.NotFoundException({
3884
- message: 'API key not found.',
3885
- messageKey: message_keys/* .FLOW_MESSAGES.API_KEY_NOT_FOUND */.wu.API_KEY_NOT_FOUND
3886
- });
3887
- await this.assertFlowExists(key.flowId);
3888
- return key;
3889
- }
3890
- /** The plain key is returned here and never again. */ async create(flowId, name, expiresAt, user) {
3891
- const expiry = expiresAt ? (0,rule_values/* .parseDateText */.hz)(expiresAt) : null;
3892
- if (expiry !== null && !(expiry > Date.now())) {
3893
- throw new common_.BadRequestException({
3894
- message: 'The expiry date of an API key must be in the future.',
3895
- messageKey: message_keys/* .FLOW_MESSAGES.API_KEY_EXPIRY_IN_PAST */.wu.API_KEY_EXPIRY_IN_PAST
3896
- });
3897
- }
3898
- await this.assertFlowExists(flowId);
3899
- const { key, prefix, hash } = generateApiKey();
3900
- const repo = await this.repo();
3901
- const saved = await repo.save(repo.create({
3902
- flowId,
3903
- name,
3904
- keyPrefix: prefix,
3905
- keyHash: hash,
3906
- isActive: true,
3907
- expiresAt: expiry === null ? null : new Date(expiry),
3908
- lastUsedAt: null,
3909
- createdById: user?.id ?? null
3910
- }));
3911
- return {
3912
- key,
3913
- info: this.info(saved)
3914
- };
3915
- }
3916
- async list(flowId) {
3917
- await this.assertFlowExists(flowId);
3918
- const rows = await (await this.repo()).find({
3919
- where: {
3920
- flowId
3921
- },
3922
- order: {
3923
- createdAt: 'DESC'
3924
- }
3925
- });
3926
- return rows.map((r)=>this.info(r));
3927
- }
3928
- async revoke(id) {
3929
- const key = await this.findKey(id);
3930
- await (await this.repo()).update({
3931
- id: key.id
3932
- }, {
3933
- isActive: false
3934
- });
3935
- }
3936
- async remove(id) {
3937
- const key = await this.findKey(id);
3938
- await (await this.repo()).softDelete({
3939
- id: key.id
3940
- });
3941
- }
3942
- /**
3943
- * Checks `presented` against the unrevoked keys of this flow. Only the flow access guard calls
3944
- * this, and only while the flow's authMode is `api_key`; revoking works in any authMode.
3945
- */ async verify(flowId, presented) {
3946
- if (!looksLikeApiKey(presented)) return 'invalid';
3947
- const repo = await this.repo();
3948
- const candidates = await repo.find({
3949
- where: {
3950
- flowId,
3951
- keyPrefix: apiKeyPrefix(presented),
3952
- isActive: true,
3953
- deletedAt: (0,external_typeorm_.IsNull)()
3954
- },
3955
- select: {
3956
- id: true,
3957
- keyHash: true,
3958
- expiresAt: true,
3959
- lastUsedAt: true
3960
- }
3961
- });
3962
- const match = candidates.find((c)=>apiKeyMatches(presented, c.keyHash));
3963
- if (!match) return 'invalid';
3964
- if (isExpired(match.expiresAt)) return 'expired';
3965
- if (!match.lastUsedAt || Date.now() - match.lastUsedAt.getTime() > TOUCH_INTERVAL_MS) {
3966
- await repo.update({
3967
- id: match.id
3968
- }, {
3969
- lastUsedAt: new Date()
3970
- });
3971
- }
3972
- return 'valid';
3973
- }
3974
- info(row) {
3975
- const status = !row.isActive ? 'revoked' : isExpired(row.expiresAt) ? 'expired' : 'active';
3976
- return {
3977
- id: row.id,
3978
- flowId: row.flowId,
3979
- name: row.name,
3980
- keyPrefix: row.keyPrefix,
3981
- isActive: row.isActive,
3982
- status,
3983
- expiresAt: row.expiresAt,
3984
- lastUsedAt: row.lastUsedAt,
3985
- createdAt: row.createdAt
3986
- };
3987
- }
3988
- }
3989
- FlowApiKeyService = _ts_decorate([
3990
- (0,common_.Injectable)({
3991
- scope: common_.Scope.REQUEST
3992
- }),
3993
- _ts_param(0, (0,common_.Inject)(entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D)),
3994
- _ts_metadata("design:type", Function),
3995
- _ts_metadata("design:paramtypes", [
3996
- typeof entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D
3997
- ])
3998
- ], FlowApiKeyService);
3999
- function isExpired(expiresAt) {
4000
- return !!expiresAt && expiresAt.getTime() <= Date.now();
4001
- }
4002
-
4003
-
4004
- },
4005
- 5229(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4006
- /* import */ var _flusys_nestjs_shared__rspack_import_0 = __webpack_require__(3422);
4007
- /* import */ var _flusys_nestjs_shared_classes__rspack_import_1 = __webpack_require__(6937);
4008
- /* import */ var _flusys_nestjs_shared_modules__rspack_import_2 = __webpack_require__(1236);
4009
- /* import */ var _nestjs_common__rspack_import_3 = __webpack_require__(6157);
4010
- /* import */ var typeorm__rspack_import_4 = __webpack_require__(7158);
4011
- /* import */ var _config_event_actions_js__rspack_import_11 = __webpack_require__(982);
4012
- /* import */ var _config_message_keys_js__rspack_import_10 = __webpack_require__(5732);
4013
- /* import */ var _entities_entity_definition_entity_js__rspack_import_5 = __webpack_require__(3189);
4014
- /* import */ var _entities_flow_definition_entity_js__rspack_import_6 = __webpack_require__(3770);
4015
- /* import */ var _flow_engine_flow_graph_validator_js__rspack_import_7 = __webpack_require__(3186);
4016
- /* import */ var _flow_engine_flow_types_js__rspack_import_8 = __webpack_require__(6256);
4017
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_9 = __webpack_require__(424);
4018
- function _ts_decorate(decorators, target, key, desc) {
4019
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4020
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
4021
- r = Reflect.decorate(decorators, target, key, desc);
4022
- } else {
4023
- for(var i = decorators.length - 1; i >= 0; i--){
4024
- if (d = decorators[i]) {
4025
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
4026
- }
4027
- }
4028
- }
4029
- return c > 3 && r && Object.defineProperty(target, key, r), r;
4030
- }
4031
- function _ts_metadata(metadataKey, metadataValue) {
4032
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
4033
- return Reflect.metadata(metadataKey, metadataValue);
4034
- }
4035
- }
4036
- function _ts_param(paramIndex, decorator) {
4037
- return function(target, key) {
4038
- decorator(target, key, paramIndex);
4039
- };
4040
- }
4041
-
4042
-
4043
-
4044
-
4045
4381
 
4046
4382
 
4047
4383
 
@@ -4051,21 +4387,28 @@ function _ts_param(paramIndex, decorator) {
4051
4387
 
4052
4388
 
4053
4389
  const defaultFlowPermissionCode = (slug)=>`entity_builder.flow.${slug}.execute`;
4054
- const flowNotFound = ()=>new _nestjs_common__rspack_import_3.NotFoundException({
4390
+ /** 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));
4391
+ const flowNotFound = ()=>new _nestjs_common__rspack_import_4.NotFoundException({
4055
4392
  message: 'Flow not found.',
4056
- messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.NOT_FOUND */.wu.NOT_FOUND
4393
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NOT_FOUND */.wu.NOT_FOUND
4394
+ });
4395
+ /** A flow that has been published at least once: its columns are what callers run. */ const isPublished = (flow)=>flow.version > 0;
4396
+ /** The versioned part of a flow (or of a draft), in a fixed key order so two snapshots compare as text. */ function snapshotOf(source) {
4397
+ return Object.fromEntries(_interfaces_flow_definition_interface_js__rspack_import_15/* .FLOW_VERSIONED_FIELDS.filter */.v.filter((key)=>source[key] !== undefined).map((key)=>[
4398
+ key,
4399
+ source[key]
4400
+ ]));
4401
+ }
4402
+ const sameSnapshot = (a, b)=>JSON.stringify(a) === JSON.stringify(b);
4403
+ /** What the designer edits: the saved draft, or the published flow when there is none. */ const workingCopy = (flow)=>({
4404
+ ...snapshotOf(flow),
4405
+ ...flow.draft
4057
4406
  });
4058
- /** Fills the defaults a flow definition gets when a field is left out. */ function toFlowSettings(dto) {
4407
+ /** What running a flow needs; everything about a run is on its Request steps. */ function toFlowSettings(dto) {
4059
4408
  return {
4060
4409
  slug: dto.slug,
4061
- authMode: dto.authMode ?? 'jwt',
4062
- runAs: dto.runAs ?? 'caller',
4063
- isTransactional: dto.isTransactional ?? false,
4064
- bodyType: dto.bodyType ?? 'object',
4065
- inputSchema: dto.inputSchema ?? [],
4066
4410
  nodes: dto.nodes ?? [],
4067
- edges: dto.edges ?? [],
4068
- timeoutMs: dto.timeoutMs ?? _flow_engine_flow_types_js__rspack_import_8/* .FLOW_LIMITS.DEFAULT_TIMEOUT_MS */.Or.DEFAULT_TIMEOUT_MS
4411
+ edges: dto.edges ?? []
4069
4412
  };
4070
4413
  }
4071
4414
  class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import_1.ApiService {
@@ -4074,11 +4417,14 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4074
4417
  provider;
4075
4418
  permissionCache;
4076
4419
  actionRegistry;
4077
- constructor(cacheManager, utilsService, provider, permissionCache, actionRegistry){
4078
- super(_config_event_actions_js__rspack_import_11/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION, cacheManager, utilsService, FlowDefinitionService.name, true, _config_event_actions_js__rspack_import_11/* .ENTITY_BUILDER_EVENT_MODULE */.S, provider, _entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G), this.cacheManager = cacheManager, this.utilsService = utilsService, this.provider = provider, this.permissionCache = permissionCache, this.actionRegistry = actionRegistry;
4420
+ notifications;
4421
+ email;
4422
+ companyAccess;
4423
+ constructor(cacheManager, utilsService, provider, permissionCache, actionRegistry, notifications, email, companyAccess){
4424
+ 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;
4079
4425
  }
4080
4426
  async findOrFail(id) {
4081
- const flow = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G)).findOne({
4427
+ const flow = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
4082
4428
  where: {
4083
4429
  id
4084
4430
  }
@@ -4086,29 +4432,28 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4086
4432
  if (!flow) throw flowNotFound();
4087
4433
  return flow;
4088
4434
  }
4089
- /** Validation with the real entity and flow lists. Returns errors and warnings without throwing. */ async validate(settings) {
4090
- const entities = await (await this.provider.getRepository(_entities_entity_definition_entity_js__rspack_import_5/* .EntityDefinition */.f)).find({
4435
+ /** Validation with the real entity and flow lists and the modules this app runs. Returns errors and warnings without throwing. */ async validate(settings) {
4436
+ const entities = await (await this.provider.getRepository(_entities_entity_definition_entity_js__rspack_import_6/* .EntityDefinition */.f)).find({
4091
4437
  select: {
4092
4438
  code: true
4093
4439
  }
4094
4440
  });
4095
- const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G)).find();
4096
- return (0,_flow_engine_flow_graph_validator_js__rspack_import_7/* .validateFlow */.n)(settings, {
4441
+ const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find();
4442
+ return (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .validateFlow */.nC)(settings, {
4097
4443
  knownEntityCodes: new Set(entities.map((e)=>e.code)),
4098
4444
  knownFlows: new Map(flows.map((flow)=>[
4099
4445
  flow.slug,
4100
- {
4101
- isActive: flow.isActive,
4102
- authMode: flow.authMode,
4103
- bodyType: flow.bodyType,
4104
- inputSchema: flow.inputSchema ?? [],
4105
- calls: calledSlugs(flow.nodes)
4106
- }
4107
- ]))
4446
+ callableOf(flow)
4447
+ ])),
4448
+ messageServices: {
4449
+ notification: !!this.notifications,
4450
+ email: !!this.email
4451
+ },
4452
+ companyMembers: !!this.companyAccess
4108
4453
  });
4109
4454
  }
4110
4455
  /** Names of the other flows whose `call_flow` nodes run `slug`. */ async callersOf(slug, excludeIds) {
4111
- const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G)).find({
4456
+ const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4112
4457
  select: {
4113
4458
  id: true,
4114
4459
  name: true,
@@ -4120,21 +4465,109 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4120
4465
  async assertNotCalled(slug, excludeIds) {
4121
4466
  const callers = await this.callersOf(slug, excludeIds);
4122
4467
  if (!callers.length) return;
4123
- throw new _nestjs_common__rspack_import_3.ConflictException({
4468
+ throw new _nestjs_common__rspack_import_4.ConflictException({
4124
4469
  message: `The flow '${slug}' is called by: ${callers.join(', ')}. Change those flows first.`,
4125
- messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.CALLED_BY_OTHER_FLOWS */.wu.CALLED_BY_OTHER_FLOWS,
4470
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.CALLED_BY_OTHER_FLOWS */.wu.CALLED_BY_OTHER_FLOWS,
4126
4471
  messageVariables: {
4127
4472
  slug,
4128
4473
  flows: callers.join(', ')
4129
4474
  }
4130
4475
  });
4131
4476
  }
4477
+ /**
4478
+ * Other flows' Call flow steps keep working: the URL name they call is not renamed, the paths they
4479
+ * start at are not dropped, and nothing else they rely on changes under them - access, body,
4480
+ * required inputs, the flow staying on, the call chain staying loop-free and within depth.
4481
+ */ async assertCallersKept(before, after, live) {
4482
+ if (after.slug !== before.slug) return this.assertNotCalled(before.slug, [
4483
+ before.id
4484
+ ]);
4485
+ await this.assertEndpointsNotCalled(before, after);
4486
+ await this.assertCallsStillValid(before, {
4487
+ ...after,
4488
+ ...live
4489
+ });
4490
+ }
4491
+ /**
4492
+ * Checks each running flow that calls this one, directly or through other flows, against the flows
4493
+ * as they are and as this change leaves them, and refuses the change when it adds a problem. A
4494
+ * caller already broken for another reason does not block it; only what this change breaks does.
4495
+ */ async assertCallsStillValid(before, after) {
4496
+ const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find();
4497
+ const running = flows.filter((flow)=>flow.id !== before.id && flow.isActive && isPublished(flow) && calledSlugs(flow.nodes).length);
4498
+ if (!running.length) return;
4499
+ const now = new Map(flows.map((flow)=>[
4500
+ flow.slug,
4501
+ callableOf(flow)
4502
+ ]));
4503
+ const next = new Map(now).set(before.slug, callableOf(after));
4504
+ const broken = [];
4505
+ for (const caller of running){
4506
+ const settings = toFlowSettings(caller);
4507
+ // Counted, not a set: two steps with the same label can share a problem, and only the new one is added.
4508
+ const existing = new Map();
4509
+ for (const problem of (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .callProblems */.r7)(settings, before.slug, now)){
4510
+ const key = JSON.stringify(problem);
4511
+ existing.set(key, (existing.get(key) ?? 0) + 1);
4512
+ }
4513
+ const added = (0,_flow_engine_flow_graph_validator_js__rspack_import_10/* .callProblems */.r7)(settings, before.slug, next).filter((problem)=>{
4514
+ const key = JSON.stringify(problem);
4515
+ const left = existing.get(key) ?? 0;
4516
+ existing.set(key, left - 1);
4517
+ return left <= 0;
4518
+ });
4519
+ if (added.length) broken.push({
4520
+ flow: caller.name,
4521
+ problems: added
4522
+ });
4523
+ }
4524
+ if (!broken.length) return;
4525
+ const names = broken.map((b)=>b.flow).join(', ');
4526
+ throw new _nestjs_common__rspack_import_4.ConflictException({
4527
+ message: `This change breaks flows that call '${before.slug}': ${names}. Change those flows first.`,
4528
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.CALLERS_BROKEN */.wu.CALLERS_BROKEN,
4529
+ messageVariables: {
4530
+ slug: before.slug,
4531
+ flows: names
4532
+ },
4533
+ errors: broken.flatMap((b)=>b.problems.map((problem)=>({
4534
+ field: 'flow',
4535
+ flow: b.flow,
4536
+ ...problem
4537
+ })))
4538
+ });
4539
+ }
4540
+ /** A URL other flows' Call flow steps start at cannot be removed or renamed from under them. */ async assertEndpointsNotCalled(before, after) {
4541
+ const kept = new Set((0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(after).map((e)=>e.path));
4542
+ const removed = (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(before).map((e)=>e.path).filter((path)=>!kept.has(path));
4543
+ if (!removed.length) return;
4544
+ const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4545
+ select: {
4546
+ id: true,
4547
+ name: true,
4548
+ nodes: true
4549
+ }
4550
+ });
4551
+ for (const path of removed){
4552
+ 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);
4553
+ if (!callers.length) continue;
4554
+ const url = `api-flows/${before.slug}${path ? `/${path}` : ''}`;
4555
+ throw new _nestjs_common__rspack_import_4.ConflictException({
4556
+ message: `The URL ${url} is called by: ${callers.join(', ')}. Change those flows first.`,
4557
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.ENDPOINT_CALLED_BY_OTHER_FLOWS */.wu.ENDPOINT_CALLED_BY_OTHER_FLOWS,
4558
+ messageVariables: {
4559
+ url,
4560
+ flows: callers.join(', ')
4561
+ }
4562
+ });
4563
+ }
4564
+ }
4132
4565
  async assertSaveable(settings, user, selfId) {
4133
4566
  const { errors, warnings } = await this.validate(settings);
4134
4567
  if (errors.length) {
4135
- throw new _nestjs_common__rspack_import_3.BadRequestException({
4568
+ throw new _nestjs_common__rspack_import_4.BadRequestException({
4136
4569
  message: 'The flow has problems.',
4137
- messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
4570
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
4138
4571
  errors: errors.map((error)=>({
4139
4572
  field: 'flow',
4140
4573
  ...error
@@ -4142,16 +4575,16 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4142
4575
  warnings
4143
4576
  });
4144
4577
  }
4145
- const clash = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G)).findOne({
4578
+ const clash = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).findOne({
4146
4579
  where: {
4147
4580
  slug: settings.slug
4148
4581
  },
4149
4582
  withDeleted: true
4150
4583
  });
4151
4584
  if (clash && clash.id !== selfId) {
4152
- throw new _nestjs_common__rspack_import_3.ConflictException({
4585
+ throw new _nestjs_common__rspack_import_4.ConflictException({
4153
4586
  message: `The URL name '${settings.slug}' is already used by another flow.`,
4154
- messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.SLUG_TAKEN */.wu.SLUG_TAKEN,
4587
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.SLUG_TAKEN */.wu.SLUG_TAKEN,
4155
4588
  messageVariables: {
4156
4589
  slug: settings.slug
4157
4590
  }
@@ -4160,7 +4593,6 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4160
4593
  await this.assertMayAuthor(settings, user);
4161
4594
  }
4162
4595
  /** The extra permissions a flow needs from whoever writes it: saving it, or test-running it as an unsaved draft. */ async assertMayAuthor(settings, user) {
4163
- if (settings.runAs === 'system') await this.assertHasPermission(user, _flusys_nestjs_shared__rspack_import_0.FLOW_DEFINITION_PERMISSIONS.SYSTEM);
4164
4596
  if (settings.nodes.some((node)=>node?.type === 'code')) await this.assertHasPermission(user, _flusys_nestjs_shared__rspack_import_0.FLOW_DEFINITION_PERMISSIONS.CODE);
4165
4597
  }
4166
4598
  async assertMayCreate(user) {
@@ -4168,13 +4600,13 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4168
4600
  }
4169
4601
  /**
4170
4602
  * A test run needs what running the flow for real would: the author permissions for its
4171
- * run-as and code nodes (a saved flow as much as a draft). A live run also needs what makes it
4172
- * real - a draft the permission to save it, a permission flow its own execute permission.
4173
- */ async assertMayTest(settings, user, run) {
4603
+ * code nodes (a saved flow as much as a draft). A live run also needs what makes it
4604
+ * real - a draft the permission to save it, a permission URL its own execute permission.
4605
+ */ async assertMayTest(settings, endpoint, user, run) {
4174
4606
  await this.assertMayAuthor(settings, user);
4175
4607
  if (run.mode !== 'live') return;
4176
4608
  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);
4177
- if (settings.authMode === 'permission') await this.assertHasPermission(user, settings.permissionCode || defaultFlowPermissionCode(settings.slug));
4609
+ if (endpoint?.authMode === 'permission') await this.permissionCache.assertPermissionLogic(user, flowPermissions(endpoint, settings.slug));
4178
4610
  }
4179
4611
  assertHasPermission(user, permission) {
4180
4612
  return this.permissionCache.assertPermission(user, permission);
@@ -4184,36 +4616,171 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4184
4616
  dto
4185
4617
  ]){
4186
4618
  await this.assertSaveable(toFlowSettings(d), user);
4187
- // JSON columns have no database default, so leaving the optional settings out must not mean NULL.
4188
- d.inputSchema ??= [];
4189
- d.bodyType ??= 'object';
4190
- d.authMode ??= 'jwt';
4191
- d.runAs ??= 'caller';
4619
+ d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_9/* .sealApiKeys */.ue)(d.nodes);
4620
+ d.version = 0;
4192
4621
  }
4193
4622
  }
4194
- async beforeUpdateOperation(dto, user, _qr) {
4623
+ /**
4624
+ * Checked against what the save produces: the draft of a published flow, the flow itself before its
4625
+ * first publish. An unpublished flow changes in place, so its callers are protected now, not at publish.
4626
+ */ async beforeUpdateOperation(dto, user, _qr) {
4195
4627
  for (const d of Array.isArray(dto) ? dto : [
4196
4628
  dto
4197
4629
  ]){
4198
4630
  const existing = await this.findOrFail(d.id);
4199
- const merged = toFlowSettings({
4200
- ...existing,
4631
+ const settings = toFlowSettings({
4632
+ ...workingCopy(existing),
4201
4633
  ...stripUndefined(d)
4202
4634
  });
4203
- await this.assertSaveable(merged, user, existing.id);
4204
- if (merged.slug !== existing.slug) await this.assertNotCalled(existing.slug, [
4205
- existing.id
4206
- ]);
4207
- d.version = existing.version + 1;
4635
+ await this.assertSaveable(settings, user, existing.id);
4636
+ if (!isPublished(existing)) await this.assertCallersKept(existing, settings, {
4637
+ isActive: d.isActive ?? existing.isActive,
4638
+ version: existing.version
4639
+ });
4640
+ if (d.nodes) d.nodes = (0,_flow_engine_flow_api_key_js__rspack_import_9/* .sealApiKeys */.ue)(d.nodes);
4641
+ }
4642
+ }
4643
+ /** A published flow keeps running as published: the edits go to its draft (none when they match what is live). */ async convertSingleDtoToEntity(dto, user) {
4644
+ const id = dto.id;
4645
+ if (!id) return super.convertSingleDtoToEntity(dto, user);
4646
+ const existing = await this.findOrFail(id);
4647
+ if (!isPublished(existing)) return super.convertSingleDtoToEntity(dto, user);
4648
+ const draft = snapshotOf({
4649
+ ...workingCopy(existing),
4650
+ ...stripUndefined(dto)
4651
+ });
4652
+ existing.draft = sameSnapshot(draft, snapshotOf(existing)) ? null : draft;
4653
+ return existing;
4654
+ }
4655
+ /**
4656
+ * Makes the draft (or, before the first publish, the flow as saved) what callers run, as the next
4657
+ * version. It is checked like a save, plus what only going live can break: other flows calling
4658
+ * this one by a URL name or path it no longer has.
4659
+ */ async publish(id, note, user) {
4660
+ const existing = await this.findOrFail(id);
4661
+ const published = isPublished(existing);
4662
+ if (published && !existing.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
4663
+ message: 'There are no changes to publish.',
4664
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NOTHING_TO_PUBLISH */.wu.NOTHING_TO_PUBLISH
4665
+ });
4666
+ const next = workingCopy(existing);
4667
+ const settings = toFlowSettings(next);
4668
+ await this.assertSaveable(settings, user, existing.id);
4669
+ await this.assertCallersKept(existing, settings, {
4670
+ isActive: next.isActive ?? existing.isActive,
4671
+ version: existing.version + 1
4672
+ });
4673
+ const qr = await this.provider.getQueryRunner();
4674
+ try {
4675
+ await qr.connect();
4676
+ await qr.startTransaction();
4677
+ const versions = qr.manager.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p);
4678
+ Object.assign(existing, next, {
4679
+ draft: null,
4680
+ version: existing.version + 1,
4681
+ updatedById: user?.id ?? null
4682
+ });
4683
+ await qr.manager.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G).save(existing);
4684
+ await versions.save(versions.create({
4685
+ flowId: existing.id,
4686
+ version: existing.version,
4687
+ definition: next,
4688
+ note: note?.trim() || null,
4689
+ publishedAt: new Date(),
4690
+ publishedById: user?.id ?? null
4691
+ }));
4692
+ await qr.commitTransaction();
4693
+ } catch (error) {
4694
+ await qr.rollbackTransaction().catch(()=>undefined);
4695
+ throw error;
4696
+ } finally{
4697
+ await qr.release().catch(()=>undefined);
4208
4698
  }
4699
+ await this.clearCaches(existing);
4700
+ await this.provisionPermissions([
4701
+ existing
4702
+ ]);
4703
+ return existing;
4704
+ }
4705
+ /** Newest first, without the definitions (`getVersion` returns one in full). */ async listVersions(flowId) {
4706
+ await this.findOrFail(flowId);
4707
+ return (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p)).find({
4708
+ where: {
4709
+ flowId
4710
+ },
4711
+ order: {
4712
+ version: 'DESC'
4713
+ },
4714
+ select: {
4715
+ id: true,
4716
+ flowId: true,
4717
+ version: true,
4718
+ note: true,
4719
+ publishedAt: true,
4720
+ publishedById: true
4721
+ }
4722
+ });
4723
+ }
4724
+ async getVersion(flowId, version) {
4725
+ const row = await (await this.provider.getRepository(_entities_flow_version_entity_js__rspack_import_8/* .FlowVersion */.p)).findOne({
4726
+ where: {
4727
+ flowId,
4728
+ version
4729
+ }
4730
+ });
4731
+ if (!row) throw new _nestjs_common__rspack_import_4.NotFoundException({
4732
+ message: `Version ${version} of this flow was not found.`,
4733
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.VERSION_NOT_FOUND */.wu.VERSION_NOT_FOUND,
4734
+ messageVariables: {
4735
+ version
4736
+ }
4737
+ });
4738
+ return row;
4739
+ }
4740
+ /**
4741
+ * Puts an old version back into the draft; callers keep the published flow until it is published
4742
+ * again. Not checked like a save (the entities it names may have changed since), but the author
4743
+ * still needs the permissions its code steps call for.
4744
+ */ async restoreVersion(flowId, version, user) {
4745
+ const flow = await this.findOrFail(flowId);
4746
+ const snapshot = snapshotOf((await this.getVersion(flowId, version)).definition);
4747
+ await this.assertMayAuthor(toFlowSettings(snapshot), user);
4748
+ flow.draft = sameSnapshot(snapshot, snapshotOf(flow)) ? null : snapshot;
4749
+ return this.saveDraft(flow, user);
4750
+ }
4751
+ async discardDraft(flowId, user) {
4752
+ const flow = await this.findOrFail(flowId);
4753
+ if (!flow.draft) throw new _nestjs_common__rspack_import_4.BadRequestException({
4754
+ message: 'This flow has no unpublished changes.',
4755
+ messageKey: _config_message_keys_js__rspack_import_14/* .FLOW_MESSAGES.NO_DRAFT */.wu.NO_DRAFT
4756
+ });
4757
+ flow.draft = null;
4758
+ return this.saveDraft(flow, user);
4759
+ }
4760
+ async saveDraft(flow, user) {
4761
+ await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).update(flow.id, {
4762
+ draft: flow.draft,
4763
+ updatedById: user?.id ?? null
4764
+ });
4765
+ await this.clearCaches(flow);
4766
+ return flow;
4767
+ }
4768
+ async clearCaches(flow) {
4769
+ if (!this.isCacheable) return;
4770
+ await Promise.all([
4771
+ this.clearCacheForAll(),
4772
+ this.clearCacheForId([
4773
+ flow
4774
+ ])
4775
+ ]);
4209
4776
  }
4210
4777
  async beforeDeleteOperation(dto, user, _qr) {
4211
4778
  const ids = Array.isArray(dto.id) ? dto.id : [
4212
4779
  dto.id
4213
4780
  ];
4214
- const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G)).find({
4781
+ const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G)).find({
4215
4782
  where: {
4216
- id: (0,typeorm__rspack_import_4.In)(ids)
4783
+ id: (0,typeorm__rspack_import_5.In)(ids)
4217
4784
  },
4218
4785
  withDeleted: true
4219
4786
  });
@@ -4221,43 +4788,70 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
4221
4788
  if (dto.type === 'restore') return;
4222
4789
  for (const flow of flows)await this.assertNotCalled(flow.slug, ids);
4223
4790
  }
4224
- async afterInsertOperation(entities) {
4225
- await this.provisionPermissions(entities);
4226
- }
4227
- async afterUpdateOperation(entities) {
4228
- await this.provisionPermissions(entities);
4229
- }
4230
- async provisionPermissions(flows) {
4231
- const needing = flows.filter((f)=>f.authMode === 'permission');
4232
- if (!needing.length) return;
4791
+ /** 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) {
4233
4792
  if (!this.actionRegistry) return;
4234
- await this.actionRegistry.registerActions(needing.map((f)=>({
4235
- code: f.permissionCode || defaultFlowPermissionCode(f.slug),
4236
- name: `Run ${f.name}`
4793
+ const actions = new Map();
4794
+ for (const flow of flows){
4795
+ for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow)){
4796
+ if (endpoint.authMode !== 'permission' || endpoint.permissions) continue;
4797
+ const code = defaultFlowPermissionCode(flow.slug);
4798
+ if (!actions.has(code)) actions.set(code, endpoint.path ? `Run ${flow.name} (${endpoint.path})` : `Run ${flow.name}`);
4799
+ }
4800
+ }
4801
+ if (actions.size) await this.actionRegistry.registerActions([
4802
+ ...actions
4803
+ ].map(([code, name])=>({
4804
+ code,
4805
+ name
4237
4806
  })));
4238
4807
  }
4239
4808
  }
4240
4809
  FlowDefinitionService = _ts_decorate([
4241
- (0,_nestjs_common__rspack_import_3.Injectable)({
4242
- scope: _nestjs_common__rspack_import_3.Scope.REQUEST
4810
+ (0,_nestjs_common__rspack_import_4.Injectable)({
4811
+ scope: _nestjs_common__rspack_import_4.Scope.REQUEST
4243
4812
  }),
4244
- _ts_param(0, (0,_nestjs_common__rspack_import_3.Inject)('CACHE_INSTANCE')),
4245
- _ts_param(1, (0,_nestjs_common__rspack_import_3.Inject)(_flusys_nestjs_shared_modules__rspack_import_2.UtilsService)),
4246
- _ts_param(2, (0,_nestjs_common__rspack_import_3.Inject)(_entity_builder_datasource_provider_js__rspack_import_9/* .EntityBuilderDataSourceProvider */.D)),
4247
- _ts_param(3, (0,_nestjs_common__rspack_import_3.Inject)(_flusys_nestjs_shared_modules__rspack_import_2.SharedPermissionCacheService)),
4248
- _ts_param(4, (0,_nestjs_common__rspack_import_3.Optional)()),
4249
- _ts_param(4, (0,_nestjs_common__rspack_import_3.Inject)(_flusys_nestjs_shared__rspack_import_0.PERMISSION_ACTION_REGISTRY)),
4813
+ _ts_param(0, (0,_nestjs_common__rspack_import_4.Inject)('CACHE_INSTANCE')),
4814
+ _ts_param(1, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.UtilsService)),
4815
+ _ts_param(2, (0,_nestjs_common__rspack_import_4.Inject)(_entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D)),
4816
+ _ts_param(3, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService)),
4817
+ _ts_param(4, (0,_nestjs_common__rspack_import_4.Optional)()),
4818
+ _ts_param(4, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared__rspack_import_0.PERMISSION_ACTION_REGISTRY)),
4819
+ _ts_param(5, (0,_nestjs_common__rspack_import_4.Optional)()),
4820
+ _ts_param(5, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.NOTIFICATION_ADAPTER)),
4821
+ _ts_param(6, (0,_nestjs_common__rspack_import_4.Optional)()),
4822
+ _ts_param(6, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.EMAIL_ADAPTER)),
4823
+ _ts_param(7, (0,_nestjs_common__rspack_import_4.Optional)()),
4824
+ _ts_param(7, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.COMPANY_ACCESS_RESOLVER)),
4250
4825
  _ts_metadata("design:type", Function),
4251
4826
  _ts_metadata("design:paramtypes", [
4252
4827
  typeof _flusys_nestjs_shared_classes__rspack_import_1.HybridCache === "undefined" ? Object : _flusys_nestjs_shared_classes__rspack_import_1.HybridCache,
4253
- typeof _flusys_nestjs_shared_modules__rspack_import_2.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_2.UtilsService,
4254
- typeof _entity_builder_datasource_provider_js__rspack_import_9/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_9/* .EntityBuilderDataSourceProvider */.D,
4255
- typeof _flusys_nestjs_shared_modules__rspack_import_2.SharedPermissionCacheService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_2.SharedPermissionCacheService,
4256
- typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry
4828
+ typeof _flusys_nestjs_shared_modules__rspack_import_3.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.UtilsService,
4829
+ typeof _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D,
4830
+ typeof _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService,
4831
+ typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry,
4832
+ typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter,
4833
+ typeof IEmailAdapter === "undefined" ? Object : IEmailAdapter,
4834
+ typeof ICompanyAccessResolver === "undefined" ? Object : ICompanyAccessResolver
4257
4835
  ])
4258
4836
  ], FlowDefinitionService);
4837
+ /** 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) {
4838
+ return (nodes ?? []).filter((node)=>node?.type === 'call_flow' && typeof node.config?.flowSlug === 'string').map((node)=>{
4839
+ const config = node.config;
4840
+ return {
4841
+ slug: config.flowSlug,
4842
+ endpoint: typeof config.endpoint === 'string' && config.endpoint ? config.endpoint : null
4843
+ };
4844
+ });
4845
+ }
4846
+ /** What a Call flow step checks about a flow: it runs only once published and switched on. */ function callableOf(flow) {
4847
+ return {
4848
+ isActive: flow.isActive && isPublished(flow),
4849
+ endpoints: (0,_flow_engine_flow_types_js__rspack_import_11/* .flowEndpoints */.Xr)(flow),
4850
+ calls: calledSlugs(flow.nodes)
4851
+ };
4852
+ }
4259
4853
  /** Slugs the `call_flow` nodes of a flow run. */ function calledSlugs(nodes) {
4260
- return (nodes ?? []).filter((node)=>node?.type === 'call_flow').map((node)=>node.config?.flowSlug).filter((slug)=>typeof slug === 'string');
4854
+ return calledTargets(nodes).map((target)=>target.slug);
4261
4855
  }
4262
4856
  function stripUndefined(value) {
4263
4857
  return Object.fromEntries(Object.entries(value).filter(([, v])=>v !== undefined));
@@ -4265,10 +4859,14 @@ function stripUndefined(value) {
4265
4859
 
4266
4860
  __webpack_require__.d(__webpack_exports__, {
4267
4861
  RK: () => (toFlowSettings),
4268
- Tk: () => (FlowDefinitionService)
4862
+ Tk: () => (FlowDefinitionService),
4863
+ eY: () => (snapshotOf)
4269
4864
  }, {
4270
4865
  Er: defaultFlowPermissionCode,
4271
- YW: flowNotFound
4866
+ HF: isPublished,
4867
+ YW: flowNotFound,
4868
+ j4: flowPermissions,
4869
+ lw: workingCopy
4272
4870
  });
4273
4871
 
4274
4872
 
@@ -4328,8 +4926,11 @@ class FlowExecutionService {
4328
4926
  await repo.save(repo.create({
4329
4927
  flowId: entry.flowId,
4330
4928
  flowSlug: entry.flowSlug,
4929
+ flowVersion: entry.flowVersion,
4331
4930
  status: entry.result.status,
4332
4931
  trigger: entry.trigger,
4932
+ endpointNodeId: entry.endpoint?.nodeId ?? null,
4933
+ endpointPath: entry.endpoint?.path ?? null,
4333
4934
  mode: entry.mode,
4334
4935
  statusCode: entry.result.statusCode,
4335
4936
  input: inputText.length > INPUT_LIMIT_CHARS ? {
@@ -4380,6 +4981,8 @@ class FlowExecutionService {
4380
4981
  const where = {};
4381
4982
  if (query.flowId) where.flowId = query.flowId;
4382
4983
  if (query.status) where.status = query.status;
4984
+ if (query.trigger) where.trigger = query.trigger;
4985
+ if (query.endpointNodeId) where.endpointNodeId = query.endpointNodeId;
4383
4986
  const [rows, total] = await repo.findAndCount({
4384
4987
  where,
4385
4988
  order: {
@@ -4392,8 +4995,11 @@ class FlowExecutionService {
4392
4995
  id: true,
4393
4996
  flowId: true,
4394
4997
  flowSlug: true,
4998
+ flowVersion: true,
4395
4999
  status: true,
4396
5000
  trigger: true,
5001
+ endpointNodeId: true,
5002
+ endpointPath: true,
4397
5003
  mode: true,
4398
5004
  statusCode: true,
4399
5005
  durationMs: true,
@@ -4438,7 +5044,7 @@ __webpack_require__.d(__webpack_exports__, {
4438
5044
 
4439
5045
 
4440
5046
  },
4441
- 4698(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5047
+ 3908(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4442
5048
 
4443
5049
  // EXPORTS
4444
5050
  __webpack_require__.d(__webpack_exports__, {
@@ -4448,10 +5054,14 @@ __webpack_require__.d(__webpack_exports__, {
4448
5054
 
4449
5055
  // EXTERNAL MODULE: external "@flusys/nestjs-shared/classes"
4450
5056
  var classes_ = __webpack_require__(6937);
4451
- // EXTERNAL MODULE: external "@flusys/nestjs-shared/exceptions"
4452
- var exceptions_ = __webpack_require__(2417);
5057
+ ;// CONCATENATED MODULE: external "@flusys/nestjs-shared/exceptions"
5058
+
5059
+ // EXTERNAL MODULE: external "@flusys/nestjs-shared/interfaces"
5060
+ var interfaces_ = __webpack_require__(5103);
4453
5061
  // EXTERNAL MODULE: external "@flusys/nestjs-shared/modules"
4454
5062
  var modules_ = __webpack_require__(1236);
5063
+ // EXTERNAL MODULE: external "@flusys/nestjs-shared/utils"
5064
+ var utils_ = __webpack_require__(9216);
4455
5065
  // EXTERNAL MODULE: external "@nestjs/common"
4456
5066
  var common_ = __webpack_require__(6157);
4457
5067
  // EXTERNAL MODULE: external "@nestjs/core"
@@ -4689,8 +5299,8 @@ const BLOCKED_REQUEST_HEADERS = new Set([
4689
5299
  });
4690
5300
  }
4691
5301
 
4692
- // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/index.ts + 2 modules
4693
- var rule_engine = __webpack_require__(5920);
5302
+ // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/index.ts + 1 modules
5303
+ var rule_engine = __webpack_require__(8429);
4694
5304
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-config.service.ts
4695
5305
  var entity_builder_config_service = __webpack_require__(4281);
4696
5306
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-datasource.provider.ts
@@ -4743,6 +5353,8 @@ function _ts_param(paramIndex, decorator) {
4743
5353
 
4744
5354
 
4745
5355
 
5356
+
5357
+
4746
5358
 
4747
5359
 
4748
5360
 
@@ -4753,41 +5365,47 @@ const publishFlowEvent = (eventName, payload, user)=>(0,classes_.publishDomainEv
4753
5365
  metadata: payload,
4754
5366
  user
4755
5367
  });
4756
- /** The run's time budget, whatever a definition 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;
5368
+ /** 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;
4757
5369
  class FlowExecutorService {
4758
5370
  provider;
4759
5371
  ruleEngine;
4760
5372
  moduleRef;
4761
5373
  permissionCache;
4762
5374
  config;
5375
+ companyAccess;
5376
+ notifications;
5377
+ email;
5378
+ branchHierarchy;
4763
5379
  logger = new common_.Logger(FlowExecutorService.name);
4764
- constructor(provider, ruleEngine, moduleRef, permissionCache, config){
5380
+ constructor(provider, ruleEngine, moduleRef, permissionCache, config, companyAccess, notifications, email, branchHierarchy){
4765
5381
  this.provider = provider;
4766
5382
  this.ruleEngine = ruleEngine;
4767
5383
  this.moduleRef = moduleRef;
4768
5384
  this.permissionCache = permissionCache;
4769
5385
  this.config = config;
5386
+ this.companyAccess = companyAccess;
5387
+ this.notifications = notifications;
5388
+ this.email = email;
5389
+ this.branchHierarchy = branchHierarchy;
4770
5390
  }
4771
- async run(definition, options) {
5391
+ async run(flow, options) {
4772
5392
  const startedAt = Date.now();
4773
- const flow = {
4774
- ...definition,
4775
- timeoutMs: flowTimeout(definition.timeoutMs)
4776
- };
5393
+ const start = (0,flow_types/* .startEndpoint */.IE)(flow, options.startAt);
4777
5394
  const state = {
5395
+ startAt: options.startAt,
4778
5396
  input: options.input,
4779
5397
  vars: {},
4780
5398
  context: {},
4781
5399
  request: options.request,
4782
5400
  user: options.user,
5401
+ authorization: options.authorization,
4783
5402
  permissions: this.ruleEngine.createPermissionResolver(options.user),
4784
5403
  mode: options.mode,
4785
- runAs: flow.runAs,
4786
- systemAuthorized: flow.runAs === 'system' && options.systemAuthorized === true,
4787
5404
  steps: 0,
4788
5405
  httpCalls: 0,
5406
+ messages: 0,
4789
5407
  writes: 0,
4790
- deadline: startedAt + flow.timeoutMs,
5408
+ deadline: startedAt + flowTimeout(start?.timeoutMs),
4791
5409
  callStack: [
4792
5410
  flow.slug
4793
5411
  ],
@@ -4795,15 +5413,7 @@ class FlowExecutorService {
4795
5413
  trace: []
4796
5414
  };
4797
5415
  try {
4798
- if (!flow_types/* .FLOW_RUN_AS.includes */.gl.includes(flow.runAs)) {
4799
- throw new flow_errors/* .FlowHttpError */.hk(403, 'This flow is not allowed to run.', undefined, {
4800
- messageKey: message_keys/* .FLOW_ERROR_MESSAGES.RUN_AS_NOT_ALLOWED */.zr.RUN_AS_NOT_ALLOWED,
4801
- messageVariables: {
4802
- runAs: String(flow.runAs)
4803
- }
4804
- });
4805
- }
4806
- await this.execute(flow, state, this.createEnv());
5416
+ await this.execute(flow, start?.isTransactional === true, state, this.createEnv());
4807
5417
  return {
4808
5418
  ...(0,flow_engine/* .successResult */.JK)(state, startedAt),
4809
5419
  ...state.savedUpTo ? {
@@ -4833,10 +5443,10 @@ class FlowExecutorService {
4833
5443
  }
4834
5444
  }
4835
5445
  /**
4836
- * Walks `flow`. A flow called from inside a transaction joins it; otherwise a transactional
4837
- * flow (or any test run) gets its own, and its held-back events are published after the commit.
4838
- */ async execute(flow, state, env) {
4839
- if (state.qr || !(flow.isTransactional || state.mode === 'dry_run')) {
5446
+ * Walks `flow`. A flow called from inside a transaction joins it; otherwise a run whose Request step
5447
+ * is transactional (or any test run) gets its own, and its held-back events are published after the commit.
5448
+ */ async execute(flow, transactional, state, env) {
5449
+ if (state.qr || !(transactional || state.mode === 'dry_run')) {
4840
5450
  await new flow_engine/* .FlowEngine */.$I(flow, env).walk(state);
4841
5451
  return;
4842
5452
  }
@@ -4898,10 +5508,10 @@ class FlowExecutorService {
4898
5508
  }
4899
5509
  /**
4900
5510
  * `call_flow`: runs another active flow as part of this run - same caller,
4901
- * transaction, test mode and step/HTTP/time budget. Returns what it answered: its `respond`
5511
+ * transaction, test mode and step/HTTP/time budget, from the Request step at `endpoint` (left out, its own URL). Returns what it answered: its `respond`
4902
5512
  * body, or the output of its last step. A 4xx/5xx answer or a rejection is passed on as the
4903
5513
  * caller's own; any other failure fails the calling node.
4904
- */ async callFlow(slug, input, parent, env) {
5514
+ */ async callFlow(slug, input, parent, env, endpoint) {
4905
5515
  if (parent.callStack.includes(slug)) {
4906
5516
  throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_RECURSION */.zr.CALLED_FLOW_RECURSION, {
4907
5517
  flow: slug,
@@ -4915,8 +5525,16 @@ class FlowExecutorService {
4915
5525
  max: flow_types/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH
4916
5526
  });
4917
5527
  const flow = await this.findCallable(slug, parent.qr);
4918
- await this.assertMayCall(flow, parent, env);
4919
- const checked = (0,flow_input_validator/* .validateFlowInput */.v)(flow.inputSchema ?? [], input, flow.bodyType);
5528
+ const settings = (0,flow_definition_service/* .toFlowSettings */.RK)(flow);
5529
+ const start = (0,flow_types/* .findEndpoint */.vS)(settings, endpoint);
5530
+ if (!start) {
5531
+ throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_ENDPOINT_NOT_FOUND */.zr.CALLED_FLOW_ENDPOINT_NOT_FOUND, {
5532
+ flow: slug,
5533
+ endpoint: endpoint ?? ''
5534
+ }, `Flow '${slug}' has no URL '${endpoint ?? ''}'.`);
5535
+ }
5536
+ await this.assertMayCall(flow, start, parent, env);
5537
+ const checked = (0,flow_input_validator/* .validateFlowInput */.v)(start.inputSchema, input, start.bodyType);
4920
5538
  if (checked.errors.length) {
4921
5539
  throw new flow_errors/* .FlowHttpError */.hk(400, 'Validation failed', checked.errors, {
4922
5540
  messageKey: message_keys/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
@@ -4925,22 +5543,21 @@ class FlowExecutorService {
4925
5543
  }
4926
5544
  });
4927
5545
  }
4928
- const settings = (0,flow_definition_service/* .toFlowSettings */.RK)(flow);
4929
5546
  const child = {
5547
+ startAt: start.nodeId,
4930
5548
  input: checked.values,
4931
5549
  vars: {},
4932
5550
  context: {},
4933
5551
  request: parent.request,
4934
5552
  user: parent.user,
5553
+ authorization: parent.authorization,
4935
5554
  permissions: parent.permissions,
4936
5555
  mode: parent.mode,
4937
- runAs: flow.authMode === 'internal' ? parent.runAs : settings.runAs,
4938
- // 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.
4939
- systemAuthorized: flow.authMode === 'internal' ? parent.systemAuthorized === true : settings.runAs === 'system',
4940
5556
  steps: parent.steps,
4941
5557
  httpCalls: parent.httpCalls,
5558
+ messages: parent.messages,
4942
5559
  writes: parent.writes,
4943
- deadline: Math.min(parent.deadline, Date.now() + flowTimeout(settings.timeoutMs)),
5560
+ deadline: Math.min(parent.deadline, Date.now() + flowTimeout(start.timeoutMs)),
4944
5561
  qr: parent.qr,
4945
5562
  callStack: [
4946
5563
  ...parent.callStack,
@@ -4955,13 +5572,14 @@ class FlowExecutorService {
4955
5572
  if (child.pendingEvents === parent.pendingEvents) parent.pendingEvents.length = queued;
4956
5573
  };
4957
5574
  try {
4958
- await this.execute(settings, child, env);
5575
+ await this.execute(settings, start.isTransactional, child, env);
4959
5576
  } catch (error) {
4960
5577
  dropQueued();
4961
5578
  throw calledFlowFailure(flow, error);
4962
5579
  } finally{
4963
5580
  parent.steps = child.steps;
4964
5581
  parent.httpCalls = child.httpCalls;
5582
+ parent.messages = child.messages;
4965
5583
  parent.writes = child.writes;
4966
5584
  }
4967
5585
  if (child.response) {
@@ -4980,7 +5598,8 @@ class FlowExecutorService {
4980
5598
  where: {
4981
5599
  slug,
4982
5600
  isActive: true,
4983
- deletedAt: (0,external_typeorm_.IsNull)()
5601
+ deletedAt: (0,external_typeorm_.IsNull)(),
5602
+ version: (0,external_typeorm_.MoreThan)(0)
4984
5603
  }
4985
5604
  });
4986
5605
  if (!flow) throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_NOT_FOUND */.zr.CALLED_FLOW_NOT_FOUND, {
@@ -4989,21 +5608,23 @@ class FlowExecutorService {
4989
5608
  return flow;
4990
5609
  }
4991
5610
  /**
4992
- * Any flow may call a function, and a flow running as the system may call any flow. One running
4993
- * as the caller may call only what that caller could call directly: public and signed-in flows,
4994
- * and permission flows when the caller holds the permission - never an API-key flow.
4995
- */ async assertMayCall(flow, parent, env) {
4996
- if (parent.runAs === 'system' && parent.systemAuthorized === true || flow.authMode === 'public' || flow.authMode === 'internal') return;
4997
- if (flow.authMode === 'api_key' || !parent.user) {
5611
+ * The called URL's own access, checked against the run's user: any flow may call a function or a
5612
+ * public URL; a signed-in one needs a user, a permission one the user holding it. An API-key URL
5613
+ * is never called from a flow, which has no key to send.
5614
+ */ async assertMayCall(flow, endpoint, parent, env) {
5615
+ const mode = endpoint.authMode;
5616
+ if (mode === 'public' || mode === 'internal') return;
5617
+ if (mode === 'api_key' || !parent.user) {
4998
5618
  throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_NOT_ALLOWED */.zr.CALLED_FLOW_NOT_ALLOWED, {
4999
5619
  flow: flow.slug,
5000
- authMode: flow.authMode
5001
- }, `Flow '${flow.slug}' (${flow.authMode}) cannot be called by this caller.`);
5620
+ authMode: mode
5621
+ }, `Flow '${flow.slug}' (${mode}) cannot be called by this caller.`);
5002
5622
  }
5003
- if (flow.authMode === 'permission') await env.assertPermission(flow.permissionCode || (0,flow_definition_service/* .defaultFlowPermissionCode */.Er)(flow.slug), parent.user);
5623
+ if (mode === 'permission') await env.assertPermissions((0,flow_definition_service/* .flowPermissions */.j4)(endpoint, flow.slug), parent.user);
5004
5624
  }
5005
5625
  createEnv() {
5006
5626
  const entityServices = new Map();
5627
+ /** One lookup per user for the whole run, however many steps ask. */ const access = new Map();
5007
5628
  let permissions;
5008
5629
  const env = {
5009
5630
  ruleEngine: this.ruleEngine,
@@ -5022,33 +5643,52 @@ class FlowExecutorService {
5022
5643
  }
5023
5644
  return entityServices.get(entityCode);
5024
5645
  },
5025
- callFlow: (slug, input, state)=>this.callFlow(slug, input, state, env),
5646
+ callFlow: (slug, input, state, endpoint)=>this.callFlow(slug, input, state, env, endpoint),
5026
5647
  atomic: (state, work)=>this.atomic(state, work),
5027
5648
  commit: (state)=>this.commitProgress(state),
5028
5649
  http: flowHttpRequest,
5650
+ notifications: this.notifications,
5651
+ email: this.email,
5652
+ ...this.companyAccess ? {
5653
+ companyMembers: (companyId, branchIds)=>this.companyAccess.getCompanyMembers(companyId, branchIds)
5654
+ } : {},
5655
+ ...this.branchHierarchy ? {
5656
+ branchTree: (branchIds)=>this.branchHierarchy.getDescendantIds(branchIds, {
5657
+ includeSelf: true
5658
+ })
5659
+ } : {},
5660
+ companyAccess: (userId)=>{
5661
+ if (!access.has(userId)) access.set(userId, this.companyAccess ? this.companyAccess.getUserAccess(userId) : Promise.resolve({
5662
+ companies: [],
5663
+ branches: []
5664
+ }));
5665
+ return access.get(userId);
5666
+ },
5029
5667
  publish: (eventName, payload, user)=>publishFlowEvent(eventName, payload, user),
5030
- assertPermission: async (code, user)=>{
5668
+ assertPermissions: async (logic, user)=>{
5031
5669
  if (!user) throw new common_.UnauthorizedException({
5032
5670
  message: 'Authentication required',
5033
5671
  messageKey: constants_.ERROR_MESSAGES.UNAUTHORIZED
5034
5672
  });
5035
- if (!this.permissionCache.isCacheConfigured()) throw new exceptions_.PermissionSystemUnavailableException();
5673
+ if (!this.permissionCache.isCacheConfigured()) throw new __rspack_external__flusys_nestjs_shared_exceptions_3edf22e9.PermissionSystemUnavailableException();
5036
5674
  permissions ??= this.permissionCache.getPermissions({
5037
5675
  userId: user.id,
5038
5676
  companyId: user.companyId,
5039
5677
  branchId: user.branchId
5040
5678
  });
5041
- const granted = await permissions;
5042
- if (!this.permissionCache.matchPermission(granted, code)) throw new exceptions_.InsufficientPermissionsException([
5043
- code
5044
- ], 'AND');
5679
+ const result = (0,utils_.evaluatePermissionLogic)(logic, await permissions);
5680
+ if (!result.passed) throw new __rspack_external__flusys_nestjs_shared_exceptions_3edf22e9.InsufficientPermissionsException(result.missingPermissions, result.operator);
5045
5681
  }
5046
5682
  };
5047
5683
  return env;
5048
5684
  }
5049
- /** After commit, so listeners see committed data. A failing publish never turns a completed run into an error. */ async publishPendingEvents(events, user) {
5685
+ /** 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) {
5050
5686
  for (const event of events){
5051
5687
  try {
5688
+ if ('delivery' in event) {
5689
+ await event.send();
5690
+ continue;
5691
+ }
5052
5692
  if ('eventName' in event) {
5053
5693
  await publishFlowEvent(event.eventName, event.payload, user);
5054
5694
  continue;
@@ -5067,6 +5707,10 @@ class FlowExecutorService {
5067
5707
  user
5068
5708
  });
5069
5709
  } catch (error) {
5710
+ if ('delivery' in event) {
5711
+ this.logger.error(`Could not send the message of "${event.delivery}" after the commit: ${error.message}`);
5712
+ continue;
5713
+ }
5070
5714
  const name = 'eventName' in event ? event.eventName : `${event.entityCode}.${event.action}`;
5071
5715
  this.logger.error(`Could not publish ${name}: ${error.message}`);
5072
5716
  }
@@ -5080,13 +5724,25 @@ FlowExecutorService = _ts_decorate([
5080
5724
  _ts_param(2, (0,common_.Inject)(core_.ModuleRef)),
5081
5725
  _ts_param(3, (0,common_.Inject)(modules_.SharedPermissionCacheService)),
5082
5726
  _ts_param(4, (0,common_.Inject)(entity_builder_config_service/* .EntityBuilderConfigService */.E)),
5727
+ _ts_param(5, (0,common_.Optional)()),
5728
+ _ts_param(5, (0,common_.Inject)(interfaces_.COMPANY_ACCESS_RESOLVER)),
5729
+ _ts_param(6, (0,common_.Optional)()),
5730
+ _ts_param(6, (0,common_.Inject)(interfaces_.NOTIFICATION_ADAPTER)),
5731
+ _ts_param(7, (0,common_.Optional)()),
5732
+ _ts_param(7, (0,common_.Inject)(interfaces_.EMAIL_ADAPTER)),
5733
+ _ts_param(8, (0,common_.Optional)()),
5734
+ _ts_param(8, (0,common_.Inject)(interfaces_.BRANCH_HIERARCHY_RESOLVER)),
5083
5735
  _ts_metadata("design:type", Function),
5084
5736
  _ts_metadata("design:paramtypes", [
5085
5737
  typeof entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : entity_builder_datasource_provider/* .EntityBuilderDataSourceProvider */.D,
5086
5738
  typeof rule_engine/* .RuleEngineService */.iI === "undefined" ? Object : rule_engine/* .RuleEngineService */.iI,
5087
5739
  typeof core_.ModuleRef === "undefined" ? Object : core_.ModuleRef,
5088
5740
  typeof modules_.SharedPermissionCacheService === "undefined" ? Object : modules_.SharedPermissionCacheService,
5089
- typeof entity_builder_config_service/* .EntityBuilderConfigService */.E === "undefined" ? Object : entity_builder_config_service/* .EntityBuilderConfigService */.E
5741
+ typeof entity_builder_config_service/* .EntityBuilderConfigService */.E === "undefined" ? Object : entity_builder_config_service/* .EntityBuilderConfigService */.E,
5742
+ typeof ICompanyAccessResolver === "undefined" ? Object : ICompanyAccessResolver,
5743
+ typeof INotificationAdapter === "undefined" ? Object : INotificationAdapter,
5744
+ typeof IEmailAdapter === "undefined" ? Object : IEmailAdapter,
5745
+ typeof IBranchHierarchyResolver === "undefined" ? Object : IBranchHierarchyResolver
5090
5746
  ])
5091
5747
  ], FlowExecutorService);
5092
5748
  /** 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) {
@@ -5122,13 +5778,14 @@ FlowExecutorService = _ts_decorate([
5122
5778
  2300(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5123
5779
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
5124
5780
  /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
5125
- /* import */ var _config_message_keys_js__rspack_import_8 = __webpack_require__(5732);
5781
+ /* import */ var _config_message_keys_js__rspack_import_9 = __webpack_require__(5732);
5126
5782
  /* import */ var _entities_flow_definition_entity_js__rspack_import_2 = __webpack_require__(3770);
5127
5783
  /* import */ var _flow_engine_flow_input_validator_js__rspack_import_3 = __webpack_require__(6390);
5128
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_4 = __webpack_require__(424);
5129
- /* import */ var _flow_definition_service_js__rspack_import_5 = __webpack_require__(5229);
5130
- /* import */ var _flow_execution_service_js__rspack_import_6 = __webpack_require__(7810);
5131
- /* import */ var _flow_executor_service_js__rspack_import_7 = __webpack_require__(4698);
5784
+ /* import */ var _flow_engine_flow_types_js__rspack_import_4 = __webpack_require__(6256);
5785
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_5 = __webpack_require__(424);
5786
+ /* import */ var _flow_definition_service_js__rspack_import_6 = __webpack_require__(5229);
5787
+ /* import */ var _flow_execution_service_js__rspack_import_7 = __webpack_require__(7810);
5788
+ /* import */ var _flow_executor_service_js__rspack_import_8 = __webpack_require__(3908);
5132
5789
  function _ts_decorate(decorators, target, key, desc) {
5133
5790
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5134
5791
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -5161,6 +5818,7 @@ function _ts_param(paramIndex, decorator) {
5161
5818
 
5162
5819
 
5163
5820
 
5821
+
5164
5822
  const HIDDEN_HEADERS = new Set([
5165
5823
  'authorization',
5166
5824
  'cookie',
@@ -5197,6 +5855,11 @@ const MAX_QUERY_VALUE_LENGTH = 2000;
5197
5855
  receivedAt: new Date().toISOString()
5198
5856
  };
5199
5857
  }
5858
+ /** The caller's own `Authorization` header, kept for Request steps that forward it: only for a signed-in (verified) caller. */ function callerAuthorization(caller, user) {
5859
+ if (!user) return undefined;
5860
+ const value = Object.entries(caller.headers).find(([key])=>key.toLowerCase() === 'authorization')?.[1];
5861
+ return typeof value === 'string' && value ? value : undefined;
5862
+ }
5200
5863
  /** 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) {
5201
5864
  if (!simulated) return caller;
5202
5865
  const headers = {
@@ -5219,7 +5882,7 @@ const inputRejected = (errors)=>({
5219
5882
  body: {
5220
5883
  success: false,
5221
5884
  message: 'Validation failed',
5222
- messageKey: _config_message_keys_js__rspack_import_8/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
5885
+ messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
5223
5886
  errors
5224
5887
  },
5225
5888
  trace: [],
@@ -5236,71 +5899,75 @@ class FlowRuntimeService {
5236
5899
  this.executions = executions;
5237
5900
  this.definitions = definitions;
5238
5901
  }
5239
- /** A function flow has no endpoint: it looks the same as an unknown one. */ async findActive(slug) {
5902
+ /** A flow never published is not live yet: it looks the same as an unknown one. */ async findActive(slug) {
5240
5903
  return (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_2/* .FlowDefinition */.G)).findOne({
5241
5904
  where: {
5242
5905
  slug,
5243
5906
  isActive: true,
5244
5907
  deletedAt: (0,typeorm__rspack_import_1.IsNull)(),
5245
- authMode: (0,typeorm__rspack_import_1.Not)('internal')
5908
+ version: (0,typeorm__rspack_import_1.MoreThan)(0)
5246
5909
  }
5247
5910
  });
5248
5911
  }
5249
- /** The public entry: a caller hit `POST api-flows/:slug`. */ async handle(flow, body, caller, user) {
5912
+ /** The public entry: a caller hit `POST api-flows/:slug` or `POST api-flows/:slug/:endpoint`. */ async handle(flow, endpoint, body, caller, user) {
5250
5913
  const startedAt = new Date();
5251
- const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(flow.inputSchema ?? [], body, flow.bodyType);
5914
+ const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
5252
5915
  if (checked.errors.length) {
5253
5916
  const result = inputRejected(checked.errors);
5254
- await this.logWebhook(flow, body, result, startedAt, user, caller);
5917
+ await this.logWebhook(flow, endpoint, body, result, startedAt, user, caller);
5255
5918
  return result;
5256
5919
  }
5257
- // A saved flow: saving it as `runAs: system` needed the system permission.
5258
- const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_5/* .toFlowSettings */.RK)(flow), {
5920
+ const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow), {
5259
5921
  mode: 'live',
5260
5922
  user,
5261
5923
  input: checked.values,
5924
+ startAt: endpoint.nodeId,
5262
5925
  request: requestInfo(caller),
5263
- exposeDetails: false,
5264
- systemAuthorized: true
5926
+ authorization: callerAuthorization(caller, user),
5927
+ exposeDetails: false
5265
5928
  });
5266
- await this.logWebhook(flow, checked.values, result, startedAt, user, caller);
5929
+ await this.logWebhook(flow, endpoint, checked.values, result, startedAt, user, caller);
5267
5930
  return result;
5268
5931
  }
5269
5932
  /** The designer's "Test run": a saved flow or an unsaved draft. */ async testRun(dto, user, caller) {
5270
5933
  let settings;
5271
- let permissionCode;
5272
5934
  let flow = null;
5273
5935
  if (dto.flowId) {
5274
5936
  flow = await this.definitions.findOrFail(dto.flowId);
5275
5937
  }
5276
5938
  if (dto.definition) {
5277
5939
  // The designer's current draft, even for a saved flow: `flow` above is kept only to attribute the run.
5278
- settings = (0,_flow_definition_service_js__rspack_import_5/* .toFlowSettings */.RK)(dto.definition);
5279
- permissionCode = dto.definition.permissionCode;
5940
+ settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(dto.definition);
5280
5941
  } else if (flow) {
5281
- settings = (0,_flow_definition_service_js__rspack_import_5/* .toFlowSettings */.RK)(flow);
5282
- permissionCode = flow.permissionCode;
5942
+ settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow);
5283
5943
  } else {
5284
5944
  throw new _nestjs_common__rspack_import_0.BadRequestException({
5285
5945
  message: 'Send a flowId or a definition to test.',
5286
- messageKey: _config_message_keys_js__rspack_import_8/* .FLOW_MESSAGES.NOTHING_TO_TEST */.wu.NOTHING_TO_TEST
5946
+ messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.NOTHING_TO_TEST */.wu.NOTHING_TO_TEST
5287
5947
  });
5288
5948
  }
5289
5949
  const mode = dto.mode ?? 'dry_run';
5290
- // Checked before anything runs; passing it is what lets a `runAs: system` test touch entities.
5291
- await this.definitions.assertMayTest({
5292
- ...settings,
5293
- permissionCode
5294
- }, user, {
5950
+ const endpoint = (0,_flow_engine_flow_types_js__rspack_import_4/* .findEndpoint */.vS)(settings, dto.endpoint);
5951
+ // Checked before anything runs.
5952
+ await this.definitions.assertMayTest(settings, endpoint, user, {
5295
5953
  mode,
5296
5954
  draft: !!dto.definition,
5297
5955
  saved: !!flow
5298
5956
  });
5957
+ if (!endpoint) {
5958
+ throw new _nestjs_common__rspack_import_0.BadRequestException({
5959
+ message: `This flow has no URL '${dto.endpoint ?? ''}'.`,
5960
+ messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.ENDPOINT_NOT_FOUND */.wu.ENDPOINT_NOT_FOUND,
5961
+ messageVariables: {
5962
+ endpoint: dto.endpoint ?? ''
5963
+ }
5964
+ });
5965
+ }
5299
5966
  const validation = await this.definitions.validate(settings);
5300
5967
  if (validation.errors.length) {
5301
5968
  throw new _nestjs_common__rspack_import_0.BadRequestException({
5302
5969
  message: 'The flow has problems.',
5303
- messageKey: _config_message_keys_js__rspack_import_8/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
5970
+ messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
5304
5971
  errors: validation.errors.map((error)=>({
5305
5972
  field: 'flow',
5306
5973
  ...error
@@ -5309,20 +5976,23 @@ class FlowRuntimeService {
5309
5976
  }
5310
5977
  const startedAt = new Date();
5311
5978
  const simulated = simulatedCaller(caller, dto.request);
5312
- const body = dto.input ?? (settings.bodyType === 'list' ? [] : {});
5313
- const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(settings.inputSchema, body, settings.bodyType);
5979
+ const body = dto.input ?? (endpoint.bodyType === 'list' ? [] : {});
5980
+ const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
5314
5981
  const result = checked.errors.length ? inputRejected(checked.errors) : await this.executor.run(settings, {
5315
5982
  mode,
5316
5983
  user,
5317
5984
  input: checked.values,
5985
+ startAt: endpoint.nodeId,
5318
5986
  request: requestInfo(simulated),
5319
- exposeDetails: true,
5320
- systemAuthorized: true
5987
+ authorization: callerAuthorization(caller, user),
5988
+ exposeDetails: true
5321
5989
  });
5322
5990
  await this.executions.record({
5323
5991
  flowId: flow?.id ?? null,
5324
5992
  flowSlug: settings.slug,
5993
+ flowVersion: dto.definition ? null : flow?.version ?? null,
5325
5994
  trigger: 'test',
5995
+ endpoint,
5326
5996
  mode,
5327
5997
  input: body,
5328
5998
  result,
@@ -5336,11 +6006,13 @@ class FlowRuntimeService {
5336
6006
  warnings: validation.warnings
5337
6007
  };
5338
6008
  }
5339
- async logWebhook(flow, input, result, startedAt, user, caller) {
6009
+ async logWebhook(flow, endpoint, input, result, startedAt, user, caller) {
5340
6010
  await this.executions.record({
5341
6011
  flowId: flow.id,
5342
6012
  flowSlug: flow.slug,
6013
+ flowVersion: flow.version,
5343
6014
  trigger: 'webhook',
6015
+ endpoint,
5344
6016
  mode: 'live',
5345
6017
  input,
5346
6018
  result,
@@ -5355,16 +6027,16 @@ FlowRuntimeService = _ts_decorate([
5355
6027
  (0,_nestjs_common__rspack_import_0.Injectable)({
5356
6028
  scope: _nestjs_common__rspack_import_0.Scope.REQUEST
5357
6029
  }),
5358
- _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D)),
5359
- _ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_flow_executor_service_js__rspack_import_7/* .FlowExecutorService */.F)),
5360
- _ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(_flow_execution_service_js__rspack_import_6/* .FlowExecutionService */.A)),
5361
- _ts_param(3, (0,_nestjs_common__rspack_import_0.Inject)(_flow_definition_service_js__rspack_import_5/* .FlowDefinitionService */.Tk)),
6030
+ _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D)),
6031
+ _ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F)),
6032
+ _ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(_flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A)),
6033
+ _ts_param(3, (0,_nestjs_common__rspack_import_0.Inject)(_flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk)),
5362
6034
  _ts_metadata("design:type", Function),
5363
6035
  _ts_metadata("design:paramtypes", [
5364
- typeof _entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_4/* .EntityBuilderDataSourceProvider */.D,
5365
- typeof _flow_executor_service_js__rspack_import_7/* .FlowExecutorService */.F === "undefined" ? Object : _flow_executor_service_js__rspack_import_7/* .FlowExecutorService */.F,
5366
- typeof _flow_execution_service_js__rspack_import_6/* .FlowExecutionService */.A === "undefined" ? Object : _flow_execution_service_js__rspack_import_6/* .FlowExecutionService */.A,
5367
- typeof _flow_definition_service_js__rspack_import_5/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_5/* .FlowDefinitionService */.Tk
6036
+ typeof _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D,
6037
+ typeof _flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F === "undefined" ? Object : _flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F,
6038
+ typeof _flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A === "undefined" ? Object : _flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A,
6039
+ typeof _flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk
5368
6040
  ])
5369
6041
  ], FlowRuntimeService);
5370
6042
 
@@ -5742,6 +6414,7 @@ const FILTER_COMPARISON_SQL = {
5742
6414
  /** Soft-deletes the record, or deletes it for good when the entity has soft delete turned off. */ async delete(id, user, options = {}) {
5743
6415
  this.ensureActive();
5744
6416
  const repository = await this.ensureRepository();
6417
+ const soft = this.definition.isSoftDeleteEnabled;
5745
6418
  await this.withTransaction(options, async (queryRunner)=>{
5746
6419
  const existing = await queryRunner.manager.findOne(repository.target, {
5747
6420
  where: {
@@ -5749,7 +6422,6 @@ const FILTER_COMPARISON_SQL = {
5749
6422
  }
5750
6423
  });
5751
6424
  if (!existing) throw this.notFound(id);
5752
- const soft = this.definition.isSoftDeleteEnabled;
5753
6425
  if (soft) {
5754
6426
  await queryRunner.manager.softDelete(repository.target, [
5755
6427
  id
@@ -5775,6 +6447,7 @@ const FILTER_COMPARISON_SQL = {
5775
6447
  ]
5776
6448
  };
5777
6449
  }, user);
6450
+ return soft ? 'deleted' : 'purged';
5778
6451
  }
5779
6452
  async findById(id, options = {}) {
5780
6453
  const repository = await this.repositoryFor(options.queryRunner);
@@ -5977,181 +6650,6 @@ __webpack_require__.d(__webpack_exports__, {
5977
6650
  });
5978
6651
 
5979
6652
 
5980
- },
5981
- 5313(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5982
- /* import */ var node_crypto__rspack_import_0 = __webpack_require__(2561);
5983
- /* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
5984
- /* import */ var _enums_field_type_enum_js__rspack_import_2 = __webpack_require__(9614);
5985
-
5986
-
5987
-
5988
- /** Columns every dynamic table may carry (the `Identity` contract). Everything else is a designed field. */ const SYSTEM_COLUMN_NAMES = [
5989
- 'id',
5990
- 'created_at',
5991
- 'updated_at',
5992
- 'deleted_at',
5993
- 'created_by_id',
5994
- 'updated_by_id',
5995
- 'deleted_by_id'
5996
- ];
5997
- /** 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 = [
5998
- _enums_field_type_enum_js__rspack_import_2/* .FieldType.LONG_TEXT */.P.LONG_TEXT,
5999
- _enums_field_type_enum_js__rspack_import_2/* .FieldType.JSON */.P.JSON,
6000
- _enums_field_type_enum_js__rspack_import_2/* .FieldType.MULTI_SELECT */.P.MULTI_SELECT
6001
- ];
6002
- const isIndexableFieldType = (fieldType)=>!NON_INDEXABLE_FIELD_TYPES.includes(fieldType);
6003
- const uuidColumn = (name, driver, options = {})=>new typeorm__rspack_import_1.TableColumn({
6004
- name,
6005
- ...driver === 'postgres' ? {
6006
- type: 'uuid'
6007
- } : {
6008
- type: 'char',
6009
- length: '36'
6010
- },
6011
- ...options
6012
- });
6013
- function buildSystemColumns(flags, driver) {
6014
- const columns = [
6015
- uuidColumn('id', driver, {
6016
- isPrimary: true,
6017
- isGenerated: true,
6018
- generationStrategy: 'uuid'
6019
- }),
6020
- new typeorm__rspack_import_1.TableColumn({
6021
- name: 'created_at',
6022
- type: 'timestamp',
6023
- isNullable: false,
6024
- default: 'CURRENT_TIMESTAMP'
6025
- }),
6026
- new typeorm__rspack_import_1.TableColumn({
6027
- name: 'updated_at',
6028
- type: 'timestamp',
6029
- isNullable: false,
6030
- default: 'CURRENT_TIMESTAMP'
6031
- })
6032
- ];
6033
- if (flags.isSoftDeleteEnabled) columns.push(softDeleteColumn());
6034
- if (flags.isAuditEnabled) columns.push(...auditColumns(driver));
6035
- return columns;
6036
- }
6037
- const softDeleteColumn = ()=>new typeorm__rspack_import_1.TableColumn({
6038
- name: 'deleted_at',
6039
- type: 'timestamp',
6040
- isNullable: true
6041
- });
6042
- const auditColumns = (driver)=>[
6043
- uuidColumn('created_by_id', driver, {
6044
- isNullable: true
6045
- }),
6046
- uuidColumn('updated_by_id', driver, {
6047
- isNullable: true
6048
- }),
6049
- uuidColumn('deleted_by_id', driver, {
6050
- isNullable: true
6051
- })
6052
- ];
6053
- const sameColumns = (columnNames, expected)=>columnNames.length === expected.length && expected.every((c)=>columnNames.includes(c));
6054
- const NAME_HASH_LENGTH = 8;
6055
- /**
6056
- * Deterministic index/constraint name within the driver's identifier limit. The hash of
6057
- * `table.column` keeps names distinct when the readable part is cut short, and when two
6058
- * table/column pairs would read the same (`eb_a_b` + `c` vs `eb_a` + `b_c`).
6059
- */ function indexName(prefix, table, column, maxLength) {
6060
- const hash = (0,node_crypto__rspack_import_0.createHash)('sha256').update(`${table}.${column}`).digest('hex').slice(0, NAME_HASH_LENGTH);
6061
- const room = maxLength - prefix.length - hash.length - 2;
6062
- return `${prefix}_${`${table}_${column}`.slice(0, room)}_${hash}`;
6063
- }
6064
- const plainIndex = (table, column, driver, adapter)=>new typeorm__rspack_import_1.TableIndex({
6065
- name: indexName('idx', table, column, adapter.maxIdentifierLength(driver)),
6066
- columnNames: [
6067
- column
6068
- ]
6069
- });
6070
- const tableUnique = (table, column, driver, adapter)=>new typeorm__rspack_import_1.TableUnique({
6071
- name: indexName('uq', table, column, adapter.maxIdentifierLength(driver)),
6072
- columnNames: [
6073
- column
6074
- ]
6075
- });
6076
- /** MySQL has no unique constraints (TypeORM refuses them), only unique indexes. */ async function addUnique(qr, table, column, driver, adapter) {
6077
- const name = indexName('uq', table.name, column, adapter.maxIdentifierLength(driver));
6078
- if (driver === 'mysql') {
6079
- await qr.createIndex(table, new typeorm__rspack_import_1.TableIndex({
6080
- name,
6081
- columnNames: [
6082
- column
6083
- ],
6084
- isUnique: true
6085
- }));
6086
- return;
6087
- }
6088
- await qr.createUniqueConstraint(table, new typeorm__rspack_import_1.TableUnique({
6089
- name,
6090
- columnNames: [
6091
- column
6092
- ]
6093
- }));
6094
- }
6095
- /** The stored column type, with a `uuid` stored before MySQL support became char(36) resolved to what MySQL accepts. */ function columnType(field, driver, adapter) {
6096
- const type = field.dbType || adapter.mapFieldType(field.fieldType, driver);
6097
- return driver === 'mysql' && type === 'uuid' ? 'char' : type;
6098
- }
6099
- /**
6100
- * Column for a field. `enforceNotNull` controls whether the DB column is NOT NULL: true when
6101
- * the table is brand new (no rows can violate it); false when adding to a table that already
6102
- * has rows (the NOT NULL is applied afterwards, once the column has been backfilled).
6103
- */ function buildFieldColumn(field, driver, adapter, options) {
6104
- const sizing = adapter.getColumnSizing(field.fieldType, driver);
6105
- return new typeorm__rspack_import_1.TableColumn({
6106
- name: field.code,
6107
- type: columnType(field, driver, adapter),
6108
- length: sizing.length,
6109
- precision: sizing.precision ?? undefined,
6110
- scale: sizing.scale,
6111
- isNullable: options.enforceNotNull ? field.nullable : true,
6112
- isUnique: options.withUnique ? field.unique : false
6113
- });
6114
- }
6115
- /** 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) {
6116
- const base = adapter.mapFieldType(fieldType, driver);
6117
- const sizing = adapter.getColumnSizing(fieldType, driver);
6118
- if (sizing.length) return `${base}(${sizing.length})`;
6119
- if (sizing.precision !== undefined) return `${base}(${sizing.precision},${sizing.scale ?? 0})`;
6120
- return base;
6121
- }
6122
- /** Coarse type family, so `character varying` (pg) and `varchar` (mysql) compare equal. */ function typeFamily(type) {
6123
- const t = type.toLowerCase();
6124
- if (t === 'uuid') return 'uuid';
6125
- if (t.includes('json')) return 'json';
6126
- if (t.includes('timestamp') || t === 'datetime') return 'timestamp';
6127
- if (t === 'date') return 'date';
6128
- if (t.includes('bool') || t === 'tinyint') return 'boolean';
6129
- if (t.includes('int') && !t.includes('point')) return 'integer';
6130
- if (t.includes('numeric') || t.includes('decimal')) return 'numeric';
6131
- if (t.includes('char') || t === 'varchar') return 'varchar';
6132
- if (t.includes('text')) return 'text';
6133
- return t;
6134
- }
6135
-
6136
- __webpack_require__.d(__webpack_exports__, {
6137
- $6: () => (addUnique),
6138
- Rm: () => (buildSystemColumns),
6139
- cg: () => (sqlTypeFor),
6140
- fu: () => (buildFieldColumn),
6141
- jp: () => (typeFamily),
6142
- mo: () => (indexName)
6143
- }, {
6144
- G7: sameColumns,
6145
- HK: auditColumns,
6146
- OG: softDeleteColumn,
6147
- d4: isIndexableFieldType,
6148
- gW: NON_INDEXABLE_FIELD_TYPES,
6149
- mq: SYSTEM_COLUMN_NAMES,
6150
- uy: tableUnique,
6151
- v7: plainIndex
6152
- });
6153
-
6154
-
6155
6653
  },
6156
6654
  7259(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6157
6655
  /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);