@flusys/nestjs-localization 9.1.2 → 9.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,9 @@ import * as __rspack_external__flusys_nestjs_shared_modules_24702bee from "@flus
5
5
  import * as __rspack_external__nestjs_common_a04939da from "@nestjs/common";
6
6
  import * as __rspack_external_typeorm from "typeorm";
7
7
  import * as __rspack_external__nestjs_core_d7b00a1a from "@nestjs/core";
8
- export const __rspack_esm_id = 692;
9
- export const __rspack_esm_ids = [692];
8
+ import * as __rspack_external__flusys_nestjs_shared_constants_81f33d5b from "@flusys/nestjs-shared/constants";
9
+ export const __rspack_esm_id = 901;
10
+ export const __rspack_esm_ids = [901];
10
11
  export const __webpack_modules__ = {
11
12
  102(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
12
13
  /**
@@ -459,7 +460,7 @@ function getLocalizationEntities() {
459
460
 
460
461
 
461
462
  },
462
- 367(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
463
+ 140(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
463
464
 
464
465
  // EXPORTS
465
466
  __webpack_require__.d(__webpack_exports__, {
@@ -601,6 +602,8 @@ LocalizationDataSourceProvider = localization_datasource_provider_ts_decorate([
601
602
 
602
603
  // EXTERNAL MODULE: external "@flusys/nestjs-shared/classes"
603
604
  var classes_ = __webpack_require__(937);
605
+ ;// CONCATENATED MODULE: external "@flusys/nestjs-shared/constants"
606
+
604
607
  // EXTERNAL MODULE: external "typeorm"
605
608
  var external_typeorm_ = __webpack_require__(158);
606
609
  // EXTERNAL MODULE: ./projects/nestjs-localization/src/config/event-actions.ts
@@ -638,19 +641,30 @@ function language_service_ts_param(paramIndex, decorator) {
638
641
 
639
642
 
640
643
 
644
+
641
645
  class LanguageService extends classes_.ApiService {
642
646
  cacheManager;
643
647
  utilsService;
644
648
  constructor(cacheManager, utilsService, dataSourceProvider){
645
649
  super(event_actions/* .LOCALIZATION_EVENT_ENTITIES.LANGUAGE */.JX.LANGUAGE, cacheManager, utilsService, LanguageService.name, true, event_actions/* .LOCALIZATION_EVENT_MODULE */.ec, dataSourceProvider, src_entities/* .Language */.TM), this.cacheManager = cacheManager, this.utilsService = utilsService;
646
650
  }
