@aglyn/aglyn 1.0.0-beta.230 → 1.0.0-beta.231

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 (112) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/crm.d.ts +14 -1
  3. package/src/lib/app-utils/crm.js +19 -2
  4. package/src/lib/app-utils/crm.js.map +1 -1
  5. package/src/lib/app-utils/docs-help.generated.d.ts +80 -2
  6. package/src/lib/app-utils/docs-help.generated.js +209 -1
  7. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  8. package/src/lib/app-utils/docs-index.generated.js +517 -20
  9. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  10. package/src/lib/app-utils/host-status.d.ts +85 -0
  11. package/src/lib/app-utils/host-status.js +115 -0
  12. package/src/lib/app-utils/host-status.js.map +1 -0
  13. package/src/lib/app-utils/lockdown.js +1 -1
  14. package/src/lib/app-utils/lockdown.js.map +1 -1
  15. package/src/lib/app-utils/media-filter.d.ts +136 -0
  16. package/src/lib/app-utils/media-filter.js +400 -0
  17. package/src/lib/app-utils/media-filter.js.map +1 -0
  18. package/src/lib/app-utils/mobile-push.d.ts +98 -0
  19. package/src/lib/app-utils/mobile-push.js +97 -0
  20. package/src/lib/app-utils/mobile-push.js.map +1 -0
  21. package/src/lib/app-utils/notification-push.d.ts +38 -0
  22. package/src/lib/app-utils/notification-push.js +54 -0
  23. package/src/lib/app-utils/notification-push.js.map +1 -0
  24. package/src/lib/app-utils/notifications.d.ts +7 -0
  25. package/src/lib/app-utils/notifications.js.map +1 -1
  26. package/src/lib/app-utils/organizations.js +5 -2
  27. package/src/lib/app-utils/organizations.js.map +1 -1
  28. package/src/lib/app-utils/plugin-host-events.generated.d.ts +1 -1
  29. package/src/lib/app-utils/plugin-host-events.generated.js +149 -0
  30. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -1
  31. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
  32. package/src/lib/app-utils/plugin-release-flags.generated.js +7 -0
  33. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
  34. package/src/lib/app-utils/realm-host-surface.generated.js +3 -0
  35. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  36. package/src/lib/app-utils/release-flags.js +6 -2
  37. package/src/lib/app-utils/release-flags.js.map +1 -1
  38. package/src/lib/app-utils/scope-tokens.d.ts +16 -1
  39. package/src/lib/app-utils/scope-tokens.js +15 -1
  40. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  41. package/src/lib/app-utils/site-list-query.d.ts +47 -0
  42. package/src/lib/app-utils/site-list-query.js +142 -0
  43. package/src/lib/app-utils/site-list-query.js.map +1 -0
  44. package/src/lib/app-utils/site-wide-outbox.d.ts +95 -0
  45. package/src/lib/app-utils/site-wide-outbox.js +117 -0
  46. package/src/lib/app-utils/site-wide-outbox.js.map +1 -0
  47. package/src/lib/app-utils/transfer-launcher-context.d.ts +6 -0
  48. package/src/lib/app-utils/transfer-launcher-context.js.map +1 -1
  49. package/src/lib/app-utils/upload-inspection.js +7 -0
  50. package/src/lib/app-utils/upload-inspection.js.map +1 -1
  51. package/src/lib/app-utils/webhook-delivery.js +4 -1
  52. package/src/lib/app-utils/webhook-delivery.js.map +1 -1
  53. package/src/lib/foundation/definitions/org-billing.types.d.ts +8 -0
  54. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  55. package/src/lib/foundation/definitions/organization.types.d.ts +18 -7
  56. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  57. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +4 -1
  58. package/src/lib/foundation/definitions/write-deny-coverage.util.js +12 -2
  59. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  60. package/src/lib/plugin-manager/enabled-plugins.js +4 -2
  61. package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
  62. package/src/lib/plugin-manager/feature-plugins.d.ts +88 -0
  63. package/src/lib/plugin-manager/feature-plugins.js +44 -0
  64. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  65. package/src/lib/plugin-manager/first-party-plugins.generated.js +222 -2
  66. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  67. package/src/lib/plugin-manager/plugin-checkout-extras.d.ts +168 -0
  68. package/src/lib/plugin-manager/plugin-checkout-extras.js +172 -0
  69. package/src/lib/plugin-manager/plugin-checkout-extras.js.map +1 -0
  70. package/src/lib/plugin-manager/plugin-contributions.d.ts +7 -0
  71. package/src/lib/plugin-manager/plugin-contributions.js +1 -1
  72. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  73. package/src/lib/plugin-manager/plugin-domain-events.d.ts +138 -0
  74. package/src/lib/plugin-manager/plugin-domain-events.js +148 -0
  75. package/src/lib/plugin-manager/plugin-domain-events.js.map +1 -0
  76. package/src/lib/plugin-manager/plugin-events.d.ts +30 -0
  77. package/src/lib/plugin-manager/plugin-events.js +4 -0
  78. package/src/lib/plugin-manager/plugin-events.js.map +1 -1
  79. package/src/lib/plugin-manager/plugin-fulfillment-providers.d.ts +101 -0
  80. package/src/lib/plugin-manager/plugin-fulfillment-providers.js +83 -0
  81. package/src/lib/plugin-manager/plugin-fulfillment-providers.js.map +1 -0
  82. package/src/lib/plugin-manager/plugin-permissions.js +21 -5
  83. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
  84. package/src/lib/plugin-manager/plugin-person-records.d.ts +90 -0
  85. package/src/lib/plugin-manager/plugin-person-records.js +26 -0
  86. package/src/lib/plugin-manager/plugin-person-records.js.map +1 -1
  87. package/src/lib/plugin-manager/plugin-product-catalog.d.ts +204 -0
  88. package/src/lib/plugin-manager/plugin-product-catalog.js +43 -0
  89. package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -0
  90. package/src/lib/plugin-manager/plugin-shipment-records.d.ts +210 -0
  91. package/src/lib/plugin-manager/plugin-shipment-records.js +63 -0
  92. package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -0
  93. package/src/lib/plugin-manager/plugin-shipping-rates.d.ts +151 -0
  94. package/src/lib/plugin-manager/plugin-shipping-rates.js +62 -0
  95. package/src/lib/plugin-manager/plugin-shipping-rates.js.map +1 -0
  96. package/src/lib/plugin-manager/plugin-sms-messaging.d.ts +103 -0
  97. package/src/lib/plugin-manager/plugin-sms-messaging.js +39 -0
  98. package/src/lib/plugin-manager/plugin-sms-messaging.js.map +1 -0
  99. package/src/lib/plugin-manager/plugin-stock-levels.d.ts +81 -0
  100. package/src/lib/plugin-manager/plugin-stock-levels.js +32 -0
  101. package/src/lib/plugin-manager/plugin-stock-levels.js.map +1 -0
  102. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +154 -0
  103. package/src/lib/plugin-manager/plugin-tax-profile.js +56 -0
  104. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  105. package/src/lib/plugin-manager/plugin-theme-font-catalog.d.ts +59 -0
  106. package/src/lib/plugin-manager/plugin-theme-font-catalog.js +40 -0
  107. package/src/lib/plugin-manager/plugin-theme-font-catalog.js.map +1 -0
  108. package/src/lib/plugin-manager/plugin-tracking-pages.d.ts +55 -0
  109. package/src/lib/plugin-manager/plugin-tracking-pages.js +76 -0
  110. package/src/lib/plugin-manager/plugin-tracking-pages.js.map +1 -0
  111. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +3 -0
  112. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginShippingAddress } from './plugin-shipping-rates';
