@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.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/backend/entities/currency.entity.d.ts +22 -0
- package/dist/backend/entities/currency.entity.d.ts.map +1 -0
- package/dist/backend/entities/currency.entity.js +77 -0
- package/dist/backend/entities/currency.entity.js.map +1 -0
- package/dist/backend/index.d.ts +53 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +108 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.d.ts +36 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +43 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/currency-ports.d.ts +23 -0
- package/dist/backend/services/currency-ports.d.ts.map +1 -0
- package/dist/backend/services/currency-ports.js +63 -0
- package/dist/backend/services/currency-ports.js.map +1 -0
- package/dist/backend/services/currency-reference-registry.d.ts +30 -0
- package/dist/backend/services/currency-reference-registry.d.ts.map +1 -0
- package/dist/backend/services/currency-reference-registry.js +54 -0
- package/dist/backend/services/currency-reference-registry.js.map +1 -0
- package/dist/backend/services/currency-seed-service.d.ts +21 -0
- package/dist/backend/services/currency-seed-service.d.ts.map +1 -0
- package/dist/backend/services/currency-seed-service.js +41 -0
- package/dist/backend/services/currency-seed-service.js.map +1 -0
- package/dist/backend/services/currency-service.d.ts +94 -0
- package/dist/backend/services/currency-service.d.ts.map +1 -0
- package/dist/backend/services/currency-service.js +269 -0
- package/dist/backend/services/currency-service.js.map +1 -0
- package/dist/manifest.d.ts +169 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +83 -0
- package/dist/manifest.js.map +1 -0
- package/docs/currencies.md +59 -0
- 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"}
|