647
- async beforeUpdateOperation(dto, _user, _queryRunner) {
651
+ async beforeInsertOperation(dto, _user, queryRunner) {
652
+ const dtos = Array.isArray(dto) ? dto : [
653
+ dto
654
+ ];
655
+ if (dtos.some((d)=>d.isDefault === true)) {
656
+ await this.lockLanguages(queryRunner.manager);
657
+ }
658
+ }
659
+ async beforeUpdateOperation(dto, _user, queryRunner) {
648
660
  const dtos = Array.isArray(dto) ? dto : [
649
661
  dto
650
662
  ];
651
663
  const ids = dtos.map((d)=>d.id);
652
- await this.ensureDataSourceRepository();
653
- const existingLanguages = await this.repository.find({
664
+ if (dtos.some((d)=>d.isDefault === true)) {
665
+ await this.lockLanguages(queryRunner.manager);
666
+ }
667
+ const existingLanguages = await queryRunner.manager.find(src_entities/* .Language */.TM, {
654
668
  where: {
655
669
  id: (0,external_typeorm_.In)(ids),
656
670
  deletedAt: (0,external_typeorm_.IsNull)()
@@ -673,12 +687,52 @@ class LanguageService extends classes_.ApiService {
673
687
  }
674
688
  }
675
689
  }
676
- async beforeDeleteOperation(dto, _user, _queryRunner) {
690
+ async afterInsertOperation(entities, _dto, _user, queryRunner) {
691
+ await this.keepSingleDefault(entities, queryRunner);
692
+ }
693
+ async afterUpdateOperation(entities, _dto, _user, queryRunner) {
694
+ await this.keepSingleDefault(entities, queryRunner);
695
+ }
696
+ async keepSingleDefault(entities, queryRunner) {
697
+ const newDefault = entities.findLast((entity)=>entity.isDefault);
698
+ if (!newDefault) {
699
+ return;
700
+ }
701
+ await queryRunner.manager.update(src_entities/* .Language */.TM, {
702
+ isDefault: true,
703
+ id: (0,external_typeorm_.Not)(newDefault.id)
704
+ }, {
705
+ isDefault: false
706
+ });
707
+ for (const entity of entities){
708
+ if (entity !== newDefault) {
709
+ entity.isDefault = false;
710
+ }
711
+ }
712
+ }
713
+ // Every default switch and delete takes this lock first (same row order), so concurrent
714
+ // callers serialize: none can demote a stale snapshot and leave two defaults, or delete
715
+ // a language that has just become the default.
716
+ async lockLanguages(manager) {
717
+ await manager.find(src_entities/* .Language */.TM, {
718
+ select: {
719
+ id: true
720
+ },
721
+ order: {
722
+ id: 'ASC'
723
+ },
724
+ withDeleted: true,
725
+ lock: {
726
+ mode: 'pessimistic_write'
727
+ }
728
+ });
729
+ }
730
+ async beforeDeleteOperation(dto, _user, queryRunner) {
677
731
  const ids = Array.isArray(dto.id) ? dto.id : [
678
732
  dto.id
679
733
  ];
680
- await this.ensureDataSourceRepository();
681
- const languages = await this.repository.find({
734
+ await this.lockLanguages(queryRunner.manager);
735
+ const languages = await queryRunner.manager.find(src_entities/* .Language */.TM, {
682
736
  where: {
683
737
  id: (0,external_typeorm_.In)(ids),
684
738
  deletedAt: (0,external_typeorm_.IsNull)()
@@ -707,6 +761,14 @@ class LanguageService extends classes_.ApiService {
707
761
  });
708
762
  }
709
763
  }
764
+ // A permanent delete cascades (FK) to the language's translations
765
+ async delete(option, user) {
766
+ const result = await super.delete(option, user);
767
+ if (option.type === 'permanent') {
768
+ await this.utilsService.clearCache(event_actions/* .LOCALIZATION_EVENT_ENTITIES.TRANSLATION */.JX.TRANSLATION, this.cacheManager, this.cacheTenant());
769
+ }
770
+ return result;
771
+ }
710
772
  async getDefaultLanguage() {
711
773
  await this.ensureDataSourceRepository();
712
774
  return this.repository.findOne({
@@ -731,36 +793,38 @@ class LanguageService extends classes_.ApiService {
731
793
  }
732
794
  async setAsDefault(id) {
733
795
  await this.ensureDataSourceRepository();
734
- const previousDefaults = await this.repository.find({
735
- where: {
736
- isDefault: true
796
+ await this.repository.manager.transaction(async (manager)=>{
797
+ await this.lockLanguages(manager);
798
+ const target = await manager.findOne(src_entities/* .Language */.TM, {
799
+ where: {
800
+ id,
801
+ deletedAt: (0,external_typeorm_.IsNull)()
802
+ }
803
+ });
804
+ if (!target) {
805
+ throw new common_.NotFoundException({
806
+ message: 'Language not found',
807
+ messageKey: __rspack_external__flusys_nestjs_shared_constants_81f33d5b.ERROR_MESSAGES.NOT_FOUND
808
+ });
737
809
  }
810
+ await manager.update(src_entities/* .Language */.TM, {
811
+ isDefault: true,
812
+ id: (0,external_typeorm_.Not)(id)
813
+ }, {
814
+ isDefault: false
815
+ });
816
+ await manager.update(src_entities/* .Language */.TM, {
817
+ id
818
+ }, {
819
+ isDefault: true
820
+ });
738
821
  });
739
- await this.repository.update({
740
- isDefault: true
741
- }, {
742
- isDefault: false
743
- });
744
- await this.repository.update({
745
- id
746
- }, {
747
- isDefault: true
748
- });
749
- const language = await this.repository.findOne({
822
+ await this.invalidateCache();
823
+ return this.repository.findOne({
750
824
  where: {
751
825
  id
752
826
  }
753
827
  });
754
- await Promise.all([
755
- this.clearCacheForAll(),
756
- this.clearCacheForId([
757
- ...previousDefaults,
758
- {
759
- id
760
- }
761
- ])
762
- ]);
763
- return language;
764
828
  }
765
829
  }
766
830
  LanguageService = language_service_ts_decorate([
@@ -886,6 +950,14 @@ class TranslationKeyService extends classes_.ApiService {
886
950
  });
887
951
  }
888
952
  }
953
+ // A permanent delete cascades (FK) to the key's translations
954
+ async delete(option, user) {
955
+ const result = await super.delete(option, user);
956
+ if (option.type === 'permanent') {
957
+ await this.utilsService.clearCache(event_actions/* .LOCALIZATION_EVENT_ENTITIES.TRANSLATION */.JX.TRANSLATION, this.cacheManager, this.cacheTenant());
958
+ }
959
+ return result;
960
+ }
889
961
  async findByModule(module) {
890
962
  await this.ensureDataSourceRepository();
891
963
  return this.repository.find({
@@ -987,6 +1059,14 @@ class TranslationService extends classes_.ApiService {
987
1059
  constructor(cacheManager, utilsService, dataSourceProvider){
988
1060
  super(event_actions/* .LOCALIZATION_EVENT_ENTITIES.TRANSLATION */.JX.TRANSLATION, cacheManager, utilsService, TranslationService.name, true, event_actions/* .LOCALIZATION_EVENT_MODULE */.ec, dataSourceProvider, src_entities/* .Translation */.wD), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider;
989
1061
  }
1062
+ // The module filter joins translation_key; a permanent language/key delete cascades rows away
1063
+ cacheDependencies() {
1064
+ return [
1065
+ event_actions/* .LOCALIZATION_EVENT_ENTITIES.TRANSLATION */.JX.TRANSLATION,
1066
+ event_actions/* .LOCALIZATION_EVENT_ENTITIES.TRANSLATION_KEY */.JX.TRANSLATION_KEY,
1067
+ event_actions/* .LOCALIZATION_EVENT_ENTITIES.LANGUAGE */.JX.LANGUAGE
1068
+ ];
1069
+ }
990
1070
  async getFilterQuery(query, filter, _user) {
991
1071
  // Handle module filter by joining to translation_key table
992
1072
  if (filter.module) {
@@ -1051,11 +1131,10 @@ class TranslationService extends classes_.ApiService {
1051
1131
  const queryRunner = ds.createQueryRunner();
1052
1132
  await queryRunner.connect();
1053
1133
  await queryRunner.startTransaction();
1134
+ const resultIds = [];
1054
1135
  try {
1055
- const resultIds = [];
1056
1136
  for (const update of updates){
1057
1137
  if (update.id) {
1058
- // Update existing translation
1059
1138
  await queryRunner.manager.update(src_entities/* .Translation */.wD, {
1060
1139
  id: update.id
1061
1140
  }, {
@@ -1064,7 +1143,6 @@ class TranslationService extends classes_.ApiService {
1064
1143
  });
1065
1144
  resultIds.push(update.id);
1066
1145
  } else {
1067
- // Create new translation
1068
1146
  const newTranslation = queryRunner.manager.create(src_entities/* .Translation */.wD, {
1069
1147
  languageId: update.languageId,
1070
1148
  translationKeyId: update.translationKeyId,
@@ -1077,28 +1155,25 @@ class TranslationService extends classes_.ApiService {
1077
1155
  }
1078
1156
  }
1079
1157
  await queryRunner.commitTransaction();
1080
- const saved = await this.repository.find({
1081
- where: {
1082
- id: (0,external_typeorm_.In)(resultIds)
1083
- }
1084
- });
1085
- await Promise.all([
1086
- this.clearCacheForAll(),
1087
- this.clearCacheForId(saved)
1088
- ]);
1089
- await this.publishDomainAction(event_actions/* .LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED */.f_.BULK_UPDATED, {
1090
- ids: saved.map((translation)=>translation.id),
1091
- metadata: {
1092
- count: saved.length
1093
- }
1094
- });
1095
- return saved;
1096
1158
  } catch (error) {
1097
1159
  await queryRunner.rollbackTransaction();
1098
1160
  throw error;
1099
1161
  } finally{
1100
1162
  await queryRunner.release();
1101
1163
  }
1164
+ await this.invalidateCache();
1165
+ const saved = await this.repository.find({
1166
+ where: {
1167
+ id: (0,external_typeorm_.In)(resultIds)
1168
+ }
1169
+ });
1170
+ await this.publishDomainAction(event_actions/* .LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED */.f_.BULK_UPDATED, {
1171
+ ids: saved.map((translation)=>translation.id),
1172
+ metadata: {
1173
+ count: saved.length
1174
+ }
1175
+ });
1176
+ return saved;
1102
1177
  }
1103
1178
  async getMissingTranslations(languageCode) {
1104
1179
  await this.ensureAllReposInitialized();
@@ -3,7 +3,7 @@ export const __rspack_esm_ids = [24];
3
3
  export const __webpack_modules__ = {};
4
4
  import { __webpack_require__ } from '../runtime.js';
5
5
  var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
6
- import * as __rspack_chunk_1 from '../692.js';
6
+ import * as __rspack_chunk_1 from '../901.js';
7
7
  __webpack_require__.C(__rspack_chunk_1);
8
8
  import * as __rspack_chunk_2 from '../349.js';
9
9
  __webpack_require__.C(__rspack_chunk_2);
@@ -37,7 +37,6 @@ Database-driven internationalization system. Manage languages, translation keys,
37
37
  - Store translated values per language per key
38
38
  - Mark translations as verified
39
39
  - Bulk upsert in a single transaction — create or update in one call
40
- - Detect missing translations for a given language
41
40
  - Track translation counts per key
42
41
 
43
42
  ### 🚀 Angular Lazy Loading
@@ -79,11 +78,10 @@ Keys are grouped by \`module\` so the frontend loads translations on demand:
79
78
  - POST /localization/language/get-active
80
79
  - POST /localization/language/get-default
81
80
  - POST /localization/translation/get-by-language
82
- - POST /localization/translation/get-by-module
83
81
 
84
82
  **Authenticated** (require JWT + permission):
85
83
  - All create, update, delete operations
86
- - get-missing, get-counts, update-many-values
84
+ - get-modules, get-counts, update-many-values, set-default
87
85
 
88
86
  ## Bulk Update
89
87