18
+ /**
19
+ * What was sold and shipped, kept by the plugin that sold it (AGL-3612).
20
+ *
21
+ * A plugin that buys a carrier label needs three things from the seller of
22
+ * the goods, and must not read the seller's documents for any of them: what
23
+ * a record holds that still has to ship (lines, weights, the address), a way
24
+ * to write a shipment back onto it once the label exists, and a way to tell
25
+ * it what the carrier says has happened since. The seller registers
26
+ * {@link PluginShipmentRecords}; the label buyer asks it. The seller's own
27
+ * transition rules, its timeline and the emails its customers get all stay
28
+ * inside the seller's write, so a label bought here and a parcel shipped by
29
+ * hand land the same way.
30
+ *
31
+ * The words are a seller's and a carrier's, never one catalog's: a record is
32
+ * anything with lines that ship — an order, a rental, a subscription box.
33
+ *
34
+ * ## Shipments announced
35
+ *
36
+ * A seller also announces each shipment it records, however it was recorded
37
+ * — a label bought here, or a carrier and number typed by hand — so a plugin
38
+ * that follows parcels can start following that one. Listeners run after the
39
+ * write; one that throws is logged and never undoes it.
40
+ *
41
+ * Import this module by its own subpath
42
+ * (`@aglyn/aglyn/plugin-manager/plugin-shipment-records`); it is not in the
43
+ * barrel.
44
+ */
45
+ /** One line of a record, as a shipper needs it. */
46
+ export interface PluginShipmentLine {
47
+ /** The line's position on the record: the id a write names it by. */
48
+ lineIndex: number;
49
+ name: string;
50
+ sku?: string;
51
+ quantity: number;
52
+ /** Units of the line not yet on any shipment. */
53
+ quantityUnshipped: number;
54
+ /** Per unit, in the record's currency. */
55
+ unitValueCents: number;
56
+ /** Per unit; absent when the seller does not know it. */
57
+ weightGrams?: number;
58
+ lengthCm?: number;
59
+ widthCm?: number;
60
+ heightCm?: number;
61
+ /** Harmonized System code, for customs. */
62
+ hsCode?: string;
63
+ /** ISO-3166 alpha-2 country the goods were made in, for customs. */
64
+ originCountry?: string;
65
+ }
66
+ /** A shipment already on the record. */
67
+ export interface PluginRecordedShipment {
68
+ id: string;
69
+ carrier?: string;
70
+ trackingNumber?: string;
71
+ trackingUrl?: string;
72
+ /** The label it was shipped under, when a label buyer wrote it. */
73
+ labelRef?: string;
74
+ lineIndexes: number[];
75
+ atMs: number;
76
+ }
77
+ /** A record with lines that ship. */
78
+ export interface PluginShippableRecord {
79
+ hostId: string;
80
+ recordId: string;
81
+ /** What a person calls it: `#1042`. */
82
+ displayRef: string;
83
+ /** The seller's own status word, shown as is. */
84
+ status: string;
85
+ /** Whether the seller would accept a shipment on it now. */
86
+ shippable: boolean;
87
+ /** ISO-4217, lower case. */
88
+ currency: string;
89
+ shipTo?: PluginShippingAddress;
90
+ customerEmail?: string;
91
+ lines: PluginShipmentLine[];
92
+ shipments: PluginRecordedShipment[];
93
+ /** The rate the customer chose at checkout, when it was a carrier's. */
94
+ chosenService?: {
95
+ serviceKey?: string;
96
+ label?: string;
97
+ amountCents?: number;
98
+ };
99
+ createdAtMs?: number;
100
+ /**
101
+ * Whether the record was paid in a payment provider's test mode (AGL-3634):
102
+ * no money moved, so nothing real may be sent for it. Absent when the
103
+ * seller does not know, which reads as live.
104
+ */
105
+ testMode?: boolean;
106
+ }
107
+ /** A shipment to write. */
108
+ export interface PluginShipmentWrite {
109
+ hostId: string;
110
+ recordId: string;
111
+ /** What is in the parcel; absent means every unit not yet shipped. */
112
+ lines?: Array<{
113
+ lineIndex: number;
114
+ quantity: number;
115
+ }>;
116
+ carrier: string;
117
+ trackingNumber: string;
118
+ trackingUrl?: string;
119
+ /** The label's file, an https URL the seller may show beside the shipment. */
120
+ labelUrl?: string;
121
+ labelRef?: string;
122
+ /** The member who bought the label, for the record's timeline. */
123
+ actorUid?: string;
124
+ }
125
+ /**
126
+ * What a write did. `already` is a success: the same label was written
127
+ * before, so a retry cannot add a second shipment.
128
+ */
129
+ export type PluginShipmentWriteOutcome = {
130
+ outcome: 'recorded';
131
+ shipmentId: string;
132
+ } | {
133
+ outcome: 'already';
134
+ shipmentId?: string;
135
+ } | {
136
+ outcome: 'no_such_record';
137
+ }
138
+ /** The seller's rules refused; `from` is the status that refused. */
139
+ | {
140
+ outcome: 'blocked';
141
+ from: string;
142
+ reason?: string;
143
+ };
144
+ /** Where a parcel is, in a carrier-neutral word. */
145
+ export type PluginTrackingStatus = 'pre_transit' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'exception' | 'returned';
146
+ export declare const PLUGIN_TRACKING_STATUSES: readonly PluginTrackingStatus[];
147
+ /** What a carrier says happened to one parcel. */
148
+ export interface PluginTrackingUpdate {
149
+ hostId: string;
150
+ recordId: string;
151
+ trackingNumber: string;
152
+ status: PluginTrackingStatus;
153
+ /** The carrier's own words for the event. */
154
+ detail?: string;
155
+ atMs: number;
156
+ }
157
+ export type PluginTrackingOutcome = {
158
+ outcome: 'recorded';
159
+ }
160
+ /** The record already says this, so nothing was written. */
161
+ | {
162
+ outcome: 'unchanged';
163
+ } | {
164
+ outcome: 'no_such_record';
165
+ } | {
166
+ outcome: 'no_such_shipment';
167
+ };
168
+ /** A place a site ships from. */
169
+ export interface PluginShipFromAddress {
170
+ id: string;
171
+ name: string;
172
+ address: PluginShippingAddress;
173
+ }
174
+ export interface PluginShipmentRecords {
175
+ /** One record, or `null` when the site has none by that id. */
176
+ read(hostId: string, recordId: string): Promise<PluginShippableRecord | null>;
177
+ /** Writes a shipment under the seller's own rules, once per label. */
178
+ recordShipment(write: PluginShipmentWrite): Promise<PluginShipmentWriteOutcome>;
179
+ /** Records what the carrier says; `delivered` moves the record on as the seller does. */
180
+ recordTracking(update: PluginTrackingUpdate): Promise<PluginTrackingOutcome>;
181
+ /** The addresses the site keeps for where it ships from. */
182
+ shipFromAddresses(hostId: string): Promise<PluginShipFromAddress[]>;
183
+ }
184
+ export declare const PLUGIN_SHIPMENT_RECORDS: import("./plugin-services").PluginServiceContract<PluginShipmentRecords>;
185
+ /** Registers the seller. A second plugin is refused naming both. */
186
+ export declare function registerPluginShipmentRecords(records: PluginShipmentRecords, options?: {
187
+ pluginId?: string;
188
+ }): void;
189
+ /** The seller, or `null` when no plugin registered one. */
190
+ export declare function pluginShipmentRecords(): PluginShipmentRecords | null;
191
+ /** A shipment a seller recorded, by any door. */
192
+ export interface PluginShipmentAnnouncement {
193
+ hostId: string;
194
+ recordId: string;
195
+ shipmentId: string;
196
+ carrier?: string;
197
+ trackingNumber?: string;
198
+ /** Present when a label buyer wrote it; a listener that bought it already follows it. */
199
+ labelRef?: string;
200
+ }
201
+ export type PluginShipmentListener = (announcement: PluginShipmentAnnouncement) => void | Promise<void>;
202
+ /** Joins the listeners. Re-registering under the same plugin replaces its own. */
203
+ export declare function registerPluginShipmentListener(listener: PluginShipmentListener, options?: {
204
+ pluginId?: string;
205
+ }): void;
206
+ /**
207
+ * Tells every listener about a shipment the seller recorded. Awaited, each
208
+ * isolated: a listener that throws is logged and the rest still hear it.
209
+ */
210
+ export declare function announcePluginShipment(announcement: PluginShipmentAnnouncement): Promise<void>;
@@ -0,0 +1,63 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
18
+ import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
19
+ export const PLUGIN_TRACKING_STATUSES = [
20
+ 'pre_transit',
21
+ 'in_transit',
22
+ 'out_for_delivery',
23
+ 'delivered',
24
+ 'exception',
25
+ 'returned'
26
+ ];
27
+ export const PLUGIN_SHIPMENT_RECORDS = definePluginServiceContract('core.shipment-records', {
28
+ multiple: false
29
+ });
30
+ /** Registers the seller. A second plugin is refused naming both. */ export function registerPluginShipmentRecords(records, options) {
31
+ registerPluginService(PLUGIN_SHIPMENT_RECORDS, records, _extends({}, (options == null ? void 0 : options.pluginId) ? {
32
+ pluginId: options.pluginId
33
+ } : {}));
34
+ }
35
+ /** The seller, or `null` when no plugin registered one. */ export function pluginShipmentRecords() {
36
+ var _ref;
37
+ var _resolvePluginServices_;
38
+ return (_ref = (_resolvePluginServices_ = resolvePluginServices(PLUGIN_SHIPMENT_RECORDS)[0]) == null ? void 0 : _resolvePluginServices_.impl) != null ? _ref : null;
39
+ }
40
+ const PLUGIN_SHIPMENT_LISTENERS = definePluginServiceContract('core.shipment-listeners', {
41
+ multiple: true
42
+ });
43
+ /** Joins the listeners. Re-registering under the same plugin replaces its own. */ export function registerPluginShipmentListener(listener, options) {
44
+ var _getRegisteringPluginId;
45
+ const pluginId = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId;
46
+ registerPluginService(PLUGIN_SHIPMENT_LISTENERS, listener, _extends({}, pluginId ? {
47
+ pluginId
48
+ } : {}));
49
+ }
50
+ /**
51
+ * Tells every listener about a shipment the seller recorded. Awaited, each
52
+ * isolated: a listener that throws is logged and the rest still hear it.
53
+ */ export async function announcePluginShipment(announcement) {
54
+ for (const entry of resolvePluginServices(PLUGIN_SHIPMENT_LISTENERS)){
55
+ try {
56
+ await entry.impl(announcement);
57
+ } catch (error) {
58
+ console.error(`[shipments] listener "${entry.pluginId}" failed for ${announcement.hostId}/${announcement.recordId}`, error);
59
+ }
60
+ }
61
+ }
62
+
63
+ //# sourceMappingURL=plugin-shipment-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-shipment-records.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport type { PluginShippingAddress } from './plugin-shipping-rates'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * What was sold and shipped, kept by the plugin that sold it (AGL-3612).\n *\n * A plugin that buys a carrier label needs three things from the seller of\n * the goods, and must not read the seller's documents for any of them: what\n * a record holds that still has to ship (lines, weights, the address), a way\n * to write a shipment back onto it once the label exists, and a way to tell\n * it what the carrier says has happened since. The seller registers\n * {@link PluginShipmentRecords}; the label buyer asks it. The seller's own\n * transition rules, its timeline and the emails its customers get all stay\n * inside the seller's write, so a label bought here and a parcel shipped by\n * hand land the same way.\n *\n * The words are a seller's and a carrier's, never one catalog's: a record is\n * anything with lines that ship — an order, a rental, a subscription box.\n *\n * ## Shipments announced\n *\n * A seller also announces each shipment it records, however it was recorded\n * — a label bought here, or a carrier and number typed by hand — so a plugin\n * that follows parcels can start following that one. Listeners run after the\n * write; one that throws is logged and never undoes it.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-shipment-records`); it is not in the\n * barrel.\n */\n\n/** One line of a record, as a shipper needs it. */\nexport interface PluginShipmentLine {\n /** The line's position on the record: the id a write names it by. */\n lineIndex: number\n name: string\n sku?: string\n quantity: number\n /** Units of the line not yet on any shipment. */\n quantityUnshipped: number\n /** Per unit, in the record's currency. */\n unitValueCents: number\n /** Per unit; absent when the seller does not know it. */\n weightGrams?: number\n lengthCm?: number\n widthCm?: number\n heightCm?: number\n /** Harmonized System code, for customs. */\n hsCode?: string\n /** ISO-3166 alpha-2 country the goods were made in, for customs. */\n originCountry?: string\n}\n\n/** A shipment already on the record. */\nexport interface PluginRecordedShipment {\n id: string\n carrier?: string\n trackingNumber?: string\n trackingUrl?: string\n /** The label it was shipped under, when a label buyer wrote it. */\n labelRef?: string\n lineIndexes: number[]\n atMs: number\n}\n\n/** A record with lines that ship. */\nexport interface PluginShippableRecord {\n hostId: string\n recordId: string\n /** What a person calls it: `#1042`. */\n displayRef: string\n /** The seller's own status word, shown as is. */\n status: string\n /** Whether the seller would accept a shipment on it now. */\n shippable: boolean\n /** ISO-4217, lower case. */\n currency: string\n shipTo?: PluginShippingAddress\n customerEmail?: string\n lines: PluginShipmentLine[]\n shipments: PluginRecordedShipment[]\n /** The rate the customer chose at checkout, when it was a carrier's. */\n chosenService?: { serviceKey?: string; label?: string; amountCents?: number }\n createdAtMs?: number\n /**\n * Whether the record was paid in a payment provider's test mode (AGL-3634):\n * no money moved, so nothing real may be sent for it. Absent when the\n * seller does not know, which reads as live.\n */\n testMode?: boolean\n}\n\n/** A shipment to write. */\nexport interface PluginShipmentWrite {\n hostId: string\n recordId: string\n /** What is in the parcel; absent means every unit not yet shipped. */\n lines?: Array<{ lineIndex: number; quantity: number }>\n carrier: string\n trackingNumber: string\n trackingUrl?: string\n /** The label's file, an https URL the seller may show beside the shipment. */\n labelUrl?: string\n labelRef?: string\n /** The member who bought the label, for the record's timeline. */\n actorUid?: string\n}\n\n/**\n * What a write did. `already` is a success: the same label was written\n * before, so a retry cannot add a second shipment.\n */\nexport type PluginShipmentWriteOutcome =\n | { outcome: 'recorded'; shipmentId: string }\n | { outcome: 'already'; shipmentId?: string }\n | { outcome: 'no_such_record' }\n /** The seller's rules refused; `from` is the status that refused. */\n | { outcome: 'blocked'; from: string; reason?: string }\n\n/** Where a parcel is, in a carrier-neutral word. */\nexport type PluginTrackingStatus =\n | 'pre_transit'\n | 'in_transit'\n | 'out_for_delivery'\n | 'delivered'\n | 'exception'\n | 'returned'\n\nexport const PLUGIN_TRACKING_STATUSES: readonly PluginTrackingStatus[] = [\n 'pre_transit',\n 'in_transit',\n 'out_for_delivery',\n 'delivered',\n 'exception',\n 'returned',\n]\n\n/** What a carrier says happened to one parcel. */\nexport interface PluginTrackingUpdate {\n hostId: string\n recordId: string\n trackingNumber: string\n status: PluginTrackingStatus\n /** The carrier's own words for the event. */\n detail?: string\n atMs: number\n}\n\nexport type PluginTrackingOutcome =\n | { outcome: 'recorded' }\n /** The record already says this, so nothing was written. */\n | { outcome: 'unchanged' }\n | { outcome: 'no_such_record' }\n | { outcome: 'no_such_shipment' }\n\n/** A place a site ships from. */\nexport interface PluginShipFromAddress {\n id: string\n name: string\n address: PluginShippingAddress\n}\n\nexport interface PluginShipmentRecords {\n /** One record, or `null` when the site has none by that id. */\n read(hostId: string, recordId: string): Promise<PluginShippableRecord | null>\n /** Writes a shipment under the seller's own rules, once per label. */\n recordShipment(write: PluginShipmentWrite): Promise<PluginShipmentWriteOutcome>\n /** Records what the carrier says; `delivered` moves the record on as the seller does. */\n recordTracking(update: PluginTrackingUpdate): Promise<PluginTrackingOutcome>\n /** The addresses the site keeps for where it ships from. */\n shipFromAddresses(hostId: string): Promise<PluginShipFromAddress[]>\n}\n\nexport const PLUGIN_SHIPMENT_RECORDS =\n definePluginServiceContract<PluginShipmentRecords>('core.shipment-records', {\n multiple: false,\n })\n\n/** Registers the seller. A second plugin is refused naming both. */\nexport function registerPluginShipmentRecords(\n records: PluginShipmentRecords,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_SHIPMENT_RECORDS, records, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The seller, or `null` when no plugin registered one. */\nexport function pluginShipmentRecords(): PluginShipmentRecords | null {\n return resolvePluginServices(PLUGIN_SHIPMENT_RECORDS)[0]?.impl ?? null\n}\n\n/** A shipment a seller recorded, by any door. */\nexport interface PluginShipmentAnnouncement {\n hostId: string\n recordId: string\n shipmentId: string\n carrier?: string\n trackingNumber?: string\n /** Present when a label buyer wrote it; a listener that bought it already follows it. */\n labelRef?: string\n}\n\nexport type PluginShipmentListener = (\n announcement: PluginShipmentAnnouncement,\n) => void | Promise<void>\n\nconst PLUGIN_SHIPMENT_LISTENERS = definePluginServiceContract<PluginShipmentListener>(\n 'core.shipment-listeners',\n { multiple: true },\n)\n\n/** Joins the listeners. Re-registering under the same plugin replaces its own. */\nexport function registerPluginShipmentListener(\n listener: PluginShipmentListener,\n options?: { pluginId?: string },\n): void {\n const pluginId = getRegisteringPluginId() ?? options?.pluginId\n registerPluginService(PLUGIN_SHIPMENT_LISTENERS, listener, {\n ...(pluginId ? { pluginId } : {}),\n })\n}\n\n/**\n * Tells every listener about a shipment the seller recorded. Awaited, each\n * isolated: a listener that throws is logged and the rest still hear it.\n */\nexport async function announcePluginShipment(\n announcement: PluginShipmentAnnouncement,\n): Promise<void> {\n for (const entry of resolvePluginServices(PLUGIN_SHIPMENT_LISTENERS)) {\n try {\n await entry.impl(announcement)\n } catch (error) {\n console.error(\n `[shipments] listener \"${entry.pluginId}\" failed for ${announcement.hostId}/${announcement.recordId}`,\n error,\n )\n }\n }\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_TRACKING_STATUSES","PLUGIN_SHIPMENT_RECORDS","multiple","registerPluginShipmentRecords","records","options","pluginId","pluginShipmentRecords","impl","PLUGIN_SHIPMENT_LISTENERS","registerPluginShipmentListener","listener","announcePluginShipment","announcement","entry","error","console","hostId","recordId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAExE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA+H1B,OAAO,MAAMC,2BAA4D;IACvE;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAsCD,OAAO,MAAMC,0BACXJ,4BAAmD,yBAAyB;IAC1EK,UAAU;AACZ,GAAE;AAEJ,kEAAkE,GAClE,OAAO,SAASC,8BACdC,OAA8B,EAC9BC,OAA+B;IAE/BP,sBAAsBG,yBAAyBG,SAAS,aAClDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yDAAyD,GACzD,OAAO,SAASC;;QACPR;IAAP,gBAAOA,0BAAAA,sBAAsBE,wBAAwB,CAAC,EAAE,qBAAjDF,wBAAmDS,IAAI,mBAAI;AACpE;AAiBA,MAAMC,4BAA4BZ,4BAChC,2BACA;IAAEK,UAAU;AAAK;AAGnB,gFAAgF,GAChF,OAAO,SAASQ,+BACdC,QAAgC,EAChCN,OAA+B;QAEdT;IAAjB,MAAMU,YAAWV,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASC,QAAQ;IAC9DR,sBAAsBW,2BAA2BE,UAAU,aACrDL,WAAW;QAAEA;IAAS,IAAI,CAAC;AAEnC;AAEA;;;CAGC,GACD,OAAO,eAAeM,uBACpBC,YAAwC;IAExC,KAAK,MAAMC,SAASf,sBAAsBU,2BAA4B;QACpE,IAAI;YACF,MAAMK,MAAMN,IAAI,CAACK;QACnB,EAAE,OAAOE,OAAO;YACdC,QAAQD,KAAK,CACX,CAAC,sBAAsB,EAAED,MAAMR,QAAQ,CAAC,aAAa,EAAEO,aAAaI,MAAM,CAAC,CAAC,EAAEJ,aAAaK,QAAQ,EAAE,EACrGH;QAEJ;IACF;AACF"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Live parcel rates, answered by the one plugin that talks to carriers
19
+ * (AGL-3612).
20
+ *
21
+ * A plugin that sells goods prices delivery from its own table until the
22
+ * merchant asks for live rates; then it needs a carrier's quote for an
23
+ * address and a parcel, and it must not learn which carrier network, which
24
+ * account or which vendor answers. A plugin that buys labels registers here;
25
+ * a plugin that sells asks here, and neither imports the other.
26
+ *
27
+ * ## The shape is a parcel and an address, nothing more
28
+ *
29
+ * Every type below is postal: where it goes, what it weighs, how big it is,
30
+ * and what it is worth for insurance and customs. Nothing names a catalog, an
31
+ * order or a store, so a booking plugin shipping a rental kit asks the same
32
+ * question in the same words.
33
+ *
34
+ * ## Nobody home is `null`, never a refusal
35
+ *
36
+ * Unlike the tax profile, an absent quoter is a perfectly good answer: the
37
+ * seller falls back to its own rates, which is what it did before anyone
38
+ * registered. {@link pluginShippingRateQuoter} answers `null`, and a quoter
39
+ * whose deployment is not configured answers `available() === false`, which
40
+ * a caller must read the same way.
41
+ *
42
+ * ## A quote is advisory until a label is bought
43
+ *
44
+ * `amountCents` is what the carrier quoted the platform for that parcel at
45
+ * that moment, BEFORE any markup or handling the seller adds. The seller
46
+ * decides what the shopper pays; the quoter never does.
47
+ *
48
+ * Import this module by its own subpath
49
+ * (`@aglyn/aglyn/plugin-manager/plugin-shipping-rates`); it is not in the
50
+ * barrel.
51
+ */
52
+ /** A postal address. `country` is ISO-3166 alpha-2; everything else is optional. */
53
+ export interface PluginShippingAddress {
54
+ name?: string;
55
+ company?: string;
56
+ line1?: string;
57
+ line2?: string;
58
+ city?: string;
59
+ /** State, province or region code, as the destination writes it. */
60
+ state?: string;
61
+ postalCode?: string;
62
+ country: string;
63
+ phone?: string;
64
+ email?: string;
65
+ /** Whether the address is a home, when the caller knows; carriers price it. */
66
+ residential?: boolean;
67
+ }
68
+ /** One parcel. Metric throughout; an adapter converts to its vendor's units. */
69
+ export interface PluginShippingParcel {
70
+ weightGrams: number;
71
+ lengthCm?: number;
72
+ widthCm?: number;
73
+ heightCm?: number;
74
+ }
75
+ /** What a seller asks: one shipment's rates to one address. */
76
+ export interface PluginShippingQuoteRequest {
77
+ /** The site the shipment leaves from; the quoter resolves its ship-from address. */
78
+ hostId: string;
79
+ to: PluginShippingAddress;
80
+ parcels: PluginShippingParcel[];
81
+ /** ISO-4217, lower case, of the amounts the caller will charge in. */
82
+ currency: string;
83
+ /** The goods' value, for insurance and customs; 0 when unknown. */
84
+ valueCents: number;
85
+ /**
86
+ * Service keys the seller offers (`'usps:priority'`), as
87
+ * {@link PluginShippingRateQuoter.listServices} names them. Empty or
88
+ * absent means every service the quoter can price.
89
+ */
90
+ services?: string[];
91
+ /** Aborted when the caller stops waiting; a quoter passes it to its fetches. */
92
+ signal?: AbortSignal;
93
+ }
94
+ /** One carrier service's price for the shipment. */
95
+ export interface PluginShippingQuote {
96
+ /** Stable across quotes: `carrier:service`, the key a seller stores. */
97
+ serviceKey: string;
98
+ carrier: string;
99
+ service: string;
100
+ /** What a shopper reads: `'USPS Priority Mail'`. */
101
+ label: string;
102
+ /** The carrier's price to the platform, before any markup or handling. */
103
+ amountCents: number;
104
+ currency: string;
105
+ /** Transit estimate in business days, when the carrier gives one. */
106
+ estimatedDays?: number;
107
+ }
108
+ /** A service a seller may choose to offer. */
109
+ export interface PluginShippingService {
110
+ serviceKey: string;
111
+ carrier: string;
112
+ label: string;
113
+ }
114
+ /** What address validation answers. */
115
+ export interface PluginShippingAddressCheck {
116
+ /** `valid` deliverable as written; `corrected` deliverable as `suggested`; `invalid` not deliverable. */
117
+ verdict: 'valid' | 'corrected' | 'invalid' | 'unknown';
118
+ /** The address the carrier would deliver to, when it differs. */
119
+ suggested?: PluginShippingAddress;
120
+ /** Why it is not deliverable, in the carrier's words. */
121
+ messages: string[];
122
+ }
123
+ export interface PluginShippingRateQuoter {
124
+ /** Whether quotes can be asked for this site now: configured and switched on. */
125
+ available(hostId: string): Promise<boolean>;
126
+ /**
127
+ * The rates. Throws on a provider failure; a caller that cannot wait
128
+ * aborts `signal` and falls back to its own rates.
129
+ */
130
+ quote(request: PluginShippingQuoteRequest): Promise<PluginShippingQuote[]>;
131
+ /** The services a seller can pick from for this site. */
132
+ listServices(hostId: string): Promise<PluginShippingService[]>;
133
+ /** Whether an address is deliverable, and the carrier's correction. */
134
+ validateAddress?(hostId: string, address: PluginShippingAddress): Promise<PluginShippingAddressCheck>;
135
+ }
136
+ export declare const PLUGIN_SHIPPING_RATE_QUOTER: import("./plugin-services").PluginServiceContract<PluginShippingRateQuoter>;
137
+ /** Registers the plugin that quotes carrier rates. A second plugin is refused. */
138
+ export declare function registerPluginShippingRateQuoter(quoter: PluginShippingRateQuoter, options?: {
139
+ pluginId?: string;
140
+ }): void;
141
+ /** The quoter, or `null` when no plugin registered one. */
142
+ export declare function pluginShippingRateQuoter(): PluginShippingRateQuoter | null;
143
+ /**
144
+ * Asks the quoter and gives up after `timeoutMs`: `null` when nobody is
145
+ * registered, the site is not available, the provider failed, or the wait
146
+ * ran out. Never throws. The caller's fallback is its own rates, so every
147
+ * failure reads the same.
148
+ */
149
+ export declare function quotePluginShippingRates(request: Omit<PluginShippingQuoteRequest, 'signal'>, options: {
150
+ timeoutMs: number;
151
+ }): Promise<PluginShippingQuote[] | null>;
@@ -0,0 +1,62 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
18
+ export const PLUGIN_SHIPPING_RATE_QUOTER = definePluginServiceContract('core.shipping-rate-quoter', {
19
+ multiple: false
20
+ });
21
+ /** Registers the plugin that quotes carrier rates. A second plugin is refused. */ export function registerPluginShippingRateQuoter(quoter, options) {
22
+ registerPluginService(PLUGIN_SHIPPING_RATE_QUOTER, quoter, _extends({}, (options == null ? void 0 : options.pluginId) ? {
23
+ pluginId: options.pluginId
24
+ } : {}));
25
+ }
26
+ /** The quoter, or `null` when no plugin registered one. */ export function pluginShippingRateQuoter() {
27
+ var _ref;
28
+ var _resolvePluginServices_;
29
+ return (_ref = (_resolvePluginServices_ = resolvePluginServices(PLUGIN_SHIPPING_RATE_QUOTER)[0]) == null ? void 0 : _resolvePluginServices_.impl) != null ? _ref : null;
30
+ }
31
+ /**
32
+ * Asks the quoter and gives up after `timeoutMs`: `null` when nobody is
33
+ * registered, the site is not available, the provider failed, or the wait
34
+ * ran out. Never throws. The caller's fallback is its own rates, so every
35
+ * failure reads the same.
36
+ */ export async function quotePluginShippingRates(request, options) {
37
+ const quoter = pluginShippingRateQuoter();
38
+ if (!quoter) return null;
39
+ const controller = new AbortController();
40
+ let timer;
41
+ const deadline = new Promise((resolve)=>{
42
+ timer = setTimeout(()=>{
43
+ controller.abort();
44
+ resolve(null);
45
+ }, Math.max(0, options.timeoutMs));
46
+ });
47
+ try {
48
+ return await Promise.race([
49
+ (async ()=>{
50
+ if (!await quoter.available(request.hostId)) return null;
51
+ return quoter.quote(_extends({}, request, {
52
+ signal: controller.signal
53
+ }));
54
+ })().catch(()=>null),
55
+ deadline
56
+ ]);
57
+ } finally{
58
+ if (timer) clearTimeout(timer);
59
+ }
60
+ }
61
+
62
+ //# sourceMappingURL=plugin-shipping-rates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-shipping-rates.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * Live parcel rates, answered by the one plugin that talks to carriers\n * (AGL-3612).\n *\n * A plugin that sells goods prices delivery from its own table until the\n * merchant asks for live rates; then it needs a carrier's quote for an\n * address and a parcel, and it must not learn which carrier network, which\n * account or which vendor answers. A plugin that buys labels registers here;\n * a plugin that sells asks here, and neither imports the other.\n *\n * ## The shape is a parcel and an address, nothing more\n *\n * Every type below is postal: where it goes, what it weighs, how big it is,\n * and what it is worth for insurance and customs. Nothing names a catalog, an\n * order or a store, so a booking plugin shipping a rental kit asks the same\n * question in the same words.\n *\n * ## Nobody home is `null`, never a refusal\n *\n * Unlike the tax profile, an absent quoter is a perfectly good answer: the\n * seller falls back to its own rates, which is what it did before anyone\n * registered. {@link pluginShippingRateQuoter} answers `null`, and a quoter\n * whose deployment is not configured answers `available() === false`, which\n * a caller must read the same way.\n *\n * ## A quote is advisory until a label is bought\n *\n * `amountCents` is what the carrier quoted the platform for that parcel at\n * that moment, BEFORE any markup or handling the seller adds. The seller\n * decides what the shopper pays; the quoter never does.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-shipping-rates`); it is not in the\n * barrel.\n */\n\n/** A postal address. `country` is ISO-3166 alpha-2; everything else is optional. */\nexport interface PluginShippingAddress {\n name?: string\n company?: string\n line1?: string\n line2?: string\n city?: string\n /** State, province or region code, as the destination writes it. */\n state?: string\n postalCode?: string\n country: string\n phone?: string\n email?: string\n /** Whether the address is a home, when the caller knows; carriers price it. */\n residential?: boolean\n}\n\n/** One parcel. Metric throughout; an adapter converts to its vendor's units. */\nexport interface PluginShippingParcel {\n weightGrams: number\n lengthCm?: number\n widthCm?: number\n heightCm?: number\n}\n\n/** What a seller asks: one shipment's rates to one address. */\nexport interface PluginShippingQuoteRequest {\n /** The site the shipment leaves from; the quoter resolves its ship-from address. */\n hostId: string\n to: PluginShippingAddress\n parcels: PluginShippingParcel[]\n /** ISO-4217, lower case, of the amounts the caller will charge in. */\n currency: string\n /** The goods' value, for insurance and customs; 0 when unknown. */\n valueCents: number\n /**\n * Service keys the seller offers (`'usps:priority'`), as\n * {@link PluginShippingRateQuoter.listServices} names them. Empty or\n * absent means every service the quoter can price.\n */\n services?: string[]\n /** Aborted when the caller stops waiting; a quoter passes it to its fetches. */\n signal?: AbortSignal\n}\n\n/** One carrier service's price for the shipment. */\nexport interface PluginShippingQuote {\n /** Stable across quotes: `carrier:service`, the key a seller stores. */\n serviceKey: string\n carrier: string\n service: string\n /** What a shopper reads: `'USPS Priority Mail'`. */\n label: string\n /** The carrier's price to the platform, before any markup or handling. */\n amountCents: number\n currency: string\n /** Transit estimate in business days, when the carrier gives one. */\n estimatedDays?: number\n}\n\n/** A service a seller may choose to offer. */\nexport interface PluginShippingService {\n serviceKey: string\n carrier: string\n label: string\n}\n\n/** What address validation answers. */\nexport interface PluginShippingAddressCheck {\n /** `valid` deliverable as written; `corrected` deliverable as `suggested`; `invalid` not deliverable. */\n verdict: 'valid' | 'corrected' | 'invalid' | 'unknown'\n /** The address the carrier would deliver to, when it differs. */\n suggested?: PluginShippingAddress\n /** Why it is not deliverable, in the carrier's words. */\n messages: string[]\n}\n\nexport interface PluginShippingRateQuoter {\n /** Whether quotes can be asked for this site now: configured and switched on. */\n available(hostId: string): Promise<boolean>\n /**\n * The rates. Throws on a provider failure; a caller that cannot wait\n * aborts `signal` and falls back to its own rates.\n */\n quote(request: PluginShippingQuoteRequest): Promise<PluginShippingQuote[]>\n /** The services a seller can pick from for this site. */\n listServices(hostId: string): Promise<PluginShippingService[]>\n /** Whether an address is deliverable, and the carrier's correction. */\n validateAddress?(\n hostId: string,\n address: PluginShippingAddress,\n ): Promise<PluginShippingAddressCheck>\n}\n\nexport const PLUGIN_SHIPPING_RATE_QUOTER =\n definePluginServiceContract<PluginShippingRateQuoter>('core.shipping-rate-quoter', {\n multiple: false,\n })\n\n/** Registers the plugin that quotes carrier rates. A second plugin is refused. */\nexport function registerPluginShippingRateQuoter(\n quoter: PluginShippingRateQuoter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_SHIPPING_RATE_QUOTER, quoter, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The quoter, or `null` when no plugin registered one. */\nexport function pluginShippingRateQuoter(): PluginShippingRateQuoter | null {\n return resolvePluginServices(PLUGIN_SHIPPING_RATE_QUOTER)[0]?.impl ?? null\n}\n\n/**\n * Asks the quoter and gives up after `timeoutMs`: `null` when nobody is\n * registered, the site is not available, the provider failed, or the wait\n * ran out. Never throws. The caller's fallback is its own rates, so every\n * failure reads the same.\n */\nexport async function quotePluginShippingRates(\n request: Omit<PluginShippingQuoteRequest, 'signal'>,\n options: { timeoutMs: number },\n): Promise<PluginShippingQuote[] | null> {\n const quoter = pluginShippingRateQuoter()\n if (!quoter) return null\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<null>((resolve) => {\n timer = setTimeout(() => {\n controller.abort()\n resolve(null)\n }, Math.max(0, options.timeoutMs))\n })\n try {\n return await Promise.race([\n (async (): Promise<PluginShippingQuote[] | null> => {\n if (!(await quoter.available(request.hostId))) return null\n return quoter.quote({ ...request, signal: controller.signal })\n })().catch((): null => null),\n deadline,\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_SHIPPING_RATE_QUOTER","multiple","registerPluginShippingRateQuoter","quoter","options","pluginId","pluginShippingRateQuoter","impl","quotePluginShippingRates","request","controller","AbortController","timer","deadline","Promise","resolve","setTimeout","abort","Math","max","timeoutMs","race","available","hostId","quote","signal","catch","clearTimeout"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAoI1B,OAAO,MAAMC,8BACXH,4BAAsD,6BAA6B;IACjFI,UAAU;AACZ,GAAE;AAEJ,gFAAgF,GAChF,OAAO,SAASC,iCACdC,MAAgC,EAChCC,OAA+B;IAE/BN,sBAAsBE,6BAA6BG,QAAQ,aACrDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yDAAyD,GACzD,OAAO,SAASC;;QACPP;IAAP,gBAAOA,0BAAAA,sBAAsBC,4BAA4B,CAAC,EAAE,qBAArDD,wBAAuDQ,IAAI,mBAAI;AACxE;AAEA;;;;;CAKC,GACD,OAAO,eAAeC,yBACpBC,OAAmD,EACnDL,OAA8B;IAE9B,MAAMD,SAASG;IACf,IAAI,CAACH,QAAQ,OAAO;IACpB,MAAMO,aAAa,IAAIC;IACvB,IAAIC;IACJ,MAAMC,WAAW,IAAIC,QAAc,CAACC;QAClCH,QAAQI,WAAW;YACjBN,WAAWO,KAAK;YAChBF,QAAQ;QACV,GAAGG,KAAKC,GAAG,CAAC,GAAGf,QAAQgB,SAAS;IAClC;IACA,IAAI;QACF,OAAO,MAAMN,QAAQO,IAAI,CAAC;YACvB,CAAA;gBACC,IAAI,CAAE,MAAMlB,OAAOmB,SAAS,CAACb,QAAQc,MAAM,GAAI,OAAO;gBACtD,OAAOpB,OAAOqB,KAAK,CAAC,aAAKf;oBAASgB,QAAQf,WAAWe,MAAM;;YAC7D,CAAA,IAAKC,KAAK,CAAC,IAAY;YACvBb;SACD;IACH,SAAU;QACR,IAAID,OAAOe,aAAaf;IAC1B;AACF"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Sending a text message, as a platform capability any plugin may ask for
19
+ * (AGL-3610) — the GENERIC half. Which vendor carries the message, how it is
20
+ * metered and billed, and which numbers may not be texted are the provider
21
+ * plugin's (`@aglyn/plugins-sms`); the caller knows none of it.
22
+ *
23
+ * Shaped like the tax profile seam (`plugin-tax-profile.ts`): one contract in
24
+ * core, one provider plugin registers it at boot through its
25
+ * `serverDeclarations`, and a consumer (commerce's order texts) resolves it by
26
+ * contract rather than importing the plugin. The difference is the absent
27
+ * case: a missing tax profile must refuse a charge, while a missing SMS
28
+ * provider is an ordinary state — most installs have none — so
29
+ * {@link pluginSmsMessaging} answers `undefined` and the caller offers email
30
+ * only.
31
+ */
32
+ export interface PluginSmsSendRequest {
33
+ /** The number to text, in any format a person types; the provider normalizes. */
34
+ to: string;
35
+ /** Plain text. The provider decides segmenting; keep it short. */
36
+ body: string;
37
+ /** The site the message is sent for. Metering and rate limits key off its workspace. */
38
+ hostId: string;
39
+ /**
40
+ * Why this text is being sent. Only `transactional` exists today: a message
41
+ * the recipient's own order or request owes them. Marketing texts need a
42
+ * consent record this contract does not carry, so it does not admit them.
43
+ */
44
+ purpose: 'transactional';
45
+ /** A short label for logs, e.g. `'order-shipped'`. */
46
+ context?: string;
47
+ /**
48
+ * Keeps the text out of the recipient's night. Given, a text that would
49
+ * land between 9 PM and 8 AM in `timeZone` (an IANA name) is held and
50
+ * delivered at 8 AM there instead; the outcome is still `sent`, with
51
+ * `scheduledForMs`. Omit it for a text the recipient is waiting on right
52
+ * now — a receipt at the counter, a sign-in code — which goes at once.
53
+ */
54
+ quietHours?: {
55
+ timeZone: string;
56
+ };
57
+ }
58
+ export type PluginSmsSendOutcome = {
59
+ status: 'sent';
60
+ /** The provider's message id. */
61
+ id: string;
62
+ /** The number as sent, E.164. */
63
+ to: string;
64
+ segments: number;
65
+ /** Held for the recipient's morning: when it will be delivered. */
66
+ scheduledForMs?: number;
67
+ }
68
+ /** No provider credentials: nothing was attempted. */
69
+ | {
70
+ status: 'not-configured';
71
+ }
72
+ /** The number could not be read as a phone number. */
73
+ | {
74
+ status: 'invalid-number';
75
+ }
76
+ /** The recipient texted STOP, or staff suppressed the number. */
77
+ | {
78
+ status: 'suppressed';
79
+ }
80
+ /** The workspace hit its text rate limit; nothing was sent. */
81
+ | {
82
+ status: 'rate-limited';
83
+ } | {
84
+ status: 'failed';
85
+ error: string;
86
+ };
87
+ export interface PluginSmsMessaging {
88
+ /**
89
+ * Whether texts can be sent at all. Cheap and synchronous, so a route can
90
+ * ask it to decide whether to OFFER a text before anything is typed.
91
+ */
92
+ isConfigured(): boolean;
93
+ /** Sends one text. Never throws: every failure is an outcome. */
94
+ send(request: PluginSmsSendRequest): Promise<PluginSmsSendOutcome>;
95
+ }
96
+ export declare const PLUGIN_SMS_MESSAGING: import("./plugin-services").PluginServiceContract<PluginSmsMessaging>;
97
+ export declare function registerPluginSmsMessaging(messaging: PluginSmsMessaging, options?: {
98
+ pluginId?: string;
99
+ }): void;
100
+ /** The registered provider, or `undefined` when no plugin offers texts. */
101
+ export declare function pluginSmsMessaging(): PluginSmsMessaging | undefined;
102
+ /** Whether a provider is registered AND configured: the UI's "offer text?" */
103
+ export declare function pluginSmsAvailable(): boolean;