@endora-commerce/mod-dictionaries 0.100.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/admin/api/client.d.ts +76 -0
  4. package/dist/admin/api/client.d.ts.map +1 -0
  5. package/dist/admin/api/client.js +87 -0
  6. package/dist/admin/api/client.js.map +1 -0
  7. package/dist/admin/components/EntryStatusBadges.d.ts +9 -0
  8. package/dist/admin/components/EntryStatusBadges.d.ts.map +1 -0
  9. package/dist/admin/components/EntryStatusBadges.js +8 -0
  10. package/dist/admin/components/EntryStatusBadges.js.map +1 -0
  11. package/dist/admin/components/LanguagePicker.d.ts +8 -0
  12. package/dist/admin/components/LanguagePicker.d.ts.map +1 -0
  13. package/dist/admin/components/LanguagePicker.js +30 -0
  14. package/dist/admin/components/LanguagePicker.js.map +1 -0
  15. package/dist/admin/components/TranslationsDrawer.d.ts +10 -0
  16. package/dist/admin/components/TranslationsDrawer.d.ts.map +1 -0
  17. package/dist/admin/components/TranslationsDrawer.js +90 -0
  18. package/dist/admin/components/TranslationsDrawer.js.map +1 -0
  19. package/dist/admin/index.d.ts +48 -0
  20. package/dist/admin/index.d.ts.map +1 -0
  21. package/dist/admin/index.js +82 -0
  22. package/dist/admin/index.js.map +1 -0
  23. package/dist/admin/pages/AuditPage.d.ts +9 -0
  24. package/dist/admin/pages/AuditPage.d.ts.map +1 -0
  25. package/dist/admin/pages/AuditPage.js +80 -0
  26. package/dist/admin/pages/AuditPage.js.map +1 -0
  27. package/dist/admin/pages/DictionaryPage.d.ts +9 -0
  28. package/dist/admin/pages/DictionaryPage.d.ts.map +1 -0
  29. package/dist/admin/pages/DictionaryPage.js +38 -0
  30. package/dist/admin/pages/DictionaryPage.js.map +1 -0
  31. package/dist/admin/tabs/CountriesTab.d.ts +3 -0
  32. package/dist/admin/tabs/CountriesTab.d.ts.map +1 -0
  33. package/dist/admin/tabs/CountriesTab.js +233 -0
  34. package/dist/admin/tabs/CountriesTab.js.map +1 -0
  35. package/dist/admin/tabs/CurrenciesTab.d.ts +3 -0
  36. package/dist/admin/tabs/CurrenciesTab.d.ts.map +1 -0
  37. package/dist/admin/tabs/CurrenciesTab.js +209 -0
  38. package/dist/admin/tabs/CurrenciesTab.js.map +1 -0
  39. package/dist/admin/tabs/LanguagesTab.d.ts +3 -0
  40. package/dist/admin/tabs/LanguagesTab.d.ts.map +1 -0
  41. package/dist/admin/tabs/LanguagesTab.js +268 -0
  42. package/dist/admin/tabs/LanguagesTab.js.map +1 -0
  43. package/dist/backend/entities/country.entity.d.ts +35 -0
  44. package/dist/backend/entities/country.entity.d.ts.map +1 -0
  45. package/dist/backend/entities/country.entity.js +107 -0
  46. package/dist/backend/entities/country.entity.js.map +1 -0
  47. package/dist/backend/entities/dictionary-translation.entity.d.ts +25 -0
  48. package/dist/backend/entities/dictionary-translation.entity.d.ts.map +1 -0
  49. package/dist/backend/entities/dictionary-translation.entity.js +65 -0
  50. package/dist/backend/entities/dictionary-translation.entity.js.map +1 -0
  51. package/dist/backend/entities/language-country.entity.d.ts +17 -0
  52. package/dist/backend/entities/language-country.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/language-country.entity.js +49 -0
  54. package/dist/backend/entities/language-country.entity.js.map +1 -0
  55. package/dist/backend/index.d.ts +85 -0
  56. package/dist/backend/index.d.ts.map +1 -0
  57. package/dist/backend/index.js +155 -0
  58. package/dist/backend/index.js.map +1 -0
  59. package/dist/backend/routes.admin.d.ts +39 -0
  60. package/dist/backend/routes.admin.d.ts.map +1 -0
  61. package/dist/backend/routes.admin.js +317 -0
  62. package/dist/backend/routes.admin.js.map +1 -0
  63. package/dist/backend/routes.storefront.d.ts +7 -0
  64. package/dist/backend/routes.storefront.d.ts.map +1 -0
  65. package/dist/backend/routes.storefront.js +34 -0
  66. package/dist/backend/routes.storefront.js.map +1 -0
  67. package/dist/backend/seed/countries.d.ts +28 -0
  68. package/dist/backend/seed/countries.d.ts.map +1 -0
  69. package/dist/backend/seed/countries.js +121 -0
  70. package/dist/backend/seed/countries.js.map +1 -0
  71. package/dist/backend/seed/currencies.d.ts +9 -0
  72. package/dist/backend/seed/currencies.d.ts.map +1 -0
  73. package/dist/backend/seed/currencies.js +76 -0
  74. package/dist/backend/seed/currencies.js.map +1 -0
  75. package/dist/backend/seed/language-countries.d.ts +7 -0
  76. package/dist/backend/seed/language-countries.d.ts.map +1 -0
  77. package/dist/backend/seed/language-countries.js +19 -0
  78. package/dist/backend/seed/language-countries.js.map +1 -0
  79. package/dist/backend/seed/translations.pl-PL.d.ts +10 -0
  80. package/dist/backend/seed/translations.pl-PL.d.ts.map +1 -0
  81. package/dist/backend/seed/translations.pl-PL.js +74 -0
  82. package/dist/backend/seed/translations.pl-PL.js.map +1 -0
  83. package/dist/backend/services/country-currency-reference.d.ts +13 -0
  84. package/dist/backend/services/country-currency-reference.d.ts.map +1 -0
  85. package/dist/backend/services/country-currency-reference.js +25 -0
  86. package/dist/backend/services/country-currency-reference.js.map +1 -0
  87. package/dist/backend/services/country-reference-registry.d.ts +30 -0
  88. package/dist/backend/services/country-reference-registry.d.ts.map +1 -0
  89. package/dist/backend/services/country-reference-registry.js +54 -0
  90. package/dist/backend/services/country-reference-registry.js.map +1 -0
  91. package/dist/backend/services/country-service.d.ts +69 -0
  92. package/dist/backend/services/country-service.d.ts.map +1 -0
  93. package/dist/backend/services/country-service.js +193 -0
  94. package/dist/backend/services/country-service.js.map +1 -0
  95. package/dist/backend/services/dictionary-cache.d.ts +27 -0
  96. package/dist/backend/services/dictionary-cache.d.ts.map +1 -0
  97. package/dist/backend/services/dictionary-cache.js +57 -0
  98. package/dist/backend/services/dictionary-cache.js.map +1 -0
  99. package/dist/backend/services/dictionary-read-service.d.ts +36 -0
  100. package/dist/backend/services/dictionary-read-service.d.ts.map +1 -0
  101. package/dist/backend/services/dictionary-read-service.js +232 -0
  102. package/dist/backend/services/dictionary-read-service.js.map +1 -0
  103. package/dist/backend/services/dictionary-validator.d.ts +26 -0
  104. package/dist/backend/services/dictionary-validator.d.ts.map +1 -0
  105. package/dist/backend/services/dictionary-validator.js +72 -0
  106. package/dist/backend/services/dictionary-validator.js.map +1 -0
  107. package/dist/backend/services/dispatch-validator-mode.d.ts +19 -0
  108. package/dist/backend/services/dispatch-validator-mode.d.ts.map +1 -0
  109. package/dist/backend/services/dispatch-validator-mode.js +19 -0
  110. package/dist/backend/services/dispatch-validator-mode.js.map +1 -0
  111. package/dist/backend/services/label-resolver.d.ts +57 -0
  112. package/dist/backend/services/label-resolver.d.ts.map +1 -0
  113. package/dist/backend/services/label-resolver.js +116 -0
  114. package/dist/backend/services/label-resolver.js.map +1 -0
  115. package/dist/backend/services/language-country-service.d.ts +26 -0
  116. package/dist/backend/services/language-country-service.d.ts.map +1 -0
  117. package/dist/backend/services/language-country-service.js +108 -0
  118. package/dist/backend/services/language-country-service.js.map +1 -0
  119. package/dist/backend/services/seed-reconciler.d.ts +22 -0
  120. package/dist/backend/services/seed-reconciler.d.ts.map +1 -0
  121. package/dist/backend/services/seed-reconciler.js +149 -0
  122. package/dist/backend/services/seed-reconciler.js.map +1 -0
  123. package/dist/backend/services/translation-service.d.ts +29 -0
  124. package/dist/backend/services/translation-service.d.ts.map +1 -0
  125. package/dist/backend/services/translation-service.js +99 -0
  126. package/dist/backend/services/translation-service.js.map +1 -0
  127. package/dist/manifest.d.ts +174 -0
  128. package/dist/manifest.d.ts.map +1 -0
  129. package/dist/manifest.js +178 -0
  130. package/dist/manifest.js.map +1 -0
  131. package/dist/migrations/20260506T112634_dictionaries_dictionary_init.d.ts +33 -0
  132. package/dist/migrations/20260506T112634_dictionaries_dictionary_init.d.ts.map +1 -0
  133. package/dist/migrations/20260506T112634_dictionaries_dictionary_init.js +157 -0
  134. package/dist/migrations/20260506T112634_dictionaries_dictionary_init.js.map +1 -0
  135. package/dist/migrations/index.d.ts +27 -0
  136. package/dist/migrations/index.d.ts.map +1 -0
  137. package/dist/migrations/index.js +29 -0
  138. package/dist/migrations/index.js.map +1 -0
  139. package/docs/dictionaries/index.md +89 -0
  140. package/i18n/en.json +88 -0
  141. package/i18n/pl.json +88 -0
  142. package/package.json +99 -0
  143. package/tailwind.css +14 -0
