@endora-commerce/mod-currencies 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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/dist/backend/entities/currency.entity.d.ts +22 -0
  4. package/dist/backend/entities/currency.entity.d.ts.map +1 -0
  5. package/dist/backend/entities/currency.entity.js +77 -0
  6. package/dist/backend/entities/currency.entity.js.map +1 -0
  7. package/dist/backend/index.d.ts +53 -0
  8. package/dist/backend/index.d.ts.map +1 -0
  9. package/dist/backend/index.js +108 -0
  10. package/dist/backend/index.js.map +1 -0
  11. package/dist/backend/routes.d.ts +36 -0
  12. package/dist/backend/routes.d.ts.map +1 -0
  13. package/dist/backend/routes.js +43 -0
  14. package/dist/backend/routes.js.map +1 -0
  15. package/dist/backend/services/currency-ports.d.ts +23 -0
  16. package/dist/backend/services/currency-ports.d.ts.map +1 -0
  17. package/dist/backend/services/currency-ports.js +63 -0
  18. package/dist/backend/services/currency-ports.js.map +1 -0
  19. package/dist/backend/services/currency-reference-registry.d.ts +30 -0
  20. package/dist/backend/services/currency-reference-registry.d.ts.map +1 -0
  21. package/dist/backend/services/currency-reference-registry.js +54 -0
  22. package/dist/backend/services/currency-reference-registry.js.map +1 -0
  23. package/dist/backend/services/currency-seed-service.d.ts +21 -0
  24. package/dist/backend/services/currency-seed-service.d.ts.map +1 -0
  25. package/dist/backend/services/currency-seed-service.js +41 -0
  26. package/dist/backend/services/currency-seed-service.js.map +1 -0
  27. package/dist/backend/services/currency-service.d.ts +94 -0
  28. package/dist/backend/services/currency-service.d.ts.map +1 -0
  29. package/dist/backend/services/currency-service.js +269 -0
  30. package/dist/backend/services/currency-service.js.map +1 -0
  31. package/dist/manifest.d.ts +169 -0
  32. package/dist/manifest.d.ts.map +1 -0
  33. package/dist/manifest.js +83 -0
  34. package/dist/manifest.js.map +1 -0
  35. package/docs/currencies.md +59 -0
  36. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ <!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
