@flusys/nestjs-entity-builder 9.1.1 → 9.2.0

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 (41) hide show
  1. package/README.md +24 -12
  2. package/config/message-keys.d.ts +29 -0
  3. package/controllers/entity-definition.controller.d.ts +1 -0
  4. package/controllers/field-definition.controller.d.ts +1 -0
  5. package/controllers/flow-definition.controller.d.ts +1 -0
  6. package/dtos/definition-bundle.dto.d.ts +1 -0
  7. package/dtos/entity-definition.dto.d.ts +1 -2
  8. package/fesm/21.js +31 -2
  9. package/fesm/458.js +8 -3
  10. package/fesm/677.js +890 -465
  11. package/fesm/{582.js → 857.js} +201 -265
  12. package/fesm/870.js +580 -247
  13. package/fesm/897.js +164 -22
  14. package/fesm/946.js +5 -1
  15. package/fesm/controllers/index.js +1 -1
  16. package/fesm/dtos/index.js +11 -0
  17. package/fesm/guards/index.js +2 -2
  18. package/fesm/index.js +39 -6
  19. package/fesm/interfaces/index.js +74 -3
  20. package/fesm/modules/index.js +1 -1
  21. package/fesm/rule-engine/index.js +2 -2
  22. package/fesm/services/index.js +13 -3
  23. package/flow-engine/flow-input.validator.d.ts +2 -1
  24. package/flow-engine/flow-run.types.d.ts +1 -0
  25. package/flow-engine/flow.types.d.ts +25 -4
  26. package/interfaces/definition-bundle.interface.d.ts +2 -1
  27. package/interfaces/generic-record.interface.d.ts +11 -1
  28. package/package.json +3 -4
  29. package/rule-engine/rule-engine.service.d.ts +2 -1
  30. package/rule-engine/rule-regex.d.ts +0 -2
  31. package/rule-engine/rule-values.d.ts +4 -1
  32. package/rule-engine/rule.interface.d.ts +2 -3
  33. package/services/definition-bundle.diff.d.ts +6 -0
  34. package/services/definition-bundle.service.d.ts +10 -2
  35. package/services/entity-flow-scaffold.d.ts +2 -1
  36. package/services/entity-permission.service.d.ts +1 -0
  37. package/services/flow-definition.service.d.ts +1 -1
  38. package/services/flow-runtime.service.d.ts +1 -0
  39. package/services/generic-entity.service.d.ts +5 -0
  40. package/services/schema-change-executor.service.d.ts +1 -0
  41. package/services/schema-dialect-adapter.service.d.ts +2 -0
