@endora-commerce/mod-addresses 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 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-addresses
4
+
5
+ Reusable address entities used by organizations, customers, and order fulfilment.
6
+
7
+ ## What this is
8
+
9
+ An **Endora Commerce module package**. Its module id is `addresses` — 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-addresses` | the module manifest — its id, version, dependencies, settings and activation |
18
+ | `@endora-commerce/mod-addresses/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
+
34
+ ## What the tarball carries
35
+
36
+ - `dist/` — the compiled JavaScript and its type declarations
37
+ - `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
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-addresses
44
+ ```
45
+
46
+ ## Licence
47
+
48
+ MIT — the text is in `LICENSE`, beside this file.
@@ -0,0 +1,23 @@
1
+ import { OptionalProps } from '@mikro-orm/core';
2
+ /**
3
+ * Address — a delivery or billing address belonging to an Organization.
4
+ * The partial unique index "one default per (organization, kind)" is enforced
5
+ * in the migration, not at the ORM layer.
6
+ */
7
+ export declare class Address {
8
+ [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'isDefault' | 'phone' | 'deletedAt';
9
+ id: string;
10
+ organizationId: string;
11
+ kind: 'delivery' | 'billing';
12
+ recipientName: string;
13
+ street: string;
14
+ city: string;
15
+ postalCode: string;
16
+ country: string;
17
+ phone?: string | null;
18
+ isDefault: boolean;
19
+ createdAt: Date;
20
+ updatedAt: Date;
21
+ deletedAt?: Date | null;
22
+ }
23
+ //# sourceMappingURL=address.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/address.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;;;GAIG;AACH,qBAEa,OAAO;IAClB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,WAAW,GACX,OAAO,GACP,WAAW,CAAC;IAGhB,EAAE,EAAE,MAAM,CAAgB;IAI1B,cAAc,EAAG,MAAM,CAAC;IAIxB,IAAI,EAAG,UAAU,GAAG,SAAS,CAAC;IAG9B,aAAa,EAAG,MAAM,CAAC;IAGvB,MAAM,EAAG,MAAM,CAAC;IAGhB,IAAI,EAAG,MAAM,CAAC;IAGd,UAAU,EAAG,MAAM,CAAC;IAGpB,OAAO,EAAG,MAAM,CAAC;IAGjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGtB,SAAS,EAAE,OAAO,CAAS;IAG3B,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACzB"}
@@ -0,0 +1,93 @@
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, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
11
+ import { OrgScoped } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * Address — a delivery or billing address belonging to an Organization.
15
+ * The partial unique index "one default per (organization, kind)" is enforced
16
+ * in the migration, not at the ORM layer.
17
+ */
18
+ let Address = class Address {
19
+ [OptionalProps];
20
+ id = randomUUID();
21
+ organizationId;
22
+ kind;
23
+ recipientName;
24
+ street;
25
+ city;
26
+ postalCode;
27
+ country;
28
+ phone;
29
+ isDefault = false;
30
+ createdAt = new Date();
31
+ updatedAt = new Date();
32
+ deletedAt;
33
+ };
34
+ __decorate([
35
+ PrimaryKey({ type: 'uuid' }),
36
+ __metadata("design:type", String)
37
+ ], Address.prototype, "id", void 0);
38
+ __decorate([
39
+ Property({ type: 'uuid' }),
40
+ Index(),
41
+ __metadata("design:type", String)
42
+ ], Address.prototype, "organizationId", void 0);
43
+ __decorate([
44
+ Property({ type: 'string', length: 16 }),
45
+ Index(),
46
+ __metadata("design:type", String)
47
+ ], Address.prototype, "kind", void 0);
48
+ __decorate([
49
+ Property({ type: 'string', length: 160 }),
50
+ __metadata("design:type", String)
51
+ ], Address.prototype, "recipientName", void 0);
52
+ __decorate([
53
+ Property({ type: 'string', length: 255 }),
54
+ __metadata("design:type", String)
55
+ ], Address.prototype, "street", void 0);
56
+ __decorate([
57
+ Property({ type: 'string', length: 120 }),
58
+ __metadata("design:type", String)
59
+ ], Address.prototype, "city", void 0);
60
+ __decorate([
61
+ Property({ type: 'string', length: 20 }),
62
+ __metadata("design:type", String)
63
+ ], Address.prototype, "postalCode", void 0);
64
+ __decorate([
65
+ Property({ type: 'string', length: 2 }),
66
+ __metadata("design:type", String)
67
+ ], Address.prototype, "country", void 0);
68
+ __decorate([
69
+ Property({ type: 'string', length: 32, nullable: true }),
70
+ __metadata("design:type", Object)
71
+ ], Address.prototype, "phone", void 0);
72
+ __decorate([
73
+ Property({ type: 'boolean' }),
74
+ __metadata("design:type", Boolean)
75
+ ], Address.prototype, "isDefault", void 0);
76
+ __decorate([
77
+ Property({ type: 'datetime', onCreate: () => new Date() }),
78
+ __metadata("design:type", Date)
79
+ ], Address.prototype, "createdAt", void 0);
80
+ __decorate([
81
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
82
+ __metadata("design:type", Date)
83
+ ], Address.prototype, "updatedAt", void 0);
84
+ __decorate([
85
+ Property({ type: 'datetime', nullable: true }),
86
+ __metadata("design:type", Object)
87
+ ], Address.prototype, "deletedAt", void 0);
88
+ Address = __decorate([
89
+ OrgScoped(),
90
+ Entity({ tableName: 'addresses' })
91
+ ], Address);
92
+ export { Address };
93
+ //# sourceMappingURL=address.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/address.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;GAIG;AAGI,IAAM,OAAO,GAAb,MAAM,OAAO;IAClB,CAAC,aAAa,CAAC,CAMC;IAGhB,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,cAAc,CAAU;IAIxB,IAAI,CAA0B;IAG9B,aAAa,CAAU;IAGvB,MAAM,CAAU;IAGhB,IAAI,CAAU;IAGd,UAAU,CAAU;IAGpB,OAAO,CAAU;IAGjB,KAAK,CAAiB;IAGtB,SAAS,GAAY,KAAK,CAAC;IAG3B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,CAAe;CACzB,CAAA;AAvCC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;mCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;+CACgB;AAIxB;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,KAAK,EAAE;;qCACsB;AAG9B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;8CACnB;AAGvB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;uCAC1B;AAGhB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;qCAC5B;AAGd;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;2CACrB;AAGpB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;;wCACvB;AAGjB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;sCACnC;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;0CACH;AAG3B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;0CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;0CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACvB;AAhDb,OAAO;IAFnB,SAAS,EAAE;IACX,MAAM,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;GACtB,OAAO,CAiDnB"}
@@ -0,0 +1,58 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { DictionaryValidator } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
5
+ import { AddressService } from './services/address-service.js';
6
+ import { Address } from './entities/address.entity.js';
7
+ /**
8
+ * `addresses` — one service, where there were three (feature 072, wave 1,
9
+ * T090).
10
+ *
11
+ * The module owns no routes: `organizations`, `customers`, `orders` and
12
+ * `quick_order` each serve their own address surface over the same table. Three
13
+ * of them built their own `AddressService`, and the constructor takes its
14
+ * dictionary validator and its audit writer as **optional** arguments — so the
15
+ * three instances were not required to agree, and one of them did not:
16
+ *
17
+ * orders/plugin.ts: options.addressService ?? new AddressService(options.emFactory)
18
+ *
19
+ * Both roots pass `addressService`, so that branch is dead today. It is a
20
+ * *loaded* dead branch, though, not an inert one: anything constructing
21
+ * `commerceModule` without the option gets an address service that validates no
22
+ * country or region code and writes no audit entry, on the checkout path.
23
+ * `currencies` shipped this exact shape in wave 1 and there it was live, which
24
+ * is the argument for removing the shape rather than the instance.
25
+ *
26
+ * A **port**, because four other modules resolve it: an address write reaching
27
+ * a module the operator switched off should get an explicit 503 rather than a
28
+ * service that half-answers.
29
+ *
30
+ * `dictionaryValidator` goes through `lazyPort`, and the reason changed under
31
+ * it. It used to be read once in the factory body — `const validator =
32
+ * ctx.cradle<…>().dictionaryValidator` — which looks deferred and is not: the
33
+ * factory body runs when the registration is first constructed. That was
34
+ * harmless while `dictionaries` registered a plain value, and stopped being
35
+ * harmless the moment that module converted and made it a port, because a
36
+ * singleton may not hold a transient gate. `lazyPort` defers it per call, which
37
+ * is what the comment always claimed was happening.
38
+ */
39
+ export interface AddressesCradle {
40
+ readonly emFactory: () => EntityManager;
41
+ readonly auditLogService: AuditPort;
42
+ readonly dictionaryValidator: DictionaryValidator | undefined;
43
+ readonly addressService: AddressService;
44
+ }
45
+ export declare function registerModule(ctx: ModuleContext): void;
46
+ /**
47
+ * The module's persisted entity classes, on the `./backend` subpath, as one
48
+ * array and **no named class export** (D-168).
49
+ *
50
+ * This is the shape the platform reads when the package is *installed*: the
51
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
52
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
53
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
54
+ * the package pass of `check-entity-tenant-classification`. A missing array is
55
+ * answered with `[]` — zero entities registered, no error anywhere.
56
+ */
57
+ export declare const entities: (typeof Address)[];
58
+ //# 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,KAAK,EAAE,mBAAmB,EAEhC,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAG/D,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA2DvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,oBAEpB,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { AddressService } from './services/address-service.js';
3
+ import { AddressReadService, createAddressServicePort } from './services/address-ports.js';
4
+ import { registerAddressCountryReferences } from './services/address-country-reference.js';
5
+ import { Address } from './entities/address.entity.js';
6
+ export function registerModule(ctx) {
7
+ // ---------------------------------------------------------------------------
8
+ // Feature 075, Phase P — the published surface.
9
+ //
10
+ // `addressService` below hands out the class; four of the ten inbound sites
11
+ // do not want a service at all, they read the `Address` entity to snapshot
12
+ // one onto an order, serialise one, or resolve a buyer's default. These two
13
+ // ports are what both halves rewire to, and every read on the first is
14
+ // scoped by organisation — three of the four direct readers apply that check
15
+ // in a second statement today, in a tenancy-critical table.
16
+ // ---------------------------------------------------------------------------
17
+ ctx.di.providePort('addressReadPort', ctx
18
+ .asFunction(({ emFactory }) => new AddressReadService(emFactory))
19
+ .singleton());
20
+ ctx.di.providePort('addressServicePort', ctx
21
+ .asFunction(() => createAddressServicePort(() => ctx.cradle().addressService))
22
+ .singleton());
23
+ ctx.di.providePort('addressService', ctx
24
+ .asFunction(({ emFactory, auditLogService }) => new AddressService(emFactory, lazyPort(ctx, 'dictionaryValidator'), auditLogService))
25
+ .singleton());
26
+ /**
27
+ * This module's rows carry a country code, so it answers "who still points at
28
+ * this country?" about its own tables (feature 077, D-87), where the owner used
29
+ * to count them with SQL naming this module's tables.
30
+ *
31
+ * A **contribution** hook: it pushes an inert descriptor into `countryReferenceRegistry`,
32
+ * an ungated registry, and carries no presence probe (D-62/D-68). Probing
33
+ * would be wrong in the dangerous direction — a switched-off module still owns
34
+ * the rows, so its country must still refuse the delete, which is the
35
+ * enumeration policy the registry states.
36
+ */
37
+ ctx.onBoot(() => {
38
+ registerAddressCountryReferences(lazyPort(ctx, 'countryReferenceRegistry'), ctx.cradle().emFactory);
39
+ });
40
+ }
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 const entities = [
53
+ Address,
54
+ ];
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC3F,OAAO,EAAE,gCAAgC,EAAE,MAAM,yCAAyC,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AA0CvD,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,8EAA8E;IAC9E,gDAAgD;IAChD,EAAE;IACF,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,uEAAuE;IACvE,6EAA6E;IAC7E,4DAA4D;IAC5D,8EAA8E;IAE9E,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,iBAAiB,EACjB,GAAG;SACA,UAAU,CAAC,CAAC,EAAE,SAAS,EAAmB,EAAE,EAAE,CAAC,IAAI,kBAAkB,CAAC,SAAS,CAAC,CAAC;SACjF,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,oBAAoB,EACpB,GAAG;SACA,UAAU,CAAC,GAAG,EAAE,CACf,wBAAwB,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAmB,CAAC,cAAc,CAAC,CAC7E;SACA,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,gBAAgB,EAChB,GAAG;SACA,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,eAAe,EAAmB,EAAE,EAAE,CAClD,IAAI,cAAc,CAChB,SAAS,EACT,QAAQ,CAAsB,GAAG,EAAE,qBAAqB,CAAC,EACzD,eAAe,CAChB,CACJ;SACA,SAAS,EAAE,CACf,CAAC;IAEF;;;;;;;;;;OAUG;IACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,gCAAgC,CAC9B,QAAQ,CAAkC,GAAG,EAAE,0BAA0B,CAAC,EAC1E,GAAG,CAAC,MAAM,EAAmB,CAAC,SAAS,CACxC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,OAAO;CACR,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { DictionaryReferenceRegistryPort } from '@endora-commerce/contracts';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ /**
4
+ * Who in `addresses` still points at a country (feature 077, D-87).
5
+ *
6
+ * `dictionaries` used to count this table itself and join it a second time
7
+ * for the orphan report, naming it in strings no import-level boundary check
8
+ * can see. An address with a country the platform does not have cannot be
9
+ * validated or rendered, so the reference blocks the delete.
10
+ */
11
+ export declare function registerAddressCountryReferences(registry: DictionaryReferenceRegistryPort, emFactory: () => EntityManager): void;
12
+ //# sourceMappingURL=address-country-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-country-reference.d.ts","sourceRoot":"","sources":["../../../src/backend/services/address-country-reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,+BAA+B,EACzC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAoBN"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Who in `addresses` still points at a country (feature 077, D-87).
3
+ *
4
+ * `dictionaries` used to count this table itself and join it a second time
5
+ * for the orphan report, naming it in strings no import-level boundary check
6
+ * can see. An address with a country the platform does not have cannot be
7
+ * validated or rendered, so the reference blocks the delete.
8
+ */
9
+ export function registerAddressCountryReferences(registry, emFactory) {
10
+ registry.register({
11
+ ownerModuleId: 'addresses',
12
+ consumer: 'addresses',
13
+ tableName: 'addresses',
14
+ columnName: 'country',
15
+ blocking: true,
16
+ countReferences: async (code) => {
17
+ const rows = (await emFactory().execute(`select count(*)::int as n from "addresses" where "country" = ?`, [code]));
18
+ return rows[0]?.n ?? 0;
19
+ },
20
+ usedCodes: async () => (await emFactory().execute(`select "country" as code, count(*)::int as count
21
+ from "addresses" where "country" is not null group by "country"`)),
22
+ });
23
+ }
24
+ //# sourceMappingURL=address-country-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-country-reference.js","sourceRoot":"","sources":["../../../src/backend/services/address-country-reference.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,UAAU,gCAAgC,CAC9C,QAAyC,EACzC,SAA8B;IAE9B,QAAQ,CAAC,QAAQ,CAAC;QAChB,aAAa,EAAE,WAAW;QAC1B,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,WAAW;QACtB,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACrC,gEAAgE,EAChE,CAAC,IAAI,CAAC,CACP,CAAyB,CAAC;YAC3B,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QACD,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACxB;2EACmE,CACpE,CAA2C;KAC/C,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AddressKindValue, AddressReadPort, AddressRecord, AddressServicePort } from '@endora-commerce/contracts';
3
+ import { Address } from '../entities/address.entity.js';
4
+ import type { AddressService } from './address-service.js';
5
+ /**
6
+ * The published face of `addresses` (feature 075, Phase P).
7
+ *
8
+ * Ten inbound sites, six-four: `orders` and `organizations` type themselves
9
+ * against `AddressService`, and four read the `Address` entity directly —
10
+ * `orders` snapshotting one onto a placed order, `customers` serialising one,
11
+ * `quick_order` resolving a buyer's default.
12
+ *
13
+ * Every read here is scoped by organisation. That is not defensiveness: the
14
+ * four direct readers each already know whose address they are asking for, and
15
+ * three of them apply the check in a second statement — which is a check that
16
+ * can be forgotten, and in a tenancy-critical table.
17
+ */
18
+ export declare class AddressReadService implements AddressReadPort {
19
+ private readonly emFactory;
20
+ constructor(emFactory: () => EntityManager);
21
+ findById(organizationId: string, addressId: string, options?: {
22
+ liveOnly?: boolean;
23
+ }): Promise<AddressRecord | null>;
24
+ findByIds(organizationId: string, addressIds: readonly string[], options?: {
25
+ liveOnly?: boolean;
26
+ }): Promise<AddressRecord[]>;
27
+ listForOrganization(organizationId: string, kind?: AddressKindValue): Promise<AddressRecord[]>;
28
+ findDefault(organizationId: string, kind: AddressKindValue): Promise<AddressRecord | null>;
29
+ }
30
+ /**
31
+ * The write side, adapting `AddressService`. The service arrives as a getter
32
+ * so the adapter resolves it per call rather than capturing this module's own
33
+ * gated port into a singleton.
34
+ */
35
+ export declare function createAddressServicePort(getService: () => AddressService): AddressServicePort;
36
+ export declare function toAddressRecord(address: Address): AddressRecord;
37
+ //# sourceMappingURL=address-ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-ports.d.ts","sourceRoot":"","sources":["../../../src/backend/services/address-ports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAS1B,SAAS,CACb,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,aAAa,EAAE,CAAC;IAUrB,mBAAmB,CACvB,cAAc,EAAE,MAAM,EACtB,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,aAAa,EAAE,CAAC;IASrB,WAAW,CACf,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CASjC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,cAAc,GAAG,kBAAkB,CAc7F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAgB/D"}
@@ -0,0 +1,88 @@
1
+ import { Address } from '../entities/address.entity.js';
2
+ /**
3
+ * The published face of `addresses` (feature 075, Phase P).
4
+ *
5
+ * Ten inbound sites, six-four: `orders` and `organizations` type themselves
6
+ * against `AddressService`, and four read the `Address` entity directly —
7
+ * `orders` snapshotting one onto a placed order, `customers` serialising one,
8
+ * `quick_order` resolving a buyer's default.
9
+ *
10
+ * Every read here is scoped by organisation. That is not defensiveness: the
11
+ * four direct readers each already know whose address they are asking for, and
12
+ * three of them apply the check in a second statement — which is a check that
13
+ * can be forgotten, and in a tenancy-critical table.
14
+ */
15
+ export class AddressReadService {
16
+ emFactory;
17
+ constructor(emFactory) {
18
+ this.emFactory = emFactory;
19
+ }
20
+ async findById(organizationId, addressId, options) {
21
+ const address = await this.emFactory().findOne(Address, {
22
+ id: addressId,
23
+ organizationId,
24
+ ...(options?.liveOnly ? { deletedAt: null } : {}),
25
+ });
26
+ return address ? toAddressRecord(address) : null;
27
+ }
28
+ async findByIds(organizationId, addressIds, options) {
29
+ if (addressIds.length === 0)
30
+ return [];
31
+ const addresses = await this.emFactory().find(Address, {
32
+ id: { $in: [...addressIds] },
33
+ organizationId,
34
+ ...(options?.liveOnly ? { deletedAt: null } : {}),
35
+ });
36
+ return addresses.map(toAddressRecord);
37
+ }
38
+ async listForOrganization(organizationId, kind) {
39
+ const addresses = await this.emFactory().find(Address, { organizationId, deletedAt: null, ...(kind === undefined ? {} : { kind }) }, { orderBy: { isDefault: 'desc', createdAt: 'asc' } });
40
+ return addresses.map(toAddressRecord);
41
+ }
42
+ async findDefault(organizationId, kind) {
43
+ const address = await this.emFactory().findOne(Address, {
44
+ organizationId,
45
+ kind,
46
+ isDefault: true,
47
+ deletedAt: null,
48
+ });
49
+ return address ? toAddressRecord(address) : null;
50
+ }
51
+ }
52
+ /**
53
+ * The write side, adapting `AddressService`. The service arrives as a getter
54
+ * so the adapter resolves it per call rather than capturing this module's own
55
+ * gated port into a singleton.
56
+ */
57
+ export function createAddressServicePort(getService) {
58
+ return {
59
+ async list(organizationId, kind) {
60
+ return (await getService().list(organizationId, kind)).map(toAddressRecord);
61
+ },
62
+ async createAddress(organizationId, input) {
63
+ return toAddressRecord(await getService().createAddress(organizationId, input));
64
+ },
65
+ async updateAddress(organizationId, addressId, patch) {
66
+ return toAddressRecord(await getService().updateAddress(organizationId, addressId, patch));
67
+ },
68
+ deleteAddress: (organizationId, addressId) => getService().deleteAddress(organizationId, addressId),
69
+ };
70
+ }
71
+ export function toAddressRecord(address) {
72
+ return {
73
+ id: address.id,
74
+ organizationId: address.organizationId,
75
+ kind: address.kind,
76
+ recipientName: address.recipientName,
77
+ street: address.street,
78
+ city: address.city,
79
+ postalCode: address.postalCode,
80
+ country: address.country,
81
+ phone: address.phone ?? null,
82
+ isDefault: address.isDefault,
83
+ createdAt: address.createdAt,
84
+ updatedAt: address.updatedAt,
85
+ deletedAt: address.deletedAt ?? null,
86
+ };
87
+ }
88
+ //# sourceMappingURL=address-ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-ports.js","sourceRoot":"","sources":["../../../src/backend/services/address-ports.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAGxD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,kBAAkB;IACA;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CACZ,cAAsB,EACtB,SAAiB,EACjB,OAAgC;QAEhC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;YACtD,EAAE,EAAE,SAAS;YACb,cAAc;YACd,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,SAAS,CACb,cAAsB,EACtB,UAA6B,EAC7B,OAAgC;QAEhC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE;YACrD,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE;YAC5B,cAAc;YACd,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAC,CAAC;QACH,OAAO,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,cAAsB,EACtB,IAAuB;QAEvB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAC3C,OAAO,EACP,EAAE,cAAc,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAC5E,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,CACrD,CAAC;QACF,OAAO,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,WAAW,CACf,cAAsB,EACtB,IAAsB;QAEtB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;YACtD,cAAc;YACd,IAAI;YACJ,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAgC;IACvE,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI;YAC7B,OAAO,CAAC,MAAM,UAAU,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC9E,CAAC;QACD,KAAK,CAAC,aAAa,CAAC,cAAc,EAAE,KAAK;YACvC,OAAO,eAAe,CAAC,MAAM,UAAU,EAAE,CAAC,aAAa,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC;QAClF,CAAC;QACD,KAAK,CAAC,aAAa,CAAC,cAAc,EAAE,SAAS,EAAE,KAAK;YAClD,OAAO,eAAe,CAAC,MAAM,UAAU,EAAE,CAAC,aAAa,CAAC,cAAc,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;QAC7F,CAAC;QACD,aAAa,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,CAC3C,UAAU,EAAE,CAAC,aAAa,CAAC,cAAc,EAAE,SAAS,CAAC;KACxD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type DictionaryValidator } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import { Address } from '../entities/address.entity.js';
5
+ /**
6
+ * AddressService (T120).
7
+ *
8
+ * `setDefault` is atomic — it clears any prior `is_default=true` address of the
9
+ * same (organization, kind), then sets the new default, inside one transaction.
10
+ * The partial unique index on addresses prevents two defaults from coexisting
11
+ * even under concurrent callers.
12
+ */
13
+ export declare class AddressService {
14
+ #private;
15
+ private readonly emFactory;
16
+ private readonly dictionaryValidator?;
17
+ private readonly auditLog?;
18
+ constructor(emFactory: () => EntityManager, dictionaryValidator?: DictionaryValidator | undefined, auditLog?: AuditPort | undefined);
19
+ list(organizationId: string, kind?: 'delivery' | 'billing'): Promise<Address[]>;
20
+ createAddress(organizationId: string, input: {
21
+ kind: 'delivery' | 'billing';
22
+ recipientName: string;
23
+ street: string;
24
+ city: string;
25
+ postalCode: string;
26
+ country: string;
27
+ phone?: string;
28
+ isDefault?: boolean;
29
+ }): Promise<Address>;
30
+ updateAddress(organizationId: string, addressId: string, patch: Partial<{
31
+ recipientName: string;
32
+ street: string;
33
+ city: string;
34
+ postalCode: string;
35
+ country: string;
36
+ phone: string;
37
+ isDefault: boolean;
38
+ }>): Promise<Address>;
39
+ deleteAddress(organizationId: string, addressId: string): Promise<void>;
40
+ private validateCountry;
41
+ }
42
+ //# sourceMappingURL=address-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/address-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD;;;;;;;GAOG;AACH,qBAAa,cAAc;;IAEvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAFT,SAAS,EAAE,MAAM,aAAa,EAC9B,mBAAmB,CAAC,EAAE,mBAAmB,YAAA,EACzC,QAAQ,CAAC,EAAE,SAAS,YAAA;IASjC,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAO/E,aAAa,CACjB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE;QACL,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;QAC7B,aAAa,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GACA,OAAO,CAAC,OAAO,CAAC;IA8Bb,aAAa,CACjB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC,GACD,OAAO,CAAC,OAAO,CAAC;IAiCb,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAc/D,eAAe;CAqB9B"}
@@ -0,0 +1,120 @@
1
+ import { DictionaryReferenceError, ERROR_CODES, dispatchValidatorMode, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
4
+ import { Address } from '../entities/address.entity.js';
5
+ /**
6
+ * AddressService (T120).
7
+ *
8
+ * `setDefault` is atomic — it clears any prior `is_default=true` address of the
9
+ * same (organization, kind), then sets the new default, inside one transaction.
10
+ * The partial unique index on addresses prevents two defaults from coexisting
11
+ * even under concurrent callers.
12
+ */
13
+ export class AddressService {
14
+ emFactory;
15
+ dictionaryValidator;
16
+ auditLog;
17
+ constructor(emFactory, dictionaryValidator, auditLog) {
18
+ this.emFactory = emFactory;
19
+ this.dictionaryValidator = dictionaryValidator;
20
+ this.auditLog = auditLog;
21
+ }
22
+ #audit(em, action, objectId, stateBefore, stateAfter) {
23
+ if (this.auditLog) {
24
+ recordAuditFromContext(this.auditLog, em, { action, objectType: 'address', objectId, stateBefore, stateAfter });
25
+ }
26
+ }
27
+ async list(organizationId, kind) {
28
+ const em = this.emFactory();
29
+ const where = { organizationId, deletedAt: null };
30
+ if (kind)
31
+ where['kind'] = kind;
32
+ return em.find(Address, where, { orderBy: { isDefault: 'desc', createdAt: 'asc' } });
33
+ }
34
+ async createAddress(organizationId, input) {
35
+ await this.validateCountry(input.country, 'create-or-change');
36
+ const em = this.emFactory();
37
+ return em.transactional(async (txEm) => {
38
+ if (input.isDefault) {
39
+ // Demote any existing default of the same kind.
40
+ await txEm.nativeUpdate(Address, { organizationId, kind: input.kind, isDefault: true, deletedAt: null }, { isDefault: false });
41
+ }
42
+ const address = txEm.create(Address, {
43
+ organizationId,
44
+ kind: input.kind,
45
+ recipientName: input.recipientName,
46
+ street: input.street,
47
+ city: input.city,
48
+ postalCode: input.postalCode,
49
+ country: input.country.toUpperCase(),
50
+ ...(input.phone ? { phone: input.phone } : {}),
51
+ isDefault: input.isDefault ?? false,
52
+ });
53
+ txEm.persist(address);
54
+ this.#audit(txEm, 'address.create', address.id, null, { organizationId, kind: address.kind });
55
+ await txEm.flush();
56
+ return address;
57
+ });
58
+ }
59
+ async updateAddress(organizationId, addressId, patch) {
60
+ const em = this.emFactory();
61
+ return em.transactional(async (txEm) => {
62
+ const address = await txEm.findOne(Address, { id: addressId, organizationId, deletedAt: null });
63
+ if (!address) {
64
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Address not found.');
65
+ }
66
+ if (patch.isDefault === true && !address.isDefault) {
67
+ await txEm.nativeUpdate(Address, { organizationId, kind: address.kind, isDefault: true, deletedAt: null }, { isDefault: false });
68
+ }
69
+ if (patch.country !== undefined) {
70
+ await this.validateCountry(patch.country, dispatchValidatorMode(address.country, patch.country));
71
+ }
72
+ if (patch.recipientName !== undefined)
73
+ address.recipientName = patch.recipientName;
74
+ if (patch.street !== undefined)
75
+ address.street = patch.street;
76
+ if (patch.city !== undefined)
77
+ address.city = patch.city;
78
+ if (patch.postalCode !== undefined)
79
+ address.postalCode = patch.postalCode;
80
+ if (patch.country !== undefined)
81
+ address.country = patch.country.toUpperCase();
82
+ if (patch.phone !== undefined)
83
+ address.phone = patch.phone;
84
+ if (patch.isDefault !== undefined)
85
+ address.isDefault = patch.isDefault;
86
+ this.#audit(txEm, 'address.update', address.id, null, { organizationId, kind: address.kind });
87
+ await txEm.flush();
88
+ return address;
89
+ });
90
+ }
91
+ async deleteAddress(organizationId, addressId) {
92
+ const em = this.emFactory();
93
+ const address = await em.findOne(Address, { id: addressId, organizationId, deletedAt: null });
94
+ if (!address) {
95
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Address not found.');
96
+ }
97
+ // ADDRESS_IN_USE check lives with the orders module (US2 T142) — the Order
98
+ // entity there references addresses via snapshot, so a true "in-use" block
99
+ // must query the Orders table. For now we soft-delete.
100
+ address.deletedAt = new Date();
101
+ this.#audit(em, 'address.delete', address.id, { organizationId, kind: address.kind }, null);
102
+ await em.flush();
103
+ }
104
+ async validateCountry(country, mode) {
105
+ if (!this.dictionaryValidator)
106
+ return;
107
+ try {
108
+ await this.dictionaryValidator.validateCountryCode(country, mode);
109
+ }
110
+ catch (err) {
111
+ if (err instanceof DictionaryReferenceError) {
112
+ throw new HttpError(409, err.code, err.code === 'DICTIONARY_ENTRY_INACTIVE'
113
+ ? `Country code ${err.entryCode} is no longer available for new entries.`
114
+ : `Country code ${err.entryCode} is not recognised.`, [{ path: 'country', issue: err.code }]);
115
+ }
116
+ throw err;
117
+ }
118
+ }
119
+ }
120
+ //# sourceMappingURL=address-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-service.js","sourceRoot":"","sources":["../../../src/backend/services/address-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Addresses module — manifest backfill (Module Lifecycle, feature 018).
3
+ *
4
+ * Predates the lifecycle system; this manifest is the static record
5
+ * required so the module participates in the registry. No install /
6
+ * uninstall hook today — the module's schema is owned by earlier
7
+ * platform-wide migrations.
8
+ */
9
+ export declare const manifest: {
10
+ id: string;
11
+ name: string;
12
+ version: string;
13
+ dependencies: string[];
14
+ description?: string | undefined;
15
+ acknowledgedDependencies?: {
16
+ moduleId: string;
17
+ port: string;
18
+ reason: string;
19
+ }[] | undefined;
20
+ nonBindingDependencies?: {
21
+ moduleId: string;
22
+ name: string;
23
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
24
+ reason: string;
25
+ whenAbsent?: string | undefined;
26
+ }[] | undefined;
27
+ activation?: {
28
+ settingCode: string;
29
+ default: boolean;
30
+ } | {
31
+ nonDeactivatable: true;
32
+ reason: string;
33
+ } | undefined;
34
+ settings?: {
35
+ moduleCode: string;
36
+ groups: {
37
+ code: string;
38
+ name: string;
39
+ salesChannelCodes?: string[] | undefined;
40
+ isSystemProtected?: boolean | undefined;
41
+ }[];
42
+ settings: {
43
+ code: string;
44
+ name: string;
45
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
46
+ defaultValue: unknown;
47
+ description?: string | undefined;
48
+ groupCode?: string | undefined;
49
+ previousDefaultValues?: unknown[] | undefined;
50
+ salesChannelCodes?: string[] | undefined;
51
+ enumOptions?: string[] | undefined;
52
+ configurationType?: string | undefined;
53
+ hidden?: boolean | undefined;
54
+ }[];
55
+ } | undefined;
56
+ i18n?: {
57
+ bundlesDir: string;
58
+ } | undefined;
59
+ docs?: false | {
60
+ dir: string;
61
+ } | undefined;
62
+ demo?: false | {
63
+ summary: string;
64
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
65
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
66
+ after?: readonly string[] | undefined;
67
+ package?: string | undefined;
68
+ } | undefined;
69
+ actions?: {
70
+ id: string;
71
+ labelKey: string;
72
+ icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
73
+ targetRoute: string;
74
+ keywords: string[];
75
+ weight: number;
76
+ descriptionKey?: string | undefined;
77
+ requiredPermission?: string | undefined;
78
+ }[] | undefined;
79
+ permissions?: {
80
+ code: string;
81
+ label: string;
82
+ module?: string | undefined;
83
+ description?: string | undefined;
84
+ requires?: string[] | undefined;
85
+ }[] | undefined;
86
+ transactionalEmails?: {
87
+ code: string;
88
+ name: string;
89
+ variables: {
90
+ key: string;
91
+ label: string;
92
+ sampleValue?: string | undefined;
93
+ description?: string | undefined;
94
+ }[];
95
+ description?: string | undefined;
96
+ group?: string | undefined;
97
+ }[] | undefined;
98
+ capabilities?: string[] | undefined;
99
+ exclusiveCapabilities?: {
100
+ key: string;
101
+ errorCode: string;
102
+ }[] | undefined;
103
+ errorCodes?: {
104
+ code: string;
105
+ tokens?: string[] | undefined;
106
+ }[] | undefined;
107
+ blocks?: {
108
+ name: string;
109
+ labelKey: string;
110
+ category: string;
111
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
112
+ fields: Record<string, {
113
+ type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
114
+ label?: string | undefined;
115
+ required?: boolean | undefined;
116
+ options?: {
117
+ label: string;
118
+ value: string | number;
119
+ }[] | undefined;
120
+ refKind?: string | undefined;
121
+ }>;
122
+ descriptionKey?: string | undefined;
123
+ defaultProps?: Record<string, unknown> | undefined;
124
+ responsiveFields?: string[] | undefined;
125
+ previewIcon?: string | undefined;
126
+ weight?: number | undefined;
127
+ }[] | undefined;
128
+ blockCategories?: {
129
+ key: string;
130
+ titleKey: string;
131
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
132
+ weight?: number | undefined;
133
+ visible?: boolean | undefined;
134
+ }[] | undefined;
135
+ env?: {
136
+ name: string;
137
+ describes: {
138
+ en: string;
139
+ pl: string;
140
+ };
141
+ requirement: {
142
+ kind: "required";
143
+ } | {
144
+ kind: "requiredWhen";
145
+ input: string;
146
+ equals: string;
147
+ } | {
148
+ kind: "optional";
149
+ without: {
150
+ en: string;
151
+ pl: string;
152
+ };
153
+ };
154
+ secret: boolean;
155
+ generable: boolean;
156
+ owner: {
157
+ kind: "platform";
158
+ } | {
159
+ kind: "application";
160
+ application: "admin" | "backend" | "storefront";
161
+ } | {
162
+ kind: "module";
163
+ moduleId: string;
164
+ };
165
+ consumers: ("admin" | "backend" | "storefront")[];
166
+ addressOf: "admin" | "backend" | "storefront" | null;
167
+ }[] | undefined;
168
+ };
169
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFnB,CAAC"}
@@ -0,0 +1,90 @@
1
+ import { defineModuleManifest } from '@endora-commerce/contracts';
2
+ /**
3
+ * Addresses module — manifest backfill (Module Lifecycle, feature 018).
4
+ *
5
+ * Predates the lifecycle system; this manifest is the static record
6
+ * required so the module participates in the registry. No install /
7
+ * uninstall hook today — the module's schema is owned by earlier
8
+ * platform-wide migrations.
9
+ */
10
+ export const manifest = defineModuleManifest({
11
+ id: 'addresses',
12
+ name: 'Addresses',
13
+ description: 'Reusable address entities used by organizations, customers, and order fulfilment.',
14
+ version: '1.0.0',
15
+ // `addresses.organization_id` foreign-keys `organizations`. The edge was
16
+ // found by the FK-drift validator (feature 065) and is genuinely declarable:
17
+ // quick_order → addresses → organizations → settings stays acyclic.
18
+ //
19
+ // `dictionaries` added with the conversion (feature 072, T090): the one
20
+ // `AddressService` resolves `dictionaryValidator` to check country and region
21
+ // codes, so the edge is real and was previously invisible to the lifecycle.
22
+ dependencies: ['dictionaries', 'organizations'],
23
+ // Feature 074 (Constitution XVII), test C2 — functional base. The flag was
24
+ // previously carried by a port edge: `organizations` resolves this module's
25
+ // `addressService`, and that reached this manifest through the other one's
26
+ // declaration. Ruling 2 withdraws that authority — a module a criterion
27
+ // module happens to resolve is not thereby core — so the ground here is now
28
+ // this module's own. Every B2B document is addressed: an order ships
29
+ // somewhere, an invoice is billed somewhere, a shipment has a destination.
30
+ // A platform that cannot record where is not a smaller commerce platform,
31
+ // it is one that cannot complete a transaction.
32
+ activation: {
33
+ nonDeactivatable: true,
34
+ reason: 'Every B2B document — order, invoice, shipment — is addressed; there is no transaction ' +
35
+ 'without one.',
36
+ },
37
+ /**
38
+ * This module's first i18n bundle — D-129's remaining sweep, MR 5.
39
+ *
40
+ * It exists because a **declaring** module that contributes no bundle file is
41
+ * `check:error-translations` exit 2 for the whole tree rather than a finding
42
+ * (`specs/090-module-owned-error-codes/contracts/error-translation-population.md`
43
+ * §2.1, §2.3; `d129-sweep.md` §3.4). The bundle lives at the **package
44
+ * root**, not under `dist`: `manifest-locations.ts` resolves a packaged
45
+ * module's `manifestPath` to its `package.json`, so `dirname` is the package
46
+ * directory. The files are a **flat** `{"a.b.c": "text"}` map, because a
47
+ * nested object fails `TranslationBundleEntriesSchema` and the boot
48
+ * reconciler logs and skips it — silently.
49
+ */
50
+ i18n: { bundlesDir: 'i18n' },
51
+ docs: { dir: 'docs' },
52
+ /**
53
+ * The two `ADDRESS_*` codes — D-129's remaining sweep, Tier B
54
+ * (`specs/090-module-owned-error-codes/d129-sweep.md` §5.2, Appendix A;
55
+ * MR 5).
56
+ *
57
+ * Both were declared by `_i18n` until this merge request, not because anybody
58
+ * judged them the platform's but because the deleted prefix chain had no rule
59
+ * for them and its last line was `return 'core'`. **D-121 T1 puts them
60
+ * here**: the noun in both is an address, which is this module's entity, and
61
+ * both refusals are about that row — who owns it, and whether anything still
62
+ * points at it.
63
+ *
64
+ * `ADDRESS_NOT_OWNED` is the sweep's cleanest disagreement between the noun
65
+ * and the raise-site count: `orders` is the only module that raises it, and it
66
+ * does so having asked this module's read port for the address and got
67
+ * nothing back. That is the caller reporting the absence of the owner's row —
68
+ * D-95.2's `INVOICE_NOT_READY` shape — and T1 does not ask who throws.
69
+ * `ADDRESS_IN_USE` is raised by nothing at all, which T1 also does not ask
70
+ * about (T10).
71
+ *
72
+ * **One of the two arrives with a sentence.** Each carried a placeholder in
73
+ * `_i18n`'s bundle — `"Address Not Owned."` / `"Błąd: address not owned."` —
74
+ * which D-186 §2 deletes rather than carries, because in this module's own
75
+ * bundle it would read as this module's answer. §5.4 keeps writing the prose
76
+ * available and is not a re-opening of that ruling, so the decision was taken
77
+ * per code: `ADDRESS_NOT_OWNED` is written, because a buyer meets it at
78
+ * checkout and the refusal has one remedy to offer; `ADDRESS_IN_USE` is
79
+ * deleted and joins `UNTRANSLATED_ERROR_CODES` under this module, because
80
+ * with no raise site there is no refusal to describe and a sentence could
81
+ * only be invented from the code's own name.
82
+ *
83
+ * **`tokens` is derived from the raise sites, not from the bundle**
84
+ * (runbook §5), and there are none: the one raise is a bare
85
+ * `HttpError(403, code, message)` with no `details` argument, measured by
86
+ * balanced-paren extraction of the call's own arguments.
87
+ */
88
+ errorCodes: [{ code: 'ADDRESS_IN_USE' }, { code: 'ADDRESS_NOT_OWNED' }],
89
+ });
90
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;IAC3C,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,WAAW,EACT,mFAAmF;IACrF,OAAO,EAAE,OAAO;IAChB,yEAAyE;IACzE,6EAA6E;IAC7E,oEAAoE;IACpE,EAAE;IACF,wEAAwE;IACxE,8EAA8E;IAC9E,4EAA4E;IAC5E,YAAY,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC;IAC/C,2EAA2E;IAC3E,4EAA4E;IAC5E,2EAA2E;IAC3E,wEAAwE;IACxE,4EAA4E;IAC5E,qEAAqE;IACrE,2EAA2E;IAC3E,0EAA0E;IAC1E,gDAAgD;IAChD,UAAU,EAAE;QACV,gBAAgB,EAAE,IAAI;QACtB,MAAM,EACJ,wFAAwF;YACxF,cAAc;KACjB;IACD;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;IAC5B,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC;CACxE,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: addresses
3
+ description: Customer postal addresses with default-per-kind invariant
4
+ ---
5
+
6
+ # `addresses`
7
+
8
+ Customer postal addresses scoped to an Organization. Each address has a
9
+ `kind` (`shipping`, `billing`, `delivery`) and a per-kind default flag.
10
+
11
+ ## Public surface
12
+
13
+ | Verb + Path | Purpose |
14
+ | --- | --- |
15
+ | `GET /api/v1/organizations/mine/addresses` | List own Organization's addresses |
16
+ | `POST /api/v1/organizations/mine/addresses` | Create; `isDefault=true` atomically demotes the previous default of the same kind |
17
+ | `PATCH /api/v1/organizations/mine/addresses/:id` | Update |
18
+ | `DELETE /api/v1/organizations/mine/addresses/:id` | Delete (rejected if referenced by an order via `409 ADDRESS_IN_USE`) |
19
+
20
+ ## Entities
21
+
22
+ `Address` — partial unique index `(organization_id, kind) WHERE
23
+ is_default = true` enforces "at most one default per kind" at the DB level.
24
+
25
+ ## Extension points
26
+
27
+ - **Country-specific validation** — `address-service.ts#createAddress`
28
+ delegates per-country format checks; add validators here when new markets
29
+ go live.
30
+ - **Snapshot on order** — Orders embed an `AddressSnapshot` value object so
31
+ later edits to the source address don't mutate historical orders.
package/i18n/en.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "errors.ADDRESS_NOT_OWNED": "That address does not belong to your organization. Choose one of your organization's saved addresses, or add a new one."
3
+ }
package/i18n/pl.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "errors.ADDRESS_NOT_OWNED": "Ten adres nie należy do Twojej organizacji. Wybierz jeden z zapisanych adresów organizacji albo dodaj nowy."
3
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@endora-commerce/mod-addresses",
3
+ "version": "0.100.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "Reusable address entities used by organizations, customers, and order fulfilment.",
7
+ "license": "MIT",
8
+ "endora": {
9
+ "type": "module",
10
+ "id": "addresses"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/endora-commerce/endora-commerce.git",
15
+ "directory": "packages/modules/addresses"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/manifest.d.ts",
23
+ "default": "./dist/manifest.js"
24
+ },
25
+ "./backend": {
26
+ "types": "./dist/backend/index.d.ts",
27
+ "default": "./dist/backend/index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "i18n",
34
+ "docs"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22.18.0"
38
+ },
39
+ "peerDependencies": {
40
+ "@mikro-orm/core": "^6",
41
+ "@mikro-orm/postgresql": "^6",
42
+ "@endora-commerce/contracts": "0.100.0",
43
+ "@endora-commerce/platform": "0.100.0"
44
+ },
45
+ "devDependencies": {
46
+ "@mikro-orm/core": "^6.6.13",
47
+ "@mikro-orm/postgresql": "^6.6.13",
48
+ "@types/node": "^22.9.0",
49
+ "typescript": "^5.9.3",
50
+ "vitest": "^4.1.11",
51
+ "@endora-commerce/contracts": "0.100.0",
52
+ "@endora-commerce/platform": "0.100.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.build.json",
56
+ "typecheck": "tsc -p tsconfig.json",
57
+ "lint": "eslint src",
58
+ "test": "vitest run"
59
+ }
60
+ }