2
+
3
+ # @endora-commerce/mod-currencies
4
+
5
+ Currency reference data and per-channel currency configuration.
6
+
7
+ ## What this is
8
+
9
+ An **Endora Commerce module package**. Its module id is `currencies` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
10
+
11
+ A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
12
+
13
+ ## Entry points
14
+
15
+ | Import | Contents |
16
+ | --- | --- |
17
+ | `@endora-commerce/mod-currencies` | the module manifest — its id, version, dependencies, settings and activation |
18
+ | `@endora-commerce/mod-currencies/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
19
+
20
+ ## Depends on
21
+
22
+ Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
23
+
24
+ **Endora packages**
25
+
26
+ - `@endora-commerce/contracts`
27
+ - `@endora-commerce/platform`
28
+
29
+ **Third-party**
30
+
31
+ - `@mikro-orm/core` ^6
32
+ - `@mikro-orm/postgresql` ^6
33
+ - `fastify` ^5
34
+
35
+ ## What the tarball carries
36
+
37
+ - `dist/` — the compiled JavaScript and its type declarations
38
+ - `docs/` — the package’s own pages in the Endora Commerce documentation site
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pnpm add @endora-commerce/mod-currencies
44
+ ```
45
+
46
+ ## Licence
47
+
48
+ MIT — the text is in `LICENSE`, beside this file.
@@ -0,0 +1,22 @@
1
+ import { OptionalProps } from '@mikro-orm/core';
2
+ /**
3
+ * Currency — ISO 4217 codes the platform accepts (T238 / FR-105).
4
+ * Exactly zero or one row has `isDefault=true`, enforced by a partial
5
+ * unique index in the migration.
6
+ */
7
+ export declare class Currency {
8
+ [OptionalProps]?: 'createdAt' | 'updatedAt' | 'isDefault' | 'isActive' | 'sortOrder' | 'symbolPosition' | 'decimalPlaces';
9
+ code: string;
10
+ label: string;
11
+ symbol: string;
12
+ /** Where the symbol appears relative to the amount. Extended by feature 017. */
13
+ symbolPosition: 'prefix' | 'suffix';
14
+ /** Currency's typical fractional precision (0 for JPY, 2 for most, 3 for BHD). */
15
+ decimalPlaces: number;
16
+ isDefault: boolean;
17
+ isActive: boolean;
18
+ sortOrder: number;
19
+ createdAt: Date;
20
+ updatedAt: Date;
21
+ }
22
+ //# sourceMappingURL=currency.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/currency.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAG9E;;;;GAIG;AACH,qBAEa,QAAQ;IACnB,CAAC,aAAa,CAAC,CAAC,EACZ,WAAW,GACX,WAAW,GACX,WAAW,GACX,UAAU,GACV,WAAW,GACX,gBAAgB,GAChB,eAAe,CAAC;IAGpB,IAAI,EAAG,MAAM,CAAC;IAGd,KAAK,EAAG,MAAM,CAAC;IAGf,MAAM,EAAG,MAAM,CAAC;IAEhB,gFAAgF;IAEhF,cAAc,EAAE,QAAQ,GAAG,QAAQ,CAAY;IAE/C,kFAAkF;IAElF,aAAa,EAAE,MAAM,CAAK;IAG1B,SAAS,EAAE,OAAO,CAAS;IAG3B,QAAQ,EAAE,OAAO,CAAQ;IAGzB,SAAS,EAAE,MAAM,CAAK;IAGtB,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
@@ -0,0 +1,77 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ /**
13
+ * Currency — ISO 4217 codes the platform accepts (T238 / FR-105).
14
+ * Exactly zero or one row has `isDefault=true`, enforced by a partial
15
+ * unique index in the migration.
16
+ */
17
+ let Currency = class Currency {
18
+ [OptionalProps];
19
+ code;
20
+ label;
21
+ symbol;
22
+ /** Where the symbol appears relative to the amount. Extended by feature 017. */
23
+ symbolPosition = 'suffix';
24
+ /** Currency's typical fractional precision (0 for JPY, 2 for most, 3 for BHD). */
25
+ decimalPlaces = 2;
26
+ isDefault = false;
27
+ isActive = true;
28
+ sortOrder = 0;
29
+ createdAt = new Date();
30
+ updatedAt = new Date();
31
+ };
32
+ __decorate([
33
+ PrimaryKey({ type: 'string', length: 3 }),
34
+ __metadata("design:type", String)
35
+ ], Currency.prototype, "code", void 0);
36
+ __decorate([
37
+ Property({ type: 'string', length: 64 }),
38
+ __metadata("design:type", String)
39
+ ], Currency.prototype, "label", void 0);
40
+ __decorate([
41
+ Property({ type: 'string', length: 8 }),
42
+ __metadata("design:type", String)
43
+ ], Currency.prototype, "symbol", void 0);
44
+ __decorate([
45
+ Property({ type: 'string', length: 8 }),
46
+ __metadata("design:type", String)
47
+ ], Currency.prototype, "symbolPosition", void 0);
48
+ __decorate([
49
+ Property({ type: 'smallint' }),
50
+ __metadata("design:type", Number)
51
+ ], Currency.prototype, "decimalPlaces", void 0);
52
+ __decorate([
53
+ Property({ type: 'boolean' }),
54
+ __metadata("design:type", Boolean)
55
+ ], Currency.prototype, "isDefault", void 0);
56
+ __decorate([
57
+ Property({ type: 'boolean' }),
58
+ __metadata("design:type", Boolean)
59
+ ], Currency.prototype, "isActive", void 0);
60
+ __decorate([
61
+ Property({ type: 'integer' }),
62
+ __metadata("design:type", Number)
63
+ ], Currency.prototype, "sortOrder", void 0);
64
+ __decorate([
65
+ Property({ type: 'datetime', onCreate: () => new Date() }),
66
+ __metadata("design:type", Date)
67
+ ], Currency.prototype, "createdAt", void 0);
68
+ __decorate([
69
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
70
+ __metadata("design:type", Date)
71
+ ], Currency.prototype, "updatedAt", void 0);
72
+ Currency = __decorate([
73
+ GlobalEntity(),
74
+ Entity({ tableName: 'currencies' })
75
+ ], Currency);
76
+ export { Currency };
77
+ //# sourceMappingURL=currency.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/currency.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEjE;;;;GAIG;AAGI,IAAM,QAAQ,GAAd,MAAM,QAAQ;IACnB,CAAC,aAAa,CAAC,CAOK;IAGpB,IAAI,CAAU;IAGd,KAAK,CAAU;IAGf,MAAM,CAAU;IAEhB,gFAAgF;IAEhF,cAAc,GAAwB,QAAQ,CAAC;IAE/C,kFAAkF;IAElF,aAAa,GAAW,CAAC,CAAC;IAG1B,SAAS,GAAY,KAAK,CAAC;IAG3B,QAAQ,GAAY,IAAI,CAAC;IAGzB,SAAS,GAAW,CAAC,CAAC;IAGtB,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AA9BC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;;sCAC5B;AAGd;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;uCAC1B;AAGf;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;;wCACxB;AAIhB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;;gDACO;AAI/C;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;;+CACL;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;2CACH;AAG3B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;0CACL;AAGzB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;2CACR;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;2CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;2CAAc;AAxClB,QAAQ;IAFpB,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;GACvB,QAAQ,CAyCpB"}
@@ -0,0 +1,53 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type DictionaryReferenceRegistryPort } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import { type ModuleContext, type RequireAdminFactory } from '@endora-commerce/platform/kernel';
5
+ import { CurrencyService } from './services/currency-service.js';
6
+ import { Currency } from './entities/currency.entity.js';
7
+ /**
8
+ * `currencies` — one service, where there were two (feature 072, wave 1).
9
+ *
10
+ * The module used to own no routes: `dictionaries` served
11
+ * `/api/v1/admin/dictionary/currencies/*` and `languages` served
12
+ * `/api/v1/admin/currencies/*`. Both write the same table, and **each built its
13
+ * own `CurrencyService` with different constructor arguments** — `dictionaries`
14
+ * passed the dictionary invalidator, `languages` passed `undefined`.
15
+ *
16
+ * That is not a tidiness problem. `CurrencyService.setDefault` and `.remove`
17
+ * call the invalidator, so the same delete performed through the second surface
18
+ * left `DictionaryValidator`'s cache holding a code whose row was gone — and
19
+ * the validator's job is to *accept or reject* codes, so the failure is a stale
20
+ * **accept**: the next write referencing that currency passes validation and
21
+ * lands a dangling reference. `test/integration/currencies/one-currency-service.test.ts`
22
+ * pins it.
23
+ *
24
+ * One registration removes the class of bug rather than this instance of it.
25
+ * The invalidator is read from the cradle at call time, because `dictionaries`
26
+ * builds it inside its own factory and is not converted yet; resolving it
27
+ * lazily is also what a port would do, so nothing here has to change again when
28
+ * `dictionaries` converts.
29
+ */
30
+ export interface CurrenciesCradle {
31
+ readonly emFactory: () => EntityManager;
32
+ readonly requireAdmin: RequireAdminFactory;
33
+ readonly auditLogService: AuditPort;
34
+ readonly eventBus: {
35
+ emit: (event: string, payload: unknown) => void;
36
+ };
37
+ readonly currencyService: CurrencyService;
38
+ readonly currencyReferenceRegistry: DictionaryReferenceRegistryPort;
39
+ }
40
+ export declare function registerModule(ctx: ModuleContext): void;
41
+ /**
42
+ * The module's persisted entity classes, on the `./backend` subpath, as one
43
+ * array and **no named class export** (D-168).
44
+ *
45
+ * This is the shape the platform reads when the package is *installed*: the
46
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
47
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
48
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
49
+ * the package pass of `check-entity-tenant-classification`. A missing array is
50
+ * answered with `[]` — zero entities registered, no error anywhere.
51
+ */
52
+ export declare const entities: (typeof Currency)[];
53
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAKL,KAAK,+BAA+B,EACrC,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAIjE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE;QAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC;IACvE,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,yBAAyB,EAAE,+BAA+B,CAAC;CACrE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAuGvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,qBAEpB,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { CURRENCY_CHANGED_EVENT, } from '@endora-commerce/contracts';
2
+ import { lazyPort, } from '@endora-commerce/platform/kernel';
3
+ import { CurrencyService } from './services/currency-service.js';
4
+ import { CurrencyReadService, createCurrencyAdminPort } from './services/currency-ports.js';
5
+ import { CurrencyReferenceRegistry } from './services/currency-reference-registry.js';
6
+ import { CurrencySeedService } from './services/currency-seed-service.js';
7
+ import { Currency } from './entities/currency.entity.js';
8
+ import { registerCurrencyRoutes } from './routes.js';
9
+ export function registerModule(ctx) {
10
+ ctx.di.register({
11
+ currencyService: ctx
12
+ .asFunction(({ emFactory, auditLogService }) => {
13
+ // Announce, do not call. Whoever caches currency data subscribes and
14
+ // drops its own; this module does not learn who they are, which is
15
+ // what keeps the dependency graph acyclic and the module detachable
16
+ // (Constitution I).
17
+ const announce = async () => {
18
+ ctx.cradle().eventBus.emit(CURRENCY_CHANGED_EVENT, {});
19
+ };
20
+ return new CurrencyService(emFactory, announce, auditLogService, () => ctx.cradle().currencyReferenceRegistry);
21
+ })
22
+ .singleton(),
23
+ });
24
+ // ---------------------------------------------------------------------------
25
+ // Feature 077 (D-87 drain) — who still points at a currency?
26
+ //
27
+ // `CurrencyService.remove` used to answer that with five hand-written counts
28
+ // over `promotions`, `price_lists`, `dictionaries` and the kernel's channels.
29
+ // Three of the four named another module's table in a string, which no
30
+ // import-level boundary check can see. Contributors push a descriptor from
31
+ // their own boot hook now, and this module asks the registry instead.
32
+ //
33
+ // A plain `ctx.di.register`, not a port: a contributor resolves it from a boot
34
+ // hook, and a boot hook that resolved a transient gate would stop the backend
35
+ // from starting the moment this module was switched off. The descriptor is
36
+ // inert; `currencyAdminPort`, which writes currencies, is the port and does
37
+ // fail closed. Enumeration policy is stated at the class.
38
+ ctx.di.register({
39
+ currencyReferenceRegistry: ctx
40
+ .asFunction(() => new CurrencyReferenceRegistry())
41
+ .singleton(),
42
+ });
43
+ /**
44
+ * The insert `dictionaries` needs against this table. Its catalogue exists so
45
+ * that `countries.default_currency_code` is satisfiable on a fresh install;
46
+ * it used to write the 53 rows here as raw SQL from its own reconciler.
47
+ */
48
+ ctx.di.providePort('currencySeedPort', ctx
49
+ .asFunction(({ emFactory }) => new CurrencySeedService(emFactory))
50
+ .singleton());
51
+ // ---------------------------------------------------------------------------
52
+ // Feature 075, Phase P — the published surface.
53
+ //
54
+ // Six of the fifteen inbound sites read the `Currency` **entity** rather than
55
+ // this module's service: `dictionaries` resolving a label or validating a
56
+ // code. These two ports are what they and the service consumers rewire to.
57
+ //
58
+ // `currencyService` above keeps its plain `ctx.di.register`, unchanged,
59
+ // because retiring it is a cut and Phase P cuts nothing.
60
+ // ---------------------------------------------------------------------------
61
+ ctx.di.providePort('currencyReadPort', ctx
62
+ .asFunction(({ emFactory }) => new CurrencyReadService(emFactory))
63
+ .singleton());
64
+ ctx.di.providePort('currencyAdminPort', ctx
65
+ .asFunction(() => createCurrencyAdminPort(() => ctx.cradle().currencyService))
66
+ .singleton());
67
+ /**
68
+ * The admin surface for this module's own table (2026-08-29), moved here from
69
+ * `languages`.
70
+ *
71
+ * `ctx.routes` is what makes the module-presence gate structural: it wraps the
72
+ * whole registration, so every route this module ever adds is covered
73
+ * (Constitution XVII item 1, "never gate per handler"). `languages` had to say
74
+ * in a comment that the currency routes answered 503 when this module was
75
+ * absent, because there the gate was a property of the two ports it happened
76
+ * to resolve rather than of the registration.
77
+ *
78
+ * The two ports are resolved lazily and are this module's own — a
79
+ * self-resolution, so no boundary is crossed and the gates are the ones every
80
+ * other consumer already gets. Destructuring them here instead would run the
81
+ * gate at *registration* time, which `buildServer` performs whatever the
82
+ * module's effective state is, and so would stop the next start rather than
83
+ * the routes (D-40).
84
+ */
85
+ ctx.routes(async (app) => {
86
+ const { requireAdmin } = ctx.cradle();
87
+ await registerCurrencyRoutes(app, {
88
+ currencyRead: lazyPort(ctx, 'currencyReadPort'),
89
+ currencyAdmin: lazyPort(ctx, 'currencyAdminPort'),
90
+ requireAdmin,
91
+ });
92
+ });
93
+ }
94
+ /**
95
+ * The module's persisted entity classes, on the `./backend` subpath, as one
96
+ * array and **no named class export** (D-168).
97
+ *
98
+ * This is the shape the platform reads when the package is *installed*: the
99
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
100
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
101
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
102
+ * the package pass of `check-entity-tenant-classification`. A missing array is
103
+ * answered with `[]` — zero entities registered, no error anywhere.
104
+ */
105
+ export const entities = [
106
+ Currency,
107
+ ];
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type CurrencyAdminPort, type CurrencyReadPort } from '@endora-commerce/contracts';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ /**
5
+ * The currency catalogue's own admin surface (2026-08-29).
6
+ *
7
+ * These four routes were registered by `languages` until this change, over the
8
+ * two ports this module publishes. Nothing about that arrangement was an
9
+ * accident of the screen they serve: the admin currency screen is
10
+ * `dictionaries`' (`/api/v1/admin/dictionary/currencies`), and no caller in
11
+ * this repository ever reached the four routes below. So `languages` hosted
12
+ * another module's write surface for no reason that survives being written
13
+ * down, and it hosted it on `catalog:write` — the code a merchandiser holds to
14
+ * edit a product description, which therefore also promoted a currency to the
15
+ * shop's default.
16
+ *
17
+ * Two things follow from the move and are the point of it. The authority is
18
+ * this module's own pair, `currencies:read` / `currencies:write`. And the
19
+ * module-presence gate is now the **registration** seam (`ctx.routes`), which
20
+ * is what Constitution XVII item 1 asks for — the hand-written "these answer
21
+ * 503 `MODULE_DISABLED` because the ports fail closed" note the old deps block
22
+ * carried was a rule an author had to remember, and it is now structural.
23
+ * `currencies` declares `activation.nonDeactivatable`, so today the gate can
24
+ * never bite; that is exactly why it should not rest on anybody's memory.
25
+ *
26
+ * `GET /api/v1/i18n/config` stays with `languages`: it answers with both
27
+ * catalogues at once and is a public read, so it is composition rather than
28
+ * ownership. `languages` keeps `currencyReadPort` for it and nothing else.
29
+ */
30
+ export interface CurrencyAdminRoutesDeps {
31
+ currencyRead: CurrencyReadPort;
32
+ currencyAdmin: CurrencyAdminPort;
33
+ requireAdmin: RequireAdminFactory;
34
+ }
35
+ export declare function registerCurrencyRoutes(app: FastifyInstance, deps: CurrencyAdminRoutesDeps): Promise<void>;
36
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAEtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,iBAAiB,CAAC;IACjC,YAAY,EAAE,mBAAmB,CAAC;CACnC;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAgDf"}
@@ -0,0 +1,43 @@
1
+ import { upsertCurrencyRequestSchema, } from '@endora-commerce/contracts';
2
+ export async function registerCurrencyRoutes(app, deps) {
3
+ const { currencyRead, currencyAdmin, requireAdmin } = deps;
4
+ app.get('/api/v1/admin/currencies', { preHandler: requireAdmin('currencies:read') }, async () => {
5
+ const rows = await currencyRead.list();
6
+ return { data: rows.map(serializeCurrency) };
7
+ });
8
+ app.put('/api/v1/admin/currencies/:code', {
9
+ preHandler: requireAdmin('currencies:write'),
10
+ schema: { body: upsertCurrencyRequestSchema },
11
+ }, async (request) => {
12
+ const body = upsertCurrencyRequestSchema.parse(request.body);
13
+ const row = await currencyAdmin.upsert({
14
+ code: request.params.code,
15
+ label: body.label,
16
+ symbol: body.symbol,
17
+ ...(body.isActive !== undefined ? { isActive: body.isActive } : {}),
18
+ ...(body.sortOrder !== undefined ? { sortOrder: body.sortOrder } : {}),
19
+ });
20
+ return { data: serializeCurrency(row) };
21
+ });
22
+ app.post('/api/v1/admin/currencies/:code/default', { preHandler: requireAdmin('currencies:write') }, async (request) => {
23
+ const row = await currencyAdmin.setDefault(request.params.code);
24
+ return { data: serializeCurrency(row) };
25
+ });
26
+ app.delete('/api/v1/admin/currencies/:code', { preHandler: requireAdmin('currencies:write') }, async (request, reply) => {
27
+ await currencyAdmin.remove(request.params.code);
28
+ return reply.status(204).send();
29
+ });
30
+ }
31
+ function serializeCurrency(c) {
32
+ return {
33
+ code: c.code,
34
+ label: c.label,
35
+ symbol: c.symbol,
36
+ isDefault: c.isDefault,
37
+ isActive: c.isActive,
38
+ sortOrder: c.sortOrder,
39
+ createdAt: c.createdAt.toISOString(),
40
+ updatedAt: c.updatedAt.toISOString(),
41
+ };
42
+ }
43
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AACA,OAAO,EACL,2BAA2B,GAI5B,MAAM,4BAA4B,CAAC;AAmCpC,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAoB,EACpB,IAA6B;IAE7B,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE3D,GAAG,CAAC,GAAG,CACL,0BAA0B,EAC1B,EAAE,UAAU,EAAE,YAAY,CAAC,iBAAiB,CAAC,EAAE,EAC/C,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,GAAG,CACL,gCAAgC,EAChC;QACE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC;QAC5C,MAAM,EAAE,EAAE,IAAI,EAAE,2BAA2B,EAAE;KAC9C,EACD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,IAAI,GAAG,2BAA2B,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC;YACrC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1C,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,IAAI,CACN,wCAAwC,EACxC,EAAE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChE,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1C,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,MAAM,CACR,gCAAgC,EAChC,EAAE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAiB;IAC1C,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;QACpC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { CurrencyAdminPort, CurrencyReadPort, CurrencyRecord } from '@endora-commerce/contracts';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import { Currency } from '../entities/currency.entity.js';
4
+ import type { CurrencyService } from './currency-service.js';
5
+ /**
6
+ * The published face of `currencies` (feature 075, Phase P).
7
+ *
8
+ * The delivery-side twin of `languages`' ports, and the same split: six of the
9
+ * fifteen inbound sites read the `Currency` **entity** — `dictionaries`
10
+ * resolving a label or validating a code — and the rest type themselves
11
+ * against `CurrencyService`.
12
+ */
13
+ export declare class CurrencyReadService implements CurrencyReadPort {
14
+ private readonly emFactory;
15
+ constructor(emFactory: () => EntityManager);
16
+ list(): Promise<CurrencyRecord[]>;
17
+ listActive(): Promise<CurrencyRecord[]>;
18
+ getDefault(): Promise<CurrencyRecord | null>;
19
+ findByCode(code: string): Promise<CurrencyRecord | null>;
20
+ }
21
+ export declare function createCurrencyAdminPort(getService: () => CurrencyService): CurrencyAdminPort;
22
+ export declare function toCurrencyRecord(currency: Currency): CurrencyRecord;
23
+ //# sourceMappingURL=currency-ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency-ports.d.ts","sourceRoot":"","sources":["../../../src/backend/services/currency-ports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,gBAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IASjC,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IASvC,UAAU,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAK5C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAI/D;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,eAAe,GAAG,iBAAiB,CAgB5F;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,cAAc,CAanE"}
@@ -0,0 +1,63 @@
1
+ import { Currency } from '../entities/currency.entity.js';
2
+ /**
3
+ * The published face of `currencies` (feature 075, Phase P).
4
+ *
5
+ * The delivery-side twin of `languages`' ports, and the same split: six of the
6
+ * fifteen inbound sites read the `Currency` **entity** — `dictionaries`
7
+ * resolving a label or validating a code — and the rest type themselves
8
+ * against `CurrencyService`.
9
+ */
10
+ export class CurrencyReadService {
11
+ emFactory;
12
+ constructor(emFactory) {
13
+ this.emFactory = emFactory;
14
+ }
15
+ async list() {
16
+ const rows = await this.emFactory().find(Currency, {}, { orderBy: { sortOrder: 'asc', code: 'asc' } });
17
+ return rows.map(toCurrencyRecord);
18
+ }
19
+ async listActive() {
20
+ const rows = await this.emFactory().find(Currency, { isActive: true }, { orderBy: { sortOrder: 'asc', code: 'asc' } });
21
+ return rows.map(toCurrencyRecord);
22
+ }
23
+ async getDefault() {
24
+ const row = await this.emFactory().findOne(Currency, { isDefault: true });
25
+ return row ? toCurrencyRecord(row) : null;
26
+ }
27
+ async findByCode(code) {
28
+ const row = await this.emFactory().findOne(Currency, { code });
29
+ return row ? toCurrencyRecord(row) : null;
30
+ }
31
+ }
32
+ export function createCurrencyAdminPort(getService) {
33
+ return {
34
+ async create(input) {
35
+ return toCurrencyRecord(await getService().create(input));
36
+ },
37
+ async upsert(input) {
38
+ return toCurrencyRecord(await getService().upsert(input));
39
+ },
40
+ async update(code, input) {
41
+ return toCurrencyRecord(await getService().update(code, input));
42
+ },
43
+ async setDefault(code) {
44
+ return toCurrencyRecord(await getService().setDefault(code));
45
+ },
46
+ remove: (code) => getService().remove(code),
47
+ };
48
+ }
49
+ export function toCurrencyRecord(currency) {
50
+ return {
51
+ code: currency.code,
52
+ label: currency.label,
53
+ symbol: currency.symbol,
54
+ symbolPosition: currency.symbolPosition,
55
+ decimalPlaces: currency.decimalPlaces,
56
+ isDefault: currency.isDefault,
57
+ isActive: currency.isActive,
58
+ sortOrder: currency.sortOrder,
59
+ createdAt: currency.createdAt,
60
+ updatedAt: currency.updatedAt,
61
+ };
62
+ }
63
+ //# sourceMappingURL=currency-ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency-ports.js","sourceRoot":"","sources":["../../../src/backend/services/currency-ports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { DictionaryReference, DictionaryReferenceDescriptor, DictionaryReferenceRegistryPort } from '@endora-commerce/contracts';
2
+ /**
3
+ * In-process registry of "who still points at this currency code" descriptors
4
+ * (feature 077, D-87 drain).
5
+ *
6
+ * Registered under `currencyReferenceRegistry` and contributed to from other modules'
7
+ * boot hooks. This module consults it before deleting a currency, and
8
+ * `dictionaries` consults it to build the orphan report.
9
+ *
10
+ * **Enumeration policy: honoured while the contributing module is absent.**
11
+ * D-39's default is to skip, and honouring needs a written reason: this is
12
+ * referential integrity, not a surface. A switched-off module still owns the
13
+ * rows that carry the code, so skipping its descriptor would let an operator
14
+ * delete a currency that comes back as a dangling reference the moment the module
15
+ * is switched on again — data loss caused by an action Constitution XVII
16
+ * promises is non-destructive and reversible.
17
+ *
18
+ * `ownerModuleId` is recorded on every descriptor even though this registry
19
+ * does not filter on it, because the alternative is a registry that could not
20
+ * express the decision either way, and because it attributes a 409 to a module.
21
+ */
22
+ export declare class CurrencyReferenceRegistry implements DictionaryReferenceRegistryPort {
23
+ private readonly descriptors;
24
+ register(descriptor: DictionaryReferenceDescriptor): void;
25
+ /** The contributing module of every registered descriptor, in registration order. */
26
+ owners(): readonly string[];
27
+ countReferences(code: string): Promise<DictionaryReference[]>;
28
+ usedCodes(): Promise<DictionaryReference[]>;
29
+ }
30
+ //# sourceMappingURL=currency-reference-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency-reference-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/currency-reference-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,6BAA6B,EAC7B,+BAA+B,EAChC,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,yBAA0B,YAAW,+BAA+B;IAC/E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuC;IAEnE,QAAQ,CAAC,UAAU,EAAE,6BAA6B,GAAG,IAAI;IAMzD,qFAAqF;IACrF,MAAM,IAAI,SAAS,MAAM,EAAE;IAIrB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAS7D,SAAS,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;CAQlD"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * In-process registry of "who still points at this currency code" descriptors
3
+ * (feature 077, D-87 drain).
4
+ *
5
+ * Registered under `currencyReferenceRegistry` and contributed to from other modules'
6
+ * boot hooks. This module consults it before deleting a currency, and
7
+ * `dictionaries` consults it to build the orphan report.
8
+ *
9
+ * **Enumeration policy: honoured while the contributing module is absent.**
10
+ * D-39's default is to skip, and honouring needs a written reason: this is
11
+ * referential integrity, not a surface. A switched-off module still owns the
12
+ * rows that carry the code, so skipping its descriptor would let an operator
13
+ * delete a currency that comes back as a dangling reference the moment the module
14
+ * is switched on again — data loss caused by an action Constitution XVII
15
+ * promises is non-destructive and reversible.
16
+ *
17
+ * `ownerModuleId` is recorded on every descriptor even though this registry
18
+ * does not filter on it, because the alternative is a registry that could not
19
+ * express the decision either way, and because it attributes a 409 to a module.
20
+ */
21
+ export class CurrencyReferenceRegistry {
22
+ descriptors = [];
23
+ register(descriptor) {
24
+ if (!this.descriptors.includes(descriptor)) {
25
+ this.descriptors.push(descriptor);
26
+ }
27
+ }
28
+ /** The contributing module of every registered descriptor, in registration order. */
29
+ owners() {
30
+ return this.descriptors.map((d) => d.ownerModuleId);
31
+ }
32
+ async countReferences(code) {
33
+ const counts = await Promise.all(this.descriptors.map(async (d) => ({ descriptor: d, count: await d.countReferences(code) })));
34
+ return counts
35
+ .filter((row) => row.count > 0)
36
+ .map(({ descriptor, count }) => describe(descriptor, code, count));
37
+ }
38
+ async usedCodes() {
39
+ const perDescriptor = await Promise.all(this.descriptors.map(async (d) => ({ descriptor: d, rows: await d.usedCodes() })));
40
+ return perDescriptor.flatMap(({ descriptor, rows }) => rows.map((row) => describe(descriptor, row.code, row.count)));
41
+ }
42
+ }
43
+ function describe(descriptor, code, count) {
44
+ return {
45
+ ownerModuleId: descriptor.ownerModuleId,
46
+ consumer: descriptor.consumer,
47
+ tableName: descriptor.tableName,
48
+ columnName: descriptor.columnName,
49
+ code,
50
+ count,
51
+ blocking: descriptor.blocking,
52
+ };
53
+ }
54
+ //# sourceMappingURL=currency-reference-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currency-reference-registry.js","sourceRoot":"","sources":["../../../src/backend/services/currency-reference-registry.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,yBAAyB;IACnB,WAAW,GAAoC,EAAE,CAAC;IAEnE,QAAQ,CAAC,UAAyC;QAChD,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,qFAAqF;IACrF,MAAM;QACJ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,IAAY;QAChC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAC7F,CAAC;QACF,OAAO,MAAM;aACV,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC;aAC9B,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAClF,CAAC;QACF,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CACpD,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAC7D,CAAC;IACJ,CAAC;CACF;AAED,SAAS,QAAQ,CACf,UAAyC,EACzC,IAAY,EACZ,KAAa;IAEb,OAAO;QACL,aAAa,EAAE,UAAU,CAAC,aAAa;QACvC,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,SAAS,EAAE,UAAU,CAAC,SAAS;QAC/B,UAAU,EAAE,UAAU,CAAC,UAAU;QACjC,IAAI;QACJ,KAAK;QACL,QAAQ,EAAE,UAAU,CAAC,QAAQ;KAC9B,CAAC;AACJ,CAAC"}