package/fesm/870.js CHANGED
@@ -383,6 +383,7 @@ _ts_decorate([
383
383
  /** Form values arrive as text: `true` / `false` become booleans, anything else is left for the validator to refuse. */ const FormBoolean = ()=>(0,class_transformer__rspack_import_1.Transform)(({ value })=>value === 'true' ? true : value === 'false' ? false : value);
384
384
  /** The form fields sent with the bundle file to plan-import / apply-import. */ class ImportBundleOptionsDto {
385
385
  deprecateMissingFields;
386
+ deactivateMissingFlows;
386
387
  confirm;
387
388
  expectedChecksum;
388
389
  expectedPlanChecksum;
@@ -397,6 +398,16 @@ _ts_decorate([
397
398
  (0,class_validator__rspack_import_2.IsBoolean)(),
398
399
  _ts_metadata("design:type", Boolean)
399
400
  ], ImportBundleOptionsDto.prototype, "deprecateMissingFields", void 0);
401
+ _ts_decorate([
402
+ (0,_nestjs_swagger__rspack_import_0.ApiPropertyOptional)({
403
+ description: 'Switch off (and publish) the live flows this environment has and the bundle does not (they are kept, not deleted)',
404
+ default: false
405
+ }),
406
+ (0,class_validator__rspack_import_2.IsOptional)(),
407
+ FormBoolean(),
408
+ (0,class_validator__rspack_import_2.IsBoolean)(),
409
+ _ts_metadata("design:type", Boolean)
410
+ ], ImportBundleOptionsDto.prototype, "deactivateMissingFlows", void 0);
400
411
  _ts_decorate([
401
412
  (0,_nestjs_swagger__rspack_import_0.ApiPropertyOptional)({
402
413
  description: 'apply-import: acknowledge steps that lose (or may lose) data',
@@ -461,9 +472,17 @@ __webpack_require__.d(__webpack_exports__, {
461
472
  3801(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
462
473
  /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
463
474
  /* import */ var node_crypto__rspack_import_1 = __webpack_require__(2561);
464
- /* import */ var _config_message_keys_js__rspack_import_2 = __webpack_require__(5732);
465
- /* import */ var _enums_entity_status_enum_js__rspack_import_3 = __webpack_require__(1313);
466
- /* import */ var _enums_field_type_enum_js__rspack_import_4 = __webpack_require__(9614);
475
+ /* import */ var _config_message_keys_js__rspack_import_6 = __webpack_require__(5732);
476
+ /* import */ var _enums_entity_status_enum_js__rspack_import_7 = __webpack_require__(1313);
477
+ /* import */ var _enums_field_type_enum_js__rspack_import_8 = __webpack_require__(9614);
478
+ /* import */ var _flow_engine_flow_types_js__rspack_import_2 = __webpack_require__(6256);
479
+ /* import */ var _rule_engine_rule_permission_js__rspack_import_3 = __webpack_require__(5191);
480
+ /* import */ var _rule_engine_rule_interface_js__rspack_import_4 = __webpack_require__(2354);
481
+ /* import */ var _flow_definition_service_js__rspack_import_5 = __webpack_require__(5229);
482
+
483
+
484
+
485
+
467
486
 
468
487
 
469
488
 
@@ -543,6 +562,84 @@ const escapeRegExp = (text)=>text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
543
562
  ].toSorted(byCode)
544
563
  };
545
564
  }
565
+ const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
566
+ /**
567
+ * The record, user, company and email configuration ids written into a flow's step settings (step ids
568
+ * are never UUIDs). Such an id belongs to the environment it was typed in: each needs a `${var:NAME}`.
569
+ */ function fixedIds(nodes) {
570
+ const ids = new Set();
571
+ mapConfigStrings(nodes, (text)=>{
572
+ for (const [id] of text.matchAll(UUID_PATTERN))ids.add(id.toLowerCase());
573
+ return text;
574
+ });
575
+ return [
576
+ ...ids
577
+ ].toSorted(byCode);
578
+ }
579
+ /** The fixed codes of a HAS_PERMISSION / HAS_ANY_PERMISSION / HAS_ALL_PERMISSIONS call; more than a check may name is refused when the flow is validated. */ function literalPermissionCodes(value) {
580
+ try {
581
+ return (0,_rule_engine_rule_permission_js__rspack_import_3/* .parsePermissionCodes */.PS)(value);
582
+ } catch {
583
+ return [];
584
+ }
585
+ }
586
+ /**
587
+ * Every permission code a flow checks: the rule of each `permission` URL (the execute permission when
588
+ * it has none), its Permission check steps, and the codes its permission functions name as literals.
589
+ */ function permissionCodes(flow) {
590
+ const codes = new Set();
591
+ const add = (list)=>list.forEach((code)=>codes.add(code));
592
+ for (const endpoint of (0,_flow_engine_flow_types_js__rspack_import_2/* .flowEndpoints */.Xr)(flow))if (endpoint.authMode === 'permission') add((0,_flusys_nestjs_shared_utils__rspack_import_0.permissionLogicCodes)((0,_flow_definition_service_js__rspack_import_5/* .flowPermissions */.j4)(endpoint, flow.slug)));
593
+ const walk = (value)=>{
594
+ if (Array.isArray(value)) return value.forEach(walk);
595
+ if (!value || typeof value !== 'object') return;
596
+ const call = value;
597
+ if (call.type === 'function' && (0,_rule_engine_rule_interface_js__rspack_import_4/* .isPermissionFunction */.A8)(call.name) && call.args?.[0]?.type === 'literal') add(literalPermissionCodes(call.args[0].value));
598
+ Object.values(value).forEach(walk);
599
+ };
600
+ for (const node of flow.nodes){
601
+ if (node?.type === 'permission_check' && node.config?.['permissions']) add((0,_flusys_nestjs_shared_utils__rspack_import_0.permissionLogicCodes)(node.config['permissions']));
602
+ walk(node?.config);
603
+ }
604
+ return [
605
+ ...codes
606
+ ].toSorted(byCode);
607
+ }
608
+ const SECRET_NAME = /authorization|token|secret|api[-_]?key|password|passwd|cookie|signature|credential/i;
609
+ const AUTH_SCHEME = /^\s*(bearer|basic|token|digest)\s+/i;
610
+ /** Shorter written text is a scheme, a separator or a placeholder's leftovers, not a credential. */ const MIN_SECRET_LENGTH = 8;
611
+ /** The text an expression writes itself: a literal, a template or concat parts without their `{{ ... }}` and `${var:NAME}`. */ function writtenText(expression) {
612
+ if (typeof expression === 'string') return expression;
613
+ if (!expression || typeof expression !== 'object') return '';
614
+ const e = expression;
615
+ if (e.type === 'literal') return typeof e.value === 'string' ? e.value : '';
616
+ if (e.type === 'template') return typeof e.template === 'string' ? e.template.replace(/\{\{[^}]*\}\}/g, '') : '';
617
+ if (e.type === 'concat' && Array.isArray(e.parts)) return e.parts.map(writtenText).join('');
618
+ return '';
619
+ }
620
+ const isWrittenSecret = (text)=>text.replace(VARIABLE_PATTERN, '').replace(AUTH_SCHEME, '').trim().length >= MIN_SECRET_LENGTH;
621
+ /**
622
+ * HTTP steps that send a credential written into the step (a header such as `Authorization` or a query
623
+ * parameter such as `api_key`): an export puts it in git as plain text unless it is a `${var:NAME}`.
624
+ */ function writtenSecrets(nodes) {
625
+ const found = [];
626
+ for (const node of nodes.filter((n)=>n?.type === 'http_request')){
627
+ const config = node.config ?? {};
628
+ for (const [name, value] of Object.entries(config.headers ?? {})){
629
+ if (SECRET_NAME.test(name) && isWrittenSecret(writtenText(value))) found.push({
630
+ step: node.name || node.id,
631
+ name
632
+ });
633
+ }
634
+ for (const [, name, value] of writtenText(config.url).matchAll(/[?&]([^=&#]+)=([^&#]*)/g)){
635
+ if (SECRET_NAME.test(name) && isWrittenSecret(value)) found.push({
636
+ step: node.name || node.id,
637
+ name
638
+ });
639
+ }
640
+ }
641
+ return found;
642
+ }
546
643
  /** Slugs the Call flow steps of a flow run. */ function calledFlowSlugs(nodes) {
547
644
  return nodes.filter((node)=>node?.type === 'call_flow' && typeof node.config?.['flowSlug'] === 'string').map((node)=>node.config['flowSlug']);
548
645
  }
@@ -613,17 +710,17 @@ function changedValues(from, to, keys) {
613
710
  const problems = [];
614
711
  const codes = new Set();
615
712
  for (const entity of entities){
616
- if (codes.has(entity.code)) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.DUPLICATE_ENTITY */.iX.DUPLICATE_ENTITY, {
713
+ if (codes.has(entity.code)) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.DUPLICATE_ENTITY */.iX.DUPLICATE_ENTITY, {
617
714
  entity: entity.code
618
715
  }));
619
- if (entity.status === _enums_entity_status_enum_js__rspack_import_3/* .EntityStatus.DRAFT */.c.DRAFT) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.ENTITY_DRAFT_IN_BUNDLE */.iX.ENTITY_DRAFT_IN_BUNDLE, {
716
+ if (entity.status === _enums_entity_status_enum_js__rspack_import_7/* .EntityStatus.DRAFT */.c.DRAFT) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.ENTITY_DRAFT_IN_BUNDLE */.iX.ENTITY_DRAFT_IN_BUNDLE, {
620
717
  entity: entity.code
621
718
  }));
622
719
  codes.add(entity.code);
623
720
  const fieldCodes = new Set();
624
721
  const fieldIds = new Set();
625
722
  for (const field of entity.fields){
626
- if (fieldCodes.has(field.code) || fieldIds.has(field.id)) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.DUPLICATE_FIELD */.iX.DUPLICATE_FIELD, {
723
+ if (fieldCodes.has(field.code) || fieldIds.has(field.id)) problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.DUPLICATE_FIELD */.iX.DUPLICATE_FIELD, {
627
724
  entity: entity.code,
628
725
  field: field.code
629
726
  }));
@@ -636,9 +733,9 @@ function changedValues(from, to, keys) {
636
733
  ...current.map((e)=>e.code)
637
734
  ]);
638
735
  for (const entity of entities){
639
- for (const field of entity.fields.filter((f)=>f.fieldType === _enums_field_type_enum_js__rspack_import_4/* .FieldType.RELATION */.P.RELATION && !f.deprecated)){
736
+ for (const field of entity.fields.filter((f)=>f.fieldType === _enums_field_type_enum_js__rspack_import_8/* .FieldType.RELATION */.P.RELATION && !f.deprecated)){
640
737
  if (!field.relationTarget || !known.has(field.relationTarget)) {
641
- problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.RELATION_TARGET_UNKNOWN */.iX.RELATION_TARGET_UNKNOWN, {
738
+ problems.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.RELATION_TARGET_UNKNOWN */.iX.RELATION_TARGET_UNKNOWN, {
642
739
  entity: entity.code,
643
740
  field: field.code,
644
741
  target: field.relationTarget ?? ''
@@ -696,7 +793,7 @@ function changedValues(from, to, keys) {
696
793
  const first = pending[0];
697
794
  const temporary = temporaryCode(first.targetFieldId, taken);
698
795
  ordered.push(step(first, first.from, temporary, first.restored || first.waited, [
699
- (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.RENAME_VIA_TEMPORARY */.iX.RENAME_VIA_TEMPORARY, {
796
+ (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.RENAME_VIA_TEMPORARY */.iX.RENAME_VIA_TEMPORARY, {
700
797
  field: first.from,
701
798
  temporary,
702
799
  to: first.to
@@ -740,7 +837,7 @@ function changedValues(from, to, keys) {
740
837
  for (const entity of sorted){
741
838
  const target = pairs.get(entity);
742
839
  if (target && target.code !== entity.code) {
743
- result.blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.ENTITY_CODE_CHANGED */.iX.ENTITY_CODE_CHANGED, {
840
+ result.blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.ENTITY_CODE_CHANGED */.iX.ENTITY_CODE_CHANGED, {
744
841
  entity: entity.code,
745
842
  current: target.code
746
843
  }));
@@ -752,15 +849,15 @@ function changedValues(from, to, keys) {
752
849
  kind: 'create_entity',
753
850
  entityCode: entity.code,
754
851
  details: {
755
- fields: live.filter((f)=>f.fieldType !== _enums_field_type_enum_js__rspack_import_4/* .FieldType.RELATION */.P.RELATION).map((f)=>f.code)
852
+ fields: live.filter((f)=>f.fieldType !== _enums_field_type_enum_js__rspack_import_8/* .FieldType.RELATION */.P.RELATION).map((f)=>f.code)
756
853
  },
757
854
  dependsOnEarlierStep: false,
758
855
  entity: {
759
856
  ...entity,
760
- fields: live.filter((f)=>f.fieldType !== _enums_field_type_enum_js__rspack_import_4/* .FieldType.RELATION */.P.RELATION)
857
+ fields: live.filter((f)=>f.fieldType !== _enums_field_type_enum_js__rspack_import_8/* .FieldType.RELATION */.P.RELATION)
761
858
  }
762
859
  });
763
- for (const field of live.filter((f)=>f.fieldType === _enums_field_type_enum_js__rspack_import_4/* .FieldType.RELATION */.P.RELATION)){
860
+ for (const field of live.filter((f)=>f.fieldType === _enums_field_type_enum_js__rspack_import_8/* .FieldType.RELATION */.P.RELATION)){
764
861
  relationAdds.push({
765
862
  kind: 'add_field',
766
863
  entityCode: entity.code,
@@ -773,7 +870,7 @@ function changedValues(from, to, keys) {
773
870
  field
774
871
  });
775
872
  }
776
- if (entity.status === _enums_entity_status_enum_js__rspack_import_3/* .EntityStatus.DEPRECATED */.c.DEPRECATED) {
873
+ if (entity.status === _enums_entity_status_enum_js__rspack_import_7/* .EntityStatus.DEPRECATED */.c.DEPRECATED) {
777
874
  entityUpdatesLast.push({
778
875
  kind: 'update_entity',
779
876
  entityCode: entity.code,
@@ -784,20 +881,20 @@ function changedValues(from, to, keys) {
784
881
  },
785
882
  dependsOnEarlierStep: true,
786
883
  changes: {
787
- status: _enums_entity_status_enum_js__rspack_import_3/* .EntityStatus.DEPRECATED */.c.DEPRECATED
884
+ status: _enums_entity_status_enum_js__rspack_import_7/* .EntityStatus.DEPRECATED */.c.DEPRECATED
788
885
  }
789
886
  });
790
887
  }
791
888
  continue;
792
889
  }
793
- if (target.status === _enums_entity_status_enum_js__rspack_import_3/* .EntityStatus.DRAFT */.c.DRAFT) {
794
- result.blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_2/* .BUNDLE_MESSAGES.ENTITY_DRAFT_HERE */.iX.ENTITY_DRAFT_HERE, {
890
+ if (target.status === _enums_entity_status_enum_js__rspack_import_7/* .EntityStatus.DRAFT */.c.DRAFT) {
891
+ result.blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_6/* .BUNDLE_MESSAGES.ENTITY_DRAFT_HERE */.iX.ENTITY_DRAFT_HERE, {
795
892
  entity: entity.code
796
893
  }));
797
894
  continue;
798
895
  }
799
896
  const entityChanges = changedValues(target, entity, ENTITY_COMPARED_KEYS);
800
- const deprecating = entityChanges['status'] === _enums_entity_status_enum_js__rspack_import_3/* .EntityStatus.DEPRECATED */.c.DEPRECATED;
897
+ const deprecating = entityChanges['status'] === _enums_entity_status_enum_js__rspack_import_7/* .EntityStatus.DEPRECATED */.c.DEPRECATED;
801
898
  if (Object.keys(entityChanges).length) {
802
899
  (deprecating ? entityUpdatesLast : entityUpdatesFirst).push({
803
900
  kind: 'update_entity',
@@ -934,9 +1031,12 @@ function changedValues(from, to, keys) {
934
1031
  __webpack_require__.d(__webpack_exports__, {
935
1032
  Og: () => (matchDefinitions),
936
1033
  Q5: () => (calledFlowSlugs),
1034
+ QJ: () => (permissionCodes),
937
1035
  dX: () => (stripApiKeys),
938
1036
  dj: () => (canonicalJson),
1037
+ f7: () => (fixedIds),
939
1038
  gT: () => (applyVariables),
1039
+ l6: () => (writtenSecrets),
940
1040
  lj: () => (extractVariables),
941
1041
  qG: () => (orderFlows),
942
1042
  zb: () => (diffEntities)
@@ -1018,26 +1118,30 @@ __webpack_require__.d(__webpack_exports__, {
1018
1118
 
1019
1119
  },
1020
1120
  7463(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1021
- /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(9216);
1022
- /* import */ var _nestjs_common__rspack_import_1 = __webpack_require__(6157);
1023
- /* import */ var node_crypto__rspack_import_2 = __webpack_require__(2561);
1024
- /* import */ var typeorm__rspack_import_3 = __webpack_require__(7158);
1025
- /* import */ var _config_message_keys_js__rspack_import_17 = __webpack_require__(5732);
1026
- /* import */ var _dtos_schema_change_dto_js__rspack_import_4 = __webpack_require__(4945);
1027
- /* import */ var _entities_entity_definition_entity_js__rspack_import_5 = __webpack_require__(3189);
1028
- /* import */ var _enums_entity_status_enum_js__rspack_import_16 = __webpack_require__(1313);
1029
- /* import */ var _entities_field_definition_entity_js__rspack_import_6 = __webpack_require__(2206);
1030
- /* import */ var _entities_flow_definition_entity_js__rspack_import_7 = __webpack_require__(3770);
1031
- /* import */ var _flow_engine_flow_errors_js__rspack_import_8 = __webpack_require__(8542);
1032
- /* import */ var _flow_engine_flow_types_js__rspack_import_9 = __webpack_require__(6256);
1033
- /* import */ var _interfaces_definition_bundle_interface_js__rspack_import_18 = __webpack_require__(8835);
1034
- /* import */ var _interfaces_flow_definition_interface_js__rspack_import_19 = __webpack_require__(9609);
1035
- /* import */ var _definition_bundle_diff_js__rspack_import_10 = __webpack_require__(3801);
1036
- /* import */ var _entity_builder_config_service_js__rspack_import_11 = __webpack_require__(4281);
1037
- /* import */ var _entity_builder_datasource_provider_js__rspack_import_12 = __webpack_require__(424);
1038
- /* import */ var _flow_definition_service_js__rspack_import_13 = __webpack_require__(5229);
1039
- /* import */ var _schema_evolution_service_js__rspack_import_14 = __webpack_require__(3606);
1040
- /* import */ var _schema_sync_service_js__rspack_import_15 = __webpack_require__(3474);
1121
+ /* import */ var _flusys_nestjs_shared_interfaces__rspack_import_0 = __webpack_require__(5103);
1122
+ /* import */ var _flusys_nestjs_shared_utils__rspack_import_1 = __webpack_require__(9216);
1123
+ /* import */ var _nestjs_common__rspack_import_2 = __webpack_require__(6157);
1124
+ /* import */ var node_crypto__rspack_import_3 = __webpack_require__(2561);
1125
+ /* import */ var typeorm__rspack_import_4 = __webpack_require__(7158);
1126
+ /* import */ var _config_message_keys_js__rspack_import_20 = __webpack_require__(5732);
1127
+ /* import */ var _dtos_schema_change_dto_js__rspack_import_5 = __webpack_require__(4945);
1128
+ /* import */ var _entities_entity_definition_entity_js__rspack_import_6 = __webpack_require__(3189);
1129
+ /* import */ var _enums_entity_status_enum_js__rspack_import_19 = __webpack_require__(1313);
1130
+ /* import */ var _enums_field_type_enum_js__rspack_import_22 = __webpack_require__(9614);
1131
+ /* import */ var _entities_field_definition_entity_js__rspack_import_7 = __webpack_require__(2206);
1132
+ /* import */ var _entities_flow_definition_entity_js__rspack_import_8 = __webpack_require__(3770);
1133
+ /* import */ var _flow_engine_flow_errors_js__rspack_import_9 = __webpack_require__(8542);
1134
+ /* import */ var _flow_engine_flow_types_js__rspack_import_10 = __webpack_require__(6256);
1135
+ /* import */ var _interfaces_definition_bundle_interface_js__rspack_import_21 = __webpack_require__(8835);
1136
+ /* import */ var _interfaces_flow_definition_interface_js__rspack_import_23 = __webpack_require__(9609);
1137
+ /* import */ var _definition_bundle_diff_js__rspack_import_11 = __webpack_require__(3801);
1138
+ /* import */ var _entity_builder_config_service_js__rspack_import_12 = __webpack_require__(4281);
1139
+ /* import */ var _entity_builder_datasource_provider_js__rspack_import_13 = __webpack_require__(424);
1140
+ /* import */ var _entity_permission_service_js__rspack_import_14 = __webpack_require__(1462);
1141
+ /* import */ var _flow_definition_service_js__rspack_import_15 = __webpack_require__(5229);
1142
+ /* import */ var _schema_dialect_adapter_service_js__rspack_import_16 = __webpack_require__(7259);
1143
+ /* import */ var _schema_evolution_service_js__rspack_import_17 = __webpack_require__(3606);
1144
+ /* import */ var _schema_sync_service_js__rspack_import_18 = __webpack_require__(3474);
1041
1145
  function _ts_decorate(decorators, target, key, desc) {
1042
1146
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1043
1147
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -1077,16 +1181,20 @@ function _ts_param(paramIndex, decorator) {
1077
1181
 
1078
1182
 
1079
1183
 
1184
+
1185
+
1186
+
1187
+
1080
1188
 
1081
1189
 
1082
1190
 
1083
1191
 
1084
1192
  /** The reasons a failed step gives besides its message: a change's blockers, a flow's validation errors. */ function failureDetails(error) {
1085
- if (!(error instanceof _nestjs_common__rspack_import_1.HttpException)) return [];
1193
+ if (!(error instanceof _nestjs_common__rspack_import_2.HttpException)) return [];
1086
1194
  const response = error.getResponse();
1087
1195
  if (typeof response !== 'object') return [];
1088
1196
  const details = response.blockers ?? response.errors ?? [];
1089
- return details.filter((d)=>typeof d?.messageKey === 'string').map((d)=>(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(d.messageKey, d.messageVariables));
1197
+ return details.filter((d)=>typeof d?.messageKey === 'string').map((d)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(d.messageKey, d.messageVariables));
1090
1198
  }
1091
1199
  /** Structural changes that, unpreviewed, may turn out to lose data (a type conversion, a column made required or unique, audit or soft delete switched off). */ const LOSSY_FIELD_CHANGES = [
1092
1200
  'fieldType',
@@ -1105,21 +1213,21 @@ function mayLoseData(step) {
1105
1213
  /**
1106
1214
  * What was reviewed: the bundle, the options and every step (kind, subject, changes, and whether its
1107
1215
  * preview loses data). Not the SQL or row counts, which change with every write and would refuse each apply.
1108
- */ function planChecksum(bundle, deprecateMissingFields, steps) {
1216
+ */ function planChecksum(bundle, options, steps) {
1109
1217
  const reviewed = steps.map((step)=>({
1110
1218
  kind: step.kind,
1111
- subject: (0,_definition_bundle_diff_js__rspack_import_10/* .bundleSubject */.bs)(step),
1219
+ subject: (0,_definition_bundle_diff_js__rspack_import_11/* .bundleSubject */.bs)(step),
1112
1220
  details: step.details,
1113
1221
  destructive: step.preview?.destructive ?? null
1114
1222
  }));
1115
- return (0,node_crypto__rspack_import_2.createHash)('sha256').update((0,_definition_bundle_diff_js__rspack_import_10/* .canonicalJson */.dj)({
1223
+ return (0,node_crypto__rspack_import_3.createHash)('sha256').update((0,_definition_bundle_diff_js__rspack_import_11/* .canonicalJson */.dj)({
1116
1224
  bundle,
1117
- deprecateMissingFields,
1225
+ ...options,
1118
1226
  steps: reviewed
1119
1227
  })).digest('hex');
1120
1228
  }
1121
- const newApiKey = ()=>`fk_${(0,node_crypto__rspack_import_2.randomBytes)(4).toString('hex')}_${(0,node_crypto__rspack_import_2.randomBytes)(16).toString('hex')}`;
1122
- const triggerPath = (node)=>typeof node.config?.['path'] === 'string' && node.config['path'] ? node.config['path'] : null;
1229
+ /** One import at a time per database: two would plan against the same state and race each other's steps. */ const IMPORT_LOCK = 'eb_bundle_import';
1230
+ const newApiKey = ()=>`fk_${(0,node_crypto__rspack_import_3.randomBytes)(4).toString('hex')}_${(0,node_crypto__rspack_import_3.randomBytes)(16).toString('hex')}`;
1123
1231
  const needsApiKey = (node)=>node?.type === 'trigger' && node.config?.['authMode'] === 'api_key';
1124
1232
  /** The stored API keys of a flow's Request steps (published and draft), by step id. */ function storedApiKeys(flow) {
1125
1233
  const keys = new Map();
@@ -1127,7 +1235,7 @@ const needsApiKey = (node)=>node?.type === 'trigger' && node.config?.['authMode'
1127
1235
  ...flow?.nodes ?? [],
1128
1236
  ...flow?.draft?.nodes ?? []
1129
1237
  ]){
1130
- if (node?.type === 'trigger' && (0,_flow_engine_flow_types_js__rspack_import_9/* .isStoredApiKey */.xA)(node.config?.['apiKey']) && !keys.has(node.id)) keys.set(node.id, node.config['apiKey']);
1238
+ if (node?.type === 'trigger' && (0,_flow_engine_flow_types_js__rspack_import_10/* .isStoredApiKey */.xA)(node.config?.['apiKey']) && !keys.has(node.id)) keys.set(node.id, node.config['apiKey']);
1131
1239
  }
1132
1240
  return keys;
1133
1241
  }
@@ -1137,12 +1245,16 @@ class DefinitionBundleService {
1137
1245
  schemaSync;
1138
1246
  evolution;
1139
1247
  flows;
1140
- constructor(provider, config, schemaSync, evolution, flows){
1248
+ dialect;
1249
+ actionRegistry;
1250
+ constructor(provider, config, schemaSync, evolution, flows, dialect, actionRegistry){
1141
1251
  this.provider = provider;
1142
1252
  this.config = config;
1143
1253
  this.schemaSync = schemaSync;
1144
1254
  this.evolution = evolution;
1145
1255
  this.flows = flows;
1256
+ this.dialect = dialect;
1257
+ this.actionRegistry = actionRegistry;
1146
1258
  }
1147
1259
  variables() {
1148
1260
  return this.config.getBundleVariables();
@@ -1152,51 +1264,78 @@ class DefinitionBundleService {
1152
1264
  const current = await this.loadEntities();
1153
1265
  const selected = dto.entityCodes ? current.filter((e)=>dto.entityCodes.includes(e.code)) : current;
1154
1266
  // A DRAFT entity has no table where it is designed, so there is nothing to promote yet.
1155
- const drafts = selected.filter((e)=>e.status === _enums_entity_status_enum_js__rspack_import_16/* .EntityStatus.DRAFT */.c.DRAFT).toSorted((a, b)=>(0,_definition_bundle_diff_js__rspack_import_10/* .byCode */.VU)(a.code, b.code));
1156
- const entities = selected.filter((e)=>e.status !== _enums_entity_status_enum_js__rspack_import_16/* .EntityStatus.DRAFT */.c.DRAFT).toSorted((a, b)=>(0,_definition_bundle_diff_js__rspack_import_10/* .byCode */.VU)(a.code, b.code)).map((entity)=>({
1267
+ const drafts = selected.filter((e)=>e.status === _enums_entity_status_enum_js__rspack_import_19/* .EntityStatus.DRAFT */.c.DRAFT).toSorted((a, b)=>(0,_definition_bundle_diff_js__rspack_import_11/* .byCode */.VU)(a.code, b.code));
1268
+ const entities = selected.filter((e)=>e.status !== _enums_entity_status_enum_js__rspack_import_19/* .EntityStatus.DRAFT */.c.DRAFT).toSorted((a, b)=>(0,_definition_bundle_diff_js__rspack_import_11/* .byCode */.VU)(a.code, b.code)).map((entity)=>({
1157
1269
  ...entity,
1158
- fields: entity.fields.toSorted((a, b)=>a.order - b.order || (0,_definition_bundle_diff_js__rspack_import_10/* .byCode */.VU)(a.code, b.code))
1270
+ fields: entity.fields.toSorted((a, b)=>a.order - b.order || (0,_definition_bundle_diff_js__rspack_import_11/* .byCode */.VU)(a.code, b.code))
1159
1271
  }));
1160
- const flowRepo = await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G);
1272
+ const flowRepo = await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_8/* .FlowDefinition */.G);
1161
1273
  const rows = (await flowRepo.find({
1162
1274
  where: dto.flowSlugs ? {
1163
- slug: (0,typeorm__rspack_import_3.In)(dto.flowSlugs)
1275
+ slug: (0,typeorm__rspack_import_4.In)(dto.flowSlugs)
1164
1276
  } : {}
1165
- })).toSorted((a, b)=>(0,_definition_bundle_diff_js__rspack_import_10/* .byCode */.VU)(a.slug, b.slug));
1277
+ })).toSorted((a, b)=>(0,_definition_bundle_diff_js__rspack_import_11/* .byCode */.VU)(a.slug, b.slug));
1166
1278
  const warnings = [
1167
- ...drafts.map((entity)=>(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.ENTITY_DRAFT_SKIPPED */.iX.ENTITY_DRAFT_SKIPPED, {
1279
+ ...drafts.map((entity)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.ENTITY_DRAFT_SKIPPED */.iX.ENTITY_DRAFT_SKIPPED, {
1168
1280
  entity: entity.code
1169
1281
  })),
1170
- ...rows.filter((flow)=>!(0,_flow_definition_service_js__rspack_import_13/* .isPublished */.HF)(flow)).map((flow)=>(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.FLOW_UNPUBLISHED_SKIPPED */.iX.FLOW_UNPUBLISHED_SKIPPED, {
1282
+ ...rows.filter((flow)=>!(0,_flow_definition_service_js__rspack_import_15/* .isPublished */.HF)(flow)).map((flow)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_UNPUBLISHED_SKIPPED */.iX.FLOW_UNPUBLISHED_SKIPPED, {
1171
1283
  flow: flow.slug
1172
1284
  }))
1173
1285
  ];
1174
1286
  const variables = this.variables();
1175
- const flows = rows.filter(_flow_definition_service_js__rspack_import_13/* .isPublished */.HF).map((flow)=>{
1176
- const snapshot = (0,_flow_definition_service_js__rspack_import_13/* .snapshotOf */.eY)(flow);
1287
+ const flows = rows.filter(_flow_definition_service_js__rspack_import_15/* .isPublished */.HF).map((flow)=>{
1288
+ const snapshot = (0,_flow_definition_service_js__rspack_import_15/* .snapshotOf */.eY)(flow);
1177
1289
  return {
1178
1290
  ...snapshot,
1179
1291
  description: snapshot.description ?? null,
1180
- nodes: (0,_definition_bundle_diff_js__rspack_import_10/* .extractVariables */.lj)((0,_definition_bundle_diff_js__rspack_import_10/* .stripApiKeys */.dX)(snapshot.nodes ?? []), variables),
1292
+ nodes: (0,_definition_bundle_diff_js__rspack_import_11/* .extractVariables */.lj)((0,_definition_bundle_diff_js__rspack_import_11/* .stripApiKeys */.dX)(snapshot.nodes ?? []), variables),
1181
1293
  edges: snapshot.edges ?? []
1182
1294
  };
1183
1295
  });
1296
+ warnings.push(...this.leftBehind(entities, flows));
1297
+ for (const flow of flows){
1298
+ for (const { step, name } of (0,_definition_bundle_diff_js__rspack_import_11/* .writtenSecrets */.l6)(flow.nodes))warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_SECRET_WRITTEN */.iX.FLOW_SECRET_WRITTEN, {
1299
+ flow: flow.slug,
1300
+ step,
1301
+ name
1302
+ }));
1303
+ }
1184
1304
  // Keys sorted at every level: PostgreSQL and MySQL hand JSON columns back in different key orders.
1185
- const bundle = JSON.parse((0,_definition_bundle_diff_js__rspack_import_10/* .canonicalJson */.dj)({
1186
- format: _interfaces_definition_bundle_interface_js__rspack_import_18/* .DEFINITION_BUNDLE_FORMAT */.D,
1305
+ const bundle = JSON.parse((0,_definition_bundle_diff_js__rspack_import_11/* .canonicalJson */.dj)({
1306
+ format: _interfaces_definition_bundle_interface_js__rspack_import_21/* .DEFINITION_BUNDLE_FORMAT */.D,
1187
1307
  version: (/* inlined export .DEFINITION_BUNDLE_VERSION */1),
1188
1308
  entities,
1189
1309
  flows
1190
1310
  }));
1191
1311
  return {
1192
1312
  bundle,
1193
- checksum: (0,_definition_bundle_diff_js__rspack_import_10/* .bundleChecksum */.i9)(bundle),
1313
+ checksum: (0,_definition_bundle_diff_js__rspack_import_11/* .bundleChecksum */.i9)(bundle),
1194
1314
  warnings
1195
1315
  };
1196
1316
  }
1317
+ /** Relations and Call flow steps that reach outside the bundle: the environment importing it must already have their targets. */ leftBehind(entities, flows) {
1318
+ const entityCodes = new Set(entities.map((e)=>e.code));
1319
+ const slugs = new Set(flows.map((f)=>f.slug));
1320
+ const relations = entities.flatMap((entity)=>entity.fields.filter((f)=>f.fieldType === _enums_field_type_enum_js__rspack_import_22/* .FieldType.RELATION */.P.RELATION && !f.deprecated && f.relationTarget && !entityCodes.has(f.relationTarget)).map((f)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.EXPORT_RELATION_LEFT_OUT */.iX.EXPORT_RELATION_LEFT_OUT, {
1321
+ entity: entity.code,
1322
+ field: f.code,
1323
+ target: f.relationTarget
1324
+ })));
1325
+ const calls = flows.flatMap((flow)=>[
1326
+ ...new Set((0,_definition_bundle_diff_js__rspack_import_11/* .calledFlowSlugs */.Q5)(flow.nodes))
1327
+ ].filter((slug)=>!slugs.has(slug)).toSorted(_definition_bundle_diff_js__rspack_import_11/* .byCode */.VU).map((called)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.EXPORT_CALLED_FLOW_LEFT_OUT */.iX.EXPORT_CALLED_FLOW_LEFT_OUT, {
1328
+ flow: flow.slug,
1329
+ called
1330
+ })));
1331
+ return [
1332
+ ...relations,
1333
+ ...calls
1334
+ ];
1335
+ }
1197
1336
  /** Every entity of this environment with its fields, relations named by target code. */ async loadEntities() {
1198
- const definitions = await (await this.provider.getRepository(_entities_entity_definition_entity_js__rspack_import_5/* .EntityDefinition */.f)).find();
1199
- const fields = await (await this.provider.getRepository(_entities_field_definition_entity_js__rspack_import_6/* .FieldDefinition */.c)).find();
1337
+ const definitions = await (await this.provider.getRepository(_entities_entity_definition_entity_js__rspack_import_6/* .EntityDefinition */.f)).find();
1338
+ const fields = await (await this.provider.getRepository(_entities_field_definition_entity_js__rspack_import_7/* .FieldDefinition */.c)).find();
1200
1339
  const codeOf = new Map(definitions.map((d)=>[
1201
1340
  d.id,
1202
1341
  d.code
@@ -1237,10 +1376,12 @@ class DefinitionBundleService {
1237
1376
  /** The plan, with what apply-import runs: the entity operations and the flows, in order. */ async buildPlan(dto, user) {
1238
1377
  const bundle = dto.bundle;
1239
1378
  const current = await this.loadEntities();
1240
- const entityDiff = (0,_definition_bundle_diff_js__rspack_import_10/* .diffEntities */.zb)(bundle.entities, current, {
1379
+ const entityDiff = (0,_definition_bundle_diff_js__rspack_import_11/* .diffEntities */.zb)(bundle.entities, current, {
1241
1380
  deprecateMissingFields: dto.deprecateMissingFields === true
1242
1381
  });
1243
- const flowDiff = await this.diffFlows(bundle.flows);
1382
+ const flowDiff = await this.diffFlows(bundle.flows, {
1383
+ deactivateMissingFlows: dto.deactivateMissingFlows === true
1384
+ });
1244
1385
  const existingCodes = new Set(current.map((e)=>e.code));
1245
1386
  const steps = [];
1246
1387
  for (const operation of entityDiff.operations){
@@ -1256,7 +1397,7 @@ class DefinitionBundleService {
1256
1397
  warnings: [
1257
1398
  ...operation.warnings ?? [],
1258
1399
  ...dependent ? [
1259
- (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.STEP_AFTER_EARLIER */.iX.STEP_AFTER_EARLIER)
1400
+ (0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.STEP_AFTER_EARLIER */.iX.STEP_AFTER_EARLIER)
1260
1401
  ] : []
1261
1402
  ],
1262
1403
  blockers: []
@@ -1266,13 +1407,15 @@ class DefinitionBundleService {
1266
1407
  step.preview = await this.previewEntityStep(operation, current, user);
1267
1408
  step.blockers.push(...step.preview.blockers);
1268
1409
  } catch (error) {
1269
- step.blockers.push((0,_flow_engine_flow_errors_js__rspack_import_8/* .errorRef */.O$)(error));
1410
+ step.blockers.push((0,_flow_engine_flow_errors_js__rspack_import_9/* .errorRef */.O$)(error));
1270
1411
  }
1271
1412
  }
1272
1413
  steps.push(step);
1273
1414
  }
1274
1415
  const schemaChanges = entityDiff.operations.length > 0;
1275
- const importedSlugs = new Set(flowDiff.planned.map((p)=>p.flow.slug));
1416
+ // Flows switched off run last, callers first: each is still running when the flows that call it are checked.
1417
+ const importedSlugs = new Set(flowDiff.planned.filter((p)=>p.kind !== 'deactivate_flow').map((p)=>p.flow.slug));
1418
+ const savedFlows = [];
1276
1419
  for (const planned of flowDiff.planned){
1277
1420
  const step = {
1278
1421
  kind: planned.kind,
@@ -1286,22 +1429,30 @@ class DefinitionBundleService {
1286
1429
  ...planned.blockers
1287
1430
  ]
1288
1431
  };
1289
- // A flow with a code step needs that permission from whoever imports it, as from whoever saves it.
1290
- await this.flows.assertMayAuthor((0,_flow_definition_service_js__rspack_import_13/* .toFlowSettings */.RK)(planned.flow), user).catch((error)=>step.blockers.push((0,_flow_engine_flow_errors_js__rspack_import_8/* .errorRef */.O$)(error)));
1291
- const dependsOnImport = schemaChanges || (0,_definition_bundle_diff_js__rspack_import_10/* .calledFlowSlugs */.Q5)(planned.flow.nodes).some((slug)=>importedSlugs.has(slug));
1432
+ if (planned.kind !== 'deactivate_flow') savedFlows.push([
1433
+ step,
1434
+ planned.flow
1435
+ ]);
1436
+ // Switching a flow off saves it too: a flow with a code step needs that permission from whoever imports it, as from whoever saves it.
1437
+ await this.flows.assertMayAuthor((0,_flow_definition_service_js__rspack_import_15/* .toFlowSettings */.RK)(planned.flow), user).catch((error)=>step.blockers.push((0,_flow_engine_flow_errors_js__rspack_import_9/* .errorRef */.O$)(error)));
1438
+ const dependsOnImport = schemaChanges || (0,_definition_bundle_diff_js__rspack_import_11/* .calledFlowSlugs */.Q5)(planned.flow.nodes).some((slug)=>importedSlugs.has(slug));
1292
1439
  if (dependsOnImport) {
1293
- step.warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.FLOWS_CHECKED_ON_APPLY */.iX.FLOWS_CHECKED_ON_APPLY));
1440
+ step.warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOWS_CHECKED_ON_APPLY */.iX.FLOWS_CHECKED_ON_APPLY));
1294
1441
  } else if (!step.blockers.length) {
1295
- const { errors, warnings } = await this.flows.validate((0,_flow_definition_service_js__rspack_import_13/* .toFlowSettings */.RK)(planned.flow));
1442
+ const { errors, warnings } = await this.flows.validate((0,_flow_definition_service_js__rspack_import_15/* .toFlowSettings */.RK)(planned.flow));
1296
1443
  step.blockers.push(...errors);
1297
1444
  step.warnings.push(...warnings);
1298
1445
  }
1299
1446
  steps.push(step);
1300
1447
  }
1301
- const checksum = (0,_definition_bundle_diff_js__rspack_import_10/* .bundleChecksum */.i9)(bundle);
1448
+ await this.addPermissionWarnings(steps, savedFlows);
1449
+ const checksum = (0,_definition_bundle_diff_js__rspack_import_11/* .bundleChecksum */.i9)(bundle);
1302
1450
  const plan = {
1303
1451
  checksum,
1304
- planChecksum: planChecksum(checksum, dto.deprecateMissingFields === true, steps),
1452
+ planChecksum: planChecksum(checksum, {
1453
+ deprecateMissingFields: dto.deprecateMissingFields === true,
1454
+ deactivateMissingFlows: dto.deactivateMissingFlows === true
1455
+ }, steps),
1305
1456
  steps,
1306
1457
  unchanged: {
1307
1458
  entities: entityDiff.unchangedEntities,
@@ -1325,6 +1476,44 @@ class DefinitionBundleService {
1325
1476
  flows: flowDiff.planned
1326
1477
  };
1327
1478
  }
1479
+ /**
1480
+ * Permissions are IAM data, never promoted: an import only creates the codes of new entities and of
1481
+ * flows that ask for their execute permission, and no role has those here yet. A flow that checks a
1482
+ * code this environment does not have (and the import does not create) refuses every caller there.
1483
+ */ async addPermissionWarnings(steps, savedFlows) {
1484
+ if (!this.actionRegistry?.findMissingCodes) return;
1485
+ const createdBy = new Map();
1486
+ for (const step of steps.filter((s)=>s.kind === 'create_entity'))for (const code of (0,_entity_permission_service_js__rspack_import_14/* .entityPermissionCodes */.Z)(step.entityCode))createdBy.set(code, step);
1487
+ for (const [step, flow] of savedFlows)if ((0,_flow_engine_flow_types_js__rspack_import_10/* .flowEndpoints */.Xr)(flow).some(_flow_definition_service_js__rspack_import_15/* .asksExecutePermission */.b$)) createdBy.set((0,_flow_definition_service_js__rspack_import_15/* .defaultFlowPermissionCode */.Er)(flow.slug), step);
1488
+ const checked = savedFlows.map(([step, flow])=>[
1489
+ step,
1490
+ (0,_definition_bundle_diff_js__rspack_import_11/* .permissionCodes */.QJ)(flow).filter((code)=>!code.includes('*'))
1491
+ ]);
1492
+ const asked = [
1493
+ ...new Set([
1494
+ ...createdBy.keys(),
1495
+ ...checked.flatMap(([, codes])=>codes)
1496
+ ])
1497
+ ];
1498
+ if (!asked.length) return;
1499
+ const missing = new Set(await this.actionRegistry.findMissingCodes(asked));
1500
+ const ungranted = new Map();
1501
+ for (const [code, step] of createdBy)if (missing.has(code)) ungranted.set(step, [
1502
+ ...ungranted.get(step) ?? [],
1503
+ code
1504
+ ]);
1505
+ for (const [step, codes] of ungranted)step.warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.PERMISSIONS_TO_GRANT */.iX.PERMISSIONS_TO_GRANT, {
1506
+ subject: (0,_definition_bundle_diff_js__rspack_import_11/* .bundleSubject */.bs)(step),
1507
+ codes: codes.join(', ')
1508
+ }));
1509
+ for (const [step, codes] of checked){
1510
+ const unknown = codes.filter((code)=>missing.has(code) && !createdBy.has(code));
1511
+ if (unknown.length) step.warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_PERMISSION_UNKNOWN */.iX.FLOW_PERMISSION_UNKNOWN, {
1512
+ flow: step.flowSlug,
1513
+ codes: unknown.join(', ')
1514
+ }));
1515
+ }
1516
+ }
1328
1517
  /** A relation to an entity this import creates: its id is only known once the entity exists. */ relationNotYetCreated(operation, existingCodes) {
1329
1518
  const target = operation.field?.relationTarget ?? operation.changes?.['relationTarget'];
1330
1519
  return !!target && !existingCodes.has(target);
@@ -1333,8 +1522,8 @@ class DefinitionBundleService {
1333
1522
  if (operation.kind === 'create_entity') return this.schemaSync.planCreateEntity(this.createRequest(operation.entity), user);
1334
1523
  return this.evolution.plan(this.changeRequest(operation, current), user);
1335
1524
  }
1336
- async diffFlows(bundleFlows) {
1337
- const repo = await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_7/* .FlowDefinition */.G);
1525
+ async diffFlows(bundleFlows, options) {
1526
+ const repo = await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_8/* .FlowDefinition */.G);
1338
1527
  const existing = await repo.find();
1339
1528
  const bySlug = new Map(existing.map((flow)=>[
1340
1529
  flow.slug,
@@ -1347,7 +1536,7 @@ class DefinitionBundleService {
1347
1536
  slug: true
1348
1537
  },
1349
1538
  where: {
1350
- slug: (0,typeorm__rspack_import_3.In)(slugs)
1539
+ slug: (0,typeorm__rspack_import_4.In)(slugs)
1351
1540
  },
1352
1541
  withDeleted: true
1353
1542
  })).filter((f)=>!bySlug.has(f.slug)).map((f)=>f.slug)) : new Set();
@@ -1360,31 +1549,37 @@ class DefinitionBundleService {
1360
1549
  };
1361
1550
  const seen = new Set();
1362
1551
  for (const slug of slugs){
1363
- if (seen.has(slug)) result.blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.DUPLICATE_FLOW */.iX.DUPLICATE_FLOW, {
1552
+ if (seen.has(slug)) result.blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.DUPLICATE_FLOW */.iX.DUPLICATE_FLOW, {
1364
1553
  flow: slug
1365
1554
  }));
1366
1555
  seen.add(slug);
1367
1556
  }
1368
- for (const bundleFlow of (0,_definition_bundle_diff_js__rspack_import_10/* .orderFlows */.qG)(bundleFlows)){
1369
- const { nodes, missing } = (0,_definition_bundle_diff_js__rspack_import_10/* .applyVariables */.gT)(bundleFlow.nodes ?? [], variables);
1557
+ for (const bundleFlow of (0,_definition_bundle_diff_js__rspack_import_11/* .orderFlows */.qG)(bundleFlows)){
1558
+ const { nodes, missing } = (0,_definition_bundle_diff_js__rspack_import_11/* .applyVariables */.gT)(bundleFlow.nodes ?? [], variables);
1370
1559
  const flow = {
1371
1560
  ...bundleFlow,
1372
1561
  nodes,
1373
1562
  edges: bundleFlow.edges ?? []
1374
1563
  };
1375
- const blockers = missing.map((name)=>(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.VARIABLE_MISSING */.iX.VARIABLE_MISSING, {
1564
+ const blockers = missing.map((name)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.VARIABLE_MISSING */.iX.VARIABLE_MISSING, {
1376
1565
  name,
1377
1566
  flow: flow.slug
1378
1567
  }));
1379
1568
  const target = bySlug.get(flow.slug);
1380
- if (!target && deleted.has(flow.slug)) blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.FLOW_SLUG_DELETED */.iX.FLOW_SLUG_DELETED, {
1569
+ if (!target && deleted.has(flow.slug)) blockers.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_SLUG_DELETED */.iX.FLOW_SLUG_DELETED, {
1381
1570
  flow: flow.slug
1382
1571
  }));
1383
1572
  const keys = storedApiKeys(target);
1384
- const warnings = flow.nodes.filter((node)=>needsApiKey(node) && !keys.has(node.id)).map((node)=>(0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.FLOW_API_KEY_GENERATED */.iX.FLOW_API_KEY_GENERATED, {
1573
+ const warnings = flow.nodes.filter((node)=>needsApiKey(node) && !keys.has(node.id)).map((node)=>(0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_API_KEY_GENERATED */.iX.FLOW_API_KEY_GENERATED, {
1385
1574
  flow: flow.slug,
1386
- path: triggerPath(node) ?? ''
1575
+ path: (0,_flow_engine_flow_types_js__rspack_import_10/* .triggerPath */.um)(node) ?? ''
1387
1576
  }));
1577
+ // Read before the variables are filled in: an id this environment provides through `${var:NAME}` is not fixed.
1578
+ const ids = (0,_definition_bundle_diff_js__rspack_import_11/* .fixedIds */.f7)(bundleFlow.nodes ?? []);
1579
+ if (ids.length) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_FIXED_IDS */.iX.FLOW_FIXED_IDS, {
1580
+ flow: flow.slug,
1581
+ ids: ids.join(', ')
1582
+ }));
1388
1583
  if (!target) {
1389
1584
  result.planned.push({
1390
1585
  kind: 'create_flow',
@@ -1396,13 +1591,13 @@ class DefinitionBundleService {
1396
1591
  });
1397
1592
  continue;
1398
1593
  }
1399
- const live = (0,_flow_definition_service_js__rspack_import_13/* .snapshotOf */.eY)(target);
1400
- const changed = _interfaces_flow_definition_interface_js__rspack_import_19/* .FLOW_VERSIONED_FIELDS.filter */.v.filter((key)=>key === 'nodes' ? (0,_definition_bundle_diff_js__rspack_import_10/* .canonicalJson */.dj)((0,_definition_bundle_diff_js__rspack_import_10/* .stripApiKeys */.dX)(live.nodes ?? [])) !== (0,_definition_bundle_diff_js__rspack_import_10/* .canonicalJson */.dj)((0,_definition_bundle_diff_js__rspack_import_10/* .stripApiKeys */.dX)(flow.nodes)) : (0,_definition_bundle_diff_js__rspack_import_10/* .canonicalJson */.dj)(live[key] ?? null) !== (0,_definition_bundle_diff_js__rspack_import_10/* .canonicalJson */.dj)(flow[key] ?? null));
1401
- if (!changed.length && (0,_flow_definition_service_js__rspack_import_13/* .isPublished */.HF)(target) && !blockers.length) {
1594
+ const live = (0,_flow_definition_service_js__rspack_import_15/* .snapshotOf */.eY)(target);
1595
+ const changed = _interfaces_flow_definition_interface_js__rspack_import_23/* .FLOW_VERSIONED_FIELDS.filter */.v.filter((key)=>key === 'nodes' ? (0,_definition_bundle_diff_js__rspack_import_11/* .canonicalJson */.dj)((0,_definition_bundle_diff_js__rspack_import_11/* .stripApiKeys */.dX)(live.nodes ?? [])) !== (0,_definition_bundle_diff_js__rspack_import_11/* .canonicalJson */.dj)((0,_definition_bundle_diff_js__rspack_import_11/* .stripApiKeys */.dX)(flow.nodes)) : (0,_definition_bundle_diff_js__rspack_import_11/* .canonicalJson */.dj)(live[key] ?? null) !== (0,_definition_bundle_diff_js__rspack_import_11/* .canonicalJson */.dj)(flow[key] ?? null));
1596
+ if (!changed.length && (0,_flow_definition_service_js__rspack_import_15/* .isPublished */.HF)(target) && !blockers.length) {
1402
1597
  result.unchanged++;
1403
1598
  continue;
1404
1599
  }
1405
- if (target.draft) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.FLOW_DRAFT_REPLACED */.iX.FLOW_DRAFT_REPLACED, {
1600
+ if (target.draft) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_DRAFT_REPLACED */.iX.FLOW_DRAFT_REPLACED, {
1406
1601
  flow: flow.slug
1407
1602
  }));
1408
1603
  result.planned.push({
@@ -1416,10 +1611,38 @@ class DefinitionBundleService {
1416
1611
  blockers
1417
1612
  });
1418
1613
  }
1419
- for (const extra of existing.filter((flow)=>!seen.has(flow.slug)))result.extras.push({
1614
+ const missing = existing.filter((flow)=>!seen.has(flow.slug));
1615
+ const deactivated = options.deactivateMissingFlows ? missing.filter((flow)=>flow.isActive && (0,_flow_definition_service_js__rspack_import_15/* .isPublished */.HF)(flow)) : [];
1616
+ for (const extra of missing.filter((flow)=>!deactivated.includes(flow)))result.extras.push({
1420
1617
  kind: 'flow',
1421
1618
  flowSlug: extra.slug
1422
1619
  });
1620
+ // Callers first: a flow is switched off only once the live flows that call it are.
1621
+ for (const live of (0,_definition_bundle_diff_js__rspack_import_11/* .orderFlows */.qG)(deactivated.map((flow)=>(0,_flow_definition_service_js__rspack_import_15/* .snapshotOf */.eY)(flow))).toReversed()){
1622
+ const target = bySlug.get(live.slug);
1623
+ const callers = bundleFlows.filter((flow)=>flow.isActive && (0,_definition_bundle_diff_js__rspack_import_11/* .calledFlowSlugs */.Q5)(flow.nodes ?? []).includes(live.slug)).map((flow)=>flow.slug);
1624
+ result.planned.push({
1625
+ kind: 'deactivate_flow',
1626
+ flow: live,
1627
+ existingId: target.id,
1628
+ details: {
1629
+ changes: [
1630
+ 'isActive'
1631
+ ]
1632
+ },
1633
+ warnings: target.draft ? [
1634
+ (0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_DRAFT_REPLACED */.iX.FLOW_DRAFT_REPLACED, {
1635
+ flow: live.slug
1636
+ })
1637
+ ] : [],
1638
+ blockers: callers.length ? [
1639
+ (0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.FLOW_DEACTIVATE_CALLED */.iX.FLOW_DEACTIVATE_CALLED, {
1640
+ flow: live.slug,
1641
+ callers: callers.toSorted(_definition_bundle_diff_js__rspack_import_11/* .byCode */.VU).join(', ')
1642
+ })
1643
+ ] : []
1644
+ });
1645
+ }
1423
1646
  return result;
1424
1647
  }
1425
1648
  // ── requests for the designer's services ────────────────────────────────────────────
@@ -1459,7 +1682,7 @@ class DefinitionBundleService {
1459
1682
  switch(operation.kind){
1460
1683
  case 'add_field':
1461
1684
  return {
1462
- action: _dtos_schema_change_dto_js__rspack_import_4/* .SchemaChangeAction.ADD_FIELD */.M3.ADD_FIELD,
1685
+ action: _dtos_schema_change_dto_js__rspack_import_5/* .SchemaChangeAction.ADD_FIELD */.M3.ADD_FIELD,
1463
1686
  entityDefinitionId: entity?.id,
1464
1687
  field: this.fieldSpec(operation.field, idOf(operation.field.relationTarget))
1465
1688
  };
@@ -1471,24 +1694,24 @@ class DefinitionBundleService {
1471
1694
  relationTargetEntityId: idOf(relationTarget) ?? null
1472
1695
  });
1473
1696
  return {
1474
- action: _dtos_schema_change_dto_js__rspack_import_4/* .SchemaChangeAction.UPDATE_FIELD */.M3.UPDATE_FIELD,
1697
+ action: _dtos_schema_change_dto_js__rspack_import_5/* .SchemaChangeAction.UPDATE_FIELD */.M3.UPDATE_FIELD,
1475
1698
  fieldDefinitionId: operation.targetFieldId,
1476
1699
  changes
1477
1700
  };
1478
1701
  }
1479
1702
  case 'deprecate_field':
1480
1703
  return {
1481
- action: _dtos_schema_change_dto_js__rspack_import_4/* .SchemaChangeAction.DEPRECATE_FIELD */.M3.DEPRECATE_FIELD,
1704
+ action: _dtos_schema_change_dto_js__rspack_import_5/* .SchemaChangeAction.DEPRECATE_FIELD */.M3.DEPRECATE_FIELD,
1482
1705
  fieldDefinitionId: operation.targetFieldId
1483
1706
  };
1484
1707
  case 'restore_field':
1485
1708
  return {
1486
- action: _dtos_schema_change_dto_js__rspack_import_4/* .SchemaChangeAction.RESTORE_FIELD */.M3.RESTORE_FIELD,
1709
+ action: _dtos_schema_change_dto_js__rspack_import_5/* .SchemaChangeAction.RESTORE_FIELD */.M3.RESTORE_FIELD,
1487
1710
  fieldDefinitionId: operation.targetFieldId
1488
1711
  };
1489
1712
  default:
1490
1713
  return {
1491
- action: _dtos_schema_change_dto_js__rspack_import_4/* .SchemaChangeAction.UPDATE_ENTITY */.M3.UPDATE_ENTITY,
1714
+ action: _dtos_schema_change_dto_js__rspack_import_5/* .SchemaChangeAction.UPDATE_ENTITY */.M3.UPDATE_ENTITY,
1492
1715
  entityDefinitionId: entity?.id,
1493
1716
  changes: operation.changes
1494
1717
  };
@@ -1496,21 +1719,44 @@ class DefinitionBundleService {
1496
1719
  }
1497
1720
  // ── apply ───────────────────────────────────────────────────────────────────────────
1498
1721
  async applyImport(dto, user) {
1499
- const checksum = (0,_definition_bundle_diff_js__rspack_import_10/* .bundleChecksum */.i9)(dto.bundle);
1722
+ const checksum = (0,_definition_bundle_diff_js__rspack_import_11/* .bundleChecksum */.i9)(dto.bundle);
1500
1723
  if (dto.expectedChecksum && dto.expectedChecksum !== checksum) {
1501
- throw new _nestjs_common__rspack_import_1.ConflictException({
1724
+ throw new _nestjs_common__rspack_import_2.ConflictException({
1502
1725
  message: 'The bundle is not the one that was reviewed. Plan the import again.',
1503
- messageKey: _config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.CHECKSUM_MISMATCH */.iX.CHECKSUM_MISMATCH,
1726
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.CHECKSUM_MISMATCH */.iX.CHECKSUM_MISMATCH,
1504
1727
  messageVariables: {
1505
1728
  checksum
1506
1729
  }
1507
1730
  });
1508
1731
  }
1732
+ return this.withImportLock(()=>this.runImport(dto, user));
1733
+ }
1734
+ /** Held on a connection of its own for the whole import (plan and every step), released before that connection goes back to the pool. */ async withImportLock(fn) {
1735
+ const driver = await this.provider.getDriver();
1736
+ const qr = await this.provider.getQueryRunner();
1737
+ try {
1738
+ await qr.connect();
1739
+ if (!await this.dialect.tryAcquireSessionLock(qr, IMPORT_LOCK, driver)) {
1740
+ throw new _nestjs_common__rspack_import_2.ConflictException({
1741
+ message: 'Another import is running in this environment. Try again when it has finished.',
1742
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.IMPORT_IN_PROGRESS */.iX.IMPORT_IN_PROGRESS
1743
+ });
1744
+ }
1745
+ try {
1746
+ return await fn();
1747
+ } finally{
1748
+ await this.dialect.releaseSessionLock(qr, IMPORT_LOCK, driver).catch(()=>undefined);
1749
+ }
1750
+ } finally{
1751
+ await qr.release().catch(()=>undefined);
1752
+ }
1753
+ }
1754
+ async runImport(dto, user) {
1509
1755
  const { plan, operations, flows } = await this.buildPlan(dto, user);
1510
1756
  if (dto.expectedPlanChecksum && dto.expectedPlanChecksum !== plan.planChecksum) {
1511
- throw new _nestjs_common__rspack_import_1.ConflictException({
1757
+ throw new _nestjs_common__rspack_import_2.ConflictException({
1512
1758
  message: 'The plan changed since it was reviewed. Plan the import again.',
1513
- messageKey: _config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.PLAN_CHANGED */.iX.PLAN_CHANGED,
1759
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.PLAN_CHANGED */.iX.PLAN_CHANGED,
1514
1760
  messageVariables: {
1515
1761
  planChecksum: plan.planChecksum
1516
1762
  }
@@ -1523,33 +1769,33 @@ class DefinitionBundleService {
1523
1769
  ...b
1524
1770
  })),
1525
1771
  ...plan.steps.flatMap((s)=>s.blockers.map((b)=>({
1526
- field: (0,_definition_bundle_diff_js__rspack_import_10/* .bundleSubject */.bs)(s),
1772
+ field: (0,_definition_bundle_diff_js__rspack_import_11/* .bundleSubject */.bs)(s),
1527
1773
  ...b
1528
1774
  })))
1529
1775
  ];
1530
1776
  if (blockers.length) {
1531
- throw new _nestjs_common__rspack_import_1.ConflictException({
1777
+ throw new _nestjs_common__rspack_import_2.ConflictException({
1532
1778
  message: 'The import is blocked. Plan it again to see why.',
1533
- messageKey: _config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.APPLY_BLOCKED */.iX.APPLY_BLOCKED,
1779
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.APPLY_BLOCKED */.iX.APPLY_BLOCKED,
1534
1780
  errors: blockers
1535
1781
  });
1536
1782
  }
1537
1783
  if (plan.mayLoseData && dto.confirm !== true) {
1538
1784
  const lossy = plan.steps.filter((s)=>s.preview?.destructive || !s.preview && mayLoseData(s)).map((s)=>({
1539
- field: (0,_definition_bundle_diff_js__rspack_import_10/* .bundleSubject */.bs)(s),
1540
- messageKey: _config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.CONFIRM_REQUIRED */.iX.CONFIRM_REQUIRED
1785
+ field: (0,_definition_bundle_diff_js__rspack_import_11/* .bundleSubject */.bs)(s),
1786
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.CONFIRM_REQUIRED */.iX.CONFIRM_REQUIRED
1541
1787
  }));
1542
- throw new _nestjs_common__rspack_import_1.BadRequestException({
1788
+ throw new _nestjs_common__rspack_import_2.BadRequestException({
1543
1789
  message: 'The import loses data or information. Review the plan and resend with confirm: true.',
1544
- messageKey: _config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.CONFIRM_REQUIRED */.iX.CONFIRM_REQUIRED,
1790
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.CONFIRM_REQUIRED */.iX.CONFIRM_REQUIRED,
1545
1791
  errors: lossy
1546
1792
  });
1547
1793
  }
1548
1794
  // A confirmation covers the steps that were reviewed, never ones the environment has grown since.
1549
1795
  if (plan.mayLoseData && !dto.expectedPlanChecksum) {
1550
- throw new _nestjs_common__rspack_import_1.BadRequestException({
1796
+ throw new _nestjs_common__rspack_import_2.BadRequestException({
1551
1797
  message: 'Send the planChecksum of the reviewed plan as expectedPlanChecksum to confirm it.',
1552
- messageKey: _config_message_keys_js__rspack_import_17/* .BUNDLE_MESSAGES.PLAN_CHECKSUM_REQUIRED */.iX.PLAN_CHECKSUM_REQUIRED
1798
+ messageKey: _config_message_keys_js__rspack_import_20/* .BUNDLE_MESSAGES.PLAN_CHECKSUM_REQUIRED */.iX.PLAN_CHECKSUM_REQUIRED
1553
1799
  });
1554
1800
  }
1555
1801
  const result = {
@@ -1577,7 +1823,7 @@ class DefinitionBundleService {
1577
1823
  result.steps.push({
1578
1824
  ...step,
1579
1825
  status: 'failed',
1580
- error: (0,_flow_engine_flow_errors_js__rspack_import_8/* .errorRef */.O$)(error),
1826
+ error: (0,_flow_engine_flow_errors_js__rspack_import_9/* .errorRef */.O$)(error),
1581
1827
  ...reasons.length ? {
1582
1828
  blockers: reasons
1583
1829
  } : {}
@@ -1608,7 +1854,7 @@ class DefinitionBundleService {
1608
1854
  if (operation.kind === 'create_entity') {
1609
1855
  const entity = operation.entity;
1610
1856
  await this.schemaSync.createEntity(this.createRequest(entity), user, {
1611
- entityId: await this.idIsFree(_entities_entity_definition_entity_js__rspack_import_5/* .EntityDefinition */.f, entity.id) ? entity.id : undefined,
1857
+ entityId: await this.idIsFree(_entities_entity_definition_entity_js__rspack_import_6/* .EntityDefinition */.f, entity.id) ? entity.id : undefined,
1612
1858
  fieldIds: Object.fromEntries(await this.freeFieldIds(entity.fields))
1613
1859
  });
1614
1860
  return;
@@ -1617,7 +1863,7 @@ class DefinitionBundleService {
1617
1863
  ...this.changeRequest(operation, current),
1618
1864
  confirm
1619
1865
  };
1620
- const fieldId = operation.kind === 'add_field' && await this.idIsFree(_entities_field_definition_entity_js__rspack_import_6/* .FieldDefinition */.c, operation.field.id) ? operation.field.id : undefined;
1866
+ const fieldId = operation.kind === 'add_field' && await this.idIsFree(_entities_field_definition_entity_js__rspack_import_7/* .FieldDefinition */.c, operation.field.id) ? operation.field.id : undefined;
1621
1867
  await this.evolution.apply(request, user, {
1622
1868
  fieldId
1623
1869
  });
@@ -1632,12 +1878,12 @@ class DefinitionBundleService {
1632
1878
  }
1633
1879
  async freeFieldIds(fields) {
1634
1880
  if (!fields.length) return [];
1635
- const taken = new Set((await (await this.provider.getRepository(_entities_field_definition_entity_js__rspack_import_6/* .FieldDefinition */.c)).find({
1881
+ const taken = new Set((await (await this.provider.getRepository(_entities_field_definition_entity_js__rspack_import_7/* .FieldDefinition */.c)).find({
1636
1882
  select: {
1637
1883
  id: true
1638
1884
  },
1639
1885
  where: {
1640
- id: (0,typeorm__rspack_import_3.In)(fields.map((f)=>f.id))
1886
+ id: (0,typeorm__rspack_import_4.In)(fields.map((f)=>f.id))
1641
1887
  },
1642
1888
  withDeleted: true
1643
1889
  })).map((f)=>f.id));
@@ -1647,6 +1893,7 @@ class DefinitionBundleService {
1647
1893
  ]);
1648
1894
  }
1649
1895
  /** Saves the flow (a Request step keeps this environment's API key, or gets a new one shown once) and publishes it. */ async applyFlowStep(planned, note, apiKeys, user) {
1896
+ if (planned.kind === 'deactivate_flow') return this.deactivateFlow(planned.existingId, note, user);
1650
1897
  const existing = planned.existingId ? await this.flows.findOrFail(planned.existingId) : undefined;
1651
1898
  const keys = storedApiKeys(existing);
1652
1899
  const generated = [];
@@ -1664,7 +1911,7 @@ class DefinitionBundleService {
1664
1911
  generated.push({
1665
1912
  flowSlug: planned.flow.slug,
1666
1913
  nodeId: node.id,
1667
- path: triggerPath(node),
1914
+ path: (0,_flow_engine_flow_types_js__rspack_import_10/* .triggerPath */.um)(node),
1668
1915
  key
1669
1916
  });
1670
1917
  return {
@@ -1688,23 +1935,38 @@ class DefinitionBundleService {
1688
1935
  apiKeys.push(...generated);
1689
1936
  await this.flows.publish(id, note, user);
1690
1937
  }
1938
+ /** Publishes the live flow switched off; an unpublished draft is replaced, never published along with it. */ async deactivateFlow(id, note, user) {
1939
+ const live = (0,_flow_definition_service_js__rspack_import_15/* .snapshotOf */.eY)(await this.flows.findOrFail(id));
1940
+ await this.flows.update({
1941
+ ...live,
1942
+ description: live.description ?? undefined,
1943
+ isActive: false,
1944
+ id
1945
+ }, user);
1946
+ await this.flows.publish(id, note, user);
1947
+ }
1691
1948
  }
1692
1949
  DefinitionBundleService = _ts_decorate([
1693
- (0,_nestjs_common__rspack_import_1.Injectable)({
1694
- scope: _nestjs_common__rspack_import_1.Scope.REQUEST
1950
+ (0,_nestjs_common__rspack_import_2.Injectable)({
1951
+ scope: _nestjs_common__rspack_import_2.Scope.REQUEST
1695
1952
  }),
1696
- _ts_param(0, (0,_nestjs_common__rspack_import_1.Inject)(_entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D)),
1697
- _ts_param(1, (0,_nestjs_common__rspack_import_1.Inject)(_entity_builder_config_service_js__rspack_import_11/* .EntityBuilderConfigService */.E)),
1698
- _ts_param(2, (0,_nestjs_common__rspack_import_1.Inject)(_schema_sync_service_js__rspack_import_15/* .SchemaSyncService */.W)),
1699
- _ts_param(3, (0,_nestjs_common__rspack_import_1.Inject)(_schema_evolution_service_js__rspack_import_14/* .SchemaEvolutionService */._)),
1700
- _ts_param(4, (0,_nestjs_common__rspack_import_1.Inject)(_flow_definition_service_js__rspack_import_13/* .FlowDefinitionService */.Tk)),
1953
+ _ts_param(0, (0,_nestjs_common__rspack_import_2.Inject)(_entity_builder_datasource_provider_js__rspack_import_13/* .EntityBuilderDataSourceProvider */.D)),
1954
+ _ts_param(1, (0,_nestjs_common__rspack_import_2.Inject)(_entity_builder_config_service_js__rspack_import_12/* .EntityBuilderConfigService */.E)),
1955
+ _ts_param(2, (0,_nestjs_common__rspack_import_2.Inject)(_schema_sync_service_js__rspack_import_18/* .SchemaSyncService */.W)),
1956
+ _ts_param(3, (0,_nestjs_common__rspack_import_2.Inject)(_schema_evolution_service_js__rspack_import_17/* .SchemaEvolutionService */._)),
1957
+ _ts_param(4, (0,_nestjs_common__rspack_import_2.Inject)(_flow_definition_service_js__rspack_import_15/* .FlowDefinitionService */.Tk)),
1958
+ _ts_param(5, (0,_nestjs_common__rspack_import_2.Inject)(_schema_dialect_adapter_service_js__rspack_import_16/* .SchemaDialectAdapterService */.I)),
1959
+ _ts_param(6, (0,_nestjs_common__rspack_import_2.Optional)()),
1960
+ _ts_param(6, (0,_nestjs_common__rspack_import_2.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_0.PERMISSION_ACTION_REGISTRY)),
1701
1961
  _ts_metadata("design:type", Function),
1702
1962
  _ts_metadata("design:paramtypes", [
1703
- typeof _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_12/* .EntityBuilderDataSourceProvider */.D,
1704
- typeof _entity_builder_config_service_js__rspack_import_11/* .EntityBuilderConfigService */.E === "undefined" ? Object : _entity_builder_config_service_js__rspack_import_11/* .EntityBuilderConfigService */.E,
1705
- typeof _schema_sync_service_js__rspack_import_15/* .SchemaSyncService */.W === "undefined" ? Object : _schema_sync_service_js__rspack_import_15/* .SchemaSyncService */.W,
1706
- typeof _schema_evolution_service_js__rspack_import_14/* .SchemaEvolutionService */._ === "undefined" ? Object : _schema_evolution_service_js__rspack_import_14/* .SchemaEvolutionService */._,
1707
- typeof _flow_definition_service_js__rspack_import_13/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_13/* .FlowDefinitionService */.Tk
1963
+ typeof _entity_builder_datasource_provider_js__rspack_import_13/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_13/* .EntityBuilderDataSourceProvider */.D,
1964
+ typeof _entity_builder_config_service_js__rspack_import_12/* .EntityBuilderConfigService */.E === "undefined" ? Object : _entity_builder_config_service_js__rspack_import_12/* .EntityBuilderConfigService */.E,
1965
+ typeof _schema_sync_service_js__rspack_import_18/* .SchemaSyncService */.W === "undefined" ? Object : _schema_sync_service_js__rspack_import_18/* .SchemaSyncService */.W,
1966
+ typeof _schema_evolution_service_js__rspack_import_17/* .SchemaEvolutionService */._ === "undefined" ? Object : _schema_evolution_service_js__rspack_import_17/* .SchemaEvolutionService */._,
1967
+ typeof _flow_definition_service_js__rspack_import_15/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_15/* .FlowDefinitionService */.Tk,
1968
+ typeof _schema_dialect_adapter_service_js__rspack_import_16/* .SchemaDialectAdapterService */.I === "undefined" ? Object : _schema_dialect_adapter_service_js__rspack_import_16/* .SchemaDialectAdapterService */.I,
1969
+ typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry
1708
1970
  ])
1709
1971
  ], DefinitionBundleService);
1710
1972
 
@@ -1746,7 +2008,7 @@ function _ts_param(paramIndex, decorator) {
1746
2008
 
1747
2009
 
1748
2010
 
1749
- /** Every cached definition list/get. Schema changes write these rows inside a transaction, past the cached services. */ const CACHED_DEFINITIONS = [
2011
+ /** Every cached definition list/get. Schema changes write these rows inside a transaction, past the cached services, so they are bumped after it commits. */ const CACHED_DEFINITIONS = [
1750
2012
  _config_event_actions_js__rspack_import_3/* .ENTITY_BUILDER_EVENT_ENTITIES.ENTITY_DEFINITION */.P.ENTITY_DEFINITION,
1751
2013
  _config_event_actions_js__rspack_import_3/* .ENTITY_BUILDER_EVENT_ENTITIES.FIELD_DEFINITION */.P.FIELD_DEFINITION,
1752
2014
  _config_event_actions_js__rspack_import_3/* .ENTITY_BUILDER_EVENT_ENTITIES.FLOW_DEFINITION */.P.FLOW_DEFINITION
@@ -1759,7 +2021,7 @@ class DefinitionCacheService {
1759
2021
  this.utilsService = utilsService;
1760
2022
  }
1761
2023
  /** `tenantId` is the tenant whose definitions changed (see `cacheTenantId`); caches are keyed per tenant. */ async clearAll(tenantId) {
1762
- await Promise.all(CACHED_DEFINITIONS.map((name)=>this.utilsService.clearCache(name, this.cacheManager, undefined, tenantId)));
2024
+ await Promise.all(CACHED_DEFINITIONS.map((name)=>this.utilsService.clearCache(name, this.cacheManager, tenantId)));
1763
2025
  }
1764
2026
  }
1765
2027
  DefinitionCacheService = _ts_decorate([
@@ -1885,11 +2147,10 @@ const DEFAULT_ENDPOINT_NAME = {
1885
2147
  delete: 'Delete'
1886
2148
  };
1887
2149
  const DEFAULT_INPUT_LABEL = {
1888
- id: 'Id',
1889
- ids: 'Ids',
1890
- search: 'Search'
2150
+ id: 'Id'
1891
2151
  };
1892
2152
  const DEFAULT_NO_MATCH_MESSAGE = 'No record matches the filter.';
2153
+ const DEFAULT_DELETE_ONLY_MESSAGE = 'Only delete is supported: a flow cannot restore or permanently delete a record.';
1893
2154
  const MAX_NAME_LENGTH = 255;
1894
2155
  /** A supplied text when it has content, the English default otherwise. */ const textOr = (value, fallback)=>value?.trim() || fallback;
1895
2156
  const INPUT_TYPE = {
@@ -1908,6 +2169,18 @@ const INPUT_TYPE = {
1908
2169
  [_enums_field_type_enum_js__rspack_import_2/* .FieldType.FILE */.P.FILE]: 'uuid'
1909
2170
  };
1910
2171
  const LIST_LIMIT = 50;
2172
+ /** The API controller endpoint each endpoint's Request step stands in for. */ const ENDPOINT_CONTRACTS = {
2173
+ insert: 'insert',
2174
+ insertMany: 'insert_many',
2175
+ getById: 'get_by_id',
2176
+ getByIds: 'get_by_ids',
2177
+ getAll: 'get_all',
2178
+ getByFilter: 'get_by_filter',
2179
+ update: 'update',
2180
+ updateMany: 'update_many',
2181
+ bulkUpsert: 'bulk_upsert',
2182
+ delete: 'delete'
2183
+ };
1911
2184
  const READ_ENDPOINTS = new Set([
1912
2185
  'getById',
1913
2186
  'getByIds',
@@ -1967,10 +2240,6 @@ const ref = (path)=>({
1967
2240
  type: 'field_ref',
1968
2241
  ref: path
1969
2242
  });
1970
- const lit = (value)=>({
1971
- type: 'literal',
1972
- value
1973
- });
1974
2243
  /** `customer_ticket` -> `customer-ticket`: an entity code always fits a flow URL name. */ const entityFlowSlug = (entityCode)=>entityCode.replace(/_/g, '-');
1975
2244
  function optionValues(options) {
1976
2245
  return (options ?? []).map((o)=>o && typeof o === 'object' && 'value' in o ? String(o.value) : String(o));
@@ -2041,38 +2310,38 @@ const optionalFieldInputs = (fields)=>fields.map((f)=>fieldInput(f, false));
2041
2310
  const idInput = (t)=>({
2042
2311
  name: 'id',
2043
2312
  label: t.inputLabels.id,
2044
- type: 'uuid',
2045
- required: true
2313
+ type: 'uuid'
2046
2314
  });
2047
- const idsInput = (t)=>({
2048
- name: 'ids',
2049
- label: t.inputLabels.ids,
2050
- type: 'array',
2051
- itemType: 'uuid',
2052
- required: true
2053
- });
2054
- const respond = (id, column, statusCode, body, row)=>({
2315
+ /** An API controller envelope answer: `<entity>.<action>.success` (or the given key), like the generic controller's. */ const respond = (id, column, statusCode, format, message, messageKey, rest = {}, row)=>({
2055
2316
  id,
2056
2317
  type: 'respond',
2057
2318
  column,
2058
2319
  row,
2059
- config: 'type' in body ? {
2060
- statusCode,
2061
- bodyExpression: body
2062
- } : {
2320
+ config: {
2063
2321
  statusCode,
2064
- body
2322
+ format,
2323
+ message,
2324
+ messageKey,
2325
+ ...rest
2065
2326
  }
2066
2327
  });
2067
2328
  /**
2068
- * The steps of one endpoint. Its step ids are `<endpoint>_<step>` (`id()`), so the endpoints of one
2069
- * flow never share a step. A bulk body is the list itself (`itemSchema` checks each item), written by
2070
- * one step that walks it (`target: many`, each item read as `loop.item`), all or nothing, and
2071
- * answered with the saved records in order.
2329
+ * The steps of one endpoint, answering the generic API controller's envelope and message keys. Its step
2330
+ * ids are `<endpoint>_<step>` (`id()`), so the endpoints of one flow never share a step. A bulk body is
2331
+ * the list itself (the declared fields check each item), written by one step that walks it (`target:
2332
+ * many`, each item read as `loop.item`), all or nothing, and answered with the saved records in order.
2333
+ * getAll reads the request's filter, sort, paging, select, withDeleted and `?q=` search, getById and
2334
+ * getByIds their select (getByIds answers one page of every match, like the controller); delete takes a list of ids and refuses restore / permanent, which no flow step does.
2072
2335
  */ function endpointGraph(entity, endpoint, texts) {
2073
2336
  const id = (step)=>`${endpoint}_${step}`;
2074
- const entityCode = entity.code;
2075
- const single = (inputSchema, step, statusCode = 200)=>({
2337
+ const { code: entityCode, label } = entity;
2338
+ const key = (action)=>`${entityCode}.${action}.success`;
2339
+ const count = (step)=>({
2340
+ messageVariables: {
2341
+ count: ref(`context.${id(step)}.count`)
2342
+ }
2343
+ });
2344
+ const single = (inputSchema, step, message, action, statusCode = 200)=>({
2076
2345
  inputSchema,
2077
2346
  steps: [
2078
2347
  {
@@ -2080,7 +2349,9 @@ const respond = (id, column, statusCode, body, row)=>({
2080
2349
  id: id('record'),
2081
2350
  column: 1
2082
2351
  },
2083
- respond(id('respond'), 2, statusCode, ref(`context.${id('record')}`))
2352
+ respond(id('respond'), 2, statusCode, 'single', message, key(action), {
2353
+ bodyExpression: ref(`context.${id('record')}`)
2354
+ })
2084
2355
  ],
2085
2356
  edges: [
2086
2357
  [
@@ -2090,8 +2361,7 @@ const respond = (id, column, statusCode, body, row)=>({
2090
2361
  ]
2091
2362
  ]
2092
2363
  });
2093
- const bulk = (itemSchema, step, statusCode)=>({
2094
- bodyType: 'list',
2364
+ const bulk = (itemSchema, step, message, action, statusCode)=>({
2095
2365
  inputSchema: itemSchema,
2096
2366
  steps: [
2097
2367
  {
@@ -2104,7 +2374,39 @@ const respond = (id, column, statusCode, body, row)=>({
2104
2374
  items: ref('input')
2105
2375
  }
2106
2376
  },
2107
- respond(id('respond'), 2, statusCode, ref(`context.${id('records')}.items`))
2377
+ respond(id('respond'), 2, statusCode, 'bulk', message, key(action), {
2378
+ bodyExpression: ref(`context.${id('records')}.items`),
2379
+ ...count('records')
2380
+ })
2381
+ ],
2382
+ edges: [
2383
+ [
2384
+ id('records'),
2385
+ 'out',
2386
+ id('respond')
2387
+ ]
2388
+ ]
2389
+ });
2390
+ const list = (query, message, action, paged = true)=>({
2391
+ inputSchema: [],
2392
+ steps: [
2393
+ {
2394
+ id: id('records'),
2395
+ type: 'entity_query',
2396
+ column: 1,
2397
+ config: {
2398
+ entityCode,
2399
+ ...query
2400
+ }
2401
+ },
2402
+ respond(id('respond'), 2, 200, 'list', message, key(action), {
2403
+ bodyExpression: ref(`context.${id('records')}.items`),
2404
+ ...paged ? {
2405
+ total: ref(`context.${id('records')}.total`),
2406
+ page: ref(`context.${id('records')}.page`),
2407
+ pageSize: ref(`context.${id('records')}.pageSize`)
2408
+ } : {}
2409
+ })
2108
2410
  ],
2109
2411
  edges: [
2110
2412
  [
@@ -2124,7 +2426,7 @@ const respond = (id, column, statusCode, body, row)=>({
2124
2426
  entityCode,
2125
2427
  fields: fieldMap(entity.fields, 'input')
2126
2428
  }
2127
- }, 201);
2429
+ }, `${label} created successfully`, 'create', 201);
2128
2430
  case 'insertMany':
2129
2431
  return bulk(insertInputs, {
2130
2432
  type: 'entity_insert',
@@ -2132,57 +2434,36 @@ const respond = (id, column, statusCode, body, row)=>({
2132
2434
  entityCode,
2133
2435
  fields: fieldMap(entity.fields, 'loop.item')
2134
2436
  }
2135
- }, 201);
2437
+ }, `${label} records created successfully`, 'create.many', 201);
2136
2438
  case 'getById':
2137
- return single([
2138
- idInput(texts)
2139
- ], {
2439
+ return single([], {
2140
2440
  type: 'entity_get',
2141
2441
  config: {
2142
2442
  entityCode,
2143
- id: ref('input.id')
2443
+ id: ref('input.id'),
2444
+ fromRequest: true
2144
2445
  }
2145
- });
2446
+ }, `${label} retrieved successfully`, 'get');
2146
2447
  case 'getByIds':
2147
- return single([
2148
- idsInput(texts)
2149
- ], {
2150
- type: 'entity_query',
2151
- config: {
2152
- entityCode,
2153
- filter: {
2154
- id: {
2155
- op: 'in',
2156
- value: ref('input.ids')
2157
- }
2158
- },
2159
- limit: _flow_engine_flow_types_js__rspack_import_1/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT
2160
- }
2161
- });
2162
- case 'getAll':
2163
- return single([
2164
- {
2165
- name: 'search',
2166
- label: texts.inputLabels.search,
2167
- type: 'string'
2448
+ return list({
2449
+ filter: {
2450
+ id: {
2451
+ op: 'in',
2452
+ value: ref('input.ids')
2453
+ }
2168
2454
  },
2169
- ...filterInputs(filterFields)
2170
- ], {
2171
- type: 'entity_query',
2172
- config: {
2173
- entityCode,
2174
- search: ref('input.search'),
2175
- filter: fieldMap(filterFields, 'input'),
2176
- limit: LIST_LIMIT
2177
- }
2178
- });
2455
+ fromRequest: true,
2456
+ limit: _flow_engine_flow_types_js__rspack_import_1/* .FLOW_LIMITS.MAX_QUERY_LIMIT */.Or.MAX_QUERY_LIMIT
2457
+ }, `${label} records retrieved successfully`, 'get.by.ids', false);
2458
+ case 'getAll':
2459
+ return list({
2460
+ fromRequest: true,
2461
+ limit: LIST_LIMIT
2462
+ }, `${label} records retrieved successfully`, 'get.all');
2179
2463
  case 'getByFilter':
2180
2464
  return {
2181
2465
  inputSchema: [
2182
- {
2183
- ...idInput(texts),
2184
- required: false
2185
- },
2466
+ idInput(texts),
2186
2467
  ...filterInputs(filterFields)
2187
2468
  ],
2188
2469
  steps: [
@@ -2215,12 +2496,10 @@ const respond = (id, column, statusCode, body, row)=>({
2215
2496
  }
2216
2497
  }
2217
2498
  },
2218
- respond(id('respond'), 3, 200, ref(`context.${id('records')}.items.0`)),
2219
- respond(id('not_found'), 3, 404, {
2220
- success: lit(false),
2221
- message: lit(texts.noMatchMessage),
2222
- messageKey: lit(_config_message_keys_js__rspack_import_4/* .GENERIC_RECORD_MESSAGES.NO_MATCH */.j4.NO_MATCH)
2223
- }, 1)
2499
+ respond(id('respond'), 3, 200, 'single', `${label} retrieved successfully`, key('get.by.filter'), {
2500
+ bodyExpression: ref(`context.${id('records')}.items.0`)
2501
+ }),
2502
+ respond(id('not_found'), 3, 404, 'message', texts.noMatchMessage, _config_message_keys_js__rspack_import_4/* .GENERIC_RECORD_MESSAGES.NO_MATCH */.j4.NO_MATCH, {}, 1)
2224
2503
  ],
2225
2504
  edges: [
2226
2505
  [
@@ -2241,34 +2520,25 @@ const respond = (id, column, statusCode, body, row)=>({
2241
2520
  ]
2242
2521
  };
2243
2522
  case 'update':
2244
- return single([
2245
- idInput(texts),
2246
- ...optionalFieldInputs(entity.fields)
2247
- ], {
2523
+ return single(optionalFieldInputs(entity.fields), {
2248
2524
  type: 'entity_update',
2249
2525
  config: {
2250
2526
  entityCode,
2251
2527
  id: ref('input.id'),
2252
2528
  fields: fieldMap(entity.fields, 'input')
2253
2529
  }
2254
- });
2530
+ }, `${label} updated successfully`, 'update');
2255
2531
  case 'updateMany':
2256
- return bulk([
2257
- idInput(texts),
2258
- ...optionalFieldInputs(entity.fields)
2259
- ], {
2532
+ return bulk(optionalFieldInputs(entity.fields), {
2260
2533
  type: 'entity_update',
2261
2534
  config: {
2262
2535
  entityCode,
2263
2536
  fields: fieldMap(entity.fields, 'loop.item')
2264
2537
  }
2265
- }, 200);
2538
+ }, `${label} records updated successfully`, 'update.many', 200);
2266
2539
  case 'bulkUpsert':
2267
2540
  return bulk([
2268
- {
2269
- ...idInput(texts),
2270
- required: false
2271
- },
2541
+ idInput(texts),
2272
2542
  ...optionalFieldInputs(entity.fields)
2273
2543
  ], {
2274
2544
  type: 'entity_update',
@@ -2277,17 +2547,60 @@ const respond = (id, column, statusCode, body, row)=>({
2277
2547
  fields: fieldMap(entity.fields, 'loop.item'),
2278
2548
  onNotFound: 'insert'
2279
2549
  }
2280
- }, 200);
2550
+ }, `${label} records upserted successfully`, 'upsert.many', 200);
2281
2551
  case 'delete':
2282
- return single([
2283
- idInput(texts)
2284
- ], {
2285
- type: 'entity_delete',
2286
- config: {
2287
- entityCode,
2288
- id: ref('input.id')
2289
- }
2290
- });
2552
+ return {
2553
+ inputSchema: [],
2554
+ steps: [
2555
+ {
2556
+ id: id('type'),
2557
+ type: 'validate',
2558
+ column: 1,
2559
+ config: {
2560
+ checks: [
2561
+ {
2562
+ id: 'type',
2563
+ field: 'type',
2564
+ message: texts.deleteOnlyMessage,
2565
+ rule: {
2566
+ operator: 'AND',
2567
+ conditions: [
2568
+ {
2569
+ field: 'input.type',
2570
+ comparison: 'equals',
2571
+ value: 'delete'
2572
+ }
2573
+ ]
2574
+ }
2575
+ }
2576
+ ]
2577
+ }
2578
+ },
2579
+ {
2580
+ id: id('records'),
2581
+ type: 'entity_delete',
2582
+ column: 2,
2583
+ config: {
2584
+ entityCode,
2585
+ target: 'many',
2586
+ items: ref('input.id')
2587
+ }
2588
+ },
2589
+ respond(id('respond'), 3, 200, 'message', `${label} records deleted successfully`, key('delete'), count('records'))
2590
+ ],
2591
+ edges: [
2592
+ [
2593
+ id('type'),
2594
+ 'out',
2595
+ id('records')
2596
+ ],
2597
+ [
2598
+ id('records'),
2599
+ 'out',
2600
+ id('respond')
2601
+ ]
2602
+ ]
2603
+ };
2291
2604
  }
2292
2605
  }
2293
2606
  /**
@@ -2302,11 +2615,10 @@ const respond = (id, column, statusCode, body, row)=>({
2302
2615
  */ function buildEntityFlow(entity, endpoints, texts = {}) {
2303
2616
  const resolved = {
2304
2617
  inputLabels: {
2305
- id: textOr(texts.inputLabels?.id, DEFAULT_INPUT_LABEL.id),
2306
- ids: textOr(texts.inputLabels?.ids, DEFAULT_INPUT_LABEL.ids),
2307
- search: textOr(texts.inputLabels?.search, DEFAULT_INPUT_LABEL.search)
2618
+ id: textOr(texts.inputLabels?.id, DEFAULT_INPUT_LABEL.id)
2308
2619
  },
2309
- noMatchMessage: textOr(texts.noMatchMessage, DEFAULT_NO_MATCH_MESSAGE)
2620
+ noMatchMessage: textOr(texts.noMatchMessage, DEFAULT_NO_MATCH_MESSAGE),
2621
+ deleteOnlyMessage: textOr(texts.deleteOnlyMessage, DEFAULT_DELETE_ONLY_MESSAGE)
2310
2622
  };
2311
2623
  const nodes = [];
2312
2624
  const edges = [];
@@ -2319,8 +2631,10 @@ const respond = (id, column, statusCode, body, row)=>({
2319
2631
  });
2320
2632
  const config = {
2321
2633
  path: ENTITY_FLOW_PATHS[endpoint],
2322
- bodyType: graph.bodyType ?? 'object',
2323
- inputSchema: graph.inputSchema,
2634
+ contract: ENDPOINT_CONTRACTS[endpoint],
2635
+ ...graph.inputSchema.length ? {
2636
+ inputSchema: graph.inputSchema
2637
+ } : {},
2324
2638
  authMode: 'permission',
2325
2639
  permissions: endpointPermissions(entity.code, endpoint),
2326
2640
  ...READ_ENDPOINTS.has(endpoint) ? {} : {
@@ -2405,6 +2719,7 @@ const CRUD_ACTIONS = [
2405
2719
  'update',
2406
2720
  'delete'
2407
2721
  ];
2722
+ /** The create / read / update / delete permission codes of a dynamic entity. */ const entityPermissionCodes = (entityCode)=>CRUD_ACTIONS.map((op)=>(0,_flusys_nestjs_shared__rspack_import_0.dynamicEntityPermission)(entityCode, op));
2408
2723
  class EntityPermissionService {
2409
2724
  actionRegistry;
2410
2725
  constructor(actionRegistry){
@@ -2424,7 +2739,7 @@ class EntityPermissionService {
2424
2739
  if (!this.actionRegistry) return;
2425
2740
  await this.actionRegistry.removeActionsByCodes([
2426
2741
  (0,_flusys_nestjs_shared__rspack_import_0.dynamicEntityPermissionParent)(definition.code),
2427
- ...CRUD_ACTIONS.map((op)=>(0,_flusys_nestjs_shared__rspack_import_0.dynamicEntityPermission)(definition.code, op))
2742
+ ...entityPermissionCodes(definition.code)
2428
2743
  ]);
2429
2744
  }
2430
2745
  }
@@ -2440,6 +2755,8 @@ EntityPermissionService = _ts_decorate([
2440
2755
 
2441
2756
  __webpack_require__.d(__webpack_exports__, {
2442
2757
  u: () => (EntityPermissionService)
2758
+ }, {
2759
+ Z: entityPermissionCodes
2443
2760
  });
2444
2761
 
2445
2762
 
@@ -3094,8 +3411,13 @@ class SchemaChangeExecutor {
3094
3411
  ].toSorted().map((code)=>`eb_entity:${code}`);
3095
3412
  const heldLocks = [];
3096
3413
  const qr = await this.dataSourceProvider.getQueryRunner();
3097
- await qr.connect();
3098
- await qr.startTransaction();
3414
+ try {
3415
+ await qr.connect();
3416
+ await qr.startTransaction();
3417
+ } catch (error) {
3418
+ await qr.release().catch(()=>undefined);
3419
+ throw (0,_database_error_mapper_js__rspack_import_6/* .mapSchemaError */.i)(error);
3420
+ }
3099
3421
  const state = this.newState(target, options.impact ?? []);
3100
3422
  let committed = false;
3101
3423
  try {
@@ -3139,9 +3461,7 @@ class SchemaChangeExecutor {
3139
3461
  }));
3140
3462
  await qr.commitTransaction();
3141
3463
  committed = true;
3142
- const entityId = outcome.entityDefinitionId ?? target.entityDefinitionId;
3143
- if (entityId) this.schemaRegistry.invalidate(entityId);
3144
- await this.definitionCache.clearAll((0,_flusys_nestjs_shared_utils__rspack_import_0.cacheTenantId)(this.dataSourceProvider));
3464
+ await this.invalidateDefinitions(target, outcome.entityDefinitionId ?? target.entityDefinitionId, state.warnings);
3145
3465
  try {
3146
3466
  await outcome.afterCommit?.();
3147
3467
  } catch (postCommitError) {
@@ -3158,6 +3478,8 @@ class SchemaChangeExecutor {
3158
3478
  } catch (error) {
3159
3479
  if (!committed) await qr.rollbackTransaction().catch(()=>undefined);
3160
3480
  const undo = !committed && driver === 'mysql' && state.undo.length > 0 ? await this.compensate(qr, target, state) : null;
3481
+ // MySQL commits implicitly at each DDL statement, so metadata written before one may have stayed.
3482
+ if (undo) await this.invalidateDefinitions(target, target.entityDefinitionId, []);
3161
3483
  await this.logFailure(target, error, state.steps, undo);
3162
3484
  throw this.withUndo((0,_database_error_mapper_js__rspack_import_6/* .mapSchemaError */.i)(error), undo);
3163
3485
  } finally{
@@ -3165,6 +3487,17 @@ class SchemaChangeExecutor {
3165
3487
  await qr.release();
3166
3488
  }
3167
3489
  }
3490
+ /** Runs only once the change's transaction has ended: a bump inside it would let a concurrent reader re-cache the old definitions. */ async invalidateDefinitions(target, entityId, warnings) {
3491
+ try {
3492
+ if (entityId) this.schemaRegistry.invalidate(entityId);
3493
+ await this.definitionCache.clearAll((0,_flusys_nestjs_shared_utils__rspack_import_0.cacheTenantId)(this.dataSourceProvider));
3494
+ } catch (cacheError) {
3495
+ this.logger.error(`Clearing the definition cache failed after '${target.action}' on '${target.entityCode}': ${cacheError.message}`);
3496
+ warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_11/* .SCHEMA_WARNING_MESSAGES.FOLLOW_UP_FAILED */.Uu.FOLLOW_UP_FAILED, {
3497
+ detail: (0,_flow_engine_flow_errors_js__rspack_import_3/* .errorRef */.O$)(cacheError)
3498
+ }));
3499
+ }
3500
+ }
3168
3501
  /**
3169
3502
  * MySQL only: runs the reverse of every DDL statement that had run, newest first, still under
3170
3503
  * the entity lock. Stops at the first undo statement that fails - running later ones against an
@@ -3575,8 +3908,8 @@ class SchemaEvolutionService {
3575
3908
  const { definition, fields } = await this.loadEntity(entityDefinitionId);
3576
3909
  const driver = await this.dataSourceProvider.getDriver();
3577
3910
  const qr = await this.dataSourceProvider.getQueryRunner();
3578
- await qr.connect();
3579
3911
  try {
3912
+ await qr.connect();
3580
3913
  const table = await qr.getTable(definition.tableName);
3581
3914
  const findings = this.diff(definition, fields, table ?? undefined, driver);
3582
3915
  let rowCount = null;