@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,157 @@
1
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
2
+ import { DeliveryMethod } from '../entities/delivery-method.entity.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 class DeliveryMethodReconciler {
41
+ async ensureMethodForAdapter(em, adapterKey, defaults) {
42
+ // command-coverage-ignore: idempotent reconciliation of adapter-backed delivery
43
+ // methods — a system-invariant repair, not an operator-initiated write.
44
+ const existing = await em.findOne(DeliveryMethod, { code: defaults.code });
45
+ if (existing) {
46
+ // Prune-safe: never clobber admin configuration. Only backfill a missing
47
+ // adapter link (e.g. a legacy row predating this feature).
48
+ if (!existing.adapter) {
49
+ existing.adapter = adapterKey;
50
+ await em.persistAndFlush(existing);
51
+ }
52
+ return { row: recordOf(existing), created: false };
53
+ }
54
+ const row = em.create(DeliveryMethod, {
55
+ code: defaults.code,
56
+ name: defaults.name,
57
+ adapter: adapterKey,
58
+ cost: defaults.cost ?? '0',
59
+ currency: defaults.currency ?? 'PLN',
60
+ status: defaults.status ?? 'active',
61
+ statusOnSuccess: defaults.statusOnSuccess ?? 'shipment_sent',
62
+ statusOnFailure: defaults.statusOnFailure ?? 'processing',
63
+ });
64
+ await em.persistAndFlush(row);
65
+ return { row: recordOf(row), created: true };
66
+ }
67
+ /**
68
+ * The seed's channel binding — one membership row in the system-default
69
+ * channel, written against **this module's own** bridge table.
70
+ *
71
+ * Not through `SalesChannelMembershipService`, and the reason is structural
72
+ * rather than a preference: that service needs the `EventBus`, the audit port
73
+ * and the channel-bridge registry, and the registry is contributed from a boot
74
+ * hook. An install composes nothing, so at this seam it holds no registration
75
+ * for `'delivery-method'` and `bridges.require` would refuse (FR-017) before
76
+ * the database was touched. `sales_channel_delivery_methods` is this module's
77
+ * since `specs/120-migration-closure-bridge-ownership/` Phase 2 (D-226), so the
78
+ * statement is the owner's own and crosses no boundary — which is what FR-064
79
+ * buys by putting the writer here instead of in the seeding module.
80
+ *
81
+ * The channel itself is read through the kernel's own `SalesChannel` entity
82
+ * rather than in SQL — `api_keys` reads it the same way — because a raw
83
+ * `select … from "sales_channels"` from a module is a `check:module-boundary`
84
+ * finding against a kernel-owned table, and correctly so: the table is not
85
+ * this module's and the entity is the platform's published name for it.
86
+ *
87
+ * `em.execute` for the insert rather than `em.getConnection().execute`, so the
88
+ * statement runs inside the caller's transaction (issue #200). The bridge has
89
+ * no entity class — the kernel's membership service writes it in SQL too.
90
+ *
91
+ * **No system-default channel answers `false` rather than raising, and that is
92
+ * measured rather than defensive.** The default channel is created by
93
+ * `DefaultChannelReconciler` at **boot**, from `composeApp` — not by a migration
94
+ * and not by an install — and `module:install` composes nothing (D-46). So a
95
+ * database that has been migrated and never booted has no default channel, and
96
+ * a seeding module installed there is the ordinary case, not a broken instance:
97
+ * `pnpm --filter backend run db:fresh && module:install dhl_parcel` is exactly
98
+ * it. The seed migration this replaced degraded the same way, silently — its
99
+ * `cross join "sales_channels" where "system_default"` produced no rows and
100
+ * inserted no membership — so answering `false` is what keeps a fresh install
101
+ * and an upgraded one at the same row state in that state too. Raising instead
102
+ * **aborts the install**, and the hook is not inside a database transaction:
103
+ * measured on a throwaway database, the first of two rows stayed and the second
104
+ * never arrived.
105
+ */
106
+ async bindToDefaultChannel(em, deliveryMethodId) {
107
+ // command-coverage-ignore: install-time seed membership for a row this seam
108
+ // just created — a system-invariant write with no request and no actor.
109
+ const defaultChannel = await em.findOne(SalesChannel, { systemDefault: true });
110
+ if (!defaultChannel)
111
+ return false;
112
+ const inserted = await em.execute('insert into "sales_channel_delivery_methods" ("sales_channel_id", "delivery_method_id") ' +
113
+ 'values (?, ?) on conflict ("sales_channel_id", "delivery_method_id") do nothing ' +
114
+ 'returning "delivery_method_id"', [defaultChannel.id, deliveryMethodId]);
115
+ return inserted.length > 0;
116
+ }
117
+ /**
118
+ * The hard-uninstall half. Channel memberships go with the row: the bridge's
119
+ * foreign key onto `delivery_methods` is `on delete cascade`.
120
+ */
121
+ async removeMethodForAdapter(em, code) {
122
+ // command-coverage-ignore: hard-uninstall removal of an adapter-backed
123
+ // delivery method — no request, no actor and nothing to attribute an audit
124
+ // entry to; the operator path is `commands/delivery-method.commands.ts`.
125
+ const removed = await em.nativeDelete(DeliveryMethod, { code });
126
+ return removed > 0;
127
+ }
128
+ }
129
+ /**
130
+ * The published record, mapped field by field rather than by handing the entity
131
+ * back (D-168/D-77): a caller reads values off it and cannot persist through it.
132
+ */
133
+ function recordOf(row) {
134
+ return {
135
+ id: row.id,
136
+ code: row.code,
137
+ name: row.name,
138
+ cost: row.cost,
139
+ currency: row.currency,
140
+ status: row.status,
141
+ adapter: row.adapter,
142
+ statusOnSuccess: row.statusOnSuccess,
143
+ statusOnFailure: row.statusOnFailure,
144
+ };
145
+ }
146
+ /**
147
+ * The runtime half of this module's install surface (feature 134, FR-064).
148
+ *
149
+ * A seeding module's `installHook` writes `createDeliveryMethodSeeder()` and
150
+ * hands `ctx.em` to each call. The factory takes no arguments because the `em`
151
+ * belongs to the call and not to the seeder: one `em`, named at every statement,
152
+ * with no second source of truth for which transaction the write lands in.
153
+ */
154
+ export function createDeliveryMethodSeeder() {
155
+ return new DeliveryMethodReconciler();
156
+ }
157
+ //# sourceMappingURL=delivery-method-reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-method-reconciler.js","sourceRoot":"","sources":["../../../src/backend/services/delivery-method-reconciler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { type OrderStatusOption, type OrderStatusRegistry } from '@endora-commerce/contracts';
2
+ /**
3
+ * OrderStatusRegistry port (feature 035 — research.md R3).
4
+ *
5
+ * `statusOnSuccess` / `statusOnFailure` on a delivery method reference *Order
6
+ * Statuses*. Those will eventually be an admin-configurable registry owned by
7
+ * the Orders module; until then this port is backed by the current hard-coded
8
+ * order `status` enum. Consumers (admin upsert validation, receive-shipment
9
+ * handler) depend only on this interface, so the future configurable registry
10
+ * drops in with no change to `delivery_methods`.
11
+ *
12
+ * Deliberately a small, isolated duplicate of the payment_methods port rather
13
+ * than a deep import across modules (constitution Principle I). The port does
14
+ * NOT touch the Order entity — applying a status is the caller's job.
15
+ *
16
+ * The interface moved to `@endora-commerce/contracts` in feature 075's Phase P. It was
17
+ * declared twice — once here and once in `payment_methods`, in the same words
18
+ * — because both modules map an outcome onto an order status; one declaration
19
+ * is what stops the two drifting. Re-exported here for the length of Phase P,
20
+ * which cuts no consumer.
21
+ */
22
+ export type { OrderStatusRegistry };
23
+ export declare class OrderStatusRegistryError extends Error {
24
+ readonly code: string;
25
+ constructor(code: string);
26
+ }
27
+ /**
28
+ * Default enum-backed implementation. Seed set = the order `status` enum.
29
+ *
30
+ * **The absent-owner policy this registry states (issue #129): honour — and the
31
+ * reason is that there is nothing to skip.** The delivery twin of
32
+ * `payment_methods/services/order-status-registry.port.ts`, and the argument is
33
+ * the same one: no module contributes to it, the option set is
34
+ * `orderStatusSchema` fixed at compile time, and `shipments` reads `has` as a
35
+ * guard before moving an order into the status a dispatched shipment names. A
36
+ * skip would silently leave a shipped order in its old status; every code here
37
+ * is one live orders are already in, and a status an order is in has to stay
38
+ * nameable while the module holding this table is off.
39
+ *
40
+ * The policy is structural rather than promised: the class takes no presence
41
+ * input, so no read of it can be made to drop a status without changing the
42
+ * policy first.
43
+ */
44
+ export declare class EnumOrderStatusRegistry implements OrderStatusRegistry {
45
+ private readonly codes;
46
+ list(): OrderStatusOption[];
47
+ has(code: string): boolean;
48
+ assertValid(code: string): void;
49
+ }
50
+ //# sourceMappingURL=order-status-registry.port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-status-registry.port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/order-status-registry.port.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,qBAAa,wBAAyB,SAAQ,KAAK;aACrB,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIzC;AAQD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgD;IAEtE,IAAI,IAAI,iBAAiB,EAAE;IAI3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAKhC"}
@@ -0,0 +1,45 @@
1
+ import { orderStatusSchema, } from '@endora-commerce/contracts';
2
+ export class OrderStatusRegistryError extends Error {
3
+ code;
4
+ constructor(code) {
5
+ super(`Unknown order status reference: "${code}".`);
6
+ this.code = code;
7
+ this.name = 'OrderStatusRegistryError';
8
+ }
9
+ }
10
+ const humanize = (code) => code
11
+ .split('_')
12
+ .map((part) => (part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part))
13
+ .join(' ');
14
+ /**
15
+ * Default enum-backed implementation. Seed set = the order `status` enum.
16
+ *
17
+ * **The absent-owner policy this registry states (issue #129): honour — and the
18
+ * reason is that there is nothing to skip.** The delivery twin of
19
+ * `payment_methods/services/order-status-registry.port.ts`, and the argument is
20
+ * the same one: no module contributes to it, the option set is
21
+ * `orderStatusSchema` fixed at compile time, and `shipments` reads `has` as a
22
+ * guard before moving an order into the status a dispatched shipment names. A
23
+ * skip would silently leave a shipped order in its old status; every code here
24
+ * is one live orders are already in, and a status an order is in has to stay
25
+ * nameable while the module holding this table is off.
26
+ *
27
+ * The policy is structural rather than promised: the class takes no presence
28
+ * input, so no read of it can be made to drop a status without changing the
29
+ * policy first.
30
+ */
31
+ export class EnumOrderStatusRegistry {
32
+ codes = orderStatusSchema.options;
33
+ list() {
34
+ return this.codes.map((code) => ({ code, label: humanize(code) }));
35
+ }
36
+ has(code) {
37
+ return this.codes.includes(code);
38
+ }
39
+ assertValid(code) {
40
+ if (!this.has(code)) {
41
+ throw new OrderStatusRegistryError(code);
42
+ }
43
+ }
44
+ }
45
+ //# sourceMappingURL=order-status-registry.port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-status-registry.port.js","sourceRoot":"","sources":["../../../src/backend/services/order-status-registry.port.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAGlB,MAAM,4BAA4B,CAAC;AAwBpC,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACrB;IAA5B,YAA4B,IAAY;QACtC,KAAK,CAAC,oCAAoC,IAAI,IAAI,CAAC,CAAC;QAD1B,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CACxC,IAAI;KACD,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;KAChF,IAAI,CAAC,GAAG,CAAC,CAAC;AAEf;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,uBAAuB;IACjB,KAAK,GAAsB,iBAAiB,CAAC,OAAO,CAAC;IAEtE,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import { ShippingAdapterRegistry } from './shipping-adapter-registry.js';
2
+ /**
3
+ * Process-wide ShippingAdapterRegistry (feature 035).
4
+ *
5
+ * `delivery_methods` pushes the two bundled offline adapters into this instance
6
+ * from its boot hook; a carrier module contributes its own the same way, naming
7
+ * itself as it does. One instance per process, so an adapter contributed by any
8
+ * module is immediately recognised by the live eligibility, admin-upsert and
9
+ * order-placement paths.
10
+ *
11
+ * Those paths read this table **per request**, never during composition — so a
12
+ * contributor's boot hook pushes and returns, with nothing to wait for, nothing
13
+ * to verify and no absence to react to. See the class doc block for the full
14
+ * push/pull shape and for why a throw there costs the next start.
15
+ *
16
+ * The presence probe is wired here rather than in the class — see the payment
17
+ * twin (issue #96).
18
+ */
19
+ export declare const shippingAdapterRegistry: ShippingAdapterRegistry;
20
+ //# sourceMappingURL=registry-singleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-singleton.d.ts","sourceRoot":"","sources":["../../../src/backend/services/registry-singleton.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAEzE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,uBAAuB,yBAEnC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { effectiveState } from '@endora-commerce/platform/kernel';
2
+ import { ShippingAdapterRegistry } from './shipping-adapter-registry.js';
3
+ /**
4
+ * Process-wide ShippingAdapterRegistry (feature 035).
5
+ *
6
+ * `delivery_methods` pushes the two bundled offline adapters into this instance
7
+ * from its boot hook; a carrier module contributes its own the same way, naming
8
+ * itself as it does. One instance per process, so an adapter contributed by any
9
+ * module is immediately recognised by the live eligibility, admin-upsert and
10
+ * order-placement paths.
11
+ *
12
+ * Those paths read this table **per request**, never during composition — so a
13
+ * contributor's boot hook pushes and returns, with nothing to wait for, nothing
14
+ * to verify and no absence to react to. See the class doc block for the full
15
+ * push/pull shape and for why a throw there costs the next start.
16
+ *
17
+ * The presence probe is wired here rather than in the class — see the payment
18
+ * twin (issue #96).
19
+ */
20
+ export const shippingAdapterRegistry = new ShippingAdapterRegistry(undefined, (moduleId) => effectiveState.isPresent(moduleId));
21
+ //# sourceMappingURL=registry-singleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-singleton.js","sourceRoot":"","sources":["../../../src/backend/services/registry-singleton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAEzE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,uBAAuB,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CACzF,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CACnC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { type ShipmentUsagePort } from '@endora-commerce/contracts';
2
+ import type { ShipmentUsageCounter } from '../commands/delivery-method.commands.js';
3
+ /** What the counter needs, so a test supplies both halves without a container. */
4
+ export interface ShipmentUsageCounterDeps {
5
+ /** `effectiveState.isPresent('shipments')`, injected so this stays testable. */
6
+ readonly isShipmentsPresent: () => boolean;
7
+ /** `lazyPort<ShipmentUsagePort>(ctx, 'shipmentUsagePort')`, resolved per call. */
8
+ readonly shipmentUsage: () => ShipmentUsagePort;
9
+ }
10
+ /**
11
+ * "How many shipments were created against this delivery method?" — asked of
12
+ * `shipments`, which owns the rows (feature 075, the `delivery_methods` shard).
13
+ *
14
+ * **Presence is decided before the port is resolved**, which is the
15
+ * `auth`/`api_keys` shape and the reason this edge is declared as
16
+ * `nonBindingDependencies` rather than as an ordinary dependency: `shipments`
17
+ * declares this module, so declaring it back closes a manifest cycle, and
18
+ * acknowledging it would put `delivery_methods` among the dependents that
19
+ * refuse the flip — making the shipment module unswitchable for as long as a
20
+ * shop offers delivery. There is deliberately no `catch` anywhere near the
21
+ * resolution: a caught gate is a fail-open degrade nobody declared.
22
+ *
23
+ * **The degradation is a refusal, not a shortcut.** `shipments.delivery_method_id`
24
+ * carries no foreign key, so this count is the only thing standing between a
25
+ * delete and permanently orphaned shipment history — and switching the module
26
+ * off is exactly when nothing on the operator's screen would show that the
27
+ * history exists. Off is meant to be reversible; a delete taken blind here is
28
+ * not. So the operator is told which switch to flip and nothing is destroyed.
29
+ */
30
+ export declare function makeShipmentUsageCounter(deps: ShipmentUsageCounterDeps): ShipmentUsageCounter;
31
+ //# sourceMappingURL=shipment-usage-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipment-usage-guard.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shipment-usage-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEjF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAEpF,kFAAkF;AAClF,MAAM,WAAW,wBAAwB;IACvC,gFAAgF;IAChF,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAC3C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,iBAAiB,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CAa7F"}
@@ -0,0 +1,33 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ /**
4
+ * "How many shipments were created against this delivery method?" — asked of
5
+ * `shipments`, which owns the rows (feature 075, the `delivery_methods` shard).
6
+ *
7
+ * **Presence is decided before the port is resolved**, which is the
8
+ * `auth`/`api_keys` shape and the reason this edge is declared as
9
+ * `nonBindingDependencies` rather than as an ordinary dependency: `shipments`
10
+ * declares this module, so declaring it back closes a manifest cycle, and
11
+ * acknowledging it would put `delivery_methods` among the dependents that
12
+ * refuse the flip — making the shipment module unswitchable for as long as a
13
+ * shop offers delivery. There is deliberately no `catch` anywhere near the
14
+ * resolution: a caught gate is a fail-open degrade nobody declared.
15
+ *
16
+ * **The degradation is a refusal, not a shortcut.** `shipments.delivery_method_id`
17
+ * carries no foreign key, so this count is the only thing standing between a
18
+ * delete and permanently orphaned shipment history — and switching the module
19
+ * off is exactly when nothing on the operator's screen would show that the
20
+ * history exists. Off is meant to be reversible; a delete taken blind here is
21
+ * not. So the operator is told which switch to flip and nothing is destroyed.
22
+ */
23
+ export function makeShipmentUsageCounter(deps) {
24
+ return async (deliveryMethodId) => {
25
+ if (!deps.isShipmentsPresent()) {
26
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'Cannot delete delivery method: the "shipments" module is switched off, so the ' +
27
+ 'platform cannot tell whether any shipment was created against this method. ' +
28
+ 'Switch shipments on and try again, or set this method\'s status to "inactive".');
29
+ }
30
+ return deps.shipmentUsage().countForDeliveryMethod(deliveryMethodId);
31
+ };
32
+ }
33
+ //# sourceMappingURL=shipment-usage-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipment-usage-guard.js","sourceRoot":"","sources":["../../../src/backend/services/shipment-usage-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAW3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAA8B;IACrE,OAAO,KAAK,EAAE,gBAAwB,EAAmB,EAAE;QACzD,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,iBAAiB,EAC7B,gFAAgF;gBAC9E,6EAA6E;gBAC7E,gFAAgF,CACnF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;IACvE,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,98 @@
1
+ import type { ShippingAdapter, ShippingAdapterRegistryPort } from '@endora-commerce/contracts';
2
+ /**
3
+ * ShippingAdapterRegistry (feature 035) — in-memory map of adapter key →
4
+ * implementation. A module is recognised as a shipping-method adapter iff it
5
+ * registers a `ShippingAdapter` here (FR-001). The bundled offline adapters
6
+ * (manual_courier / personal_pickup) are the first entries; a carrier module
7
+ * contributes its own from its boot hook.
8
+ *
9
+ * Owner-stamped and presence-filtered, exactly like the payment twin — see
10
+ * `payment_methods/services/payment-adapter-registry.ts` for the reasoning
11
+ * (issue #96, D-39). The mechanism is identical; the exposure is not, because
12
+ * today the only contributor is `delivery_methods` itself. Keeping the two
13
+ * registries the same shape is what stops a carrier module from landing on the
14
+ * defect the payment side just paid to remove.
15
+ *
16
+ * Collision policy mirrors the PaymentAdapterRegistry: last-writer-wins with a
17
+ * warning, so a re-registration during a hot reload or re-enable does not throw.
18
+ *
19
+ * **The push happens once, during composition; every read happens later, per
20
+ * operation.** That half was written down only for the payment twin, and the
21
+ * omission is what a contributor infers a rule from: nothing here is read while
22
+ * modules are being composed, so an adapter that is not in the table yet — or
23
+ * whose owner is switched off — is not a fault a contributor can observe, let
24
+ * alone react to. The reads are `ShippingMethodEligibilityService.filter` on
25
+ * `GET /api/v1/delivery-methods`, the `isRegistered` guard on the admin upsert
26
+ * `PUT /api/v1/admin/delivery-methods/:code`, `orders` re-validating the chosen
27
+ * method and firing `onOrderCreated` at placement, `ShipmentService.create`
28
+ * firing `onShipmentCreated`, and the order-confirmation e-mail resolving
29
+ * `renderers.email`. Each of those runs inside a request, after every boot hook
30
+ * has run.
31
+ *
32
+ * So a contributor's boot hook **pushes and returns**: it does not check that
33
+ * the table already holds anything, does not verify that `delivery_methods` is
34
+ * present, and never treats an absent adapter as fatal. A throw there is not a
35
+ * delivery method dropping out — `runBootHooks` attributes the failure to the
36
+ * module and re-throws `ModuleCompositionError`, and `index.ts` turns that into
37
+ * `process.exit(1)`, so an operator's switch takes down the next start instead
38
+ * of one adapter. Nor may the push probe presence (D-67/D-68): the enumeration
39
+ * here already answers that question, per read, and a probe at the push would
40
+ * make switching a carrier back on require a restart. A hook that also *does
41
+ * work* is split in two before either rule applies — the working half probes,
42
+ * the contributing half never does.
43
+ *
44
+ * There is likewise no order to get right between contributors. Every push
45
+ * lands in one process-wide table (`registry-singleton.ts`) during composition,
46
+ * and no read of it happens until a request does; an adapter a colleague
47
+ * contributes is visible to the first read either way.
48
+ */
49
+ export interface RegistryLogger {
50
+ warn(message: string): void;
51
+ }
52
+ /** One contributed adapter, with the module that contributed it. */
53
+ export interface ShippingAdapterEntry {
54
+ readonly adapter: ShippingAdapter;
55
+ readonly module: string;
56
+ }
57
+ /**
58
+ * `implements` the shape feature 075's Phase P published, which is what stops
59
+ * the registry and its contract drifting: `orders` and `shipments` read the
60
+ * published type, and `tsc` refuses the day a method here stops matching.
61
+ */
62
+ export declare class ShippingAdapterRegistry implements ShippingAdapterRegistryPort {
63
+ private readonly log;
64
+ private readonly isModulePresent;
65
+ private readonly entries;
66
+ constructor(log?: RegistryLogger, isModulePresent?: (moduleId: string) => boolean);
67
+ register(adapter: ShippingAdapter, moduleId: string): void;
68
+ unregister(adapterKey: string): void;
69
+ /** Whether any module has contributed this key — presence-blind. */
70
+ isRegistered(adapterKey: string): boolean;
71
+ /** The contributed entry, presence-blind. Admin and diagnostics read this. */
72
+ entry(adapterKey: string): ShippingAdapterEntry | undefined;
73
+ /** The module that contributed `adapterKey`, or `null` when nobody did. */
74
+ ownerOf(adapterKey: string): string | null;
75
+ /**
76
+ * The module that contributed `adapterKey` and is **not** effectively
77
+ * present, or `null` when the adapter is available or was never contributed.
78
+ *
79
+ * The fourth reader, and the only one that answers the question instead of
80
+ * exposing the table. `get()` returns `undefined` for two situations an
81
+ * operator cannot act on identically — a key nobody ever contributed, and a
82
+ * key whose carrier module is switched off — so `shipments` asks this one to
83
+ * tell them apart before it decides what state to open the row in (issue
84
+ * #250). Its payment twin is `GatewayRefundRegistry.absentOwnerFor` (D-71).
85
+ */
86
+ absentOwnerFor(adapterKey: string): string | null;
87
+ /** Registered AND its owner effectively present. */
88
+ isAvailable(adapterKey: string): boolean;
89
+ /** The adapter, or `undefined` when unregistered or its owner is absent. */
90
+ get(adapterKey: string): ShippingAdapter | undefined;
91
+ /** The adapter, or a throw — see the payment twin for the two cases. */
92
+ resolve(adapterKey: string): ShippingAdapter;
93
+ /** Adapter keys whose owner is present (stable insertion order). */
94
+ list(): string[];
95
+ /** Every registered adapter key, presence-blind. */
96
+ listAll(): string[];
97
+ }
98
+ //# sourceMappingURL=shipping-adapter-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipping-adapter-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shipping-adapter-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAG/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAMD,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,2BAA2B;IAIvE,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAJlC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;gBAGhD,GAAG,GAAE,cAA8B,EACnC,eAAe,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAoB;IAG9E,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAW1D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIpC,oEAAoE;IACpE,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIzC,8EAA8E;IAC9E,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS;IAI3D,2EAA2E;IAC3E,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAI1C;;;;;;;;;;OAUG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMjD,oDAAoD;IACpD,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAKxC,4EAA4E;IAC5E,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAMpD,wEAAwE;IACxE,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe;IAW5C,oEAAoE;IACpE,IAAI,IAAI,MAAM,EAAE;IAMhB,oDAAoD;IACpD,OAAO,IAAI,MAAM,EAAE;CAGpB"}
@@ -0,0 +1,92 @@
1
+ import { ModuleDisabledError } from '@endora-commerce/platform/kernel';
2
+ const consoleLogger = {
3
+ warn: (message) => console.warn(message),
4
+ };
5
+ /**
6
+ * `implements` the shape feature 075's Phase P published, which is what stops
7
+ * the registry and its contract drifting: `orders` and `shipments` read the
8
+ * published type, and `tsc` refuses the day a method here stops matching.
9
+ */
10
+ export class ShippingAdapterRegistry {
11
+ log;
12
+ isModulePresent;
13
+ entries = new Map();
14
+ constructor(log = consoleLogger, isModulePresent = () => true) {
15
+ this.log = log;
16
+ this.isModulePresent = isModulePresent;
17
+ }
18
+ register(adapter, moduleId) {
19
+ const existing = this.entries.get(adapter.adapterKey);
20
+ if (existing && existing.module !== moduleId) {
21
+ this.log.warn(`ShippingAdapterRegistry: adapter "${adapter.adapterKey}" re-registered by ` +
22
+ `module "${moduleId}" (was "${existing.module}"); overwriting previous registration.`);
23
+ }
24
+ this.entries.set(adapter.adapterKey, { adapter, module: moduleId });
25
+ }
26
+ unregister(adapterKey) {
27
+ this.entries.delete(adapterKey);
28
+ }
29
+ /** Whether any module has contributed this key — presence-blind. */
30
+ isRegistered(adapterKey) {
31
+ return this.entries.has(adapterKey);
32
+ }
33
+ /** The contributed entry, presence-blind. Admin and diagnostics read this. */
34
+ entry(adapterKey) {
35
+ return this.entries.get(adapterKey);
36
+ }
37
+ /** The module that contributed `adapterKey`, or `null` when nobody did. */
38
+ ownerOf(adapterKey) {
39
+ return this.entries.get(adapterKey)?.module ?? null;
40
+ }
41
+ /**
42
+ * The module that contributed `adapterKey` and is **not** effectively
43
+ * present, or `null` when the adapter is available or was never contributed.
44
+ *
45
+ * The fourth reader, and the only one that answers the question instead of
46
+ * exposing the table. `get()` returns `undefined` for two situations an
47
+ * operator cannot act on identically — a key nobody ever contributed, and a
48
+ * key whose carrier module is switched off — so `shipments` asks this one to
49
+ * tell them apart before it decides what state to open the row in (issue
50
+ * #250). Its payment twin is `GatewayRefundRegistry.absentOwnerFor` (D-71).
51
+ */
52
+ absentOwnerFor(adapterKey) {
53
+ const entry = this.entries.get(adapterKey);
54
+ if (!entry || this.isModulePresent(entry.module))
55
+ return null;
56
+ return entry.module;
57
+ }
58
+ /** Registered AND its owner effectively present. */
59
+ isAvailable(adapterKey) {
60
+ const entry = this.entries.get(adapterKey);
61
+ return entry !== undefined && this.isModulePresent(entry.module);
62
+ }
63
+ /** The adapter, or `undefined` when unregistered or its owner is absent. */
64
+ get(adapterKey) {
65
+ const entry = this.entries.get(adapterKey);
66
+ if (!entry || !this.isModulePresent(entry.module))
67
+ return undefined;
68
+ return entry.adapter;
69
+ }
70
+ /** The adapter, or a throw — see the payment twin for the two cases. */
71
+ resolve(adapterKey) {
72
+ const entry = this.entries.get(adapterKey);
73
+ if (!entry) {
74
+ throw new Error(`ShippingAdapterRegistry: no adapter registered for key "${adapterKey}".`);
75
+ }
76
+ if (!this.isModulePresent(entry.module)) {
77
+ throw new ModuleDisabledError(entry.module);
78
+ }
79
+ return entry.adapter;
80
+ }
81
+ /** Adapter keys whose owner is present (stable insertion order). */
82
+ list() {
83
+ return [...this.entries.entries()]
84
+ .filter(([, entry]) => this.isModulePresent(entry.module))
85
+ .map(([key]) => key);
86
+ }
87
+ /** Every registered adapter key, presence-blind. */
88
+ listAll() {
89
+ return [...this.entries.keys()];
90
+ }
91
+ }
92
+ //# sourceMappingURL=shipping-adapter-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipping-adapter-registry.js","sourceRoot":"","sources":["../../../src/backend/services/shipping-adapter-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { ShippingSurface } from '@endora-commerce/contracts';
2
+ import type { DeliveryMethod } from '../entities/delivery-method.entity.js';
3
+ import type { ShippingAdapterRegistry } from './shipping-adapter-registry.js';
4
+ /**
5
+ * ShippingMethodEligibilityService (feature 035, FR-011/FR-012/FR-013/FR-014).
6
+ *
7
+ * Given the active methods (already filtered by status and the per-Organization
8
+ * allow-list at the route), keeps only those whose adapter is currently
9
+ * registered (FR-003 — a disabled adapter drops out) and whose surface
10
+ * validator returns true. Sales-channel-assignment filtering is applied by the
11
+ * caller via the membership bridge.
12
+ */
13
+ export interface ShippingEligibilityContext {
14
+ salesChannelId: string | null;
15
+ organizationId: string | null;
16
+ customerAccountId: string | null;
17
+ surface: ShippingSurface;
18
+ }
19
+ export declare class ShippingMethodEligibilityService {
20
+ private readonly registry;
21
+ constructor(registry: ShippingAdapterRegistry);
22
+ filter(methods: DeliveryMethod[], ctx: ShippingEligibilityContext): Promise<DeliveryMethod[]>;
23
+ }
24
+ //# sourceMappingURL=shipping-method-eligibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipping-method-eligibility.d.ts","sourceRoot":"","sources":["../../../src/backend/services/shipping-method-eligibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,qBAAa,gCAAgC;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,uBAAuB;IAExD,MAAM,CACV,OAAO,EAAE,cAAc,EAAE,EACzB,GAAG,EAAE,0BAA0B,GAC9B,OAAO,CAAC,cAAc,EAAE,CAAC;CAuC7B"}