@endora-commerce/mod-delivery-methods 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/delivery-methods-client.d.ts +41 -0
  4. package/dist/admin/api/delivery-methods-client.d.ts.map +1 -0
  5. package/dist/admin/api/delivery-methods-client.js +20 -0
  6. package/dist/admin/api/delivery-methods-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +34 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +44 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/DeliveryMethodsPage.d.ts +4 -0
  12. package/dist/admin/pages/DeliveryMethodsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/DeliveryMethodsPage.js +167 -0
  14. package/dist/admin/pages/DeliveryMethodsPage.js.map +1 -0
  15. package/dist/admin/renderers/registry.d.ts +15 -0
  16. package/dist/admin/renderers/registry.d.ts.map +1 -0
  17. package/dist/admin/renderers/registry.js +18 -0
  18. package/dist/admin/renderers/registry.js.map +1 -0
  19. package/dist/backend/adapters/built-in-adapters.d.ts +46 -0
  20. package/dist/backend/adapters/built-in-adapters.d.ts.map +1 -0
  21. package/dist/backend/adapters/built-in-adapters.js +62 -0
  22. package/dist/backend/adapters/built-in-adapters.js.map +1 -0
  23. package/dist/backend/commands/delivery-method.commands.d.ts +61 -0
  24. package/dist/backend/commands/delivery-method.commands.d.ts.map +1 -0
  25. package/dist/backend/commands/delivery-method.commands.js +107 -0
  26. package/dist/backend/commands/delivery-method.commands.js.map +1 -0
  27. package/dist/backend/demo/reset.d.ts +12 -0
  28. package/dist/backend/demo/reset.d.ts.map +1 -0
  29. package/dist/backend/demo/reset.js +13 -0
  30. package/dist/backend/demo/reset.js.map +1 -0
  31. package/dist/backend/demo/rows.d.ts +24 -0
  32. package/dist/backend/demo/rows.d.ts.map +1 -0
  33. package/dist/backend/demo/rows.js +22 -0
  34. package/dist/backend/demo/rows.js.map +1 -0
  35. package/dist/backend/demo/seed.d.ts +14 -0
  36. package/dist/backend/demo/seed.d.ts.map +1 -0
  37. package/dist/backend/demo/seed.js +28 -0
  38. package/dist/backend/demo/seed.js.map +1 -0
  39. package/dist/backend/entities/delivery-method.entity.d.ts +30 -0
  40. package/dist/backend/entities/delivery-method.entity.d.ts.map +1 -0
  41. package/dist/backend/entities/delivery-method.entity.js +91 -0
  42. package/dist/backend/entities/delivery-method.entity.js.map +1 -0
  43. package/dist/backend/index.d.ts +88 -0
  44. package/dist/backend/index.d.ts.map +1 -0
  45. package/dist/backend/index.js +144 -0
  46. package/dist/backend/index.js.map +1 -0
  47. package/dist/backend/routes.d.ts +51 -0
  48. package/dist/backend/routes.d.ts.map +1 -0
  49. package/dist/backend/routes.js +152 -0
  50. package/dist/backend/routes.js.map +1 -0
  51. package/dist/backend/services/delivery-method-read-port.d.ts +25 -0
  52. package/dist/backend/services/delivery-method-read-port.d.ts.map +1 -0
  53. package/dist/backend/services/delivery-method-read-port.js +56 -0
  54. package/dist/backend/services/delivery-method-read-port.js.map +1 -0
  55. package/dist/backend/services/delivery-method-reconciler.d.ts +97 -0
  56. package/dist/backend/services/delivery-method-reconciler.d.ts.map +1 -0
  57. package/dist/backend/services/delivery-method-reconciler.js +157 -0
  58. package/dist/backend/services/delivery-method-reconciler.js.map +1 -0
  59. package/dist/backend/services/order-status-registry.port.d.ts +50 -0
  60. package/dist/backend/services/order-status-registry.port.d.ts.map +1 -0
  61. package/dist/backend/services/order-status-registry.port.js +45 -0
  62. package/dist/backend/services/order-status-registry.port.js.map +1 -0
  63. package/dist/backend/services/registry-singleton.d.ts +20 -0
  64. package/dist/backend/services/registry-singleton.d.ts.map +1 -0
  65. package/dist/backend/services/registry-singleton.js +21 -0
  66. package/dist/backend/services/registry-singleton.js.map +1 -0
  67. package/dist/backend/services/shipment-usage-guard.d.ts +31 -0
  68. package/dist/backend/services/shipment-usage-guard.d.ts.map +1 -0
  69. package/dist/backend/services/shipment-usage-guard.js +33 -0
  70. package/dist/backend/services/shipment-usage-guard.js.map +1 -0
  71. package/dist/backend/services/shipping-adapter-registry.d.ts +98 -0
  72. package/dist/backend/services/shipping-adapter-registry.d.ts.map +1 -0
  73. package/dist/backend/services/shipping-adapter-registry.js +92 -0
  74. package/dist/backend/services/shipping-adapter-registry.js.map +1 -0
  75. package/dist/backend/services/shipping-method-eligibility.d.ts +24 -0
  76. package/dist/backend/services/shipping-method-eligibility.d.ts.map +1 -0
  77. package/dist/backend/services/shipping-method-eligibility.js +46 -0
  78. package/dist/backend/services/shipping-method-eligibility.js.map +1 -0
  79. package/dist/install/index.d.ts +59 -0
  80. package/dist/install/index.d.ts.map +1 -0
  81. package/dist/install/index.js +59 -0
  82. package/dist/install/index.js.map +1 -0
  83. package/dist/manifest.d.ts +193 -0
  84. package/dist/manifest.d.ts.map +1 -0
  85. package/dist/manifest.js +185 -0
  86. package/dist/manifest.js.map +1 -0
  87. package/dist/migrations/20260611T140354_delivery_methods_shipping_methods_adapter_and_shipments.d.ts +30 -0
  88. package/dist/migrations/20260611T140354_delivery_methods_shipping_methods_adapter_and_shipments.d.ts.map +1 -0
  89. package/dist/migrations/20260611T140354_delivery_methods_shipping_methods_adapter_and_shipments.js +72 -0
  90. package/dist/migrations/20260611T140354_delivery_methods_shipping_methods_adapter_and_shipments.js.map +1 -0
  91. package/dist/migrations/20260611T140416_delivery_methods_fix_in_person_pickup_adapter.d.ts +24 -0
  92. package/dist/migrations/20260611T140416_delivery_methods_fix_in_person_pickup_adapter.d.ts.map +1 -0
  93. package/dist/migrations/20260611T140416_delivery_methods_fix_in_person_pickup_adapter.js +28 -0
  94. package/dist/migrations/20260611T140416_delivery_methods_fix_in_person_pickup_adapter.js.map +1 -0
  95. package/dist/migrations/20260912T094638_delivery_methods_sales_channel_delivery_methods.d.ts +27 -0
  96. package/dist/migrations/20260912T094638_delivery_methods_sales_channel_delivery_methods.d.ts.map +1 -0
  97. package/dist/migrations/20260912T094638_delivery_methods_sales_channel_delivery_methods.js +44 -0
  98. package/dist/migrations/20260912T094638_delivery_methods_sales_channel_delivery_methods.js.map +1 -0
  99. package/dist/migrations/index.d.ts +29 -0
  100. package/dist/migrations/index.d.ts.map +1 -0
  101. package/dist/migrations/index.js +33 -0
  102. package/dist/migrations/index.js.map +1 -0
  103. package/dist/ports/index.d.ts +129 -0
  104. package/dist/ports/index.d.ts.map +1 -0
  105. package/dist/ports/index.js +2 -0
  106. package/dist/ports/index.js.map +1 -0
  107. package/docs/delivery_methods.md +197 -0
  108. package/i18n/en.json +5 -0
  109. package/i18n/pl.json +5 -0
  110. package/package.json +100 -0
  111. package/tailwind.css +14 -0
