@aglyn/aglyn 1.0.0-beta.229 → 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.
- package/package.json +11 -11
- package/src/lib/app-utils/analytics-events.d.ts +18 -0
- package/src/lib/app-utils/analytics-events.js +2 -0
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +14 -1
- package/src/lib/app-utils/crm.js +19 -2
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +111 -9
- package/src/lib/app-utils/docs-help.generated.js +272 -3
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +810 -61
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/host-status.d.ts +85 -0
- package/src/lib/app-utils/host-status.js +115 -0
- package/src/lib/app-utils/host-status.js.map +1 -0
- package/src/lib/app-utils/lockdown.js +1 -1
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/media-filter.d.ts +136 -0
- package/src/lib/app-utils/media-filter.js +400 -0
- package/src/lib/app-utils/media-filter.js.map +1 -0
- package/src/lib/app-utils/mobile-push.d.ts +98 -0
- package/src/lib/app-utils/mobile-push.js +97 -0
- package/src/lib/app-utils/mobile-push.js.map +1 -0
- package/src/lib/app-utils/notification-push.d.ts +38 -0
- package/src/lib/app-utils/notification-push.js +54 -0
- package/src/lib/app-utils/notification-push.js.map +1 -0
- package/src/lib/app-utils/notifications.d.ts +7 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/organizations.js +5 -2
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.js +20 -0
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.js +164 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.js +7 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +3 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.js +6 -2
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/scope-tokens.d.ts +16 -1
- package/src/lib/app-utils/scope-tokens.js +15 -1
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/site-journey.d.ts +143 -0
- package/src/lib/app-utils/site-journey.js +282 -0
- package/src/lib/app-utils/site-journey.js.map +1 -0
- package/src/lib/app-utils/site-list-query.d.ts +47 -0
- package/src/lib/app-utils/site-list-query.js +142 -0
- package/src/lib/app-utils/site-list-query.js.map +1 -0
- package/src/lib/app-utils/site-wide-outbox.d.ts +95 -0
- package/src/lib/app-utils/site-wide-outbox.js +117 -0
- package/src/lib/app-utils/site-wide-outbox.js.map +1 -0
- package/src/lib/app-utils/transfer-launcher-context.d.ts +6 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -1
- package/src/lib/app-utils/upload-inspection.js +7 -0
- package/src/lib/app-utils/upload-inspection.js.map +1 -1
- package/src/lib/app-utils/webhook-delivery.js +4 -1
- package/src/lib/app-utils/webhook-delivery.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +24 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +18 -7
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +4 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +12 -2
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/plugin-manager/enabled-plugins.js +4 -2
- package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +173 -0
- package/src/lib/plugin-manager/feature-plugins.js +62 -1
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +251 -2
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +192 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js +157 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.d.ts +168 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.js +172 -0
- package/src/lib/plugin-manager/plugin-checkout-extras.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-contributions.js +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-domain-events.d.ts +138 -0
- package/src/lib/plugin-manager/plugin-domain-events.js +148 -0
- package/src/lib/plugin-manager/plugin-domain-events.js.map +1 -0
- package/src/lib/plugin-manager/plugin-events.d.ts +51 -0
- package/src/lib/plugin-manager/plugin-events.js +4 -0
- package/src/lib/plugin-manager/plugin-events.js.map +1 -1
- package/src/lib/plugin-manager/plugin-fulfillment-providers.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-fulfillment-providers.js +83 -0
- package/src/lib/plugin-manager/plugin-fulfillment-providers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-permissions.js +21 -5
- package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +90 -0
- package/src/lib/plugin-manager/plugin-person-records.js +26 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -1
- package/src/lib/plugin-manager/plugin-product-catalog.d.ts +204 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js +43 -0
- package/src/lib/plugin-manager/plugin-product-catalog.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipment-records.d.ts +210 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js +63 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.d.ts +151 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.js +62 -0
- package/src/lib/plugin-manager/plugin-shipping-rates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.d.ts +103 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.js +39 -0
- package/src/lib/plugin-manager/plugin-sms-messaging.js.map +1 -0
- package/src/lib/plugin-manager/plugin-stock-levels.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-stock-levels.js +32 -0
- package/src/lib/plugin-manager/plugin-stock-levels.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +154 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js +56 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-theme-font-catalog.d.ts +59 -0
- package/src/lib/plugin-manager/plugin-theme-font-catalog.js +40 -0
- package/src/lib/plugin-manager/plugin-theme-font-catalog.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.js +76 -0
- package/src/lib/plugin-manager/plugin-tracking-pages.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +3 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
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_SMS_MESSAGING = definePluginServiceContract('core.messaging.sms', {
|
|
19
|
+
multiple: false
|
|
20
|
+
});
|
|
21
|
+
export function registerPluginSmsMessaging(messaging, options) {
|
|
22
|
+
registerPluginService(PLUGIN_SMS_MESSAGING, messaging, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
23
|
+
pluginId: options.pluginId
|
|
24
|
+
} : {}));
|
|
25
|
+
}
|
|
26
|
+
/** The registered provider, or `undefined` when no plugin offers texts. */ export function pluginSmsMessaging() {
|
|
27
|
+
var _resolvePluginServices_;
|
|
28
|
+
return (_resolvePluginServices_ = resolvePluginServices(PLUGIN_SMS_MESSAGING)[0]) == null ? void 0 : _resolvePluginServices_.impl;
|
|
29
|
+
}
|
|
30
|
+
/** Whether a provider is registered AND configured: the UI's "offer text?" */ export function pluginSmsAvailable() {
|
|
31
|
+
try {
|
|
32
|
+
var _pluginSmsMessaging;
|
|
33
|
+
return ((_pluginSmsMessaging = pluginSmsMessaging()) == null ? void 0 : _pluginSmsMessaging.isConfigured()) === true;
|
|
34
|
+
} catch (unused) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=plugin-sms-messaging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-sms-messaging.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 * Sending a text message, as a platform capability any plugin may ask for\n * (AGL-3610) — the GENERIC half. Which vendor carries the message, how it is\n * metered and billed, and which numbers may not be texted are the provider\n * plugin's (`@aglyn/plugins-sms`); the caller knows none of it.\n *\n * Shaped like the tax profile seam (`plugin-tax-profile.ts`): one contract in\n * core, one provider plugin registers it at boot through its\n * `serverDeclarations`, and a consumer (commerce's order texts) resolves it by\n * contract rather than importing the plugin. The difference is the absent\n * case: a missing tax profile must refuse a charge, while a missing SMS\n * provider is an ordinary state — most installs have none — so\n * {@link pluginSmsMessaging} answers `undefined` and the caller offers email\n * only.\n */\nexport interface PluginSmsSendRequest {\n /** The number to text, in any format a person types; the provider normalizes. */\n to: string\n /** Plain text. The provider decides segmenting; keep it short. */\n body: string\n /** The site the message is sent for. Metering and rate limits key off its workspace. */\n hostId: string\n /**\n * Why this text is being sent. Only `transactional` exists today: a message\n * the recipient's own order or request owes them. Marketing texts need a\n * consent record this contract does not carry, so it does not admit them.\n */\n purpose: 'transactional'\n /** A short label for logs, e.g. `'order-shipped'`. */\n context?: string\n /**\n * Keeps the text out of the recipient's night. Given, a text that would\n * land between 9 PM and 8 AM in `timeZone` (an IANA name) is held and\n * delivered at 8 AM there instead; the outcome is still `sent`, with\n * `scheduledForMs`. Omit it for a text the recipient is waiting on right\n * now — a receipt at the counter, a sign-in code — which goes at once.\n */\n quietHours?: { timeZone: string }\n}\n\nexport type PluginSmsSendOutcome =\n | {\n status: 'sent'\n /** The provider's message id. */\n id: string\n /** The number as sent, E.164. */\n to: string\n segments: number\n /** Held for the recipient's morning: when it will be delivered. */\n scheduledForMs?: number\n }\n /** No provider credentials: nothing was attempted. */\n | { status: 'not-configured' }\n /** The number could not be read as a phone number. */\n | { status: 'invalid-number' }\n /** The recipient texted STOP, or staff suppressed the number. */\n | { status: 'suppressed' }\n /** The workspace hit its text rate limit; nothing was sent. */\n | { status: 'rate-limited' }\n | { status: 'failed'; error: string }\n\nexport interface PluginSmsMessaging {\n /**\n * Whether texts can be sent at all. Cheap and synchronous, so a route can\n * ask it to decide whether to OFFER a text before anything is typed.\n */\n isConfigured(): boolean\n /** Sends one text. Never throws: every failure is an outcome. */\n send(request: PluginSmsSendRequest): Promise<PluginSmsSendOutcome>\n}\n\nexport const PLUGIN_SMS_MESSAGING =\n definePluginServiceContract<PluginSmsMessaging>('core.messaging.sms', {\n multiple: false,\n })\n\nexport function registerPluginSmsMessaging(\n messaging: PluginSmsMessaging,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_SMS_MESSAGING, messaging, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The registered provider, or `undefined` when no plugin offers texts. */\nexport function pluginSmsMessaging(): PluginSmsMessaging | undefined {\n return resolvePluginServices(PLUGIN_SMS_MESSAGING)[0]?.impl\n}\n\n/** Whether a provider is registered AND configured: the UI's \"offer text?\" */\nexport function pluginSmsAvailable(): boolean {\n try {\n return pluginSmsMessaging()?.isConfigured() === true\n } catch {\n return false\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_SMS_MESSAGING","multiple","registerPluginSmsMessaging","messaging","options","pluginId","pluginSmsMessaging","impl","pluginSmsAvailable","isConfigured"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAyE1B,OAAO,MAAMC,uBACXH,4BAAgD,sBAAsB;IACpEI,UAAU;AACZ,GAAE;AAEJ,OAAO,SAASC,2BACdC,SAA6B,EAC7BC,OAA+B;IAE/BN,sBAAsBE,sBAAsBG,WAAW,aACjDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yEAAyE,GACzE,OAAO,SAASC;QACPP;IAAP,QAAOA,0BAAAA,sBAAsBC,qBAAqB,CAAC,EAAE,qBAA9CD,wBAAgDQ,IAAI;AAC7D;AAEA,4EAA4E,GAC5E,OAAO,SAASC;IACd,IAAI;YACKF;QAAP,OAAOA,EAAAA,sBAAAA,yCAAAA,oBAAsBG,YAAY,QAAO;IAClD,EAAE,eAAM;QACN,OAAO;IACT;AACF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
* Stock counts kept somewhere else (AGL-3634).
|
|
19
|
+
*
|
|
20
|
+
* When a warehouse that is not the merchant's — a fulfillment network, a
|
|
21
|
+
* supplier — holds the goods, its count is the true one, and the store's own
|
|
22
|
+
* count should say what it says. The plugin that reads that count must not
|
|
23
|
+
* write the seller's products itself, so the seller registers
|
|
24
|
+
* {@link PluginStockLevels} and applies each count under its own rules: the
|
|
25
|
+
* ledger row that explains the change, the sold-out flag, the low-stock
|
|
26
|
+
* alert, back-in-stock email.
|
|
27
|
+
*
|
|
28
|
+
* Counts are matched by SKU, the one identifier a warehouse and a store
|
|
29
|
+
* share. A count the seller cannot apply says why, per SKU, so the plugin
|
|
30
|
+
* that sent it can show the merchant: no product has that SKU, the product
|
|
31
|
+
* does not track stock, or it counts stock per location (where one number
|
|
32
|
+
* would erase the others).
|
|
33
|
+
*
|
|
34
|
+
* Import this module by its own subpath
|
|
35
|
+
* (`@aglyn/aglyn/plugin-manager/plugin-stock-levels`); it is not in the
|
|
36
|
+
* barrel.
|
|
37
|
+
*/
|
|
38
|
+
/** One SKU's count: the units that can be sold now. */
|
|
39
|
+
export interface PluginStockLevel {
|
|
40
|
+
sku: string;
|
|
41
|
+
quantity: number;
|
|
42
|
+
}
|
|
43
|
+
export type PluginStockLevelOutcome =
|
|
44
|
+
/** The count changed to `after`. */
|
|
45
|
+
'updated'
|
|
46
|
+
/** The count already said this. */
|
|
47
|
+
| 'unchanged'
|
|
48
|
+
/** No live product of the store has this SKU. */
|
|
49
|
+
| 'unknown_sku'
|
|
50
|
+
/** The product does not track stock, so there is no count to set. */
|
|
51
|
+
| 'untracked'
|
|
52
|
+
/** The product counts stock per location; one number would erase the others. */
|
|
53
|
+
| 'per_location'
|
|
54
|
+
/** The count was not a whole number of zero or more. */
|
|
55
|
+
| 'invalid'
|
|
56
|
+
/** The write did not land; the next sync tries again. */
|
|
57
|
+
| 'failed';
|
|
58
|
+
export interface PluginStockLevelResult {
|
|
59
|
+
sku: string;
|
|
60
|
+
outcome: PluginStockLevelOutcome;
|
|
61
|
+
/** The count before, when the seller read one. */
|
|
62
|
+
before?: number;
|
|
63
|
+
/** The count after, when the seller wrote one. */
|
|
64
|
+
after?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface PluginStockLevelRequest {
|
|
67
|
+
hostId: string;
|
|
68
|
+
levels: readonly PluginStockLevel[];
|
|
69
|
+
/** Who counted: shown beside the change in the store's stock history, e.g. `ShipBob`. */
|
|
70
|
+
source: string;
|
|
71
|
+
}
|
|
72
|
+
export interface PluginStockLevels {
|
|
73
|
+
/** Sets each SKU's count; one result per SKU asked, in order. */
|
|
74
|
+
setAvailable(request: PluginStockLevelRequest): Promise<PluginStockLevelResult[]>;
|
|
75
|
+
}
|
|
76
|
+
/** Registers the seller. A second plugin is refused naming both. */
|
|
77
|
+
export declare function registerPluginStockLevels(levels: PluginStockLevels, options?: {
|
|
78
|
+
pluginId?: string;
|
|
79
|
+
}): void;
|
|
80
|
+
/** The seller, or `null` when no plugin registered one. */
|
|
81
|
+
export declare function pluginStockLevels(): PluginStockLevels | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
const PLUGIN_STOCK_LEVELS = definePluginServiceContract('core.stock-levels', {
|
|
19
|
+
multiple: false
|
|
20
|
+
});
|
|
21
|
+
/** Registers the seller. A second plugin is refused naming both. */ export function registerPluginStockLevels(levels, options) {
|
|
22
|
+
registerPluginService(PLUGIN_STOCK_LEVELS, levels, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
23
|
+
pluginId: options.pluginId
|
|
24
|
+
} : {}));
|
|
25
|
+
}
|
|
26
|
+
/** The seller, or `null` when no plugin registered one. */ export function pluginStockLevels() {
|
|
27
|
+
var _ref;
|
|
28
|
+
var _resolvePluginServices_;
|
|
29
|
+
return (_ref = (_resolvePluginServices_ = resolvePluginServices(PLUGIN_STOCK_LEVELS)[0]) == null ? void 0 : _resolvePluginServices_.impl) != null ? _ref : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=plugin-stock-levels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-stock-levels.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 * Stock counts kept somewhere else (AGL-3634).\n *\n * When a warehouse that is not the merchant's — a fulfillment network, a\n * supplier — holds the goods, its count is the true one, and the store's own\n * count should say what it says. The plugin that reads that count must not\n * write the seller's products itself, so the seller registers\n * {@link PluginStockLevels} and applies each count under its own rules: the\n * ledger row that explains the change, the sold-out flag, the low-stock\n * alert, back-in-stock email.\n *\n * Counts are matched by SKU, the one identifier a warehouse and a store\n * share. A count the seller cannot apply says why, per SKU, so the plugin\n * that sent it can show the merchant: no product has that SKU, the product\n * does not track stock, or it counts stock per location (where one number\n * would erase the others).\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-stock-levels`); it is not in the\n * barrel.\n */\n\n/** One SKU's count: the units that can be sold now. */\nexport interface PluginStockLevel {\n sku: string\n quantity: number\n}\n\nexport type PluginStockLevelOutcome =\n /** The count changed to `after`. */\n | 'updated'\n /** The count already said this. */\n | 'unchanged'\n /** No live product of the store has this SKU. */\n | 'unknown_sku'\n /** The product does not track stock, so there is no count to set. */\n | 'untracked'\n /** The product counts stock per location; one number would erase the others. */\n | 'per_location'\n /** The count was not a whole number of zero or more. */\n | 'invalid'\n /** The write did not land; the next sync tries again. */\n | 'failed'\n\nexport interface PluginStockLevelResult {\n sku: string\n outcome: PluginStockLevelOutcome\n /** The count before, when the seller read one. */\n before?: number\n /** The count after, when the seller wrote one. */\n after?: number\n}\n\nexport interface PluginStockLevelRequest {\n hostId: string\n levels: readonly PluginStockLevel[]\n /** Who counted: shown beside the change in the store's stock history, e.g. `ShipBob`. */\n source: string\n}\n\nexport interface PluginStockLevels {\n /** Sets each SKU's count; one result per SKU asked, in order. */\n setAvailable(request: PluginStockLevelRequest): Promise<PluginStockLevelResult[]>\n}\n\nconst PLUGIN_STOCK_LEVELS = definePluginServiceContract<PluginStockLevels>('core.stock-levels', {\n multiple: false,\n})\n\n/** Registers the seller. A second plugin is refused naming both. */\nexport function registerPluginStockLevels(levels: PluginStockLevels, options?: { pluginId?: string }): void {\n registerPluginService(PLUGIN_STOCK_LEVELS, levels, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The seller, or `null` when no plugin registered one. */\nexport function pluginStockLevels(): PluginStockLevels | null {\n return resolvePluginServices(PLUGIN_STOCK_LEVELS)[0]?.impl ?? null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_STOCK_LEVELS","multiple","registerPluginStockLevels","levels","options","pluginId","pluginStockLevels","impl"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAmE1B,MAAMC,sBAAsBH,4BAA+C,qBAAqB;IAC9FI,UAAU;AACZ;AAEA,kEAAkE,GAClE,OAAO,SAASC,0BAA0BC,MAAyB,EAAEC,OAA+B;IAClGN,sBAAsBE,qBAAqBG,QAAQ,aAC7CC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yDAAyD,GACzD,OAAO,SAASC;;QACPP;IAAP,gBAAOA,0BAAAA,sBAAsBC,oBAAoB,CAAC,EAAE,qBAA7CD,wBAA+CQ,IAAI,mBAAI;AAChE"}
|
|
@@ -98,3 +98,157 @@ export declare function registerPluginTaxProfile(profile: PluginTaxProfile, opti
|
|
|
98
98
|
export declare function pluginTaxProfileOwner(): string | null;
|
|
99
99
|
/** The tax rule. THROWS when no plugin registered one; see the module note. */
|
|
100
100
|
export declare function pluginTaxProfile(): PluginTaxProfile;
|
|
101
|
+
/**
|
|
102
|
+
* AN OUTSIDE TAX ENGINE, answering for a merchant who connected one
|
|
103
|
+
* (AGL-3631).
|
|
104
|
+
*
|
|
105
|
+
* The tax profile above is the merchant's own flat arithmetic. Some merchants
|
|
106
|
+
* calculate tax in a service of their own — an Avalara AvaTax or TaxJar
|
|
107
|
+
* account, under their own registrations — and want the plugin that charges
|
|
108
|
+
* to ask that service instead of a rate table. This contract is that
|
|
109
|
+
* question, asked the same way whichever service answers it:
|
|
110
|
+
*
|
|
111
|
+
* - {@link PluginTaxEngine.status} — whether a site has an engine connected
|
|
112
|
+
* and which one;
|
|
113
|
+
* - {@link PluginTaxEngine.quote} — the tax on a basket, by line, for a
|
|
114
|
+
* destination (or the site's own address for an in-person sale);
|
|
115
|
+
* - {@link PluginTaxEngine.validateAddress} — the engine's own reading of an
|
|
116
|
+
* address, for a plugin that wants it checked before it ships or taxes.
|
|
117
|
+
*
|
|
118
|
+
* Recording a paid sale with the engine, and reversing it on a refund or a
|
|
119
|
+
* cancellation, is NOT asked here. The seller's plugin already announces
|
|
120
|
+
* those facts as domain events (`order.paid`, `order.refunded`,
|
|
121
|
+
* `order.cancelled`), with retries, so the engine's plugin subscribes to them
|
|
122
|
+
* and the seller never has to know an engine records anything.
|
|
123
|
+
*
|
|
124
|
+
* ## A slot, and an empty one is an answer
|
|
125
|
+
*
|
|
126
|
+
* One plugin owns outside engines; it dispatches to whichever service a site
|
|
127
|
+
* connected. Unlike the tax profile, an empty slot is a normal state — most
|
|
128
|
+
* deployments carry no engine — so {@link pluginTaxEngine} answers `null`,
|
|
129
|
+
* and a caller that wanted one prices the sale its usual way and says so.
|
|
130
|
+
*
|
|
131
|
+
* ## The engine is slow and outside, so the caller holds a deadline
|
|
132
|
+
*
|
|
133
|
+
* {@link quotePluginTaxEngine} never throws and never waits past its
|
|
134
|
+
* deadline: a checkout that hung on a vendor would lose the sale, and one
|
|
135
|
+
* that failed outright would lose it too. It answers what happened —
|
|
136
|
+
* `unavailable`, `timeout` or `error` — so the caller can fall back to the
|
|
137
|
+
* tax path it had before, flag the order, and log why.
|
|
138
|
+
*/
|
|
139
|
+
/** A postal address as a tax engine reads it. `country` is ISO-3166 alpha-2. */
|
|
140
|
+
export interface PluginTaxAddress {
|
|
141
|
+
line1?: string;
|
|
142
|
+
line2?: string;
|
|
143
|
+
city?: string;
|
|
144
|
+
/** State, province or region code, e.g. `TX`. */
|
|
145
|
+
region?: string;
|
|
146
|
+
postalCode?: string;
|
|
147
|
+
country: string;
|
|
148
|
+
}
|
|
149
|
+
/** One taxable line of a basket. Money is integer cents in the request's currency. */
|
|
150
|
+
export interface PluginTaxEngineLine {
|
|
151
|
+
/** The caller's own id for the line, echoed back on the answer. */
|
|
152
|
+
id: string;
|
|
153
|
+
/** The product the line sells, so the engine's plugin can find its tax code. */
|
|
154
|
+
productId?: string;
|
|
155
|
+
variantId?: string;
|
|
156
|
+
sku?: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
quantity: number;
|
|
159
|
+
/** The line's total after any discount on it, EXCLUSIVE of tax. */
|
|
160
|
+
amountCents: number;
|
|
161
|
+
/** A tax code the caller already holds; otherwise the engine's plugin supplies one. */
|
|
162
|
+
taxCode?: string;
|
|
163
|
+
/** A line the seller marked tax-exempt: quoted at zero whatever the engine says. */
|
|
164
|
+
exempt?: boolean;
|
|
165
|
+
}
|
|
166
|
+
/** What a caller asks an engine. */
|
|
167
|
+
export interface PluginTaxEngineQuoteRequest {
|
|
168
|
+
hostId: string;
|
|
169
|
+
/** ISO 4217, lower or upper case. */
|
|
170
|
+
currency: string;
|
|
171
|
+
/** Where the sale happens: `pos` is in person, taxed at the site's own address. */
|
|
172
|
+
channel: 'online' | 'pos' | 'invoice';
|
|
173
|
+
lines: readonly PluginTaxEngineLine[];
|
|
174
|
+
/**
|
|
175
|
+
* A discount on the whole basket, spread by the engine's plugin across the
|
|
176
|
+
* lines that are not exempt. Line-level discounts are already inside each
|
|
177
|
+
* line's `amountCents`.
|
|
178
|
+
*/
|
|
179
|
+
discountCents?: number;
|
|
180
|
+
/** Shipping charged, when the caller wants it quoted. */
|
|
181
|
+
shippingCents?: number;
|
|
182
|
+
/** The destination. Absent or `null`, the engine taxes at the site's own address. */
|
|
183
|
+
shipTo?: PluginTaxAddress | null;
|
|
184
|
+
/** The buyer, so an exemption the merchant recorded for them applies. */
|
|
185
|
+
customer?: {
|
|
186
|
+
email?: string | null;
|
|
187
|
+
id?: string | null;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** One line of an answer. */
|
|
191
|
+
export interface PluginTaxEngineQuoteLine {
|
|
192
|
+
id: string;
|
|
193
|
+
taxCents: number;
|
|
194
|
+
}
|
|
195
|
+
/** What an engine answered. Every figure is integer cents. */
|
|
196
|
+
export interface PluginTaxEngineQuote {
|
|
197
|
+
/** The engine's id, e.g. `avalara`. */
|
|
198
|
+
provider: string;
|
|
199
|
+
/** Its name in the merchant's words, e.g. `Avalara AvaTax`. */
|
|
200
|
+
providerLabel: string;
|
|
201
|
+
taxCents: number;
|
|
202
|
+
lines: readonly PluginTaxEngineQuoteLine[];
|
|
203
|
+
shippingTaxCents: number;
|
|
204
|
+
/** Whether the answer came from the engine's test environment. */
|
|
205
|
+
sandbox: boolean;
|
|
206
|
+
}
|
|
207
|
+
/** Whether a site has an engine connected. */
|
|
208
|
+
export interface PluginTaxEngineStatus {
|
|
209
|
+
connected: boolean;
|
|
210
|
+
provider?: string;
|
|
211
|
+
providerLabel?: string;
|
|
212
|
+
sandbox?: boolean;
|
|
213
|
+
}
|
|
214
|
+
/** An engine's reading of an address. */
|
|
215
|
+
export interface PluginTaxAddressValidation {
|
|
216
|
+
valid: boolean;
|
|
217
|
+
/** The address as the engine normalized it, when it could. */
|
|
218
|
+
normalized: PluginTaxAddress | null;
|
|
219
|
+
/** What the engine said about it, in its own words. */
|
|
220
|
+
messages: readonly string[];
|
|
221
|
+
}
|
|
222
|
+
export interface PluginTaxEngine {
|
|
223
|
+
status(hostId: string): Promise<PluginTaxEngineStatus>;
|
|
224
|
+
/** THROWS on any failure: no connection, a refusal, a network error. */
|
|
225
|
+
quote(request: PluginTaxEngineQuoteRequest): Promise<PluginTaxEngineQuote>;
|
|
226
|
+
/** THROWS when no engine is connected for the site or the engine fails. */
|
|
227
|
+
validateAddress(hostId: string, address: PluginTaxAddress): Promise<PluginTaxAddressValidation>;
|
|
228
|
+
}
|
|
229
|
+
export declare const PLUGIN_TAX_ENGINE: import("./plugin-services").PluginServiceContract<PluginTaxEngine>;
|
|
230
|
+
/** Registers the plugin that answers for outside tax engines. */
|
|
231
|
+
export declare function registerPluginTaxEngine(engine: PluginTaxEngine, options?: {
|
|
232
|
+
pluginId?: string;
|
|
233
|
+
}): void;
|
|
234
|
+
/** The engine's plugin, or `null` when no plugin registered one. */
|
|
235
|
+
export declare function pluginTaxEngine(): PluginTaxEngine | null;
|
|
236
|
+
/** How long a quote may take before the caller prices the sale without it. */
|
|
237
|
+
export declare const PLUGIN_TAX_ENGINE_QUOTE_TIMEOUT_MS = 5000;
|
|
238
|
+
/** What {@link quotePluginTaxEngine} came to. */
|
|
239
|
+
export type PluginTaxEngineQuoteOutcome = {
|
|
240
|
+
ok: true;
|
|
241
|
+
quote: PluginTaxEngineQuote;
|
|
242
|
+
} | {
|
|
243
|
+
ok: false;
|
|
244
|
+
/** No engine plugin, or none connected for the site. */
|
|
245
|
+
reason: 'unavailable' | 'timeout' | 'error';
|
|
246
|
+
message: string;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Asks the engine for a quote within a deadline. Never throws: see the
|
|
250
|
+
* module note on why the caller, not the engine, holds the clock.
|
|
251
|
+
*/
|
|
252
|
+
export declare function quotePluginTaxEngine(request: PluginTaxEngineQuoteRequest, options?: {
|
|
253
|
+
timeoutMs?: number;
|
|
254
|
+
}): Promise<PluginTaxEngineQuoteOutcome>;
|
|
@@ -35,5 +35,61 @@ export const PLUGIN_TAX_PROFILE = definePluginServiceContract('core.tax-profile'
|
|
|
35
35
|
}
|
|
36
36
|
return entry.impl;
|
|
37
37
|
}
|
|
38
|
+
export const PLUGIN_TAX_ENGINE = definePluginServiceContract('core.tax-engine', {
|
|
39
|
+
multiple: false
|
|
40
|
+
});
|
|
41
|
+
/** Registers the plugin that answers for outside tax engines. */ export function registerPluginTaxEngine(engine, options) {
|
|
42
|
+
registerPluginService(PLUGIN_TAX_ENGINE, engine, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
43
|
+
pluginId: options.pluginId
|
|
44
|
+
} : {}));
|
|
45
|
+
}
|
|
46
|
+
/** The engine's plugin, or `null` when no plugin registered one. */ export function pluginTaxEngine() {
|
|
47
|
+
var _ref;
|
|
48
|
+
var _resolvePluginServices_;
|
|
49
|
+
return (_ref = (_resolvePluginServices_ = resolvePluginServices(PLUGIN_TAX_ENGINE)[0]) == null ? void 0 : _resolvePluginServices_.impl) != null ? _ref : null;
|
|
50
|
+
}
|
|
51
|
+
/** How long a quote may take before the caller prices the sale without it. */ export const PLUGIN_TAX_ENGINE_QUOTE_TIMEOUT_MS = 5000;
|
|
52
|
+
/**
|
|
53
|
+
* Asks the engine for a quote within a deadline. Never throws: see the
|
|
54
|
+
* module note on why the caller, not the engine, holds the clock.
|
|
55
|
+
*/ export async function quotePluginTaxEngine(request, options = {}) {
|
|
56
|
+
var _options_timeoutMs;
|
|
57
|
+
const engine = pluginTaxEngine();
|
|
58
|
+
if (!engine) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
reason: 'unavailable',
|
|
62
|
+
message: 'no tax engine plugin is registered'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const timeoutMs = Math.max(1, (_options_timeoutMs = options.timeoutMs) != null ? _options_timeoutMs : PLUGIN_TAX_ENGINE_QUOTE_TIMEOUT_MS);
|
|
66
|
+
let timer;
|
|
67
|
+
const deadline = new Promise((resolve)=>{
|
|
68
|
+
timer = setTimeout(()=>resolve({
|
|
69
|
+
ok: false,
|
|
70
|
+
reason: 'timeout',
|
|
71
|
+
message: `the tax engine did not answer within ${timeoutMs} ms`
|
|
72
|
+
}), timeoutMs);
|
|
73
|
+
});
|
|
74
|
+
const asked = Promise.resolve().then(()=>engine.quote(request)).then((quote)=>({
|
|
75
|
+
ok: true,
|
|
76
|
+
quote
|
|
77
|
+
}), (error)=>{
|
|
78
|
+
var _ref;
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
reason: 'error',
|
|
82
|
+
message: String((_ref = error == null ? void 0 : error.message) != null ? _ref : error).slice(0, 300)
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
try {
|
|
86
|
+
return await Promise.race([
|
|
87
|
+
asked,
|
|
88
|
+
deadline
|
|
89
|
+
]);
|
|
90
|
+
} finally{
|
|
91
|
+
if (timer) clearTimeout(timer);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
38
94
|
|
|
39
95
|
//# sourceMappingURL=plugin-tax-profile.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-tax-profile.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 * The tenant's tax rule, answered by the one plugin that owns it (AGL-3080).\n *\n * More than one plugin takes money — a storefront sells goods, a calendar\n * sells appointments — and a merchant has ONE tax profile. The plugin that\n * keeps it works out what a flat rate adds to a charge and which regime a\n * settled payment was taxed under; any other plugin that charges asks here.\n * Importing the owner's model instead is how two money paths end up with two\n * rounding rules, and the second is found by an accountant.\n *\n * ## Where the rate is kept, and the arithmetic over it\n *\n * The merchant's rates are the owner's settings, stored where the owner keeps\n * them, so a caller asks for one — {@link PluginTaxProfile.flatRate}, the\n * contract's one read, by site and by the kind of charge — and never reads\n * the owner's documents itself. A plugin that knew where another plugin kept\n * a merchant's tax settings would charge untaxed, and record it as untaxed,\n * the day those settings moved.\n *\n * The other two questions are pure and synchronous arithmetic over values the\n * caller already holds: the rate it was handed, a charge in cents, a settled\n * payment object. Nobody is asked who they are.\n *\n * ## No profile is a refusal, never a zero\n *\n * {@link pluginTaxProfile} THROWS when no plugin registered one. Every other\n * seam here answers `null` for \"nobody home\", and this one must not: a caller\n * that read `null` as \"no tax\" would charge a customer an untaxed total and\n * record it as untaxed, silently, and the merchant would owe the difference.\n * A refused sale is seen the same day.\n *\n * It cannot happen in a working build. Both apps load every plugin's server\n * entry before a plugin handler, a cron or the billing webhook runs\n * (`ensureAll`), so the owner's registration is in place wherever a charge is\n * priced; `tax-profile-is-registered.spec.ts` in each app holds that.\n *\n * ## One owner\n *\n * A workspace has one tax profile, so the contract is a slot: a second\n * plugin's profile is refused naming both and the incumbent keeps serving.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-tax-profile`); it is not in the barrel.\n */\n\n/** What a flat rate adds to one charge. All zero and empty when it adds nothing. */\nexport interface PluginResolvedFlatTax {\n taxCents: number\n /** What the line is called on the receipt. */\n label: string\n pct: number\n}\n\nexport interface PluginTaxProfile {\n /**\n * The merchant's flat rate for one kind of charge on a site, as the owner\n * stores it — to be handed to {@link PluginTaxProfile.flatTax} as read. `charge` names what\n * is being sold in the owner's words for its rates (`service` for an\n * appointment). A site that set none, or a kind the owner keeps no rate\n * for, answers `undefined`, which `flatTax` prices at zero. Server-side:\n * the owner reads its own settings document.\n */\n flatRate(hostId: string, charge: string): Promise<unknown>\n /**\n * Tax, EXCLUSIVE, for a flat merchant rate on one charged amount. `rate` is\n * the merchant's stored setting, passed as read: the owner decides what a\n * usable rate is, and an absent, zero, negative or out-of-range one answers\n * zero rather than throwing.\n */\n flatTax(\n rate: unknown,\n chargeCents: number,\n fallbackLabel: string,\n ): PluginResolvedFlatTax\n /**\n * Which regime a settled payment was taxed under, as the owner records it.\n * `manualTaxCents` is the tax this caller added as a line of its own, which\n * the payment processor reports as no tax at all.\n */\n taxModeOf(settledPayment: unknown, manualTaxCents?: number): string\n}\n\nexport const PLUGIN_TAX_PROFILE = definePluginServiceContract<PluginTaxProfile>(\n 'core.tax-profile',\n { multiple: false },\n)\n\n/** Registers the plugin that owns the tenant's tax rule. */\nexport function registerPluginTaxProfile(\n profile: PluginTaxProfile,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_TAX_PROFILE, profile, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The owner, or `null` — for a caller that only wants to know who it is. */\nexport function pluginTaxProfileOwner(): string | null {\n return resolvePluginServices(PLUGIN_TAX_PROFILE)[0]?.pluginId ?? null\n}\n\n/** The tax rule. THROWS when no plugin registered one; see the module note. */\nexport function pluginTaxProfile(): PluginTaxProfile {\n const entry = resolvePluginServices(PLUGIN_TAX_PROFILE)[0]\n if (!entry) {\n throw new Error(\n 'no plugin registered a tax profile, so this charge cannot be priced: ' +\n 'refusing rather than charging it untaxed',\n )\n }\n return entry.impl\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_TAX_PROFILE","multiple","registerPluginTaxProfile","profile","options","pluginId","pluginTaxProfileOwner","pluginTaxProfile","entry","Error","impl"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAoF1B,OAAO,MAAMC,qBAAqBH,4BAChC,oBACA;IAAEI,UAAU;AAAM,GACnB;AAED,0DAA0D,GAC1D,OAAO,SAASC,yBACdC,OAAyB,EACzBC,OAA+B;IAE/BN,sBAAsBE,oBAAoBG,SAAS,aAC7CC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,2EAA2E,GAC3E,OAAO,SAASC;;QACPP;IAAP,gBAAOA,0BAAAA,sBAAsBC,mBAAmB,CAAC,EAAE,qBAA5CD,wBAA8CM,QAAQ,mBAAI;AACnE;AAEA,6EAA6E,GAC7E,OAAO,SAASE;IACd,MAAMC,QAAQT,sBAAsBC,mBAAmB,CAAC,EAAE;IAC1D,IAAI,CAACQ,OAAO;QACV,MAAM,IAAIC,MACR,0EACE;IAEN;IACA,OAAOD,MAAME,IAAI;AACnB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-tax-profile.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 * The tenant's tax rule, answered by the one plugin that owns it (AGL-3080).\n *\n * More than one plugin takes money — a storefront sells goods, a calendar\n * sells appointments — and a merchant has ONE tax profile. The plugin that\n * keeps it works out what a flat rate adds to a charge and which regime a\n * settled payment was taxed under; any other plugin that charges asks here.\n * Importing the owner's model instead is how two money paths end up with two\n * rounding rules, and the second is found by an accountant.\n *\n * ## Where the rate is kept, and the arithmetic over it\n *\n * The merchant's rates are the owner's settings, stored where the owner keeps\n * them, so a caller asks for one — {@link PluginTaxProfile.flatRate}, the\n * contract's one read, by site and by the kind of charge — and never reads\n * the owner's documents itself. A plugin that knew where another plugin kept\n * a merchant's tax settings would charge untaxed, and record it as untaxed,\n * the day those settings moved.\n *\n * The other two questions are pure and synchronous arithmetic over values the\n * caller already holds: the rate it was handed, a charge in cents, a settled\n * payment object. Nobody is asked who they are.\n *\n * ## No profile is a refusal, never a zero\n *\n * {@link pluginTaxProfile} THROWS when no plugin registered one. Every other\n * seam here answers `null` for \"nobody home\", and this one must not: a caller\n * that read `null` as \"no tax\" would charge a customer an untaxed total and\n * record it as untaxed, silently, and the merchant would owe the difference.\n * A refused sale is seen the same day.\n *\n * It cannot happen in a working build. Both apps load every plugin's server\n * entry before a plugin handler, a cron or the billing webhook runs\n * (`ensureAll`), so the owner's registration is in place wherever a charge is\n * priced; `tax-profile-is-registered.spec.ts` in each app holds that.\n *\n * ## One owner\n *\n * A workspace has one tax profile, so the contract is a slot: a second\n * plugin's profile is refused naming both and the incumbent keeps serving.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-tax-profile`); it is not in the barrel.\n */\n\n/** What a flat rate adds to one charge. All zero and empty when it adds nothing. */\nexport interface PluginResolvedFlatTax {\n taxCents: number\n /** What the line is called on the receipt. */\n label: string\n pct: number\n}\n\nexport interface PluginTaxProfile {\n /**\n * The merchant's flat rate for one kind of charge on a site, as the owner\n * stores it — to be handed to {@link PluginTaxProfile.flatTax} as read. `charge` names what\n * is being sold in the owner's words for its rates (`service` for an\n * appointment). A site that set none, or a kind the owner keeps no rate\n * for, answers `undefined`, which `flatTax` prices at zero. Server-side:\n * the owner reads its own settings document.\n */\n flatRate(hostId: string, charge: string): Promise<unknown>\n /**\n * Tax, EXCLUSIVE, for a flat merchant rate on one charged amount. `rate` is\n * the merchant's stored setting, passed as read: the owner decides what a\n * usable rate is, and an absent, zero, negative or out-of-range one answers\n * zero rather than throwing.\n */\n flatTax(\n rate: unknown,\n chargeCents: number,\n fallbackLabel: string,\n ): PluginResolvedFlatTax\n /**\n * Which regime a settled payment was taxed under, as the owner records it.\n * `manualTaxCents` is the tax this caller added as a line of its own, which\n * the payment processor reports as no tax at all.\n */\n taxModeOf(settledPayment: unknown, manualTaxCents?: number): string\n}\n\nexport const PLUGIN_TAX_PROFILE = definePluginServiceContract<PluginTaxProfile>(\n 'core.tax-profile',\n { multiple: false },\n)\n\n/** Registers the plugin that owns the tenant's tax rule. */\nexport function registerPluginTaxProfile(\n profile: PluginTaxProfile,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_TAX_PROFILE, profile, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The owner, or `null` — for a caller that only wants to know who it is. */\nexport function pluginTaxProfileOwner(): string | null {\n return resolvePluginServices(PLUGIN_TAX_PROFILE)[0]?.pluginId ?? null\n}\n\n/** The tax rule. THROWS when no plugin registered one; see the module note. */\nexport function pluginTaxProfile(): PluginTaxProfile {\n const entry = resolvePluginServices(PLUGIN_TAX_PROFILE)[0]\n if (!entry) {\n throw new Error(\n 'no plugin registered a tax profile, so this charge cannot be priced: ' +\n 'refusing rather than charging it untaxed',\n )\n }\n return entry.impl\n}\n\n/**\n * AN OUTSIDE TAX ENGINE, answering for a merchant who connected one\n * (AGL-3631).\n *\n * The tax profile above is the merchant's own flat arithmetic. Some merchants\n * calculate tax in a service of their own — an Avalara AvaTax or TaxJar\n * account, under their own registrations — and want the plugin that charges\n * to ask that service instead of a rate table. This contract is that\n * question, asked the same way whichever service answers it:\n *\n * - {@link PluginTaxEngine.status} — whether a site has an engine connected\n * and which one;\n * - {@link PluginTaxEngine.quote} — the tax on a basket, by line, for a\n * destination (or the site's own address for an in-person sale);\n * - {@link PluginTaxEngine.validateAddress} — the engine's own reading of an\n * address, for a plugin that wants it checked before it ships or taxes.\n *\n * Recording a paid sale with the engine, and reversing it on a refund or a\n * cancellation, is NOT asked here. The seller's plugin already announces\n * those facts as domain events (`order.paid`, `order.refunded`,\n * `order.cancelled`), with retries, so the engine's plugin subscribes to them\n * and the seller never has to know an engine records anything.\n *\n * ## A slot, and an empty one is an answer\n *\n * One plugin owns outside engines; it dispatches to whichever service a site\n * connected. Unlike the tax profile, an empty slot is a normal state — most\n * deployments carry no engine — so {@link pluginTaxEngine} answers `null`,\n * and a caller that wanted one prices the sale its usual way and says so.\n *\n * ## The engine is slow and outside, so the caller holds a deadline\n *\n * {@link quotePluginTaxEngine} never throws and never waits past its\n * deadline: a checkout that hung on a vendor would lose the sale, and one\n * that failed outright would lose it too. It answers what happened —\n * `unavailable`, `timeout` or `error` — so the caller can fall back to the\n * tax path it had before, flag the order, and log why.\n */\n\n/** A postal address as a tax engine reads it. `country` is ISO-3166 alpha-2. */\nexport interface PluginTaxAddress {\n line1?: string\n line2?: string\n city?: string\n /** State, province or region code, e.g. `TX`. */\n region?: string\n postalCode?: string\n country: string\n}\n\n/** One taxable line of a basket. Money is integer cents in the request's currency. */\nexport interface PluginTaxEngineLine {\n /** The caller's own id for the line, echoed back on the answer. */\n id: string\n /** The product the line sells, so the engine's plugin can find its tax code. */\n productId?: string\n variantId?: string\n sku?: string\n description?: string\n quantity: number\n /** The line's total after any discount on it, EXCLUSIVE of tax. */\n amountCents: number\n /** A tax code the caller already holds; otherwise the engine's plugin supplies one. */\n taxCode?: string\n /** A line the seller marked tax-exempt: quoted at zero whatever the engine says. */\n exempt?: boolean\n}\n\n/** What a caller asks an engine. */\nexport interface PluginTaxEngineQuoteRequest {\n hostId: string\n /** ISO 4217, lower or upper case. */\n currency: string\n /** Where the sale happens: `pos` is in person, taxed at the site's own address. */\n channel: 'online' | 'pos' | 'invoice'\n lines: readonly PluginTaxEngineLine[]\n /**\n * A discount on the whole basket, spread by the engine's plugin across the\n * lines that are not exempt. Line-level discounts are already inside each\n * line's `amountCents`.\n */\n discountCents?: number\n /** Shipping charged, when the caller wants it quoted. */\n shippingCents?: number\n /** The destination. Absent or `null`, the engine taxes at the site's own address. */\n shipTo?: PluginTaxAddress | null\n /** The buyer, so an exemption the merchant recorded for them applies. */\n customer?: { email?: string | null; id?: string | null }\n}\n\n/** One line of an answer. */\nexport interface PluginTaxEngineQuoteLine {\n id: string\n taxCents: number\n}\n\n/** What an engine answered. Every figure is integer cents. */\nexport interface PluginTaxEngineQuote {\n /** The engine's id, e.g. `avalara`. */\n provider: string\n /** Its name in the merchant's words, e.g. `Avalara AvaTax`. */\n providerLabel: string\n taxCents: number\n lines: readonly PluginTaxEngineQuoteLine[]\n shippingTaxCents: number\n /** Whether the answer came from the engine's test environment. */\n sandbox: boolean\n}\n\n/** Whether a site has an engine connected. */\nexport interface PluginTaxEngineStatus {\n connected: boolean\n provider?: string\n providerLabel?: string\n sandbox?: boolean\n}\n\n/** An engine's reading of an address. */\nexport interface PluginTaxAddressValidation {\n valid: boolean\n /** The address as the engine normalized it, when it could. */\n normalized: PluginTaxAddress | null\n /** What the engine said about it, in its own words. */\n messages: readonly string[]\n}\n\nexport interface PluginTaxEngine {\n status(hostId: string): Promise<PluginTaxEngineStatus>\n /** THROWS on any failure: no connection, a refusal, a network error. */\n quote(request: PluginTaxEngineQuoteRequest): Promise<PluginTaxEngineQuote>\n /** THROWS when no engine is connected for the site or the engine fails. */\n validateAddress(\n hostId: string,\n address: PluginTaxAddress,\n ): Promise<PluginTaxAddressValidation>\n}\n\nexport const PLUGIN_TAX_ENGINE = definePluginServiceContract<PluginTaxEngine>(\n 'core.tax-engine',\n { multiple: false },\n)\n\n/** Registers the plugin that answers for outside tax engines. */\nexport function registerPluginTaxEngine(\n engine: PluginTaxEngine,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_TAX_ENGINE, engine, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The engine's plugin, or `null` when no plugin registered one. */\nexport function pluginTaxEngine(): PluginTaxEngine | null {\n return resolvePluginServices(PLUGIN_TAX_ENGINE)[0]?.impl ?? null\n}\n\n/** How long a quote may take before the caller prices the sale without it. */\nexport const PLUGIN_TAX_ENGINE_QUOTE_TIMEOUT_MS = 5_000\n\n/** What {@link quotePluginTaxEngine} came to. */\nexport type PluginTaxEngineQuoteOutcome =\n | { ok: true; quote: PluginTaxEngineQuote }\n | {\n ok: false\n /** No engine plugin, or none connected for the site. */\n reason: 'unavailable' | 'timeout' | 'error'\n message: string\n }\n\n/**\n * Asks the engine for a quote within a deadline. Never throws: see the\n * module note on why the caller, not the engine, holds the clock.\n */\nexport async function quotePluginTaxEngine(\n request: PluginTaxEngineQuoteRequest,\n options: { timeoutMs?: number } = {},\n): Promise<PluginTaxEngineQuoteOutcome> {\n const engine = pluginTaxEngine()\n if (!engine) {\n return {\n ok: false,\n reason: 'unavailable',\n message: 'no tax engine plugin is registered',\n }\n }\n const timeoutMs = Math.max(1, options.timeoutMs ?? PLUGIN_TAX_ENGINE_QUOTE_TIMEOUT_MS)\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<PluginTaxEngineQuoteOutcome>((resolve) => {\n timer = setTimeout(\n () =>\n resolve({\n ok: false,\n reason: 'timeout',\n message: `the tax engine did not answer within ${timeoutMs} ms`,\n }),\n timeoutMs,\n )\n })\n const asked = Promise.resolve()\n .then(() => engine.quote(request))\n .then(\n (quote): PluginTaxEngineQuoteOutcome => ({ ok: true, quote }),\n (error: unknown): PluginTaxEngineQuoteOutcome => ({\n ok: false,\n reason: 'error',\n message: String((error as Error)?.message ?? error).slice(0, 300),\n }),\n )\n try {\n return await Promise.race([asked, deadline])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_TAX_PROFILE","multiple","registerPluginTaxProfile","profile","options","pluginId","pluginTaxProfileOwner","pluginTaxProfile","entry","Error","impl","PLUGIN_TAX_ENGINE","registerPluginTaxEngine","engine","pluginTaxEngine","PLUGIN_TAX_ENGINE_QUOTE_TIMEOUT_MS","quotePluginTaxEngine","request","ok","reason","message","timeoutMs","Math","max","timer","deadline","Promise","resolve","setTimeout","asked","then","quote","error","String","slice","race","clearTimeout"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAoF1B,OAAO,MAAMC,qBAAqBH,4BAChC,oBACA;IAAEI,UAAU;AAAM,GACnB;AAED,0DAA0D,GAC1D,OAAO,SAASC,yBACdC,OAAyB,EACzBC,OAA+B;IAE/BN,sBAAsBE,oBAAoBG,SAAS,aAC7CC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,2EAA2E,GAC3E,OAAO,SAASC;;QACPP;IAAP,gBAAOA,0BAAAA,sBAAsBC,mBAAmB,CAAC,EAAE,qBAA5CD,wBAA8CM,QAAQ,mBAAI;AACnE;AAEA,6EAA6E,GAC7E,OAAO,SAASE;IACd,MAAMC,QAAQT,sBAAsBC,mBAAmB,CAAC,EAAE;IAC1D,IAAI,CAACQ,OAAO;QACV,MAAM,IAAIC,MACR,0EACE;IAEN;IACA,OAAOD,MAAME,IAAI;AACnB;AA2IA,OAAO,MAAMC,oBAAoBd,4BAC/B,mBACA;IAAEI,UAAU;AAAM,GACnB;AAED,+DAA+D,GAC/D,OAAO,SAASW,wBACdC,MAAuB,EACvBT,OAA+B;IAE/BN,sBAAsBa,mBAAmBE,QAAQ,aAC3CT,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,kEAAkE,GAClE,OAAO,SAASS;;QACPf;IAAP,gBAAOA,0BAAAA,sBAAsBY,kBAAkB,CAAC,EAAE,qBAA3CZ,wBAA6CW,IAAI,mBAAI;AAC9D;AAEA,4EAA4E,GAC5E,OAAO,MAAMK,qCAAqC,KAAK;AAYvD;;;CAGC,GACD,OAAO,eAAeC,qBACpBC,OAAoC,EACpCb,UAAkC,CAAC,CAAC;QAUNA;IAR9B,MAAMS,SAASC;IACf,IAAI,CAACD,QAAQ;QACX,OAAO;YACLK,IAAI;YACJC,QAAQ;YACRC,SAAS;QACX;IACF;IACA,MAAMC,YAAYC,KAAKC,GAAG,CAAC,IAAGnB,qBAAAA,QAAQiB,SAAS,YAAjBjB,qBAAqBW;IACnD,IAAIS;IACJ,MAAMC,WAAW,IAAIC,QAAqC,CAACC;QACzDH,QAAQI,WACN,IACED,QAAQ;gBACNT,IAAI;gBACJC,QAAQ;gBACRC,SAAS,CAAC,qCAAqC,EAAEC,UAAU,GAAG,CAAC;YACjE,IACFA;IAEJ;IACA,MAAMQ,QAAQH,QAAQC,OAAO,GAC1BG,IAAI,CAAC,IAAMjB,OAAOkB,KAAK,CAACd,UACxBa,IAAI,CACH,CAACC,QAAwC,CAAA;YAAEb,IAAI;YAAMa;QAAM,CAAA,GAC3D,CAACC;;eAAiD;YAChDd,IAAI;YACJC,QAAQ;YACRC,SAASa,eAAQD,yBAAD,AAACA,MAAiBZ,OAAO,mBAAIY,OAAOE,KAAK,CAAC,GAAG;QAC/D;;IAEJ,IAAI;QACF,OAAO,MAAMR,QAAQS,IAAI,CAAC;YAACN;YAAOJ;SAAS;IAC7C,SAAU;QACR,IAAID,OAAOY,aAAaZ;IAC1B;AACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 { HostThemeFontCategory, HostThemeFontMetrics } from '@aglyn/shared-data-types';
|
|
18
|
+
/**
|
|
19
|
+
* What a published page needs to know about a font family a theme names
|
|
20
|
+
* (AGL-3656), asked of whichever plugin catalogs fonts rather than read off a
|
|
21
|
+
* list in core.
|
|
22
|
+
*
|
|
23
|
+
* The page loads only the weights and italics a family really offers — a
|
|
24
|
+
* theme asking for an 800 the family does not have would otherwise get the
|
|
25
|
+
* browser's nearest face, or a synthesized bold — and sizes a local fallback
|
|
26
|
+
* face to the family's own metrics, so nothing moves when the web font swaps
|
|
27
|
+
* in. With no catalog registered, a page loads what the theme lists and
|
|
28
|
+
* swaps with no metric-matched fallback, which is what every page did before.
|
|
29
|
+
*/
|
|
30
|
+
export interface ThemeFontFacts {
|
|
31
|
+
family: string;
|
|
32
|
+
category: HostThemeFontCategory;
|
|
33
|
+
/** The upright weights the family offers. */
|
|
34
|
+
weights: number[];
|
|
35
|
+
/** The italic weights the family offers. */
|
|
36
|
+
italics: number[];
|
|
37
|
+
/**
|
|
38
|
+
* The weight range of the family's variable font, when it has one. Such a
|
|
39
|
+
* family can be served as one file holding every weight or as a file per
|
|
40
|
+
* weight, and the page picks whichever is smaller for the weights it uses.
|
|
41
|
+
*/
|
|
42
|
+
variableWeights?: [number, number];
|
|
43
|
+
/** Of the regular face; absent when the catalog could not read them. */
|
|
44
|
+
metrics?: HostThemeFontMetrics;
|
|
45
|
+
}
|
|
46
|
+
export interface PluginThemeFontCatalog {
|
|
47
|
+
/** The family's facts, matched case-insensitively, or undefined when unknown. */
|
|
48
|
+
facts(family: string): Promise<ThemeFontFacts | undefined>;
|
|
49
|
+
}
|
|
50
|
+
export declare const PLUGIN_THEME_FONT_CATALOG: import("./plugin-services").PluginServiceContract<PluginThemeFontCatalog>;
|
|
51
|
+
export declare function registerPluginThemeFontCatalog(catalog: PluginThemeFontCatalog, options?: {
|
|
52
|
+
pluginId?: string;
|
|
53
|
+
}): void;
|
|
54
|
+
/**
|
|
55
|
+
* A family's facts from the registered catalog; undefined when no plugin
|
|
56
|
+
* catalogs fonts, the family is not in it, or the catalog failed. Never
|
|
57
|
+
* throws: a page renders without a fact rather than without its theme.
|
|
58
|
+
*/
|
|
59
|
+
export declare function themeFontFacts(family: string): Promise<ThemeFontFacts | undefined>;
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { definePluginServiceContract, registerPluginService, resolvePluginService } from "./plugin-services.js";
|
|
18
|
+
export const PLUGIN_THEME_FONT_CATALOG = definePluginServiceContract('core.theme.fontCatalog', {
|
|
19
|
+
multiple: false
|
|
20
|
+
});
|
|
21
|
+
export function registerPluginThemeFontCatalog(catalog, options) {
|
|
22
|
+
registerPluginService(PLUGIN_THEME_FONT_CATALOG, catalog, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
23
|
+
pluginId: options.pluginId
|
|
24
|
+
} : {}));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A family's facts from the registered catalog; undefined when no plugin
|
|
28
|
+
* catalogs fonts, the family is not in it, or the catalog failed. Never
|
|
29
|
+
* throws: a page renders without a fact rather than without its theme.
|
|
30
|
+
*/ export async function themeFontFacts(family) {
|
|
31
|
+
const catalog = resolvePluginService(PLUGIN_THEME_FONT_CATALOG);
|
|
32
|
+
if (!catalog) return undefined;
|
|
33
|
+
try {
|
|
34
|
+
return await catalog.facts(family);
|
|
35
|
+
} catch (unused) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=plugin-theme-font-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-theme-font-catalog.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 type {\n HostThemeFontCategory,\n HostThemeFontMetrics,\n} from '@aglyn/shared-data-types'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n} from './plugin-services'\n\n/**\n * What a published page needs to know about a font family a theme names\n * (AGL-3656), asked of whichever plugin catalogs fonts rather than read off a\n * list in core.\n *\n * The page loads only the weights and italics a family really offers — a\n * theme asking for an 800 the family does not have would otherwise get the\n * browser's nearest face, or a synthesized bold — and sizes a local fallback\n * face to the family's own metrics, so nothing moves when the web font swaps\n * in. With no catalog registered, a page loads what the theme lists and\n * swaps with no metric-matched fallback, which is what every page did before.\n */\nexport interface ThemeFontFacts {\n family: string\n category: HostThemeFontCategory\n /** The upright weights the family offers. */\n weights: number[]\n /** The italic weights the family offers. */\n italics: number[]\n /**\n * The weight range of the family's variable font, when it has one. Such a\n * family can be served as one file holding every weight or as a file per\n * weight, and the page picks whichever is smaller for the weights it uses.\n */\n variableWeights?: [number, number]\n /** Of the regular face; absent when the catalog could not read them. */\n metrics?: HostThemeFontMetrics\n}\n\nexport interface PluginThemeFontCatalog {\n /** The family's facts, matched case-insensitively, or undefined when unknown. */\n facts(family: string): Promise<ThemeFontFacts | undefined>\n}\n\nexport const PLUGIN_THEME_FONT_CATALOG =\n definePluginServiceContract<PluginThemeFontCatalog>('core.theme.fontCatalog', {\n multiple: false,\n })\n\nexport function registerPluginThemeFontCatalog(\n catalog: PluginThemeFontCatalog,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_THEME_FONT_CATALOG, catalog, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/**\n * A family's facts from the registered catalog; undefined when no plugin\n * catalogs fonts, the family is not in it, or the catalog failed. Never\n * throws: a page renders without a fact rather than without its theme.\n */\nexport async function themeFontFacts(\n family: string,\n): Promise<ThemeFontFacts | undefined> {\n const catalog = resolvePluginService(PLUGIN_THEME_FONT_CATALOG)\n if (!catalog) return undefined\n try {\n return await catalog.facts(family)\n } catch {\n return undefined\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","PLUGIN_THEME_FONT_CATALOG","multiple","registerPluginThemeFontCatalog","catalog","options","pluginId","themeFontFacts","family","undefined","facts"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAMD,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,QACf,uBAAmB;AAoC1B,OAAO,MAAMC,4BACXH,4BAAoD,0BAA0B;IAC5EI,UAAU;AACZ,GAAE;AAEJ,OAAO,SAASC,+BACdC,OAA+B,EAC/BC,OAA+B;IAE/BN,sBAAsBE,2BAA2BG,SAAS,aACpDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA;;;;CAIC,GACD,OAAO,eAAeC,eACpBC,MAAc;IAEd,MAAMJ,UAAUJ,qBAAqBC;IACrC,IAAI,CAACG,SAAS,OAAOK;IACrB,IAAI;QACF,OAAO,MAAML,QAAQM,KAAK,CAACF;IAC7B,EAAE,eAAM;QACN,OAAOC;IACT;AACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
* A branded tracking page for a parcel, offered by another plugin
|
|
19
|
+
* (AGL-3635).
|
|
20
|
+
*
|
|
21
|
+
* A seller links each shipment to the carrier's own tracker. A merchant who
|
|
22
|
+
* follows parcels through a tracking service has a page of their own for it
|
|
23
|
+
* — their logo, their help links, their other products — and wants buyers
|
|
24
|
+
* sent there instead. A plugin that knows such a page registers here; the
|
|
25
|
+
* seller asks before it draws a shipment for a buyer, and keeps the
|
|
26
|
+
* carrier's link when nobody answers.
|
|
27
|
+
*
|
|
28
|
+
* The words are a parcel's: a site, the record it shipped for, a carrier and
|
|
29
|
+
* a number. Import this module by its own subpath
|
|
30
|
+
* (`@aglyn/aglyn/plugin-manager/plugin-tracking-pages`); it is not in the
|
|
31
|
+
* barrel.
|
|
32
|
+
*/
|
|
33
|
+
export interface PluginTrackingPageRequest {
|
|
34
|
+
hostId: string;
|
|
35
|
+
recordId: string;
|
|
36
|
+
/** The carrier as the record names it: free text. */
|
|
37
|
+
carrier?: string | null;
|
|
38
|
+
trackingNumber: string;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
/** The page's URL, or `null` when this plugin has none for that parcel. */
|
|
42
|
+
export type PluginTrackingPageProvider = (request: PluginTrackingPageRequest) => Promise<string | null>;
|
|
43
|
+
/** Joins the providers. Re-registering under the same plugin replaces its own. */
|
|
44
|
+
export declare function registerPluginTrackingPage(provider: PluginTrackingPageProvider, options?: {
|
|
45
|
+
pluginId?: string;
|
|
46
|
+
}): void;
|
|
47
|
+
/**
|
|
48
|
+
* The first provider's page for each parcel, asked together and given up
|
|
49
|
+
* on after `timeoutMs`. Answers a map from tracking number to an `https:`
|
|
50
|
+
* URL; a parcel no provider answered for is absent, and the seller keeps
|
|
51
|
+
* its own link. Never throws.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolvePluginTrackingPages(requests: ReadonlyArray<Omit<PluginTrackingPageRequest, 'signal'>>, options: {
|
|
54
|
+
timeoutMs: number;
|
|
55
|
+
}): Promise<Map<string, string>>;
|