@@ -0,0 +1,193 @@
1
+ // CountryService — feature 017 / T025.
2
+ //
3
+ // Owns the platform-wide Country registry. Same invariants as the legacy
4
+ // LanguageService / CurrencyService (default rotation, at-least-one-active,
5
+ // default-cannot-be-deactivated, immutable code on update) plus FK
6
+ // protection on hard-delete that scans every consumer table named in
7
+ // data-model.md (addresses, taxes, warehouses, organizations registered
8
+ // address JSONB) — through `countryReferenceRegistry` since feature 077's D-87
9
+ // drain, where the scan used to be raw SQL naming those four tables directly.
10
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
11
+ import { HttpError } from '@endora-commerce/platform/http';
12
+ import { Country } from '../entities/country.entity.js';
13
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
14
+ export class CountryService {
15
+ emFactory;
16
+ invalidateDictionaryCache;
17
+ auditLog;
18
+ referenceRegistry;
19
+ constructor(emFactory, invalidateDictionaryCache, auditLog,
20
+ /**
21
+ * Resolved per call rather than captured: the registry is a singleton this
22
+ * module owns, but the accessor keeps the constructor honest for the tests
23
+ * that build the service without one.
24
+ */
25
+ referenceRegistry) {
26
+ this.emFactory = emFactory;
27
+ this.invalidateDictionaryCache = invalidateDictionaryCache;
28
+ this.auditLog = auditLog;
29
+ this.referenceRegistry = referenceRegistry;
30
+ }
31
+ #audit(em, action, objectId, stateBefore, stateAfter) {
32
+ if (this.auditLog) {
33
+ recordAuditFromContext(this.auditLog, em, {
34
+ action,
35
+ objectType: 'country',
36
+ objectId,
37
+ stateBefore,
38
+ stateAfter,
39
+ });
40
+ }
41
+ }
42
+ async list() {
43
+ const em = this.emFactory();
44
+ return em.find(Country, {}, { orderBy: { sortOrder: 'asc', label: 'asc' } });
45
+ }
46
+ async listActive() {
47
+ const em = this.emFactory();
48
+ return em.find(Country, { isActive: true }, { orderBy: { sortOrder: 'asc', label: 'asc' } });
49
+ }
50
+ async getDefault() {
51
+ const em = this.emFactory();
52
+ return em.findOne(Country, { isDefault: true });
53
+ }
54
+ async getByCode(code) {
55
+ const em = this.emFactory();
56
+ return em.findOne(Country, { code });
57
+ }
58
+ async create(input) {
59
+ const em = this.emFactory();
60
+ const existing = await em.findOne(Country, { code: input.code });
61
+ if (existing) {
62
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, `Country ${input.code} already exists.`);
63
+ }
64
+ const row = em.create(Country, {
65
+ code: input.code,
66
+ alpha3Code: input.alpha3Code,
67
+ numericCode: input.numericCode,
68
+ label: input.label,
69
+ region: input.region,
70
+ ...(input.subregion !== undefined ? { subregion: input.subregion } : {}),
71
+ ...(input.dialCode !== undefined ? { dialCode: input.dialCode } : {}),
72
+ ...(input.isEuMember !== undefined ? { isEuMember: input.isEuMember } : {}),
73
+ ...(input.defaultCurrencyCode !== undefined
74
+ ? { defaultCurrencyCode: input.defaultCurrencyCode }
75
+ : {}),
76
+ ...(input.isActive !== undefined ? { isActive: input.isActive } : {}),
77
+ ...(input.sortOrder !== undefined ? { sortOrder: input.sortOrder } : {}),
78
+ });
79
+ em.persist(row);
80
+ this.#audit(em, 'country.create', row.code, null, { label: row.label });
81
+ await em.flush();
82
+ await this.invalidateDictionaryCache?.();
83
+ return row;
84
+ }
85
+ async update(code, input) {
86
+ const em = this.emFactory();
87
+ const existing = await em.findOne(Country, { code });
88
+ if (!existing) {
89
+ throw new HttpError(404, ERROR_CODES.DICTIONARY_ENTRY_NOT_FOUND, `Country ${code} not found.`);
90
+ }
91
+ // Default-cannot-be-deactivated invariant.
92
+ if (input.isActive === false && existing.isDefault) {
93
+ throw new HttpError(409, ERROR_CODES.DICTIONARY_DEFAULT_CANNOT_BE_DEACTIVATED, 'Cannot deactivate the default country. Promote a different country first.');
94
+ }
95
+ // At-least-one-active invariant — guard the deactivation path.
96
+ if (input.isActive === false && existing.isActive) {
97
+ const otherActive = await em.count(Country, { isActive: true, code: { $ne: code } });
98
+ if (otherActive === 0) {
99
+ throw new HttpError(409, ERROR_CODES.DICTIONARY_LAST_ACTIVE_ENTRY, 'At least one country must remain active.');
100
+ }
101
+ }
102
+ if (input.alpha3Code !== undefined)
103
+ existing.alpha3Code = input.alpha3Code;
104
+ if (input.numericCode !== undefined)
105
+ existing.numericCode = input.numericCode;
106
+ if (input.label !== undefined)
107
+ existing.label = input.label;
108
+ if (input.region !== undefined)
109
+ existing.region = input.region;
110
+ if (input.subregion !== undefined)
111
+ existing.subregion = input.subregion;
112
+ if (input.dialCode !== undefined)
113
+ existing.dialCode = input.dialCode;
114
+ if (input.isEuMember !== undefined)
115
+ existing.isEuMember = input.isEuMember;
116
+ if (input.defaultCurrencyCode !== undefined) {
117
+ existing.defaultCurrencyCode = input.defaultCurrencyCode;
118
+ }
119
+ if (input.isActive !== undefined)
120
+ existing.isActive = input.isActive;
121
+ if (input.sortOrder !== undefined)
122
+ existing.sortOrder = input.sortOrder;
123
+ this.#audit(em, 'country.update', existing.code, null, { label: existing.label, isActive: existing.isActive });
124
+ await em.flush();
125
+ await this.invalidateDictionaryCache?.();
126
+ return existing;
127
+ }
128
+ async setDefault(code) {
129
+ const em = this.emFactory();
130
+ const target = await em.findOne(Country, { code });
131
+ if (!target) {
132
+ throw new HttpError(404, ERROR_CODES.DICTIONARY_ENTRY_NOT_FOUND, `Country ${code} not found.`);
133
+ }
134
+ if (!target.isActive) {
135
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Cannot mark an inactive country as default.');
136
+ }
137
+ if (target.isDefault)
138
+ return target;
139
+ // Demote-then-promote in two steps inside the EM's current scope
140
+ // (or its own implicit tx if not already in one). em.nativeUpdate is
141
+ // tx-aware — it honours an outer transaction when one is open.
142
+ await em.nativeUpdate(Country, { isDefault: true }, { isDefault: false });
143
+ await em.nativeUpdate(Country, { code }, { isDefault: true });
144
+ target.isDefault = true;
145
+ this.#audit(em, 'country.set_default', code, null, { isDefault: true });
146
+ await em.flush();
147
+ await this.invalidateDictionaryCache?.();
148
+ return target;
149
+ }
150
+ /**
151
+ * Every consumer reference to `code` (feature 077, D-87).
152
+ *
153
+ * Three hand-written `count(*)` statements over `addresses`, `taxes` and
154
+ * `organizations` used to live here, and a comment explaining that
155
+ * `warehouses` was skipped because its country sits inside a JSONB column and
156
+ * "covering that path requires a separate scan that v1 defers". The scan is
157
+ * `inventory`'s to write and it writes it: the descriptor it contributes
158
+ * answers the same question about its own table, so the gap closed as a side
159
+ * effect of moving the question to the module that can answer it.
160
+ */
161
+ async countDependents(code) {
162
+ const registry = this.referenceRegistry?.();
163
+ return registry ? registry.countReferences(code) : [];
164
+ }
165
+ async remove(code) {
166
+ const em = this.emFactory();
167
+ const row = await em.findOne(Country, { code });
168
+ if (!row) {
169
+ throw new HttpError(404, ERROR_CODES.DICTIONARY_ENTRY_NOT_FOUND, `Country ${code} not found.`);
170
+ }
171
+ if (row.isDefault) {
172
+ throw new HttpError(409, ERROR_CODES.DICTIONARY_DEFAULT_CANNOT_BE_DEACTIVATED, 'Cannot delete the default country. Promote a different country first.');
173
+ }
174
+ const dependents = await this.countDependents(code);
175
+ // Which references refuse the delete is the contributing module's call,
176
+ // carried on the descriptor.
177
+ const total = dependents
178
+ .filter((reference) => reference.blocking)
179
+ .reduce((sum, reference) => sum + reference.count, 0);
180
+ if (total > 0) {
181
+ throw new HttpError(409, ERROR_CODES.DICTIONARY_ENTRY_HAS_DEPENDENTS, `Country ${code} cannot be deleted because ${total} consumer reference(s) exist.`, [
182
+ {
183
+ path: 'consumers',
184
+ issue: JSON.stringify(dependents),
185
+ },
186
+ ]);
187
+ }
188
+ this.#audit(em, 'country.delete', row.code, { label: row.label }, null);
189
+ await em.removeAndFlush(row);
190
+ await this.invalidateDictionaryCache?.();
191
+ }
192
+ }
193
+ //# sourceMappingURL=country-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"country-service.js","sourceRoot":"","sources":["../../../src/backend/services/country-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { Redis } from 'ioredis';
2
+ export declare const DICTIONARY_CACHE_KEY_PREFIX = "dictionary:";
3
+ export declare const DICTIONARY_CACHE_DEFAULT_TTL_SECONDS: number;
4
+ /**
5
+ * The channel segment of a **platform-wide** registry key (issue #101), the
6
+ * same reserved segment the settings cache uses. A uuid can never spell it, so
7
+ * it cannot collide with a real channel — which the literal `'default'` it
8
+ * replaces could and did, because that is also a channel code.
9
+ */
10
+ export declare const GLOBAL_CACHE_KEY_SEGMENT = "__global__";
11
+ export interface DictionaryCacheOptions {
12
+ ttlSeconds?: number;
13
+ enabled?: boolean;
14
+ }
15
+ export declare class DictionaryCache {
16
+ private readonly redis;
17
+ private readonly ttlSeconds;
18
+ private readonly enabled;
19
+ constructor(redis: Redis, options?: DictionaryCacheOptions);
20
+ /** `channelKey` is a channel **id**, or {@link GLOBAL_CACHE_KEY_SEGMENT}. */
21
+ static registryKey(channelKey: string, locale: string): string;
22
+ static byCodeKey(entryType: string, entryCode: string, locale: string): string;
23
+ get<T>(key: string): Promise<T | null>;
24
+ set<T>(key: string, payload: T): Promise<void>;
25
+ invalidateAll(): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=dictionary-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary-cache.d.ts","sourceRoot":"","sources":["../../../src/backend/services/dictionary-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,eAAO,MAAM,2BAA2B,gBAAgB,CAAC;AACzD,eAAO,MAAM,oCAAoC,QAAU,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,eAAe;IAId,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEL,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,sBAA2B;IAK/E,6EAA6E;IAC7E,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAI9D,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIxE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAYtC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAerC"}
@@ -0,0 +1,57 @@
1
+ export const DICTIONARY_CACHE_KEY_PREFIX = 'dictionary:';
2
+ export const DICTIONARY_CACHE_DEFAULT_TTL_SECONDS = 60 * 60;
3
+ /**
4
+ * The channel segment of a **platform-wide** registry key (issue #101), the
5
+ * same reserved segment the settings cache uses. A uuid can never spell it, so
6
+ * it cannot collide with a real channel — which the literal `'default'` it
7
+ * replaces could and did, because that is also a channel code.
8
+ */
9
+ export const GLOBAL_CACHE_KEY_SEGMENT = '__global__';
10
+ export class DictionaryCache {
11
+ redis;
12
+ ttlSeconds;
13
+ enabled;
14
+ constructor(redis, options = {}) {
15
+ this.redis = redis;
16
+ this.ttlSeconds = options.ttlSeconds ?? DICTIONARY_CACHE_DEFAULT_TTL_SECONDS;
17
+ this.enabled = (options.enabled ?? true) && this.ttlSeconds > 0;
18
+ }
19
+ /** `channelKey` is a channel **id**, or {@link GLOBAL_CACHE_KEY_SEGMENT}. */
20
+ static registryKey(channelKey, locale) {
21
+ return `${DICTIONARY_CACHE_KEY_PREFIX}registry:v1:${channelKey}:${locale}`;
22
+ }
23
+ static byCodeKey(entryType, entryCode, locale) {
24
+ return `${DICTIONARY_CACHE_KEY_PREFIX}by-code:v1:${entryType}:${entryCode}:${locale}`;
25
+ }
26
+ async get(key) {
27
+ if (!this.enabled)
28
+ return null;
29
+ const raw = await this.redis.get(key);
30
+ if (raw === null)
31
+ return null;
32
+ try {
33
+ return JSON.parse(raw);
34
+ }
35
+ catch {
36
+ await this.redis.del(key);
37
+ return null;
38
+ }
39
+ }
40
+ async set(key, payload) {
41
+ if (!this.enabled)
42
+ return;
43
+ await this.redis.set(key, JSON.stringify(payload), 'EX', this.ttlSeconds);
44
+ }
45
+ async invalidateAll() {
46
+ if (!this.enabled)
47
+ return;
48
+ let cursor = '0';
49
+ do {
50
+ const [next, keys] = await this.redis.scan(cursor, 'MATCH', `${DICTIONARY_CACHE_KEY_PREFIX}*:v1:*`, 'COUNT', 200);
51
+ cursor = next;
52
+ if (keys.length > 0)
53
+ await this.redis.del(...keys);
54
+ } while (cursor !== '0');
55
+ }
56
+ }
57
+ //# sourceMappingURL=dictionary-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary-cache.js","sourceRoot":"","sources":["../../../src/backend/services/dictionary-cache.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAa,CAAC;AACzD,MAAM,CAAC,MAAM,oCAAoC,GAAG,EAAE,GAAG,EAAE,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAOrD,MAAM,OAAO,eAAe;IAIG;IAHZ,UAAU,CAAS;IACnB,OAAO,CAAU;IAElC,YAA6B,KAAY,EAAE,UAAkC,EAAE;QAAlD,UAAK,GAAL,KAAK,CAAO;QACvC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,oCAAoC,CAAC;QAC7E,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IAClE,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,WAAW,CAAC,UAAkB,EAAE,MAAc;QACnD,OAAO,GAAG,2BAA2B,eAAe,UAAU,IAAI,MAAM,EAAE,CAAC;IAC7E,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,SAAiB,EAAE,SAAiB,EAAE,MAAc;QACnE,OAAO,GAAG,2BAA2B,cAAc,SAAS,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC;IACxF,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,OAAU;QAClC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,MAAM,GAAG,GAAG,CAAC;QACjB,GAAG,CAAC;YACF,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CACxC,MAAM,EACN,OAAO,EACP,GAAG,2BAA2B,QAAQ,EACtC,OAAO,EACP,GAAG,CACJ,CAAC;YACF,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QACrD,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;IAC3B,CAAC;CACF"}
@@ -0,0 +1,36 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CurrencyReadPort, type DictionaryByCodeResponse, type DictionaryEntryType, type DictionaryRegistryResponse, type LanguageReadPort } from '@endora-commerce/contracts';
3
+ import { DictionaryCache } from './dictionary-cache.js';
4
+ import { LabelResolver } from './label-resolver.js';
5
+ export interface DictionaryRegistryArgs {
6
+ channelCode?: string;
7
+ locale?: string;
8
+ }
9
+ export interface DictionaryByCodeArgs {
10
+ entryType: DictionaryEntryType;
11
+ entryCode: string;
12
+ locale?: string;
13
+ }
14
+ export declare class DictionaryReadService {
15
+ private readonly emFactory;
16
+ private readonly currencies;
17
+ private readonly languages;
18
+ private readonly cache?;
19
+ private readonly labelResolver;
20
+ /**
21
+ * Feature 075, Phase C — the registry is assembled from three tables, and
22
+ * only `countries` is this module's. The currency and language rows come
23
+ * from their owners' read ports instead of `em.find(Currency, …)` /
24
+ * `em.find(Language, …)`, which kept answering out of modules an operator had
25
+ * switched off, because deactivation drops no tables.
26
+ *
27
+ * Each port call is one statement, exactly like the query it replaces, so the
28
+ * cold-build ceiling issue #142 pinned at seven is unchanged.
29
+ */
30
+ constructor(emFactory: () => EntityManager, currencies: CurrencyReadPort, languages: LanguageReadPort, cache?: DictionaryCache | undefined, labelResolver?: LabelResolver);
31
+ getRegistry(args?: DictionaryRegistryArgs): Promise<DictionaryRegistryResponse>;
32
+ getByCode(args: DictionaryByCodeArgs): Promise<DictionaryByCodeResponse>;
33
+ private resolveContext;
34
+ private resolveDefaultLocale;
35
+ }
36
+ //# sourceMappingURL=dictionary-read-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary-read-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/dictionary-read-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAAE,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,aAAa,EAAkC,MAAM,qBAAqB,CAAC;AAEpF,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAY9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAfhC;;;;;;;;;OASG;gBAEgB,SAAS,EAAE,MAAM,aAAa,EAC9B,UAAU,EAAE,gBAAgB,EAC5B,SAAS,EAAE,gBAAgB,EAC3B,KAAK,CAAC,EAAE,eAAe,YAAA,EACvB,aAAa,GAAE,aAI/B;IAGG,WAAW,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC;IA4EnF,SAAS,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,wBAAwB,CAAC;YAuFhE,cAAc;YA+Bd,oBAAoB;CAInC"}
@@ -0,0 +1,232 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
4
+ import { Country } from '../entities/country.entity.js';
5
+ import { LanguageCountry } from '../entities/language-country.entity.js';
6
+ import { DictionaryCache, GLOBAL_CACHE_KEY_SEGMENT } from './dictionary-cache.js';
7
+ import { LabelResolver, labelKey } from './label-resolver.js';
8
+ export class DictionaryReadService {
9
+ emFactory;
10
+ currencies;
11
+ languages;
12
+ cache;
13
+ labelResolver;
14
+ /**
15
+ * Feature 075, Phase C — the registry is assembled from three tables, and
16
+ * only `countries` is this module's. The currency and language rows come
17
+ * from their owners' read ports instead of `em.find(Currency, …)` /
18
+ * `em.find(Language, …)`, which kept answering out of modules an operator had
19
+ * switched off, because deactivation drops no tables.
20
+ *
21
+ * Each port call is one statement, exactly like the query it replaces, so the
22
+ * cold-build ceiling issue #142 pinned at seven is unchanged.
23
+ */
24
+ constructor(emFactory, currencies, languages, cache, labelResolver = new LabelResolver(emFactory, currencies, languages)) {
25
+ this.emFactory = emFactory;
26
+ this.currencies = currencies;
27
+ this.languages = languages;
28
+ this.cache = cache;
29
+ this.labelResolver = labelResolver;
30
+ }
31
+ async getRegistry(args = {}) {
32
+ const ctx = await this.resolveContext(args);
33
+ const cacheKey = DictionaryCache.registryKey(ctx.cacheChannelKey, ctx.locale);
34
+ const cached = await this.cache?.get(cacheKey);
35
+ if (cached)
36
+ return cached;
37
+ const em = this.emFactory();
38
+ const [countries, currencies, languages, links] = await Promise.all([
39
+ em.find(Country, { isActive: true }, { orderBy: { sortOrder: 'asc', label: 'asc' } }),
40
+ this.currencies.listActive(),
41
+ this.languages.listActive(),
42
+ em.find(LanguageCountry, {}),
43
+ ]);
44
+ const scopedCurrencies = ctx.channel
45
+ ? currencies.filter((row) => ctx.channel?.currencies.includes(row.code))
46
+ : currencies;
47
+ const scopedLanguages = ctx.channel
48
+ ? languages.filter((row) => ctx.channel?.languages.includes(row.code))
49
+ : languages;
50
+ const countryLinks = groupCountryLinks(links);
51
+ // Issue #142 — one batch, not one resolution per entry. The rows above
52
+ // already carry their canonical labels, so the only thing left to read is
53
+ // the translation each entry may have in the locale's fallback chain, and
54
+ // that is one statement for the whole registry however large it grows.
55
+ const labels = await this.labelResolver.resolveLabels([
56
+ ...countries.map((row) => canonical('country', row.code, row.label)),
57
+ ...scopedCurrencies.map((row) => canonical('currency', row.code, row.label)),
58
+ ...scopedLanguages.map((row) => canonical('language', row.code, row.label)),
59
+ ], ctx.locale);
60
+ const payload = {
61
+ data: {
62
+ countries: countries.map((row) => ({
63
+ code: row.code,
64
+ label: labels.get(labelKey('country', row.code)) ?? row.label,
65
+ alpha3Code: row.alpha3Code,
66
+ numericCode: row.numericCode,
67
+ region: row.region,
68
+ subregion: row.subregion ?? null,
69
+ dialCode: row.dialCode ?? null,
70
+ isEuMember: row.isEuMember,
71
+ defaultCurrencyCode: row.defaultCurrencyCode ?? null,
72
+ sortOrder: row.sortOrder,
73
+ })),
74
+ currencies: scopedCurrencies.map((row) => ({
75
+ code: row.code,
76
+ label: labels.get(labelKey('currency', row.code)) ?? row.label,
77
+ symbol: row.symbol,
78
+ symbolPosition: row.symbolPosition,
79
+ decimalPlaces: row.decimalPlaces,
80
+ sortOrder: row.sortOrder,
81
+ })),
82
+ languages: scopedLanguages.map((row) => ({
83
+ code: row.code,
84
+ label: labels.get(labelKey('language', row.code)) ?? row.label,
85
+ nativeLabel: row.nativeLabel || row.label,
86
+ isRtl: row.isRtl,
87
+ fallbackCode: row.fallbackCode ?? null,
88
+ countries: countryLinks.get(row.code) ?? [],
89
+ sortOrder: row.sortOrder,
90
+ })),
91
+ defaults: {
92
+ country: countries.find((row) => row.isDefault)?.code ?? null,
93
+ currency: ctx.channel?.defaultCurrency ?? scopedCurrencies.find((row) => row.isDefault)?.code ?? null,
94
+ language: ctx.channel?.defaultLanguage ?? scopedLanguages.find((row) => row.isDefault)?.code ?? null,
95
+ },
96
+ },
97
+ };
98
+ await this.cache?.set(cacheKey, payload);
99
+ return payload;
100
+ }
101
+ async getByCode(args) {
102
+ const locale = args.locale ?? (await this.resolveDefaultLocale());
103
+ const cacheKey = DictionaryCache.byCodeKey(args.entryType, args.entryCode, locale);
104
+ const cached = await this.cache?.get(cacheKey);
105
+ if (cached)
106
+ return cached;
107
+ const em = this.emFactory();
108
+ if (args.entryType === 'country') {
109
+ const row = await em.findOne(Country, { code: args.entryCode });
110
+ if (!row)
111
+ throw notFound('country', args.entryCode);
112
+ const payload = {
113
+ data: {
114
+ entryType: 'country',
115
+ entry: {
116
+ code: row.code,
117
+ label: await this.labelResolver.resolveLabel({
118
+ entryType: 'country',
119
+ entryCode: row.code,
120
+ locale,
121
+ }),
122
+ alpha3Code: row.alpha3Code,
123
+ numericCode: row.numericCode,
124
+ region: row.region,
125
+ subregion: row.subregion ?? null,
126
+ dialCode: row.dialCode ?? null,
127
+ isEuMember: row.isEuMember,
128
+ defaultCurrencyCode: row.defaultCurrencyCode ?? null,
129
+ sortOrder: row.sortOrder,
130
+ isActive: row.isActive,
131
+ },
132
+ },
133
+ };
134
+ await this.cache?.set(cacheKey, payload);
135
+ return payload;
136
+ }
137
+ if (args.entryType === 'currency') {
138
+ const row = await this.currencies.findByCode(args.entryCode);
139
+ if (!row)
140
+ throw notFound('currency', args.entryCode);
141
+ const payload = {
142
+ data: {
143
+ entryType: 'currency',
144
+ entry: {
145
+ code: row.code,
146
+ label: await this.labelResolver.resolveLabel({
147
+ entryType: 'currency',
148
+ entryCode: row.code,
149
+ locale,
150
+ }),
151
+ symbol: row.symbol,
152
+ symbolPosition: row.symbolPosition,
153
+ decimalPlaces: row.decimalPlaces,
154
+ sortOrder: row.sortOrder,
155
+ isActive: row.isActive,
156
+ },
157
+ },
158
+ };
159
+ await this.cache?.set(cacheKey, payload);
160
+ return payload;
161
+ }
162
+ const row = await this.languages.findByCode(args.entryCode);
163
+ if (!row)
164
+ throw notFound('language', args.entryCode);
165
+ const links = await em.find(LanguageCountry, { languageCode: row.code });
166
+ const payload = {
167
+ data: {
168
+ entryType: 'language',
169
+ entry: {
170
+ code: row.code,
171
+ label: await this.labelResolver.resolveLabel({
172
+ entryType: 'language',
173
+ entryCode: row.code,
174
+ locale,
175
+ }),
176
+ nativeLabel: row.nativeLabel || row.label,
177
+ isRtl: row.isRtl,
178
+ fallbackCode: row.fallbackCode ?? null,
179
+ countries: links.map((link) => link.countryCode),
180
+ sortOrder: row.sortOrder,
181
+ isActive: row.isActive,
182
+ },
183
+ },
184
+ };
185
+ await this.cache?.set(cacheKey, payload);
186
+ return payload;
187
+ }
188
+ async resolveContext(args) {
189
+ const em = this.emFactory();
190
+ const channel = args.channelCode
191
+ ? await em.findOne(SalesChannel, { code: args.channelCode })
192
+ : await em.findOne(SalesChannel, { systemDefault: true });
193
+ if (args.channelCode && !channel) {
194
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Sales channel ${args.channelCode} not found.`);
195
+ }
196
+ const locale = args.locale ?? channel?.defaultLanguage ?? (await this.resolveDefaultLocale());
197
+ return {
198
+ channel: channel ?? null,
199
+ locale,
200
+ // Issue #101 — the key is the channel this read actually resolved, not
201
+ // the literal `'default'` it used to be. A code is not an identity: the
202
+ // flag moves (D-51), so an entry keyed `'default'` outlived the channel
203
+ // it was built from and served the previous default's languages and
204
+ // currencies under the new one's name. `__global__` is the reserved
205
+ // segment for the platform-wide tier, the same one the settings cache
206
+ // uses; a uuid can never spell it, so it cannot collide with a channel.
207
+ cacheChannelKey: channel?.id ?? GLOBAL_CACHE_KEY_SEGMENT,
208
+ };
209
+ }
210
+ async resolveDefaultLocale() {
211
+ const language = await this.languages.getDefault();
212
+ return language?.code ?? 'en-US';
213
+ }
214
+ }
215
+ function canonical(entryType, entryCode, canonicalLabel) {
216
+ return { entryType, entryCode, canonicalLabel };
217
+ }
218
+ function groupCountryLinks(links) {
219
+ const out = new Map();
220
+ for (const link of links) {
221
+ const list = out.get(link.languageCode) ?? [];
222
+ list.push(link.countryCode);
223
+ out.set(link.languageCode, list);
224
+ }
225
+ for (const list of out.values())
226
+ list.sort();
227
+ return out;
228
+ }
229
+ function notFound(entryType, entryCode) {
230
+ return new HttpError(404, ERROR_CODES.DICTIONARY_ENTRY_NOT_FOUND, `Dictionary ${entryType} ${entryCode} not found.`);
231
+ }
232
+ //# sourceMappingURL=dictionary-read-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary-read-service.js","sourceRoot":"","sources":["../../../src/backend/services/dictionary-read-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CurrencyReadPort, type DictionaryValidator as DictionaryValidatorPort, type DictionaryWriteMode, type LanguageReadPort } from '@endora-commerce/contracts';
3
+ export declare class DictionaryValidator implements DictionaryValidatorPort {
4
+ private readonly emFactory;
5
+ private readonly currencies;
6
+ private readonly languages;
7
+ private readonly cache;
8
+ /**
9
+ * Feature 075, Phase C — `country` is this module's own table and stays an
10
+ * `em.findOne`; `currency` and `language` are not, and go through their
11
+ * owners' read ports. The reads used to be `em.findOne(Currency, …)` /
12
+ * `em.findOne(Language, …)` against tables deactivation does not drop, so a
13
+ * write validated against a switched-off `currencies` was accepted as valid.
14
+ * Fail-closed is the right answer for a validator: an unvalidated code
15
+ * written into an order or a tax rule is worse than a refused write, and both
16
+ * owners are binding `dependencies` of this manifest.
17
+ */
18
+ constructor(emFactory: () => EntityManager, currencies: CurrencyReadPort, languages: LanguageReadPort);
19
+ validateCountryCode(code: string, mode: DictionaryWriteMode): Promise<void>;
20
+ validateCurrencyCode(code: string, mode: DictionaryWriteMode): Promise<void>;
21
+ validateLanguageCode(code: string, mode: DictionaryWriteMode): Promise<void>;
22
+ invalidate(): void;
23
+ private validate;
24
+ private getState;
25
+ }
26
+ //# sourceMappingURL=dictionary-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary-validator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/dictionary-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,mBAAmB,IAAI,uBAAuB,EACnD,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAC;AAWpC,qBAAa,mBAAoB,YAAW,uBAAuB;IAc/D,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAf5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IAEvD;;;;;;;;;OASG;gBAEgB,SAAS,EAAE,MAAM,aAAa,EAC9B,UAAU,EAAE,gBAAgB,EAC5B,SAAS,EAAE,gBAAgB;IAGxC,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlF,UAAU,IAAI,IAAI;YAIJ,QAAQ;YAsBR,QAAQ;CAmBvB"}