@@ -0,0 +1,144 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { effectiveState } from '@endora-commerce/platform/kernel';
3
+ import { builtInShippingAdapters } from './adapters/built-in-adapters.js';
4
+ import { registerDeliveryMethodsAdminRoutes, registerDeliveryMethodsPublicRoutes, } from './routes.js';
5
+ import { EnumOrderStatusRegistry } from './services/order-status-registry.port.js';
6
+ import { shippingAdapterRegistry } from './services/registry-singleton.js';
7
+ import { DeliveryMethodReadService } from './services/delivery-method-read-port.js';
8
+ import { ShippingMethodEligibilityService } from './services/shipping-method-eligibility.js';
9
+ import { makeShipmentUsageCounter } from './services/shipment-usage-guard.js';
10
+ import { DeliveryMethod } from './entities/delivery-method.entity.js';
11
+ export function registerModule(ctx) {
12
+ /**
13
+ * Feature 120 (FR-015) — the sales-channel membership bridge this module owns.
14
+ *
15
+ * The platform used to hold a map **total** over
16
+ * `ChannelMemberEntityTypeSchema`, naming this module's bridge table from
17
+ * inside the kernel. That is D-52/D-53's prohibition expressed in a contract
18
+ * instead of an import, and it meant a membership call for 'delivery-method' on an
19
+ * instance that never installed this module executed SQL against a relation
20
+ * that is not there (D-226). The module whose migration creates the table is
21
+ * the one that says where it is; a member nothing registers now refuses at
22
+ * the call instead.
23
+ *
24
+ * A **contribution** hook and deliberately **unprobed** (D-62/D-68): a
25
+ * registration is an inert statement about the schema rather than a
26
+ * capability, the rows outlive an operator switching this module off, and
27
+ * probing would make a runtime activation flip require a restart. It is
28
+ * declared first in this module so any later hook of its own that binds a
29
+ * membership finds the bridge already there.
30
+ */
31
+ ctx.onBoot(() => {
32
+ const { salesChannelBridgeRegistry } = ctx.cradle();
33
+ for (const bridge of salesChannelBridges)
34
+ salesChannelBridgeRegistry.register(bridge);
35
+ });
36
+ ctx.di.register({
37
+ shippingAdapterRegistry: ctx.asFunction(() => shippingAdapterRegistry).singleton(),
38
+ shippingMethodEligibility: ctx
39
+ .asFunction((cradle) => new ShippingMethodEligibilityService(cradle.shippingAdapterRegistry))
40
+ .singleton(),
41
+ shippingOrderStatusRegistry: ctx.asFunction(() => new EnumOrderStatusRegistry()).singleton(),
42
+ });
43
+ /**
44
+ * Feature 075, Phase P — the row-level read model.
45
+ *
46
+ * A **port**, unlike the registry registrations above. The distinction is
47
+ * the deactivation-consequence one: `shippingAdapterRegistry` is a
48
+ * contribution seam whose absent-owner policy lives inside it and whose
49
+ * edges classify as `contributes`, so a gate over the registration would
50
+ * withdraw the table rather than one contributor's entry. A read of this
51
+ * module's own table is nothing of the kind — with `delivery_methods` off, a
52
+ * caller asking which methods exist should be told the module is off.
53
+ */
54
+ ctx.di.providePort('deliveryMethodReadPort', ctx
55
+ .asFunction(({ emFactory }) => new DeliveryMethodReadService(emFactory))
56
+ .singleton());
57
+ ctx.routes(async (app) => {
58
+ const { emFactory, requireAdmin, shippingAdapterRegistry: registry, shippingMethodEligibility, shippingOrderStatusRegistry, } = ctx.cradle();
59
+ const membership = ctx.cradle().salesChannelMembershipPort;
60
+ /** Composed from the two halves, without a `catch` — see the payment twin. */
61
+ const resolveAllowList = async (req) => {
62
+ const cradle = ctx.cradle();
63
+ const organizationId = cradle.customerOrganizationIdResolver(req);
64
+ if (organizationId === null)
65
+ return null;
66
+ return cradle.organizationRestrictionPort.allowedIdsFor(organizationId, 'deliveryMethodIds');
67
+ };
68
+ await registerDeliveryMethodsPublicRoutes(app, {
69
+ emFactory,
70
+ registry,
71
+ eligibility: shippingMethodEligibility,
72
+ resolveOrganizationDeliveryMethodAllowList: resolveAllowList,
73
+ });
74
+ await registerDeliveryMethodsAdminRoutes(app, {
75
+ emFactory,
76
+ requireAdmin,
77
+ commandBus: ctx.cradle().commandBus,
78
+ registry,
79
+ orderStatusRegistry: shippingOrderStatusRegistry,
80
+ /**
81
+ * Feature 075 — the FR-003 delete guard, asked of the module that owns
82
+ * the rows instead of counting its table in raw SQL.
83
+ *
84
+ * The port is resolved **per call**, inside the counter, and only after
85
+ * `effectiveState` has answered: `lazyPort`'s proxy may not be captured
86
+ * into a singleton, and a gate resolved before the first request is one
87
+ * of the three fail-open shapes the deactivation-consequence ledger
88
+ * refuses. The edge is `nonBindingDependencies` rather than a
89
+ * dependency — see the manifest for why the cycle forbids the ordinary
90
+ * declaration, and `shipment-usage-guard.ts` for what absence means.
91
+ */
92
+ countShipmentsForMethod: makeShipmentUsageCounter({
93
+ isShipmentsPresent: () => effectiveState.isPresent('shipments'),
94
+ shipmentUsage: () => lazyPort(ctx, 'shipmentUsagePort'),
95
+ }),
96
+ ...(membership === undefined ? {} : { salesChannelMembership: membership }),
97
+ });
98
+ });
99
+ /**
100
+ * The two shipping kinds this module implements, pushed into the registry it
101
+ * holds — from a boot hook rather than from the registry's own factory
102
+ * (issue #96).
103
+ *
104
+ * Seeding inside the factory tied "which adapters exist" to whoever resolved
105
+ * the name first, and left the entries unowned: nothing recorded that these
106
+ * two came from this module, so the enumeration had nothing to filter on when
107
+ * an operator switched a contributor off. A boot hook runs once per
108
+ * composition, regardless of effective state, which is exactly the D-39
109
+ * contract — the push is ungated, the *enumeration* answers presence.
110
+ */
111
+ ctx.onBoot(() => {
112
+ const registry = ctx.cradle().shippingAdapterRegistry;
113
+ for (const adapter of builtInShippingAdapters()) {
114
+ registry.register(adapter, 'delivery_methods');
115
+ }
116
+ });
117
+ }
118
+ /**
119
+ * The module's persisted entity classes, on the `./backend` subpath, as one
120
+ * array and **no named class export** (D-168).
121
+ *
122
+ * This is the shape the platform reads when the package is *installed*: the
123
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
124
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
125
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
126
+ * the package pass of `check-entity-tenant-classification`. A missing array is
127
+ * answered with `[]` — zero entities registered, no error anywhere.
128
+ */
129
+ export const entities = [
130
+ DeliveryMethod,
131
+ ];
132
+ /**
133
+ * The sales-channel membership bridge this module owns (feature 120, FR-015).
134
+ *
135
+ * Exported as well as registered because the owning module is the only honest
136
+ * source for it: a caller that composes no platform — the bare-database test
137
+ * harness, which constructs the kernel's membership service directly — declares
138
+ * these from here rather than from a list of nine it keeps itself, which is
139
+ * exactly the total map this feature deleted.
140
+ */
141
+ export const salesChannelBridges = [
142
+ { entityType: 'delivery-method', table: 'sales_channel_delivery_methods', entityIdColumn: 'delivery_method_id' },
143
+ ];
144
+ //# 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,51 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import { type ShipmentUsageCounter } from './commands/delivery-method.commands.js';
5
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
6
+ import type { ShippingAdapterRegistry } from './services/shipping-adapter-registry.js';
7
+ import type { ShippingMethodEligibilityService } from './services/shipping-method-eligibility.js';
8
+ import { type OrderStatusRegistry } from './services/order-status-registry.port.js';
9
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
10
+ /**
11
+ * Delivery-method catalog routes (feature 035 — adapter framework).
12
+ *
13
+ * Public read endpoint lists active, eligible methods for storefront checkout;
14
+ * admin mutation lives under `/api/v1/admin/delivery-methods`. Eligibility
15
+ * validators (validateUseOnStorefront) + registered-adapter filtering are
16
+ * layered on by the eligibility service; the org allow-list filter is retained.
17
+ */
18
+ export interface DeliveryMethodsPublicDeps {
19
+ emFactory: () => EntityManager;
20
+ /**
21
+ * Feature 026 US4 — per-Organization allow-list of delivery-method IDs.
22
+ * Empty / null ⇒ platform defaults apply (every active method is offered).
23
+ */
24
+ resolveOrganizationDeliveryMethodAllowList?: (req: FastifyRequest) => Promise<string[] | null>;
25
+ /** Feature 035 — resolves a method's storefront renderer key. */
26
+ registry?: ShippingAdapterRegistry;
27
+ /** Feature 035 — applies adapter `validateUseOnStorefront` + registered-adapter filtering. */
28
+ eligibility?: ShippingMethodEligibilityService;
29
+ }
30
+ export interface DeliveryMethodsAdminDeps {
31
+ emFactory: () => EntityManager;
32
+ requireAdmin: RequireAdminFactory;
33
+ /** Issue #125 — the two admin writes run on the bus (Principle XIII). */
34
+ commandBus: CommandBus;
35
+ /** Feature 005 / T027b — new delivery methods auto-bind to the system default. */
36
+ salesChannelMembership?: SalesChannelMembershipPort;
37
+ /** Feature 035 — validates `adapter` against the registered adapters. */
38
+ registry?: ShippingAdapterRegistry;
39
+ /** Feature 035 — validates `statusOn*` references + powers /admin/order-statuses. */
40
+ orderStatusRegistry?: OrderStatusRegistry;
41
+ /**
42
+ * Feature 075 — the FR-003 delete guard's count, asked of `shipments` because
43
+ * the rows are its own. Required, not optional: a guard that can be left out
44
+ * is a guard that silently is, and it is the only thing protecting shipment
45
+ * history from a delete (there is no foreign key).
46
+ */
47
+ countShipmentsForMethod: ShipmentUsageCounter;
48
+ }
49
+ export declare function registerDeliveryMethodsPublicRoutes(app: FastifyInstance, deps: DeliveryMethodsPublicDeps): Promise<void>;
50
+ export declare function registerDeliveryMethodsAdminRoutes(app: FastifyInstance, deps: DeliveryMethodsAdminDeps): Promise<void>;
51
+ //# 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,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,2CAA2C,CAAC;AAClG,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;GAOG;AACH,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B;;;OAGG;IACH,0CAA0C,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/F,iEAAiE;IACjE,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,8FAA8F;IAC9F,WAAW,CAAC,EAAE,gCAAgC,CAAC;CAChD;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,UAAU,EAAE,UAAU,CAAC;IACvB,kFAAkF;IAClF,sBAAsB,CAAC,EAAE,0BAA0B,CAAC;IACpD,yEAAyE;IACzE,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,qFAAqF;IACrF,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;;;OAKG;IACH,uBAAuB,EAAE,oBAAoB,CAAC;CAC/C;AAMD,wBAAsB,mCAAmC,CACvD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED,wBAAsB,kCAAkC,CACtD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
@@ -0,0 +1,152 @@
1
+ import { ERROR_CODES, deliveryMethodUpsertSchema } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { makeDeleteDeliveryMethodCommand, makeUpsertDeliveryMethodCommand, } from './commands/delivery-method.commands.js';
4
+ import { DeliveryMethod } from './entities/delivery-method.entity.js';
5
+ import { OrderStatusRegistryError, } from './services/order-status-registry.port.js';
6
+ function rendererKeyFor(deps, adapter) {
7
+ return deps.registry?.get(adapter)?.renderers?.storefront ?? null;
8
+ }
9
+ export async function registerDeliveryMethodsPublicRoutes(app, deps) {
10
+ app.get('/api/v1/delivery-methods', async (request) => {
11
+ const em = deps.emFactory();
12
+ let rows = await em.find(DeliveryMethod, { status: 'active' }, { orderBy: { code: 'asc' } });
13
+ // Feature 026 US4 — intersect with the Organization's delivery-method
14
+ // allow-list when one is configured.
15
+ if (deps.resolveOrganizationDeliveryMethodAllowList) {
16
+ const allowList = await deps.resolveOrganizationDeliveryMethodAllowList(request);
17
+ if (allowList && allowList.length > 0) {
18
+ const allowSet = new Set(allowList);
19
+ rows = rows.filter((m) => allowSet.has(m.id));
20
+ }
21
+ }
22
+ // Feature 035 — adapter validateUseOnStorefront + registered-adapter filter.
23
+ if (deps.eligibility) {
24
+ rows = await deps.eligibility.filter(rows, {
25
+ salesChannelId: null,
26
+ organizationId: null,
27
+ customerAccountId: null,
28
+ surface: 'storefront',
29
+ });
30
+ }
31
+ return { data: rows.map((m) => serializeDeliveryMethod(m, deps)) };
32
+ });
33
+ }
34
+ export async function registerDeliveryMethodsAdminRoutes(app, deps) {
35
+ const requireAdmin = deps.requireAdmin;
36
+ app.get('/api/v1/admin/delivery-methods', { preHandler: requireAdmin('delivery_methods:read') }, async () => {
37
+ const em = deps.emFactory();
38
+ const rows = await em.find(DeliveryMethod, {}, { orderBy: { code: 'asc' } });
39
+ const data = await Promise.all(rows.map((m) => serializeAdmin(m, deps)));
40
+ return { data };
41
+ });
42
+ // Note: `GET /api/v1/admin/order-statuses` is registered once by the
43
+ // payment-methods admin routes (same OrderStatusRegistry data); the admin
44
+ // delivery-methods page reuses that endpoint for its status selectors. Since
45
+ // this module took its own codes it is gated
46
+ // `requireAdminAny(['payment_methods:read', 'delivery_methods:read'])` — an
47
+ // any-of over the two editors' read codes, so whichever screen an operator
48
+ // may open, the shared status list opens with it.
49
+ app.put('/api/v1/admin/delivery-methods/:code', {
50
+ preHandler: requireAdmin('delivery_methods:write'),
51
+ schema: { body: deliveryMethodUpsertSchema },
52
+ }, async (request) => {
53
+ const body = deliveryMethodUpsertSchema.parse(request.body);
54
+ const em = deps.emFactory();
55
+ // Only hard-reject an *explicitly* provided unknown adapter. A row whose
56
+ // derived adapter is not registered is retained but excluded from
57
+ // selection by the eligibility filter (FR-003), so admins can still
58
+ // manage catalog rows whose adapter module is currently disabled.
59
+ if (body.adapter !== undefined && deps.registry && !deps.registry.isRegistered(body.adapter)) {
60
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, `Unknown shipping adapter "${body.adapter}". Register an adapter module first.`);
61
+ }
62
+ // Validate any explicitly-provided Order-status references (FR-008).
63
+ if (deps.orderStatusRegistry) {
64
+ for (const ref of [body.statusOnSuccess, body.statusOnFailure]) {
65
+ if (ref !== undefined)
66
+ assertValidStatus(deps.orderStatusRegistry, ref);
67
+ }
68
+ }
69
+ // The read below only tells the Command whether this call creates or
70
+ // updates; the write itself happens inside the bus transaction, which is
71
+ // also where the adapter is resolved against the row it finds (#125).
72
+ const existing = await em.findOne(DeliveryMethod, { code: request.params.code });
73
+ const { method: row, created: isNew } = await deps.commandBus.run(makeUpsertDeliveryMethodCommand({
74
+ code: request.params.code,
75
+ body,
76
+ existingId: existing?.id ?? null,
77
+ }));
78
+ // Channel membership is the sales-channel bridge's own write, on its own
79
+ // EntityManager, so it stays outside the Command rather than pretending to
80
+ // share its transaction.
81
+ if (isNew && deps.salesChannelMembership) {
82
+ await deps.salesChannelMembership.bindToDefaultIfEmpty('delivery-method', row.id);
83
+ }
84
+ // Replace sales-channel membership when an explicit (non-empty) set is given.
85
+ if (body.salesChannelIds && body.salesChannelIds.length > 0 && deps.salesChannelMembership) {
86
+ await replaceChannelMembership(deps.salesChannelMembership, row.id, body.salesChannelIds);
87
+ }
88
+ return { data: await serializeAdmin(row, deps) };
89
+ });
90
+ app.delete('/api/v1/admin/delivery-methods/:id', { preHandler: requireAdmin('delivery_methods:write') }, async (request, reply) => {
91
+ // The 404 and the delete-guard (FR-003) live inside the Command, so a
92
+ // method that is gone is answered before another module is asked about
93
+ // it. The count itself comes from `shipments` — see the Command's
94
+ // `ShipmentUsageCounter` for why it no longer runs on this transaction.
95
+ await deps.commandBus.run(makeDeleteDeliveryMethodCommand(request.params.id, deps.countShipmentsForMethod));
96
+ return reply.status(204).send();
97
+ });
98
+ }
99
+ function assertValidStatus(registry, ref) {
100
+ try {
101
+ registry.assertValid(ref);
102
+ }
103
+ catch (err) {
104
+ if (err instanceof OrderStatusRegistryError) {
105
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, `Unknown order status "${ref}".`);
106
+ }
107
+ throw err;
108
+ }
109
+ }
110
+ async function replaceChannelMembership(membership, methodId, desiredChannelIds) {
111
+ const current = await membership.listChannelsForEntity('delivery-method', methodId);
112
+ const currentIds = new Set(current.map((c) => c.id));
113
+ const desired = new Set(desiredChannelIds);
114
+ // Add first so a later removal never transiently leaves zero channels.
115
+ for (const id of desired) {
116
+ if (!currentIds.has(id))
117
+ await membership.addToChannel(id, 'delivery-method', methodId);
118
+ }
119
+ for (const id of currentIds) {
120
+ if (!desired.has(id))
121
+ await membership.removeFromChannel(id, 'delivery-method', methodId);
122
+ }
123
+ }
124
+ function serializeDeliveryMethod(m, deps) {
125
+ return {
126
+ id: m.id,
127
+ code: m.code,
128
+ name: m.name,
129
+ adapter: m.adapter,
130
+ cost: { amount: Number(m.cost), currency: m.currency },
131
+ status: m.status,
132
+ rendererKey: rendererKeyFor(deps, m.adapter),
133
+ };
134
+ }
135
+ async function serializeAdmin(m, deps) {
136
+ const channels = deps.salesChannelMembership
137
+ ? await deps.salesChannelMembership.listChannelsForEntity('delivery-method', m.id)
138
+ : [];
139
+ return {
140
+ id: m.id,
141
+ code: m.code,
142
+ adapter: m.adapter,
143
+ name: m.name,
144
+ cost: { amount: Number(m.cost), currency: m.currency },
145
+ status: m.status,
146
+ statusOnSuccess: m.statusOnSuccess,
147
+ statusOnFailure: m.statusOnFailure,
148
+ salesChannelIds: channels.map((c) => c.id),
149
+ rendererKey: deps.registry?.get(m.adapter)?.renderers?.admin ?? null,
150
+ };
151
+ }
152
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { DeliveryMethodReadPort, DeliveryMethodRecord } from '@endora-commerce/contracts';
3
+ import { DeliveryMethod } from '../entities/delivery-method.entity.js';
4
+ /**
5
+ * The row-level read model `delivery_methods` publishes (feature 075, Phase P).
6
+ *
7
+ * Five of the thirteen inbound sites read the entity: `orders` resolving the
8
+ * method at placement, `shipments` resolving it at dispatch and again in its
9
+ * receive handler, `quick_order` resolving a buyer's default, and the dev
10
+ * seed.
11
+ *
12
+ * `cost` stays a decimal string — it lands verbatim in the order's
13
+ * `deliveryMethodSnapshot`, and a `number` cannot round-trip it.
14
+ */
15
+ export declare class DeliveryMethodReadService implements DeliveryMethodReadPort {
16
+ private readonly emFactory;
17
+ constructor(emFactory: () => EntityManager);
18
+ findById(id: string): Promise<DeliveryMethodRecord | null>;
19
+ findByIds(ids: readonly string[]): Promise<DeliveryMethodRecord[]>;
20
+ findByCode(code: string): Promise<DeliveryMethodRecord | null>;
21
+ listAll(): Promise<DeliveryMethodRecord[]>;
22
+ listActive(): Promise<DeliveryMethodRecord[]>;
23
+ }
24
+ export declare function toDeliveryMethodRecord(method: DeliveryMethod): DeliveryMethodRecord;
25
+ //# sourceMappingURL=delivery-method-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-method-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/delivery-method-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAEvE;;;;;;;;;;GAUG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAK1D,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAMlE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAK9D,OAAO,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAK1C,UAAU,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;CAQpD;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,oBAAoB,CAcnF"}
@@ -0,0 +1,56 @@
1
+ import { DeliveryMethod } from '../entities/delivery-method.entity.js';
2
+ /**
3
+ * The row-level read model `delivery_methods` publishes (feature 075, Phase P).
4
+ *
5
+ * Five of the thirteen inbound sites read the entity: `orders` resolving the
6
+ * method at placement, `shipments` resolving it at dispatch and again in its
7
+ * receive handler, `quick_order` resolving a buyer's default, and the dev
8
+ * seed.
9
+ *
10
+ * `cost` stays a decimal string — it lands verbatim in the order's
11
+ * `deliveryMethodSnapshot`, and a `number` cannot round-trip it.
12
+ */
13
+ export class DeliveryMethodReadService {
14
+ emFactory;
15
+ constructor(emFactory) {
16
+ this.emFactory = emFactory;
17
+ }
18
+ async findById(id) {
19
+ const method = await this.emFactory().findOne(DeliveryMethod, { id });
20
+ return method ? toDeliveryMethodRecord(method) : null;
21
+ }
22
+ async findByIds(ids) {
23
+ if (ids.length === 0)
24
+ return [];
25
+ const methods = await this.emFactory().find(DeliveryMethod, { id: { $in: [...ids] } });
26
+ return methods.map(toDeliveryMethodRecord);
27
+ }
28
+ async findByCode(code) {
29
+ const method = await this.emFactory().findOne(DeliveryMethod, { code });
30
+ return method ? toDeliveryMethodRecord(method) : null;
31
+ }
32
+ async listAll() {
33
+ const methods = await this.emFactory().find(DeliveryMethod, {}, { orderBy: { code: 'asc' } });
34
+ return methods.map(toDeliveryMethodRecord);
35
+ }
36
+ async listActive() {
37
+ const methods = await this.emFactory().find(DeliveryMethod, { status: 'active' }, { orderBy: { code: 'asc' } });
38
+ return methods.map(toDeliveryMethodRecord);
39
+ }
40
+ }
41
+ export function toDeliveryMethodRecord(method) {
42
+ return {
43
+ id: method.id,
44
+ code: method.code,
45
+ name: method.name,
46
+ cost: method.cost,
47
+ currency: method.currency,
48
+ status: method.status,
49
+ adapter: method.adapter,
50
+ statusOnSuccess: method.statusOnSuccess,
51
+ statusOnFailure: method.statusOnFailure,
52
+ createdAt: method.createdAt,
53
+ updatedAt: method.updatedAt,
54
+ };
55
+ }
56
+ //# sourceMappingURL=delivery-method-read-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-method-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/delivery-method-read-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAEvE;;;;;;;;;;GAUG;AACH,MAAM,OAAO,yBAAyB;IACP;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACtE,OAAO,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAsB;QACpC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QACvF,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9F,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACzC,cAAc,EACd,EAAE,MAAM,EAAE,QAAQ,EAAE,EACpB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAC7B,CAAC;QACF,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAsB;IAC3D,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,97 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { DeliveryMethodSeedApi, DeliveryMethodSeedDefaults, DeliveryMethodSeedOutcome } from '../../ports/index.js';
3
+ /**
4
+ * DeliveryMethodReconciler (feature 035, FR-002) — the implementation behind
5
+ * `DeliveryMethodSeedApi`, this module's published install surface
6
+ * (`../../ports/index.ts`, `createDeliveryMethodSeeder` below).
7
+ *
8
+ * A module that must create its `delivery_methods` row from code calls
9
+ * `ensureMethodForAdapter` from its **install hook**, so installing a
10
+ * shipping-method module surfaces a configurable entry at `/delivery-methods`
11
+ * with no core change. Idempotent and prune-safe: an existing row (matched by
12
+ * `code`) keeps its admin-edited configuration; the reconciler only fills a
13
+ * missing `adapter` link.
14
+ *
15
+ * The row and the adapter are contributed at **different moments, on purpose**.
16
+ * This row is durable state and is written once, at install; the adapter is an
17
+ * in-memory entry in a per-process table and is pushed from the contributing
18
+ * module's boot hook, on every composition (see `shipping-adapter-registry.ts`).
19
+ * Neither waits for the other: a row whose `adapter` key nothing has contributed
20
+ * is simply not offered, and the registry is not read until a request reads it.
21
+ *
22
+ * **The `EntityManager` is a parameter of every method and is never held**
23
+ * (feature 134, FR-064; D-169). It used to be a constructor `emFactory`, which is
24
+ * the shape of a service resolved from a container — and the caller this class
25
+ * was written for has no container: `ModuleLifecycleContext` is
26
+ * `{ em, redis, log, module }` and `module:install` composes nothing (D-46). The
27
+ * hook's own `ctx.em` is therefore what every statement here runs on, handed in
28
+ * at the call, so the write lands in the transaction the orchestrator will
29
+ * commit or revert rather than on a fork of it.
30
+ *
31
+ * **It no longer rebinds sales-channel membership (issue #96)** — see the
32
+ * payment twin (`payment_methods/services/payment-method-reconciler.ts`).
33
+ * `bindToDefaultChannel` exists, and is the seed's own *once, for the rows it
34
+ * creates* half rather than a reconcile: the caller guards it on
35
+ * `created === true`. What issue #96 removed was the unconditional call, which
36
+ * brought a method an operator had deliberately unbound from every channel back
37
+ * to Default at the next boot, and nothing said so. "Unbound" is a state an
38
+ * operator is entitled to reach and to keep.
39
+ */
40
+ export declare class DeliveryMethodReconciler implements DeliveryMethodSeedApi {
41
+ ensureMethodForAdapter(em: EntityManager, adapterKey: string, defaults: DeliveryMethodSeedDefaults): Promise<DeliveryMethodSeedOutcome>;
42
+ /**
43
+ * The seed's channel binding — one membership row in the system-default
44
+ * channel, written against **this module's own** bridge table.
45
+ *
46
+ * Not through `SalesChannelMembershipService`, and the reason is structural
47
+ * rather than a preference: that service needs the `EventBus`, the audit port
48
+ * and the channel-bridge registry, and the registry is contributed from a boot
49
+ * hook. An install composes nothing, so at this seam it holds no registration
50
+ * for `'delivery-method'` and `bridges.require` would refuse (FR-017) before
51
+ * the database was touched. `sales_channel_delivery_methods` is this module's
52
+ * since `specs/120-migration-closure-bridge-ownership/` Phase 2 (D-226), so the
53
+ * statement is the owner's own and crosses no boundary — which is what FR-064
54
+ * buys by putting the writer here instead of in the seeding module.
55
+ *
56
+ * The channel itself is read through the kernel's own `SalesChannel` entity
57
+ * rather than in SQL — `api_keys` reads it the same way — because a raw
58
+ * `select … from "sales_channels"` from a module is a `check:module-boundary`
59
+ * finding against a kernel-owned table, and correctly so: the table is not
60
+ * this module's and the entity is the platform's published name for it.
61
+ *
62
+ * `em.execute` for the insert rather than `em.getConnection().execute`, so the
63
+ * statement runs inside the caller's transaction (issue #200). The bridge has
64
+ * no entity class — the kernel's membership service writes it in SQL too.
65
+ *
66
+ * **No system-default channel answers `false` rather than raising, and that is
67
+ * measured rather than defensive.** The default channel is created by
68
+ * `DefaultChannelReconciler` at **boot**, from `composeApp` — not by a migration
69
+ * and not by an install — and `module:install` composes nothing (D-46). So a
70
+ * database that has been migrated and never booted has no default channel, and
71
+ * a seeding module installed there is the ordinary case, not a broken instance:
72
+ * `pnpm --filter backend run db:fresh && module:install dhl_parcel` is exactly
73
+ * it. The seed migration this replaced degraded the same way, silently — its
74
+ * `cross join "sales_channels" where "system_default"` produced no rows and
75
+ * inserted no membership — so answering `false` is what keeps a fresh install
76
+ * and an upgraded one at the same row state in that state too. Raising instead
77
+ * **aborts the install**, and the hook is not inside a database transaction:
78
+ * measured on a throwaway database, the first of two rows stayed and the second
79
+ * never arrived.
80
+ */
81
+ bindToDefaultChannel(em: EntityManager, deliveryMethodId: string): Promise<boolean>;
82
+ /**
83
+ * The hard-uninstall half. Channel memberships go with the row: the bridge's
84
+ * foreign key onto `delivery_methods` is `on delete cascade`.
85
+ */
86
+ removeMethodForAdapter(em: EntityManager, code: string): Promise<boolean>;
87
+ }
88
+ /**
89
+ * The runtime half of this module's install surface (feature 134, FR-064).
90
+ *
91
+ * A seeding module's `installHook` writes `createDeliveryMethodSeeder()` and
92
+ * hands `ctx.em` to each call. The factory takes no arguments because the `em`
93
+ * belongs to the call and not to the seeder: one `em`, named at every statement,
94
+ * with no second source of truth for which transaction the write lands in.
95
+ */
96
+ export declare function createDeliveryMethodSeeder(): DeliveryMethodSeedApi;
97
+ //# sourceMappingURL=delivery-method-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-method-reconciler.d.ts","sourceRoot":"","sources":["../../../src/backend/services/delivery-method-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EACV,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EAE1B,MAAM,sBAAsB,CAAC;AAG9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,wBAAyB,YAAW,qBAAqB;IAC9D,sBAAsB,CAC1B,EAAE,EAAE,aAAa,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAAC,yBAAyB,CAAC;IA4BrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACG,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAezF;;;OAGG;IACG,sBAAsB,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAOhF;AAoBD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,IAAI,qBAAqB,CAElE"}