@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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/admin/api/client.d.ts +76 -0
- package/dist/admin/api/client.d.ts.map +1 -0
- package/dist/admin/api/client.js +87 -0
- package/dist/admin/api/client.js.map +1 -0
- package/dist/admin/components/EntryStatusBadges.d.ts +9 -0
- package/dist/admin/components/EntryStatusBadges.d.ts.map +1 -0
- package/dist/admin/components/EntryStatusBadges.js +8 -0
- package/dist/admin/components/EntryStatusBadges.js.map +1 -0
- package/dist/admin/components/LanguagePicker.d.ts +8 -0
- package/dist/admin/components/LanguagePicker.d.ts.map +1 -0
- package/dist/admin/components/LanguagePicker.js +30 -0
- package/dist/admin/components/LanguagePicker.js.map +1 -0
- package/dist/admin/components/TranslationsDrawer.d.ts +10 -0
- package/dist/admin/components/TranslationsDrawer.d.ts.map +1 -0
- package/dist/admin/components/TranslationsDrawer.js +90 -0
- package/dist/admin/components/TranslationsDrawer.js.map +1 -0
- package/dist/admin/index.d.ts +48 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +82 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AuditPage.d.ts +9 -0
- package/dist/admin/pages/AuditPage.d.ts.map +1 -0
- package/dist/admin/pages/AuditPage.js +80 -0
- package/dist/admin/pages/AuditPage.js.map +1 -0
- package/dist/admin/pages/DictionaryPage.d.ts +9 -0
- package/dist/admin/pages/DictionaryPage.d.ts.map +1 -0
- package/dist/admin/pages/DictionaryPage.js +38 -0
- package/dist/admin/pages/DictionaryPage.js.map +1 -0
- package/dist/admin/tabs/CountriesTab.d.ts +3 -0
- package/dist/admin/tabs/CountriesTab.d.ts.map +1 -0
- package/dist/admin/tabs/CountriesTab.js +233 -0
- package/dist/admin/tabs/CountriesTab.js.map +1 -0
- package/dist/admin/tabs/CurrenciesTab.d.ts +3 -0
- package/dist/admin/tabs/CurrenciesTab.d.ts.map +1 -0
- package/dist/admin/tabs/CurrenciesTab.js +209 -0
- package/dist/admin/tabs/CurrenciesTab.js.map +1 -0
- package/dist/admin/tabs/LanguagesTab.d.ts +3 -0
- package/dist/admin/tabs/LanguagesTab.d.ts.map +1 -0
- package/dist/admin/tabs/LanguagesTab.js +268 -0
- package/dist/admin/tabs/LanguagesTab.js.map +1 -0
- package/dist/backend/entities/country.entity.d.ts +35 -0
- package/dist/backend/entities/country.entity.d.ts.map +1 -0
- package/dist/backend/entities/country.entity.js +107 -0
- package/dist/backend/entities/country.entity.js.map +1 -0
- package/dist/backend/entities/dictionary-translation.entity.d.ts +25 -0
- package/dist/backend/entities/dictionary-translation.entity.d.ts.map +1 -0
- package/dist/backend/entities/dictionary-translation.entity.js +65 -0
- package/dist/backend/entities/dictionary-translation.entity.js.map +1 -0
- package/dist/backend/entities/language-country.entity.d.ts +17 -0
- package/dist/backend/entities/language-country.entity.d.ts.map +1 -0
- package/dist/backend/entities/language-country.entity.js +49 -0
- package/dist/backend/entities/language-country.entity.js.map +1 -0
- package/dist/backend/index.d.ts +85 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +155 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +39 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +317 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +7 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +34 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/seed/countries.d.ts +28 -0
- package/dist/backend/seed/countries.d.ts.map +1 -0
- package/dist/backend/seed/countries.js +121 -0
- package/dist/backend/seed/countries.js.map +1 -0
- package/dist/backend/seed/currencies.d.ts +9 -0
- package/dist/backend/seed/currencies.d.ts.map +1 -0
- package/dist/backend/seed/currencies.js +76 -0
- package/dist/backend/seed/currencies.js.map +1 -0
- package/dist/backend/seed/language-countries.d.ts +7 -0
- package/dist/backend/seed/language-countries.d.ts.map +1 -0
- package/dist/backend/seed/language-countries.js +19 -0
- package/dist/backend/seed/language-countries.js.map +1 -0
- package/dist/backend/seed/translations.pl-PL.d.ts +10 -0
- package/dist/backend/seed/translations.pl-PL.d.ts.map +1 -0
- package/dist/backend/seed/translations.pl-PL.js +74 -0
- package/dist/backend/seed/translations.pl-PL.js.map +1 -0
- package/dist/backend/services/country-currency-reference.d.ts +13 -0
- package/dist/backend/services/country-currency-reference.d.ts.map +1 -0
- package/dist/backend/services/country-currency-reference.js +25 -0
- package/dist/backend/services/country-currency-reference.js.map +1 -0
- package/dist/backend/services/country-reference-registry.d.ts +30 -0
- package/dist/backend/services/country-reference-registry.d.ts.map +1 -0
- package/dist/backend/services/country-reference-registry.js +54 -0
- package/dist/backend/services/country-reference-registry.js.map +1 -0
- package/dist/backend/services/country-service.d.ts +69 -0
- package/dist/backend/services/country-service.d.ts.map +1 -0
- package/dist/backend/services/country-service.js +193 -0
- package/dist/backend/services/country-service.js.map +1 -0
- package/dist/backend/services/dictionary-cache.d.ts +27 -0
- package/dist/backend/services/dictionary-cache.d.ts.map +1 -0
- package/dist/backend/services/dictionary-cache.js +57 -0
- package/dist/backend/services/dictionary-cache.js.map +1 -0
- package/dist/backend/services/dictionary-read-service.d.ts +36 -0
- package/dist/backend/services/dictionary-read-service.d.ts.map +1 -0
- package/dist/backend/services/dictionary-read-service.js +232 -0
- package/dist/backend/services/dictionary-read-service.js.map +1 -0
- package/dist/backend/services/dictionary-validator.d.ts +26 -0
- package/dist/backend/services/dictionary-validator.d.ts.map +1 -0
- package/dist/backend/services/dictionary-validator.js +72 -0
- package/dist/backend/services/dictionary-validator.js.map +1 -0
- package/dist/backend/services/dispatch-validator-mode.d.ts +19 -0
- package/dist/backend/services/dispatch-validator-mode.d.ts.map +1 -0
- package/dist/backend/services/dispatch-validator-mode.js +19 -0
- package/dist/backend/services/dispatch-validator-mode.js.map +1 -0
- package/dist/backend/services/label-resolver.d.ts +57 -0
- package/dist/backend/services/label-resolver.d.ts.map +1 -0
- package/dist/backend/services/label-resolver.js +116 -0
- package/dist/backend/services/label-resolver.js.map +1 -0
- package/dist/backend/services/language-country-service.d.ts +26 -0
- package/dist/backend/services/language-country-service.d.ts.map +1 -0
- package/dist/backend/services/language-country-service.js +108 -0
- package/dist/backend/services/language-country-service.js.map +1 -0
- package/dist/backend/services/seed-reconciler.d.ts +22 -0
- package/dist/backend/services/seed-reconciler.d.ts.map +1 -0
- package/dist/backend/services/seed-reconciler.js +149 -0
- package/dist/backend/services/seed-reconciler.js.map +1 -0
- package/dist/backend/services/translation-service.d.ts +29 -0
- package/dist/backend/services/translation-service.d.ts.map +1 -0
- package/dist/backend/services/translation-service.js +99 -0
- package/dist/backend/services/translation-service.js.map +1 -0
- package/dist/manifest.d.ts +174 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +178 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260506T112634_dictionaries_dictionary_init.d.ts +33 -0
- package/dist/migrations/20260506T112634_dictionaries_dictionary_init.d.ts.map +1 -0
- package/dist/migrations/20260506T112634_dictionaries_dictionary_init.js +157 -0
- package/dist/migrations/20260506T112634_dictionaries_dictionary_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/dictionaries/index.md +89 -0
- package/i18n/en.json +88 -0
- package/i18n/pl.json +88 -0
- package/package.json +99 -0
- 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"}
|