@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,148 @@
|
|
|
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
|
+
const EVENT_NAME = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9_]*)+$/;
|
|
19
|
+
/**
|
|
20
|
+
* A token for one event name: lower-case words joined by dots, the first
|
|
21
|
+
* naming what the event is about (`order.paid`, `return.requested`).
|
|
22
|
+
*/ export function definePluginDomainEvent(id) {
|
|
23
|
+
const name = String(id != null ? id : '').trim();
|
|
24
|
+
if (!EVENT_NAME.test(name)) {
|
|
25
|
+
throw new Error(`plugin event name "${id}" must be dotted lower-case words, e.g. "order.paid"`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
id: name
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const REGISTRY_KEY = Symbol.for('@aglyn/aglyn:plugin-domain-events');
|
|
32
|
+
const globalScope = globalThis;
|
|
33
|
+
function registry() {
|
|
34
|
+
if (!globalScope[REGISTRY_KEY]) {
|
|
35
|
+
globalScope[REGISTRY_KEY] = {
|
|
36
|
+
declarations: new Map(),
|
|
37
|
+
subscriptions: new Map()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return globalScope[REGISTRY_KEY];
|
|
41
|
+
}
|
|
42
|
+
const nameOf = (event)=>typeof event === 'string' ? event : event.id;
|
|
43
|
+
function ownerOf(kind, name, options) {
|
|
44
|
+
var _ref, _getRegisteringPluginId;
|
|
45
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
46
|
+
if (!pluginId) {
|
|
47
|
+
throw new Error(`plugin event "${name}" ${kind} with no owner: pass { pluginId } outside a plugin register fn`);
|
|
48
|
+
}
|
|
49
|
+
return pluginId;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Declares the events a plugin raises. One plugin owns a name: a second
|
|
53
|
+
* plugin declaring it throws, so two plugins cannot raise one event with two
|
|
54
|
+
* payloads. The owner declaring again replaces its entry.
|
|
55
|
+
*/ export function declarePluginDomainEvents(declarations, options) {
|
|
56
|
+
for (const declaration of declarations){
|
|
57
|
+
var _declaration_payloadKeys;
|
|
58
|
+
const name = nameOf(declaration.event);
|
|
59
|
+
if (!EVENT_NAME.test(name)) {
|
|
60
|
+
throw new Error(`plugin event name "${name}" must be dotted lower-case words`);
|
|
61
|
+
}
|
|
62
|
+
const pluginId = ownerOf('declared', name, options);
|
|
63
|
+
const existing = registry().declarations.get(name);
|
|
64
|
+
if (existing && existing.pluginId !== pluginId) {
|
|
65
|
+
throw new Error(`plugin event "${name}" is already declared by "${existing.pluginId}"; "${pluginId}" cannot declare it too`);
|
|
66
|
+
}
|
|
67
|
+
registry().declarations.set(name, {
|
|
68
|
+
pluginId,
|
|
69
|
+
event: name,
|
|
70
|
+
label: declaration.label,
|
|
71
|
+
description: declaration.description,
|
|
72
|
+
payloadKeys: [
|
|
73
|
+
...(_declaration_payloadKeys = declaration.payloadKeys) != null ? _declaration_payloadKeys : []
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Every declared event, sorted by name. */ export function listPluginDomainEvents() {
|
|
79
|
+
return [
|
|
80
|
+
...registry().declarations.values()
|
|
81
|
+
].sort((a, b)=>a.event.localeCompare(b.event));
|
|
82
|
+
}
|
|
83
|
+
/** The declaration of one event, or `null` when no plugin declares it. */ export function pluginDomainEventDeclaration(event) {
|
|
84
|
+
var _registry_declarations_get;
|
|
85
|
+
return (_registry_declarations_get = registry().declarations.get(event)) != null ? _registry_declarations_get : null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Subscribes to an event. A subscriber subscribing again to the same event
|
|
89
|
+
* replaces its earlier handler, so a module evaluated twice delivers once.
|
|
90
|
+
*
|
|
91
|
+
* The subscriber is named by plugin id, or `pluginId:name` when a plugin
|
|
92
|
+
* subscribes more than one handler to an event (its webhooks and its
|
|
93
|
+
* workflow triggers, say) — and that name is what the outbox records as
|
|
94
|
+
* delivered, so it must be stable.
|
|
95
|
+
*/ export function subscribePluginDomainEvent(event, handler, options) {
|
|
96
|
+
var _registry_subscriptions_get;
|
|
97
|
+
const name = nameOf(event);
|
|
98
|
+
const owner = ownerOf('subscribed', name, options);
|
|
99
|
+
const pluginId = (options == null ? void 0 : options.name) ? `${owner}:${options.name}` : owner;
|
|
100
|
+
const list = ((_registry_subscriptions_get = registry().subscriptions.get(name)) != null ? _registry_subscriptions_get : []).filter((entry)=>entry.pluginId !== pluginId);
|
|
101
|
+
list.push({
|
|
102
|
+
pluginId,
|
|
103
|
+
handler: handler
|
|
104
|
+
});
|
|
105
|
+
registry().subscriptions.set(name, list);
|
|
106
|
+
}
|
|
107
|
+
/** The plugins subscribed to an event, in subscription order. */ export function listPluginDomainEventSubscribers(event) {
|
|
108
|
+
var _registry_subscriptions_get;
|
|
109
|
+
return ((_registry_subscriptions_get = registry().subscriptions.get(event)) != null ? _registry_subscriptions_get : []).map((entry)=>entry.pluginId);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Hands an envelope to every subscriber not in `skip`, one after another. A
|
|
113
|
+
* throw is caught and reported and does not stop the others. The outbox
|
|
114
|
+
* drain calls this; a raiser never does.
|
|
115
|
+
*/ export async function deliverPluginDomainEvent(envelope, options = {}) {
|
|
116
|
+
var _registry_subscriptions_get;
|
|
117
|
+
const result = {
|
|
118
|
+
delivered: [],
|
|
119
|
+
failed: []
|
|
120
|
+
};
|
|
121
|
+
for (const { pluginId, handler } of (_registry_subscriptions_get = registry().subscriptions.get(envelope.event)) != null ? _registry_subscriptions_get : []){
|
|
122
|
+
var _options_skip;
|
|
123
|
+
if ((_options_skip = options.skip) == null ? void 0 : _options_skip.has(pluginId)) continue;
|
|
124
|
+
try {
|
|
125
|
+
var _ref;
|
|
126
|
+
var _options_attempts;
|
|
127
|
+
await handler(_extends({}, envelope, {
|
|
128
|
+
attempt: ((_ref = (_options_attempts = options.attempts) == null ? void 0 : _options_attempts[pluginId]) != null ? _ref : 0) + 1
|
|
129
|
+
}));
|
|
130
|
+
result.delivered.push(pluginId);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
var _ref1;
|
|
133
|
+
const message = String((_ref1 = error == null ? void 0 : error.message) != null ? _ref1 : error).slice(0, 500);
|
|
134
|
+
result.failed.push({
|
|
135
|
+
pluginId,
|
|
136
|
+
error: message
|
|
137
|
+
});
|
|
138
|
+
console.error(`[plugin-events] ${pluginId} failed on ${envelope.event} ${envelope.id}`, error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/** Test seam: forget every declaration and subscription. */ export function resetPluginDomainEventsForTests() {
|
|
144
|
+
registry().declarations.clear();
|
|
145
|
+
registry().subscriptions.clear();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//# sourceMappingURL=plugin-domain-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-domain-events.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'\n\n/**\n * Events a PLUGIN raises for other plugins (AGL-3611).\n *\n * `plugin-events.ts` carries the platform's own facts — a seat add-on, a paid\n * invoice — with payloads core declares. This is the same idea for facts\n * that belong to a plugin: one plugin declares an event and its payload,\n * raises it when the thing happens, and any other plugin subscribes by the\n * event's name without importing the plugin that raises it. Core names\n * nothing a plugin raises.\n *\n * DELIVERY IS RELIABLE, NOT FIRE-AND-FORGET. Raising an event writes an\n * outbox document (`plugin-event-outbox.ts` in the admin data lib), in the\n * same transaction as the write it reports where the raiser has one, and a\n * scheduled drain hands it to each subscriber in turn. A subscriber that\n * throws is retried with backoff, alone: the ones that already took the\n * event are not called again. Delivery is AT LEAST ONCE, so every envelope\n * carries `id`, stable across retries, for a subscriber to dedupe on.\n *\n * TYPED BY TOKEN. `definePluginDomainEvent<Payload>(name)` gives the raiser\n * and a subscriber the same payload type; a subscriber in another plugin\n * restates the shape it reads and defines its own token under the same\n * name, as a widget restates a zone's props.\n *\n * The registry lives on `globalThis` (AGL-3412): subscriptions register from\n * the plugins' server declarations at boot, in a module graph the routes and\n * the job runner do not share.\n */\n\n/** A typed handle on one event name. */\nexport interface PluginDomainEvent<Payload> {\n readonly id: string\n /** Type-level only: the payload the event carries. */\n readonly __payload?: Payload\n}\n\n/** The payload an event token carries. */\nexport type PluginDomainEventPayload<Event> = Event extends PluginDomainEvent<infer Payload>\n ? Payload\n : never\n\nconst EVENT_NAME = /^[a-z][a-zA-Z0-9]*(\\.[a-z][a-zA-Z0-9_]*)+$/\n\n/**\n * A token for one event name: lower-case words joined by dots, the first\n * naming what the event is about (`order.paid`, `return.requested`).\n */\nexport function definePluginDomainEvent<Payload>(id: string): PluginDomainEvent<Payload> {\n const name = String(id ?? '').trim()\n if (!EVENT_NAME.test(name)) {\n throw new Error(`plugin event name \"${id}\" must be dotted lower-case words, e.g. \"order.paid\"`)\n }\n return { id: name }\n}\n\n/** How a plugin describes an event it raises, for pickers and the docs. */\nexport interface PluginDomainEventDeclaration {\n event: PluginDomainEvent<unknown> | string\n /** \"Order paid\". */\n label: string\n /** One sentence: when it is raised. */\n description: string\n /** The top-level payload keys, for a person writing a subscriber. */\n payloadKeys?: readonly string[]\n}\n\n/** A declared event, with the plugin that raises it. */\nexport interface DeclaredPluginDomainEvent {\n pluginId: string\n event: string\n label: string\n description: string\n payloadKeys: readonly string[]\n}\n\n/** What a subscriber is handed. */\nexport interface PluginDomainEventEnvelope<Payload = unknown> {\n /** Stable across retries: the idempotency key for a subscriber. */\n id: string\n event: string\n /** The site the event happened on. */\n hostId: string\n /** The site's org, when the raiser knew it. */\n orgId: string | null\n /** When the fact happened, not when it was delivered. */\n occurredAtMs: number\n /** 1 on the first delivery to this subscriber, then 2, 3, … */\n attempt: number\n payload: Payload\n}\n\nexport type PluginDomainEventHandler<Payload = unknown> = (\n envelope: PluginDomainEventEnvelope<Payload>,\n) => void | Promise<void>\n\ninterface Subscription {\n pluginId: string\n handler: PluginDomainEventHandler<unknown>\n}\n\ninterface Registry {\n declarations: Map<string, DeclaredPluginDomainEvent>\n subscriptions: Map<string, Subscription[]>\n}\n\nconst REGISTRY_KEY = Symbol.for('@aglyn/aglyn:plugin-domain-events')\nconst globalScope = globalThis as typeof globalThis & { [REGISTRY_KEY]?: Registry }\n\nfunction registry(): Registry {\n if (!globalScope[REGISTRY_KEY]) {\n globalScope[REGISTRY_KEY] = { declarations: new Map(), subscriptions: new Map() }\n }\n return globalScope[REGISTRY_KEY] as Registry\n}\n\nconst nameOf = (event: PluginDomainEvent<unknown> | string): string =>\n typeof event === 'string' ? event : event.id\n\nfunction ownerOf(kind: string, name: string, options?: { pluginId?: string }): string {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `plugin event \"${name}\" ${kind} with no owner: pass { pluginId } outside a plugin register fn`,\n )\n }\n return pluginId\n}\n\n/**\n * Declares the events a plugin raises. One plugin owns a name: a second\n * plugin declaring it throws, so two plugins cannot raise one event with two\n * payloads. The owner declaring again replaces its entry.\n */\nexport function declarePluginDomainEvents(\n declarations: readonly PluginDomainEventDeclaration[],\n options?: { pluginId?: string },\n): void {\n for (const declaration of declarations) {\n const name = nameOf(declaration.event)\n if (!EVENT_NAME.test(name)) {\n throw new Error(`plugin event name \"${name}\" must be dotted lower-case words`)\n }\n const pluginId = ownerOf('declared', name, options)\n const existing = registry().declarations.get(name)\n if (existing && existing.pluginId !== pluginId) {\n throw new Error(\n `plugin event \"${name}\" is already declared by \"${existing.pluginId}\"; \"${pluginId}\" cannot declare it too`,\n )\n }\n registry().declarations.set(name, {\n pluginId,\n event: name,\n label: declaration.label,\n description: declaration.description,\n payloadKeys: [...(declaration.payloadKeys ?? [])],\n })\n }\n}\n\n/** Every declared event, sorted by name. */\nexport function listPluginDomainEvents(): DeclaredPluginDomainEvent[] {\n return [...registry().declarations.values()].sort((a, b) => a.event.localeCompare(b.event))\n}\n\n/** The declaration of one event, or `null` when no plugin declares it. */\nexport function pluginDomainEventDeclaration(event: string): DeclaredPluginDomainEvent | null {\n return registry().declarations.get(event) ?? null\n}\n\n/**\n * Subscribes to an event. A subscriber subscribing again to the same event\n * replaces its earlier handler, so a module evaluated twice delivers once.\n *\n * The subscriber is named by plugin id, or `pluginId:name` when a plugin\n * subscribes more than one handler to an event (its webhooks and its\n * workflow triggers, say) — and that name is what the outbox records as\n * delivered, so it must be stable.\n */\nexport function subscribePluginDomainEvent<Payload>(\n event: PluginDomainEvent<Payload> | string,\n handler: PluginDomainEventHandler<Payload>,\n options?: { pluginId?: string; name?: string },\n): void {\n const name = nameOf(event as PluginDomainEvent<unknown> | string)\n const owner = ownerOf('subscribed', name, options)\n const pluginId = options?.name ? `${owner}:${options.name}` : owner\n const list = (registry().subscriptions.get(name) ?? []).filter((entry) => entry.pluginId !== pluginId)\n list.push({ pluginId, handler: handler as PluginDomainEventHandler<unknown> })\n registry().subscriptions.set(name, list)\n}\n\n/** The plugins subscribed to an event, in subscription order. */\nexport function listPluginDomainEventSubscribers(event: string): string[] {\n return (registry().subscriptions.get(event) ?? []).map((entry) => entry.pluginId)\n}\n\n/** What one delivery pass came to. */\nexport interface PluginDomainEventDeliveryResult {\n /** Subscribers that took the event on this pass. */\n delivered: string[]\n /** Subscribers that threw, with what they said. */\n failed: Array<{ pluginId: string; error: string }>\n}\n\n/**\n * Hands an envelope to every subscriber not in `skip`, one after another. A\n * throw is caught and reported and does not stop the others. The outbox\n * drain calls this; a raiser never does.\n */\nexport async function deliverPluginDomainEvent(\n envelope: Omit<PluginDomainEventEnvelope, 'attempt'>,\n options: { skip?: ReadonlySet<string>; attempts?: Readonly<Record<string, number>> } = {},\n): Promise<PluginDomainEventDeliveryResult> {\n const result: PluginDomainEventDeliveryResult = { delivered: [], failed: [] }\n for (const { pluginId, handler } of registry().subscriptions.get(envelope.event) ?? []) {\n if (options.skip?.has(pluginId)) continue\n try {\n await handler({ ...envelope, attempt: (options.attempts?.[pluginId] ?? 0) + 1 })\n result.delivered.push(pluginId)\n } catch (error) {\n const message = String((error as Error)?.message ?? error).slice(0, 500)\n result.failed.push({ pluginId, error: message })\n console.error(`[plugin-events] ${pluginId} failed on ${envelope.event} ${envelope.id}`, error)\n }\n }\n return result\n}\n\n/** Test seam: forget every declaration and subscription. */\nexport function resetPluginDomainEventsForTests(): void {\n registry().declarations.clear()\n registry().subscriptions.clear()\n}\n"],"names":["getRegisteringPluginId","EVENT_NAME","definePluginDomainEvent","id","name","String","trim","test","Error","REGISTRY_KEY","Symbol","for","globalScope","globalThis","registry","declarations","Map","subscriptions","nameOf","event","ownerOf","kind","options","pluginId","declarePluginDomainEvents","declaration","existing","get","set","label","description","payloadKeys","listPluginDomainEvents","values","sort","a","b","localeCompare","pluginDomainEventDeclaration","subscribePluginDomainEvent","handler","owner","list","filter","entry","push","listPluginDomainEventSubscribers","map","deliverPluginDomainEvent","envelope","result","delivered","failed","skip","has","attempt","attempts","error","message","slice","console","resetPluginDomainEventsForTests","clear"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AA0CxE,MAAMC,aAAa;AAEnB;;;CAGC,GACD,OAAO,SAASC,wBAAiCC,EAAU;IACzD,MAAMC,OAAOC,OAAOF,aAAAA,KAAM,IAAIG,IAAI;IAClC,IAAI,CAACL,WAAWM,IAAI,CAACH,OAAO;QAC1B,MAAM,IAAII,MAAM,CAAC,mBAAmB,EAAEL,GAAG,oDAAoD,CAAC;IAChG;IACA,OAAO;QAAEA,IAAIC;IAAK;AACpB;AAoDA,MAAMK,eAAeC,OAAOC,GAAG,CAAC;AAChC,MAAMC,cAAcC;AAEpB,SAASC;IACP,IAAI,CAACF,WAAW,CAACH,aAAa,EAAE;QAC9BG,WAAW,CAACH,aAAa,GAAG;YAAEM,cAAc,IAAIC;YAAOC,eAAe,IAAID;QAAM;IAClF;IACA,OAAOJ,WAAW,CAACH,aAAa;AAClC;AAEA,MAAMS,SAAS,CAACC,QACd,OAAOA,UAAU,WAAWA,QAAQA,MAAMhB,EAAE;AAE9C,SAASiB,QAAQC,IAAY,EAAEjB,IAAY,EAAEkB,OAA+B;QACxDtB,MAAAA;IAAlB,MAAMuB,WAAW,EAACvB,QAAAA,0BAAAA,oCAAAA,0BAA4BsB,2BAAAA,QAASC,QAAQ,YAA7CvB,OAAiD,IAAIM,IAAI;IAC3E,IAAI,CAACiB,UAAU;QACb,MAAM,IAAIf,MACR,CAAC,cAAc,EAAEJ,KAAK,EAAE,EAAEiB,KAAK,8DAA8D,CAAC;IAElG;IACA,OAAOE;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASC,0BACdT,YAAqD,EACrDO,OAA+B;IAE/B,KAAK,MAAMG,eAAeV,aAAc;YAiBlBU;QAhBpB,MAAMrB,OAAOc,OAAOO,YAAYN,KAAK;QACrC,IAAI,CAAClB,WAAWM,IAAI,CAACH,OAAO;YAC1B,MAAM,IAAII,MAAM,CAAC,mBAAmB,EAAEJ,KAAK,iCAAiC,CAAC;QAC/E;QACA,MAAMmB,WAAWH,QAAQ,YAAYhB,MAAMkB;QAC3C,MAAMI,WAAWZ,WAAWC,YAAY,CAACY,GAAG,CAACvB;QAC7C,IAAIsB,YAAYA,SAASH,QAAQ,KAAKA,UAAU;YAC9C,MAAM,IAAIf,MACR,CAAC,cAAc,EAAEJ,KAAK,0BAA0B,EAAEsB,SAASH,QAAQ,CAAC,IAAI,EAAEA,SAAS,uBAAuB,CAAC;QAE/G;QACAT,WAAWC,YAAY,CAACa,GAAG,CAACxB,MAAM;YAChCmB;YACAJ,OAAOf;YACPyB,OAAOJ,YAAYI,KAAK;YACxBC,aAAaL,YAAYK,WAAW;YACpCC,aAAa;oBAAKN,2BAAAA,YAAYM,WAAW,YAAvBN,2BAA2B,EAAE;aAAE;QACnD;IACF;AACF;AAEA,0CAA0C,GAC1C,OAAO,SAASO;IACd,OAAO;WAAIlB,WAAWC,YAAY,CAACkB,MAAM;KAAG,CAACC,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEhB,KAAK,CAACkB,aAAa,CAACD,EAAEjB,KAAK;AAC3F;AAEA,wEAAwE,GACxE,OAAO,SAASmB,6BAA6BnB,KAAa;QACjDL;IAAP,QAAOA,6BAAAA,WAAWC,YAAY,CAACY,GAAG,CAACR,kBAA5BL,6BAAsC;AAC/C;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASyB,2BACdpB,KAA0C,EAC1CqB,OAA0C,EAC1ClB,OAA8C;QAKhCR;IAHd,MAAMV,OAAOc,OAAOC;IACpB,MAAMsB,QAAQrB,QAAQ,cAAchB,MAAMkB;IAC1C,MAAMC,WAAWD,CAAAA,2BAAAA,QAASlB,IAAI,IAAG,GAAGqC,MAAM,CAAC,EAAEnB,QAAQlB,IAAI,EAAE,GAAGqC;IAC9D,MAAMC,OAAO,EAAC5B,8BAAAA,WAAWG,aAAa,CAACU,GAAG,CAACvB,iBAA7BU,8BAAsC,EAAE,EAAE6B,MAAM,CAAC,CAACC,QAAUA,MAAMrB,QAAQ,KAAKA;IAC7FmB,KAAKG,IAAI,CAAC;QAAEtB;QAAUiB,SAASA;IAA6C;IAC5E1B,WAAWG,aAAa,CAACW,GAAG,CAACxB,MAAMsC;AACrC;AAEA,+DAA+D,GAC/D,OAAO,SAASI,iCAAiC3B,KAAa;QACpDL;IAAR,OAAO,EAACA,8BAAAA,WAAWG,aAAa,CAACU,GAAG,CAACR,kBAA7BL,8BAAuC,EAAE,EAAEiC,GAAG,CAAC,CAACH,QAAUA,MAAMrB,QAAQ;AAClF;AAUA;;;;CAIC,GACD,OAAO,eAAeyB,yBACpBC,QAAoD,EACpD3B,UAAuF,CAAC,CAAC;QAGrDR;IADpC,MAAMoC,SAA0C;QAAEC,WAAW,EAAE;QAAEC,QAAQ,EAAE;IAAC;IAC5E,KAAK,MAAM,EAAE7B,QAAQ,EAAEiB,OAAO,EAAE,KAAI1B,8BAAAA,WAAWG,aAAa,CAACU,GAAG,CAACsB,SAAS9B,KAAK,aAA3CL,8BAAgD,EAAE,CAAE;YAClFQ;QAAJ,KAAIA,gBAAAA,QAAQ+B,IAAI,qBAAZ/B,cAAcgC,GAAG,CAAC/B,WAAW;QACjC,IAAI;;gBACqCD;YAAvC,MAAMkB,QAAQ,aAAKS;gBAAUM,SAAS,UAACjC,oBAAAA,QAAQkC,QAAQ,qBAAhBlC,iBAAkB,CAACC,SAAS,mBAAI,KAAK;;YAC5E2B,OAAOC,SAAS,CAACN,IAAI,CAACtB;QACxB,EAAE,OAAOkC,OAAO;;YACd,MAAMC,UAAUrD,gBAAQoD,yBAAD,AAACA,MAAiBC,OAAO,oBAAID,OAAOE,KAAK,CAAC,GAAG;YACpET,OAAOE,MAAM,CAACP,IAAI,CAAC;gBAAEtB;gBAAUkC,OAAOC;YAAQ;YAC9CE,QAAQH,KAAK,CAAC,CAAC,gBAAgB,EAAElC,SAAS,WAAW,EAAE0B,SAAS9B,KAAK,CAAC,CAAC,EAAE8B,SAAS9C,EAAE,EAAE,EAAEsD;QAC1F;IACF;IACA,OAAOP;AACT;AAEA,0DAA0D,GAC1D,OAAO,SAASW;IACd/C,WAAWC,YAAY,CAAC+C,KAAK;IAC7BhD,WAAWG,aAAa,CAAC6C,KAAK;AAChC"}
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
* billing-webhook hooks are the precedent for one vendor's events; this is
|
|
25
25
|
* the same shape for the platform's own.
|
|
26
26
|
*
|
|
27
|
+
* Events a PLUGIN raises for other plugins — an order paid, a return
|
|
28
|
+
* refunded — are `plugin-domain-events.ts` (AGL-3611): declared by the plugin,
|
|
29
|
+
* delivered through an outbox, at least once.
|
|
30
|
+
*
|
|
27
31
|
* Handlers run sequentially and are ISOLATED: a plugin that throws is
|
|
28
32
|
* logged and the next one runs, because these are consequences of a write
|
|
29
33
|
* that has already happened — an activity row, a notification — and a
|
|
@@ -129,6 +133,53 @@ export interface PluginEventPayloads {
|
|
|
129
133
|
data: Readonly<Record<string, unknown>>;
|
|
130
134
|
}>;
|
|
131
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* A site's email was opened, or a link in it clicked (AGL-3605): the
|
|
138
|
+
* delivery events one webhook delivery recorded for a send tagged with the
|
|
139
|
+
* site, the FIRST of each type per message only — the delivery log decides
|
|
140
|
+
* which those are, so a replay or a reader opening one email six times is
|
|
141
|
+
* one event. Raised by the plugin whose webhook reads the provider, after
|
|
142
|
+
* the log has recorded them; a subscriber that counts engagement of its own
|
|
143
|
+
* reads it here rather than subscribing to the provider.
|
|
144
|
+
*/
|
|
145
|
+
'host.email.engaged': {
|
|
146
|
+
hostId: string;
|
|
147
|
+
events: ReadonlyArray<{
|
|
148
|
+
/** The recipient, lowercased. */
|
|
149
|
+
to: string;
|
|
150
|
+
/** `opened` or `clicked`. */
|
|
151
|
+
type: string;
|
|
152
|
+
/** When it happened, epoch ms. */
|
|
153
|
+
at: number;
|
|
154
|
+
firstOfType: boolean;
|
|
155
|
+
}>;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* A custom domain was connected to a site and the hosting provider accepted
|
|
159
|
+
* it (AGL-3629). Raised once the claim is written, so `domain` is the site's
|
|
160
|
+
* `cname` and no other site's. The site may still be awaiting a certificate
|
|
161
|
+
* or a DNS change; a plugin that needs the domain registered somewhere — a
|
|
162
|
+
* payment provider, a search console — does that now rather than waiting
|
|
163
|
+
* for traffic.
|
|
164
|
+
*/
|
|
165
|
+
'host.domain.attached': {
|
|
166
|
+
orgId: string | null;
|
|
167
|
+
hostId: string;
|
|
168
|
+
domain: string;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* A domain stopped serving a site (AGL-3629): its custom domain was
|
|
172
|
+
* disconnected, or the site was deleted — which releases its platform
|
|
173
|
+
* subdomain too, raised as a second event with that name. A plugin undoes
|
|
174
|
+
* whatever it registered for the name, and must check the name is not
|
|
175
|
+
* still served by ANOTHER site first (a `www.` twin, a reused subdomain).
|
|
176
|
+
*/
|
|
177
|
+
'host.domain.released': {
|
|
178
|
+
orgId: string | null;
|
|
179
|
+
hostId: string;
|
|
180
|
+
domain: string;
|
|
181
|
+
reason: 'detached' | 'site-deleted';
|
|
182
|
+
};
|
|
132
183
|
/** One permission key moved on a subject of the org. */
|
|
133
184
|
'org.permissions.changed': {
|
|
134
185
|
orgId: string;
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
* billing-webhook hooks are the precedent for one vendor's events; this is
|
|
24
24
|
* the same shape for the platform's own.
|
|
25
25
|
*
|
|
26
|
+
* Events a PLUGIN raises for other plugins — an order paid, a return
|
|
27
|
+
* refunded — are `plugin-domain-events.ts` (AGL-3611): declared by the plugin,
|
|
28
|
+
* delivered through an outbox, at least once.
|
|
29
|
+
*
|
|
26
30
|
* Handlers run sequentially and are ISOLATED: a plugin that throws is
|
|
27
31
|
* logged and the next one runs, because these are consequences of a write
|
|
28
32
|
* that has already happened — an activity row, a notification — and a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-events.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\n/**\n * Platform events a plugin listens to (AGL-2939, AGL-2940).\n *\n * Core raises a small number of facts about the workspace that a plugin\n * may care about — a seat add-on was bought or dropped, a permission key\n * moved on a member, a role or a site — and a plugin subscribes from its\n * declarations rather than core calling the plugin's own writer. The\n * billing-webhook hooks are the precedent for one vendor's events; this is\n * the same shape for the platform's own.\n *\n * Handlers run sequentially and are ISOLATED: a plugin that throws is\n * logged and the next one runs, because these are consequences of a write\n * that has already happened — an activity row, a notification — and a\n * failure in one must not undo or hide the write for the customer.\n */\n\nimport type { OrgSeatAddons } from '../foundation'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** Who did it, as the activity logs record an actor. */\nexport interface PluginEventActor {\n uid: string | null\n email: string | null\n}\n\n/** The payload each platform event carries. */\nexport interface PluginEventPayloads {\n /** An org's purchased seat add-ons were written (a purchase, a removal, a webhook mirror). */\n 'org.seatAddons.changed': {\n orgId: string\n actor: PluginEventActor\n before: OrgSeatAddons | null | undefined\n after: OrgSeatAddons | null | undefined\n }\n /**\n * The workspace's Stripe invoice was paid, in full, by a real charge.\n *\n * Raised from the platform billing webhook once the invoice has resolved\n * to a workspace, so a plugin that bills its own usage learns the outcome\n * of the invoice it asked for without subscribing to Stripe itself. The\n * invoice's own `metadata` travels with it, unread by core: a plugin that\n * stamped `pluginId` on the invoice it created recognizes its own by it,\n * and every other handler ignores the event.\n *\n * `paidOutOfBand` is Stripe's own flag for an invoice marked paid without\n * a charge. A plugin counting payment history must not count one: it is a\n * staff action, not evidence that money moved.\n */\n 'billing.invoice.paid': {\n orgId: string\n invoiceId: string\n amountPaidCents: number\n currency: string\n paidOutOfBand: boolean\n metadata: Readonly<Record<string, string>>\n }\n /** A charge attempt on the workspace's invoice failed or needs action. */\n 'billing.invoice.failed': {\n orgId: string\n invoiceId: string\n amountDueCents: number\n metadata: Readonly<Record<string, string>>\n }\n /**\n * An invoice was closed without being paid — voided, or written off as\n * uncollectible.\n *\n * ONE event for both Stripe events because they are one fact to a plugin:\n * this invoice will never be paid and nothing more is owed on it. A\n * handler that must tell them apart reads `reason`; one that only needs\n * to stop waiting does not.\n */\n 'billing.invoice.closed': {\n orgId: string\n invoiceId: string\n reason: 'voided' | 'uncollectible'\n metadata: Readonly<Record<string, string>>\n }\n /** A cardholder disputed a charge of the workspace's. */\n 'billing.dispute.opened': {\n orgId: string\n chargeId: string\n invoiceId: string | null\n amountCents: number\n }\n /**\n * The workspace's default payment method changed, or its type did.\n *\n * `defaultType` is Stripe's own type string (`card`, `us_bank_account`,\n * `link`, …), or `null` when the customer has no default. Core states the\n * fact and takes no view: which types a plugin will extend credit against\n * is the plugin's decision, not the platform's.\n */\n 'billing.paymentMethod.changed': {\n orgId: string\n defaultType: string | null\n }\n /**\n * Records were REMOVED from a collection, by a path that is not the\n * owning plugin's own (AGL-3330): a deletion through the public API, a\n * person's erasure.\n *\n * Raised after the delete has landed, with each removed document's data\n * as it stood, so a plugin that keeps a figure over those rows — a counter\n * no increment can take back — can recount what they fed. Core names the\n * collection and takes no view of what it holds.\n *\n * `hostIds` are the sites the records were on: the one site for a site's\n * collection, every site of the organization for an organization row.\n */\n 'host.records.removed': {\n orgId: string | null\n hostIds: readonly string[]\n collection: string\n records: ReadonlyArray<{ id: string; data: Readonly<Record<string, unknown>> }>\n }\n /** One permission key moved on a subject of the org. */\n 'org.permissions.changed': {\n orgId: string\n actor: PluginEventActor\n subject: {\n type: 'org' | 'role' | 'member' | 'host'\n id?: string | null\n name?: string | null\n }\n permission: string\n granted: boolean\n }\n}\n\nexport type PluginEventName = keyof PluginEventPayloads\n\nexport type PluginEventHandler<E extends PluginEventName> = (\n payload: PluginEventPayloads[E],\n) => void | Promise<void>\n\ninterface Registration {\n pluginId: string\n handler: PluginEventHandler<PluginEventName>\n}\n\nconst handlers = new Map<PluginEventName, Registration[]>()\n\n/**\n * Subscribes to an event. Owner = the loader's marker inside a register fn,\n * else `options.pluginId`; a subscription with neither throws. A plugin\n * re-subscribing replaces its earlier handler for that event, so a module\n * evaluated twice does not write two rows.\n */\nexport function registerPluginEventHandler<E extends PluginEventName>(\n event: E,\n handler: PluginEventHandler<E>,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `plugin event \"${event}\" subscribed with no owner: pass { pluginId } ` +\n 'when subscribing outside a plugin register fn',\n )\n }\n const list = (handlers.get(event) ?? []).filter((entry) => entry.pluginId !== pluginId)\n list.push({ pluginId, handler: handler as PluginEventHandler<PluginEventName> })\n handlers.set(event, list)\n}\n\n/**\n * Raises an event. Every handler runs, in subscription order; a throw is\n * logged with the plugin's id and does not stop the others.\n */\nexport async function runPluginEventHandlers<E extends PluginEventName>(\n event: E,\n payload: PluginEventPayloads[E],\n): Promise<{ handled: number; failed: string[] }> {\n let handled = 0\n const failed: string[] = []\n for (const { pluginId, handler } of handlers.get(event) ?? []) {\n try {\n await handler(payload)\n handled += 1\n } catch (error) {\n failed.push(pluginId)\n console.error(`[plugins] ${pluginId} failed on event ${event}`, error)\n }\n }\n return { handled, failed }\n}\n\n/** The plugins subscribed to an event, for diagnostics. */\nexport function listPluginEventHandlers(event: PluginEventName): string[] {\n return (handlers.get(event) ?? []).map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every subscription. */\nexport function resetPluginEventHandlersForTests(): void {\n handlers.clear()\n}\n"],"names":["getRegisteringPluginId","handlers","Map","registerPluginEventHandler","event","handler","options","pluginId","trim","Error","list","get","filter","entry","push","set","runPluginEventHandlers","payload","handled","failed","error","console","listPluginEventHandlers","map","resetPluginEventHandlersForTests","clear"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;CAcC,GAGD,SAASA,sBAAsB,QAAQ,qCAAiC;AA4HxE,MAAMC,WAAW,IAAIC;AAErB;;;;;CAKC,GACD,OAAO,SAASC,2BACdC,KAAQ,EACRC,OAA8B,EAC9BC,OAA+B;QAEbN,MAAAA,yBAOJC;IAPd,MAAMM,WAAW,EAACP,QAAAA,0BAAAA,oCAAAA,0BAA4BM,2BAAAA,QAASC,QAAQ,YAA7CP,OAAiD,IAAIQ,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,CAAC,cAAc,EAAEL,MAAM,8CAA8C,CAAC,GACpE;IAEN;IACA,MAAMM,OAAO,EAACT,gBAAAA,SAASU,GAAG,CAACP,kBAAbH,gBAAuB,EAAE,EAAEW,MAAM,CAAC,CAACC,QAAUA,MAAMN,QAAQ,KAAKA;IAC9EG,KAAKI,IAAI,CAAC;QAAEP;QAAUF,SAASA;IAA+C;IAC9EJ,SAASc,GAAG,CAACX,OAAOM;AACtB;AAEA;;;CAGC,GACD,OAAO,eAAeM,uBACpBZ,KAAQ,EACRa,OAA+B;QAIKhB;IAFpC,IAAIiB,UAAU;IACd,MAAMC,SAAmB,EAAE;IAC3B,KAAK,MAAM,EAAEZ,QAAQ,EAAEF,OAAO,EAAE,KAAIJ,gBAAAA,SAASU,GAAG,CAACP,kBAAbH,gBAAuB,EAAE,CAAE;QAC7D,IAAI;YACF,MAAMI,QAAQY;YACdC,WAAW;QACb,EAAE,OAAOE,OAAO;YACdD,OAAOL,IAAI,CAACP;YACZc,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEb,SAAS,iBAAiB,EAAEH,OAAO,EAAEgB;QAClE;IACF;IACA,OAAO;QAAEF;QAASC;IAAO;AAC3B;AAEA,yDAAyD,GACzD,OAAO,SAASG,wBAAwBlB,KAAsB;QACpDH;IAAR,OAAO,EAACA,gBAAAA,SAASU,GAAG,CAACP,kBAAbH,gBAAuB,EAAE,EAAEsB,GAAG,CAAC,CAACV,QAAUA,MAAMN,QAAQ;AAClE;AAEA,0CAA0C,GAC1C,OAAO,SAASiB;IACdvB,SAASwB,KAAK;AAChB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-events.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\n/**\n * Platform events a plugin listens to (AGL-2939, AGL-2940).\n *\n * Core raises a small number of facts about the workspace that a plugin\n * may care about — a seat add-on was bought or dropped, a permission key\n * moved on a member, a role or a site — and a plugin subscribes from its\n * declarations rather than core calling the plugin's own writer. The\n * billing-webhook hooks are the precedent for one vendor's events; this is\n * the same shape for the platform's own.\n *\n * Events a PLUGIN raises for other plugins — an order paid, a return\n * refunded — are `plugin-domain-events.ts` (AGL-3611): declared by the plugin,\n * delivered through an outbox, at least once.\n *\n * Handlers run sequentially and are ISOLATED: a plugin that throws is\n * logged and the next one runs, because these are consequences of a write\n * that has already happened — an activity row, a notification — and a\n * failure in one must not undo or hide the write for the customer.\n */\n\nimport type { OrgSeatAddons } from '../foundation'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** Who did it, as the activity logs record an actor. */\nexport interface PluginEventActor {\n uid: string | null\n email: string | null\n}\n\n/** The payload each platform event carries. */\nexport interface PluginEventPayloads {\n /** An org's purchased seat add-ons were written (a purchase, a removal, a webhook mirror). */\n 'org.seatAddons.changed': {\n orgId: string\n actor: PluginEventActor\n before: OrgSeatAddons | null | undefined\n after: OrgSeatAddons | null | undefined\n }\n /**\n * The workspace's Stripe invoice was paid, in full, by a real charge.\n *\n * Raised from the platform billing webhook once the invoice has resolved\n * to a workspace, so a plugin that bills its own usage learns the outcome\n * of the invoice it asked for without subscribing to Stripe itself. The\n * invoice's own `metadata` travels with it, unread by core: a plugin that\n * stamped `pluginId` on the invoice it created recognizes its own by it,\n * and every other handler ignores the event.\n *\n * `paidOutOfBand` is Stripe's own flag for an invoice marked paid without\n * a charge. A plugin counting payment history must not count one: it is a\n * staff action, not evidence that money moved.\n */\n 'billing.invoice.paid': {\n orgId: string\n invoiceId: string\n amountPaidCents: number\n currency: string\n paidOutOfBand: boolean\n metadata: Readonly<Record<string, string>>\n }\n /** A charge attempt on the workspace's invoice failed or needs action. */\n 'billing.invoice.failed': {\n orgId: string\n invoiceId: string\n amountDueCents: number\n metadata: Readonly<Record<string, string>>\n }\n /**\n * An invoice was closed without being paid — voided, or written off as\n * uncollectible.\n *\n * ONE event for both Stripe events because they are one fact to a plugin:\n * this invoice will never be paid and nothing more is owed on it. A\n * handler that must tell them apart reads `reason`; one that only needs\n * to stop waiting does not.\n */\n 'billing.invoice.closed': {\n orgId: string\n invoiceId: string\n reason: 'voided' | 'uncollectible'\n metadata: Readonly<Record<string, string>>\n }\n /** A cardholder disputed a charge of the workspace's. */\n 'billing.dispute.opened': {\n orgId: string\n chargeId: string\n invoiceId: string | null\n amountCents: number\n }\n /**\n * The workspace's default payment method changed, or its type did.\n *\n * `defaultType` is Stripe's own type string (`card`, `us_bank_account`,\n * `link`, …), or `null` when the customer has no default. Core states the\n * fact and takes no view: which types a plugin will extend credit against\n * is the plugin's decision, not the platform's.\n */\n 'billing.paymentMethod.changed': {\n orgId: string\n defaultType: string | null\n }\n /**\n * Records were REMOVED from a collection, by a path that is not the\n * owning plugin's own (AGL-3330): a deletion through the public API, a\n * person's erasure.\n *\n * Raised after the delete has landed, with each removed document's data\n * as it stood, so a plugin that keeps a figure over those rows — a counter\n * no increment can take back — can recount what they fed. Core names the\n * collection and takes no view of what it holds.\n *\n * `hostIds` are the sites the records were on: the one site for a site's\n * collection, every site of the organization for an organization row.\n */\n 'host.records.removed': {\n orgId: string | null\n hostIds: readonly string[]\n collection: string\n records: ReadonlyArray<{ id: string; data: Readonly<Record<string, unknown>> }>\n }\n /**\n * A site's email was opened, or a link in it clicked (AGL-3605): the\n * delivery events one webhook delivery recorded for a send tagged with the\n * site, the FIRST of each type per message only — the delivery log decides\n * which those are, so a replay or a reader opening one email six times is\n * one event. Raised by the plugin whose webhook reads the provider, after\n * the log has recorded them; a subscriber that counts engagement of its own\n * reads it here rather than subscribing to the provider.\n */\n 'host.email.engaged': {\n hostId: string\n events: ReadonlyArray<{\n /** The recipient, lowercased. */\n to: string\n /** `opened` or `clicked`. */\n type: string\n /** When it happened, epoch ms. */\n at: number\n firstOfType: boolean\n }>\n }\n /**\n * A custom domain was connected to a site and the hosting provider accepted\n * it (AGL-3629). Raised once the claim is written, so `domain` is the site's\n * `cname` and no other site's. The site may still be awaiting a certificate\n * or a DNS change; a plugin that needs the domain registered somewhere — a\n * payment provider, a search console — does that now rather than waiting\n * for traffic.\n */\n 'host.domain.attached': {\n orgId: string | null\n hostId: string\n domain: string\n }\n /**\n * A domain stopped serving a site (AGL-3629): its custom domain was\n * disconnected, or the site was deleted — which releases its platform\n * subdomain too, raised as a second event with that name. A plugin undoes\n * whatever it registered for the name, and must check the name is not\n * still served by ANOTHER site first (a `www.` twin, a reused subdomain).\n */\n 'host.domain.released': {\n orgId: string | null\n hostId: string\n domain: string\n reason: 'detached' | 'site-deleted'\n }\n /** One permission key moved on a subject of the org. */\n 'org.permissions.changed': {\n orgId: string\n actor: PluginEventActor\n subject: {\n type: 'org' | 'role' | 'member' | 'host'\n id?: string | null\n name?: string | null\n }\n permission: string\n granted: boolean\n }\n}\n\nexport type PluginEventName = keyof PluginEventPayloads\n\nexport type PluginEventHandler<E extends PluginEventName> = (\n payload: PluginEventPayloads[E],\n) => void | Promise<void>\n\ninterface Registration {\n pluginId: string\n handler: PluginEventHandler<PluginEventName>\n}\n\nconst handlers = new Map<PluginEventName, Registration[]>()\n\n/**\n * Subscribes to an event. Owner = the loader's marker inside a register fn,\n * else `options.pluginId`; a subscription with neither throws. A plugin\n * re-subscribing replaces its earlier handler for that event, so a module\n * evaluated twice does not write two rows.\n */\nexport function registerPluginEventHandler<E extends PluginEventName>(\n event: E,\n handler: PluginEventHandler<E>,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `plugin event \"${event}\" subscribed with no owner: pass { pluginId } ` +\n 'when subscribing outside a plugin register fn',\n )\n }\n const list = (handlers.get(event) ?? []).filter((entry) => entry.pluginId !== pluginId)\n list.push({ pluginId, handler: handler as PluginEventHandler<PluginEventName> })\n handlers.set(event, list)\n}\n\n/**\n * Raises an event. Every handler runs, in subscription order; a throw is\n * logged with the plugin's id and does not stop the others.\n */\nexport async function runPluginEventHandlers<E extends PluginEventName>(\n event: E,\n payload: PluginEventPayloads[E],\n): Promise<{ handled: number; failed: string[] }> {\n let handled = 0\n const failed: string[] = []\n for (const { pluginId, handler } of handlers.get(event) ?? []) {\n try {\n await handler(payload)\n handled += 1\n } catch (error) {\n failed.push(pluginId)\n console.error(`[plugins] ${pluginId} failed on event ${event}`, error)\n }\n }\n return { handled, failed }\n}\n\n/** The plugins subscribed to an event, for diagnostics. */\nexport function listPluginEventHandlers(event: PluginEventName): string[] {\n return (handlers.get(event) ?? []).map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every subscription. */\nexport function resetPluginEventHandlersForTests(): void {\n handlers.clear()\n}\n"],"names":["getRegisteringPluginId","handlers","Map","registerPluginEventHandler","event","handler","options","pluginId","trim","Error","list","get","filter","entry","push","set","runPluginEventHandlers","payload","handled","failed","error","console","listPluginEventHandlers","map","resetPluginEventHandlersForTests","clear"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;CAkBC,GAGD,SAASA,sBAAsB,QAAQ,qCAAiC;AA2KxE,MAAMC,WAAW,IAAIC;AAErB;;;;;CAKC,GACD,OAAO,SAASC,2BACdC,KAAQ,EACRC,OAA8B,EAC9BC,OAA+B;QAEbN,MAAAA,yBAOJC;IAPd,MAAMM,WAAW,EAACP,QAAAA,0BAAAA,oCAAAA,0BAA4BM,2BAAAA,QAASC,QAAQ,YAA7CP,OAAiD,IAAIQ,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,CAAC,cAAc,EAAEL,MAAM,8CAA8C,CAAC,GACpE;IAEN;IACA,MAAMM,OAAO,EAACT,gBAAAA,SAASU,GAAG,CAACP,kBAAbH,gBAAuB,EAAE,EAAEW,MAAM,CAAC,CAACC,QAAUA,MAAMN,QAAQ,KAAKA;IAC9EG,KAAKI,IAAI,CAAC;QAAEP;QAAUF,SAASA;IAA+C;IAC9EJ,SAASc,GAAG,CAACX,OAAOM;AACtB;AAEA;;;CAGC,GACD,OAAO,eAAeM,uBACpBZ,KAAQ,EACRa,OAA+B;QAIKhB;IAFpC,IAAIiB,UAAU;IACd,MAAMC,SAAmB,EAAE;IAC3B,KAAK,MAAM,EAAEZ,QAAQ,EAAEF,OAAO,EAAE,KAAIJ,gBAAAA,SAASU,GAAG,CAACP,kBAAbH,gBAAuB,EAAE,CAAE;QAC7D,IAAI;YACF,MAAMI,QAAQY;YACdC,WAAW;QACb,EAAE,OAAOE,OAAO;YACdD,OAAOL,IAAI,CAACP;YACZc,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEb,SAAS,iBAAiB,EAAEH,OAAO,EAAEgB;QAClE;IACF;IACA,OAAO;QAAEF;QAASC;IAAO;AAC3B;AAEA,yDAAyD,GACzD,OAAO,SAASG,wBAAwBlB,KAAsB;QACpDH;IAAR,OAAO,EAACA,gBAAAA,SAASU,GAAG,CAACP,kBAAbH,gBAAuB,EAAE,EAAEsB,GAAG,CAAC,CAACV,QAAUA,MAAMN,QAAQ;AAClE;AAEA,0CAA0C,GAC1C,OAAO,SAASiB;IACdvB,SAASwB,KAAK;AAChB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
* Outside parties that fulfill a seller's lines (AGL-3634): a fulfillment
|
|
19
|
+
* network that picks and packs from its own warehouses, a print-on-demand or
|
|
20
|
+
* dropship supplier that makes and sends the goods itself.
|
|
21
|
+
*
|
|
22
|
+
* Each one takes some of a record's lines away from the merchant's own
|
|
23
|
+
* packing table. Whoever else ships that record — a label buyer, another
|
|
24
|
+
* provider, the merchant by hand — must know which units are already spoken
|
|
25
|
+
* for, or the customer gets two parcels and the merchant pays twice. The
|
|
26
|
+
* provider plugins never import one another and never import the seller, so
|
|
27
|
+
* each registers a {@link PluginFulfillmentProvider} here and answers one
|
|
28
|
+
* question: which units of this record do you hold that have not shipped?
|
|
29
|
+
*
|
|
30
|
+
* A HOLD IS ONLY WHAT HAS NOT SHIPPED. Once the provider ships, it writes the
|
|
31
|
+
* shipment onto the record through `core.shipment-records`, the record's own
|
|
32
|
+
* unshipped count drops, and the hold drops with it. A canceled or failed
|
|
33
|
+
* hand-off is no hold: the units are the merchant's again.
|
|
34
|
+
*
|
|
35
|
+
* A provider that cannot answer is reported by name in `unanswered` rather
|
|
36
|
+
* than read as holding nothing, so a caller deciding whether to ship can say
|
|
37
|
+
* why it is waiting instead of shipping a parcel that may already be on its
|
|
38
|
+
* way from somewhere else.
|
|
39
|
+
*
|
|
40
|
+
* Import this module by its own subpath
|
|
41
|
+
* (`@aglyn/aglyn/plugin-manager/plugin-fulfillment-providers`); it is not in
|
|
42
|
+
* the barrel.
|
|
43
|
+
*/
|
|
44
|
+
/** Units of one line a provider has taken and not yet shipped. */
|
|
45
|
+
export interface PluginFulfillmentHold {
|
|
46
|
+
/** The provider's id, unique across plugins: `shipbob`, `printful`. */
|
|
47
|
+
providerId: string;
|
|
48
|
+
/** What a person calls it. */
|
|
49
|
+
providerLabel: string;
|
|
50
|
+
/** The line's position on the record, as `core.shipment-records` names it. */
|
|
51
|
+
lineIndex: number;
|
|
52
|
+
/** Units of the line the provider holds that have not shipped. */
|
|
53
|
+
quantity: number;
|
|
54
|
+
/**
|
|
55
|
+
* `pending` — taken and not yet accepted by the provider (queued, or being
|
|
56
|
+
* sent); `accepted` — the provider has it and is fulfilling it.
|
|
57
|
+
*/
|
|
58
|
+
state: 'pending' | 'accepted';
|
|
59
|
+
/** The provider's own reference for the hand-off, for a person to look it up there. */
|
|
60
|
+
reference?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface PluginFulfillmentProvider {
|
|
63
|
+
/** Stable and unique across plugins; a plugin may register several. */
|
|
64
|
+
id: string;
|
|
65
|
+
label: string;
|
|
66
|
+
/** The units of one record this provider holds; empty when none. */
|
|
67
|
+
holds(hostId: string, recordId: string): Promise<PluginFulfillmentHold[]>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Registers a provider. A plugin registers each of its providers under the
|
|
71
|
+
* provider's id, and registering the same id again replaces it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function registerPluginFulfillmentProvider(provider: PluginFulfillmentProvider, options?: {
|
|
74
|
+
pluginId?: string;
|
|
75
|
+
}): void;
|
|
76
|
+
/** Every registered provider, with the plugin that registered it. */
|
|
77
|
+
export declare function listPluginFulfillmentProviders(): Array<{
|
|
78
|
+
pluginId: string;
|
|
79
|
+
id: string;
|
|
80
|
+
label: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** What every provider holds of one record. */
|
|
83
|
+
export interface PluginFulfillmentHolds {
|
|
84
|
+
holds: PluginFulfillmentHold[];
|
|
85
|
+
/** Providers that threw: they may hold units nobody can see. */
|
|
86
|
+
unanswered: Array<{
|
|
87
|
+
providerId: string;
|
|
88
|
+
providerLabel: string;
|
|
89
|
+
}>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Asks every provider — or every provider but those of `exceptPluginId`, so
|
|
93
|
+
* a provider deciding what it may take does not count itself — what it holds
|
|
94
|
+
* of one record. Each is isolated: one that throws is logged and named in
|
|
95
|
+
* `unanswered`, and the rest still answer. A hold of no units is dropped.
|
|
96
|
+
*/
|
|
97
|
+
export declare function pluginFulfillmentHolds(hostId: string, recordId: string, options?: {
|
|
98
|
+
exceptPluginId?: string;
|
|
99
|
+
}): Promise<PluginFulfillmentHolds>;
|
|
100
|
+
/** Units held per line, summed across providers. */
|
|
101
|
+
export declare function heldQuantitiesByLine(holds: readonly PluginFulfillmentHold[]): Map<number, number>;
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
const PLUGIN_FULFILLMENT_PROVIDERS = definePluginServiceContract('core.fulfillment-providers', {
|
|
20
|
+
multiple: true
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Registers a provider. A plugin registers each of its providers under the
|
|
24
|
+
* provider's id, and registering the same id again replaces it.
|
|
25
|
+
*/ export function registerPluginFulfillmentProvider(provider, options) {
|
|
26
|
+
var _ref, _getRegisteringPluginId;
|
|
27
|
+
const id = String((_ref = provider == null ? void 0 : provider.id) != null ? _ref : '').trim();
|
|
28
|
+
if (!id) throw new Error('a fulfillment provider needs an id');
|
|
29
|
+
const pluginId = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId;
|
|
30
|
+
registerPluginService(PLUGIN_FULFILLMENT_PROVIDERS, provider, _extends({
|
|
31
|
+
key: id
|
|
32
|
+
}, pluginId ? {
|
|
33
|
+
pluginId
|
|
34
|
+
} : {}));
|
|
35
|
+
}
|
|
36
|
+
/** Every registered provider, with the plugin that registered it. */ export function listPluginFulfillmentProviders() {
|
|
37
|
+
return resolvePluginServices(PLUGIN_FULFILLMENT_PROVIDERS).map((entry)=>({
|
|
38
|
+
pluginId: entry.pluginId,
|
|
39
|
+
id: entry.impl.id,
|
|
40
|
+
label: entry.impl.label
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Asks every provider — or every provider but those of `exceptPluginId`, so
|
|
45
|
+
* a provider deciding what it may take does not count itself — what it holds
|
|
46
|
+
* of one record. Each is isolated: one that throws is logged and named in
|
|
47
|
+
* `unanswered`, and the rest still answer. A hold of no units is dropped.
|
|
48
|
+
*/ export async function pluginFulfillmentHolds(hostId, recordId, options) {
|
|
49
|
+
const result = {
|
|
50
|
+
holds: [],
|
|
51
|
+
unanswered: []
|
|
52
|
+
};
|
|
53
|
+
const entries = resolvePluginServices(PLUGIN_FULFILLMENT_PROVIDERS).filter((entry)=>!(options == null ? void 0 : options.exceptPluginId) || entry.pluginId !== options.exceptPluginId);
|
|
54
|
+
await Promise.all(entries.map(async (entry)=>{
|
|
55
|
+
try {
|
|
56
|
+
const holds = await entry.impl.holds(hostId, recordId);
|
|
57
|
+
for (const hold of holds != null ? holds : []){
|
|
58
|
+
const quantity = Math.floor(Number(hold == null ? void 0 : hold.quantity));
|
|
59
|
+
const lineIndex = Math.floor(Number(hold == null ? void 0 : hold.lineIndex));
|
|
60
|
+
if (!(quantity > 0) || !(lineIndex >= 0)) continue;
|
|
61
|
+
result.holds.push(_extends({}, hold, {
|
|
62
|
+
quantity,
|
|
63
|
+
lineIndex
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`[fulfillment-providers] "${entry.impl.id}" of "${entry.pluginId}" failed for ${hostId}/${recordId}`, error);
|
|
68
|
+
result.unanswered.push({
|
|
69
|
+
providerId: entry.impl.id,
|
|
70
|
+
providerLabel: entry.impl.label
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}));
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
/** Units held per line, summed across providers. */ export function heldQuantitiesByLine(holds) {
|
|
77
|
+
var _held_get;
|
|
78
|
+
const held = new Map();
|
|
79
|
+
for (const hold of holds)held.set(hold.lineIndex, ((_held_get = held.get(hold.lineIndex)) != null ? _held_get : 0) + hold.quantity);
|
|
80
|
+
return held;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=plugin-fulfillment-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-fulfillment-providers.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 {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * Outside parties that fulfill a seller's lines (AGL-3634): a fulfillment\n * network that picks and packs from its own warehouses, a print-on-demand or\n * dropship supplier that makes and sends the goods itself.\n *\n * Each one takes some of a record's lines away from the merchant's own\n * packing table. Whoever else ships that record — a label buyer, another\n * provider, the merchant by hand — must know which units are already spoken\n * for, or the customer gets two parcels and the merchant pays twice. The\n * provider plugins never import one another and never import the seller, so\n * each registers a {@link PluginFulfillmentProvider} here and answers one\n * question: which units of this record do you hold that have not shipped?\n *\n * A HOLD IS ONLY WHAT HAS NOT SHIPPED. Once the provider ships, it writes the\n * shipment onto the record through `core.shipment-records`, the record's own\n * unshipped count drops, and the hold drops with it. A canceled or failed\n * hand-off is no hold: the units are the merchant's again.\n *\n * A provider that cannot answer is reported by name in `unanswered` rather\n * than read as holding nothing, so a caller deciding whether to ship can say\n * why it is waiting instead of shipping a parcel that may already be on its\n * way from somewhere else.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-fulfillment-providers`); it is not in\n * the barrel.\n */\n\n/** Units of one line a provider has taken and not yet shipped. */\nexport interface PluginFulfillmentHold {\n /** The provider's id, unique across plugins: `shipbob`, `printful`. */\n providerId: string\n /** What a person calls it. */\n providerLabel: string\n /** The line's position on the record, as `core.shipment-records` names it. */\n lineIndex: number\n /** Units of the line the provider holds that have not shipped. */\n quantity: number\n /**\n * `pending` — taken and not yet accepted by the provider (queued, or being\n * sent); `accepted` — the provider has it and is fulfilling it.\n */\n state: 'pending' | 'accepted'\n /** The provider's own reference for the hand-off, for a person to look it up there. */\n reference?: string\n}\n\nexport interface PluginFulfillmentProvider {\n /** Stable and unique across plugins; a plugin may register several. */\n id: string\n label: string\n /** The units of one record this provider holds; empty when none. */\n holds(hostId: string, recordId: string): Promise<PluginFulfillmentHold[]>\n}\n\nconst PLUGIN_FULFILLMENT_PROVIDERS = definePluginServiceContract<PluginFulfillmentProvider>(\n 'core.fulfillment-providers',\n { multiple: true },\n)\n\n/**\n * Registers a provider. A plugin registers each of its providers under the\n * provider's id, and registering the same id again replaces it.\n */\nexport function registerPluginFulfillmentProvider(\n provider: PluginFulfillmentProvider,\n options?: { pluginId?: string },\n): void {\n const id = String(provider?.id ?? '').trim()\n if (!id) throw new Error('a fulfillment provider needs an id')\n const pluginId = getRegisteringPluginId() ?? options?.pluginId\n registerPluginService(PLUGIN_FULFILLMENT_PROVIDERS, provider, {\n key: id,\n ...(pluginId ? { pluginId } : {}),\n })\n}\n\n/** Every registered provider, with the plugin that registered it. */\nexport function listPluginFulfillmentProviders(): Array<{ pluginId: string; id: string; label: string }> {\n return resolvePluginServices(PLUGIN_FULFILLMENT_PROVIDERS).map((entry) => ({\n pluginId: entry.pluginId,\n id: entry.impl.id,\n label: entry.impl.label,\n }))\n}\n\n/** What every provider holds of one record. */\nexport interface PluginFulfillmentHolds {\n holds: PluginFulfillmentHold[]\n /** Providers that threw: they may hold units nobody can see. */\n unanswered: Array<{ providerId: string; providerLabel: string }>\n}\n\n/**\n * Asks every provider — or every provider but those of `exceptPluginId`, so\n * a provider deciding what it may take does not count itself — what it holds\n * of one record. Each is isolated: one that throws is logged and named in\n * `unanswered`, and the rest still answer. A hold of no units is dropped.\n */\nexport async function pluginFulfillmentHolds(\n hostId: string,\n recordId: string,\n options?: { exceptPluginId?: string },\n): Promise<PluginFulfillmentHolds> {\n const result: PluginFulfillmentHolds = { holds: [], unanswered: [] }\n const entries = resolvePluginServices(PLUGIN_FULFILLMENT_PROVIDERS).filter(\n (entry) => !options?.exceptPluginId || entry.pluginId !== options.exceptPluginId,\n )\n await Promise.all(\n entries.map(async (entry) => {\n try {\n const holds = await entry.impl.holds(hostId, recordId)\n for (const hold of holds ?? []) {\n const quantity = Math.floor(Number(hold?.quantity))\n const lineIndex = Math.floor(Number(hold?.lineIndex))\n if (!(quantity > 0) || !(lineIndex >= 0)) continue\n result.holds.push({ ...hold, quantity, lineIndex })\n }\n } catch (error) {\n console.error(\n `[fulfillment-providers] \"${entry.impl.id}\" of \"${entry.pluginId}\" failed for ${hostId}/${recordId}`,\n error,\n )\n result.unanswered.push({ providerId: entry.impl.id, providerLabel: entry.impl.label })\n }\n }),\n )\n return result\n}\n\n/** Units held per line, summed across providers. */\nexport function heldQuantitiesByLine(holds: readonly PluginFulfillmentHold[]): Map<number, number> {\n const held = new Map<number, number>()\n for (const hold of holds) held.set(hold.lineIndex, (held.get(hold.lineIndex) ?? 0) + hold.quantity)\n return held\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_FULFILLMENT_PROVIDERS","multiple","registerPluginFulfillmentProvider","provider","options","id","String","trim","Error","pluginId","key","listPluginFulfillmentProviders","map","entry","impl","label","pluginFulfillmentHolds","hostId","recordId","result","holds","unanswered","entries","filter","exceptPluginId","Promise","all","hold","quantity","Math","floor","Number","lineIndex","push","error","console","providerId","providerLabel","heldQuantitiesByLine","held","Map","set","get"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAyD1B,MAAMC,+BAA+BH,4BACnC,8BACA;IAAEI,UAAU;AAAK;AAGnB;;;CAGC,GACD,OAAO,SAASC,kCACdC,QAAmC,EACnCC,OAA+B;cAIdR;IAFjB,MAAMS,KAAKC,eAAOH,4BAAAA,SAAUE,EAAE,mBAAI,IAAIE,IAAI;IAC1C,IAAI,CAACF,IAAI,MAAM,IAAIG,MAAM;IACzB,MAAMC,YAAWb,0BAAAA,oCAAAA,0BAA4BQ,2BAAAA,QAASK,QAAQ;IAC9DX,sBAAsBE,8BAA8BG,UAAU;QAC5DO,KAAKL;OACDI,WAAW;QAAEA;IAAS,IAAI,CAAC;AAEnC;AAEA,mEAAmE,GACnE,OAAO,SAASE;IACd,OAAOZ,sBAAsBC,8BAA8BY,GAAG,CAAC,CAACC,QAAW,CAAA;YACzEJ,UAAUI,MAAMJ,QAAQ;YACxBJ,IAAIQ,MAAMC,IAAI,CAACT,EAAE;YACjBU,OAAOF,MAAMC,IAAI,CAACC,KAAK;QACzB,CAAA;AACF;AASA;;;;;CAKC,GACD,OAAO,eAAeC,uBACpBC,MAAc,EACdC,QAAgB,EAChBd,OAAqC;IAErC,MAAMe,SAAiC;QAAEC,OAAO,EAAE;QAAEC,YAAY,EAAE;IAAC;IACnE,MAAMC,UAAUvB,sBAAsBC,8BAA8BuB,MAAM,CACxE,CAACV,QAAU,EAACT,2BAAAA,QAASoB,cAAc,KAAIX,MAAMJ,QAAQ,KAAKL,QAAQoB,cAAc;IAElF,MAAMC,QAAQC,GAAG,CACfJ,QAAQV,GAAG,CAAC,OAAOC;QACjB,IAAI;YACF,MAAMO,QAAQ,MAAMP,MAAMC,IAAI,CAACM,KAAK,CAACH,QAAQC;YAC7C,KAAK,MAAMS,QAAQP,gBAAAA,QAAS,EAAE,CAAE;gBAC9B,MAAMQ,WAAWC,KAAKC,KAAK,CAACC,OAAOJ,wBAAAA,KAAMC,QAAQ;gBACjD,MAAMI,YAAYH,KAAKC,KAAK,CAACC,OAAOJ,wBAAAA,KAAMK,SAAS;gBACnD,IAAI,CAAEJ,CAAAA,WAAW,CAAA,KAAM,CAAEI,CAAAA,aAAa,CAAA,GAAI;gBAC1Cb,OAAOC,KAAK,CAACa,IAAI,CAAC,aAAKN;oBAAMC;oBAAUI;;YACzC;QACF,EAAE,OAAOE,OAAO;YACdC,QAAQD,KAAK,CACX,CAAC,yBAAyB,EAAErB,MAAMC,IAAI,CAACT,EAAE,CAAC,MAAM,EAAEQ,MAAMJ,QAAQ,CAAC,aAAa,EAAEQ,OAAO,CAAC,EAAEC,UAAU,EACpGgB;YAEFf,OAAOE,UAAU,CAACY,IAAI,CAAC;gBAAEG,YAAYvB,MAAMC,IAAI,CAACT,EAAE;gBAAEgC,eAAexB,MAAMC,IAAI,CAACC,KAAK;YAAC;QACtF;IACF;IAEF,OAAOI;AACT;AAEA,kDAAkD,GAClD,OAAO,SAASmB,qBAAqBlB,KAAuC;QAEtBmB;IADpD,MAAMA,OAAO,IAAIC;IACjB,KAAK,MAAMb,QAAQP,MAAOmB,KAAKE,GAAG,CAACd,KAAKK,SAAS,EAAE,EAACO,YAAAA,KAAKG,GAAG,CAACf,KAAKK,SAAS,aAAvBO,YAA4B,KAAKZ,KAAKC,QAAQ;IAClG,OAAOW;AACT"}
|
|
@@ -21,7 +21,24 @@
|
|
|
21
21
|
* `ConsolePluginPageProps.permissions` and API-side resolution carry them
|
|
22
22
|
* with zero app changes. Register at module scope from both the client
|
|
23
23
|
* barrel and the /server entry (the AGL-428 pattern).
|
|
24
|
-
*/
|
|
24
|
+
*/ var _globalScope_REGISTRY_KEY;
|
|
25
|
+
/*
|
|
26
|
+
* One registry per process, keyed on `globalThis` under `Symbol.for`, like
|
|
27
|
+
* `plugin-services.ts`. Next compiles the boot file and the route handlers
|
|
28
|
+
* apart, so each can hold its own copy of this module: with the map at module
|
|
29
|
+
* scope, a plugin's keys registered by one copy were absent from the copy
|
|
30
|
+
* `resolveOrgPermissions` read, an absent key reads as refused, and the
|
|
31
|
+
* register's `managePos` gate answered 403 to the site's own owner.
|
|
32
|
+
*/ const REGISTRY_KEY = Symbol.for('@aglyn/aglyn:plugin-permissions');
|
|
33
|
+
const globalScope = globalThis;
|
|
34
|
+
const tables = (_globalScope_REGISTRY_KEY = globalScope[REGISTRY_KEY]) != null ? _globalScope_REGISTRY_KEY : globalScope[REGISTRY_KEY] = {
|
|
35
|
+
permissions: new Map(),
|
|
36
|
+
listeners: new Set(),
|
|
37
|
+
version: {
|
|
38
|
+
value: 0
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const pluginPermissions = tables.permissions;
|
|
25
42
|
/*
|
|
26
43
|
* Whoever resolved a permission map before a plugin registered holds a map
|
|
27
44
|
* without that plugin's keys, and a key that is absent reads as refused
|
|
@@ -31,8 +48,7 @@
|
|
|
31
48
|
* they hold by default until the next full render. So registration is
|
|
32
49
|
* observable, the way `subscribePluginEntitlements` makes the org catalog's
|
|
33
50
|
* registrations observable, and the provider re-reads on every change.
|
|
34
|
-
*/
|
|
35
|
-
const listeners = new Set();
|
|
51
|
+
*/ const listeners = tables.listeners;
|
|
36
52
|
/** Calls `listener` after every registration; answers the unsubscribe. */ export function subscribePluginPermissions(listener) {
|
|
37
53
|
listeners.add(listener);
|
|
38
54
|
return ()=>{
|
|
@@ -43,10 +59,10 @@ const listeners = new Set();
|
|
|
43
59
|
* A number that changes whenever the registry does — the snapshot for a
|
|
44
60
|
* `useSyncExternalStore` over `subscribePluginPermissions`.
|
|
45
61
|
*/ export function pluginPermissionsVersion() {
|
|
46
|
-
return
|
|
62
|
+
return tables.version.value;
|
|
47
63
|
}
|
|
48
64
|
function notifyListeners() {
|
|
49
|
-
|
|
65
|
+
tables.version.value += 1;
|
|
50
66
|
for (const listener of [
|
|
51
67
|
...listeners
|
|
52
68
|
])listener();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-permissions.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\n/**\n * Plugin-declared permissions (AGL-435, Strapi RBAC-actions parity): a\n * plugin contributes NAMED permission keys with per-role-tier defaults.\n * `resolveRolePermissions` spreads them into every resolved set (custom\n * roles override key-by-key exactly like built-in keys), so\n * `ConsolePluginPageProps.permissions` and API-side resolution carry them\n * with zero app changes. Register at module scope from both the client\n * barrel and the /server entry (the AGL-428 pattern).\n */\n\nexport interface PluginPermission {\n /** Stable key ('managePos') — persisted in custom role docs. */\n key: string\n pluginId: string\n label: string\n description?: string\n /** Verdict per built-in role tier when a role doesn't name the key. */\n defaults: { admin: boolean; editor: boolean; viewer: boolean }\n}\n\nconst
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-permissions.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\n/**\n * Plugin-declared permissions (AGL-435, Strapi RBAC-actions parity): a\n * plugin contributes NAMED permission keys with per-role-tier defaults.\n * `resolveRolePermissions` spreads them into every resolved set (custom\n * roles override key-by-key exactly like built-in keys), so\n * `ConsolePluginPageProps.permissions` and API-side resolution carry them\n * with zero app changes. Register at module scope from both the client\n * barrel and the /server entry (the AGL-428 pattern).\n */\n\nexport interface PluginPermission {\n /** Stable key ('managePos') — persisted in custom role docs. */\n key: string\n pluginId: string\n label: string\n description?: string\n /** Verdict per built-in role tier when a role doesn't name the key. */\n defaults: { admin: boolean; editor: boolean; viewer: boolean }\n}\n\n/*\n * One registry per process, keyed on `globalThis` under `Symbol.for`, like\n * `plugin-services.ts`. Next compiles the boot file and the route handlers\n * apart, so each can hold its own copy of this module: with the map at module\n * scope, a plugin's keys registered by one copy were absent from the copy\n * `resolveOrgPermissions` read, an absent key reads as refused, and the\n * register's `managePos` gate answered 403 to the site's own owner.\n */\nconst REGISTRY_KEY = Symbol.for('@aglyn/aglyn:plugin-permissions')\n\ninterface PluginPermissionTables {\n permissions: Map<string, PluginPermission>\n listeners: Set<() => void>\n version: { value: number }\n}\n\nconst globalScope = globalThis as typeof globalThis & {\n [REGISTRY_KEY]?: PluginPermissionTables\n}\n\nconst tables: PluginPermissionTables =\n globalScope[REGISTRY_KEY] ??\n (globalScope[REGISTRY_KEY] = {\n permissions: new Map(),\n listeners: new Set(),\n version: { value: 0 },\n })\nconst pluginPermissions = tables.permissions\n\n/*\n * Whoever resolved a permission map before a plugin registered holds a map\n * without that plugin's keys, and a key that is absent reads as refused\n * (AGL-3228). The console's permission provider resolved its map when the\n * member document arrived; on a cold load of a plugin's deep link the\n * plugin's chunk can register AFTER that, so the owner was refused a surface\n * they hold by default until the next full render. So registration is\n * observable, the way `subscribePluginEntitlements` makes the org catalog's\n * registrations observable, and the provider re-reads on every change.\n */\nconst listeners = tables.listeners\n\n/** Calls `listener` after every registration; answers the unsubscribe. */\nexport function subscribePluginPermissions(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\n/**\n * A number that changes whenever the registry does — the snapshot for a\n * `useSyncExternalStore` over `subscribePluginPermissions`.\n */\nexport function pluginPermissionsVersion(): number {\n return tables.version.value\n}\n\nfunction notifyListeners(): void {\n tables.version.value += 1\n for (const listener of [...listeners]) listener()\n}\n\n/**\n * Idempotent per key FOR ITS OWNER — the declaring plugin may re-register\n * (hot reload, a second surface, a repeated init) and its entry is replaced.\n *\n * A key already declared by a DIFFERENT plugin is refused (AGL-2484). This\n * map keyed on `permission.key` alone, so a later registration could redeclare\n * someone else's key with its own tier defaults — and these defaults are not\n * cosmetic: `managePos` is enforced server-side in\n * `libs/plugins/commerce/src/lib/server/pos-order.ts`, so moving\n * `defaults.viewer` to `true` hands a viewer the register. Reaching this\n * needs `PLUGIN_REMOTE_SERVER=enabled` (off by default), realm trust and a\n * signature; the registry closing its own door is the cheap part of that\n * stack.\n *\n * Per KEY, not per batch: a bundle that collides on one key still gets the\n * keys that are genuinely its own.\n */\nexport function registerPluginPermissions(\n permissions: readonly PluginPermission[],\n): void {\n for (const permission of permissions) {\n const incumbent = pluginPermissions.get(permission.key)\n if (incumbent !== undefined && incumbent.pluginId !== permission.pluginId) {\n console.error(\n `[plugins] refused permission key \"${permission.key}\" to ` +\n `\"${permission.pluginId}\": already declared by ` +\n `\"${incumbent.pluginId}\"`,\n )\n continue\n }\n pluginPermissions.set(permission.key, permission)\n }\n notifyListeners()\n}\n\nexport function listPluginPermissions(): PluginPermission[] {\n return [...pluginPermissions.values()]\n}\n\n/**\n * The registered keys alone (AGL-2474). `resolveRolePermissions` needs the\n * grantable key SPACE, not the tier verdicts: it used to iterate\n * `ORG_ROLE_PERMISSION_KEYS` — the legacy six — when applying overrides and\n * custom roles, so a plugin-declared key materialised into the resolved map\n * and could then only ever hold its tier default. Read live rather than\n * snapshotted: registration happens at plugin module scope, which can run\n * after any module-level constant here has already been evaluated.\n */\nexport function listPluginPermissionKeys(): string[] {\n return [...pluginPermissions.keys()]\n}\n\n/** Tier defaults for every registered plugin permission. */\nexport function pluginPermissionDefaults(\n tier: 'admin' | 'editor' | 'viewer',\n): Record<string, boolean> {\n const defaults: Record<string, boolean> = {}\n for (const permission of pluginPermissions.values()) {\n defaults[permission.key] = permission.defaults[tier]\n }\n return defaults\n}\n"],"names":["globalScope","REGISTRY_KEY","Symbol","for","globalThis","tables","permissions","Map","listeners","Set","version","value","pluginPermissions","subscribePluginPermissions","listener","add","delete","pluginPermissionsVersion","notifyListeners","registerPluginPermissions","permission","incumbent","get","key","undefined","pluginId","console","error","set","listPluginPermissions","values","listPluginPermissionKeys","keys","pluginPermissionDefaults","tier","defaults"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;CAQC,OAiCCA;AArBF;;;;;;;CAOC,GACD,MAAMC,eAAeC,OAAOC,GAAG,CAAC;AAQhC,MAAMH,cAAcI;AAIpB,MAAMC,UACJL,4BAAAA,WAAW,CAACC,aAAa,YAAzBD,4BACCA,WAAW,CAACC,aAAa,GAAG;IAC3BK,aAAa,IAAIC;IACjBC,WAAW,IAAIC;IACfC,SAAS;QAAEC,OAAO;IAAE;AACtB;AACF,MAAMC,oBAAoBP,OAAOC,WAAW;AAE5C;;;;;;;;;CASC,GACD,MAAME,YAAYH,OAAOG,SAAS;AAElC,wEAAwE,GACxE,OAAO,SAASK,2BAA2BC,QAAoB;IAC7DN,UAAUO,GAAG,CAACD;IACd,OAAO;QACLN,UAAUQ,MAAM,CAACF;IACnB;AACF;AAEA;;;CAGC,GACD,OAAO,SAASG;IACd,OAAOZ,OAAOK,OAAO,CAACC,KAAK;AAC7B;AAEA,SAASO;IACPb,OAAOK,OAAO,CAACC,KAAK,IAAI;IACxB,KAAK,MAAMG,YAAY;WAAIN;KAAU,CAAEM;AACzC;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASK,0BACdb,WAAwC;IAExC,KAAK,MAAMc,cAAcd,YAAa;QACpC,MAAMe,YAAYT,kBAAkBU,GAAG,CAACF,WAAWG,GAAG;QACtD,IAAIF,cAAcG,aAAaH,UAAUI,QAAQ,KAAKL,WAAWK,QAAQ,EAAE;YACzEC,QAAQC,KAAK,CACX,CAAC,kCAAkC,EAAEP,WAAWG,GAAG,CAAC,KAAK,CAAC,GACxD,CAAC,CAAC,EAAEH,WAAWK,QAAQ,CAAC,uBAAuB,CAAC,GAChD,CAAC,CAAC,EAAEJ,UAAUI,QAAQ,CAAC,CAAC,CAAC;YAE7B;QACF;QACAb,kBAAkBgB,GAAG,CAACR,WAAWG,GAAG,EAAEH;IACxC;IACAF;AACF;AAEA,OAAO,SAASW;IACd,OAAO;WAAIjB,kBAAkBkB,MAAM;KAAG;AACxC;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC;IACd,OAAO;WAAInB,kBAAkBoB,IAAI;KAAG;AACtC;AAEA,0DAA0D,GAC1D,OAAO,SAASC,yBACdC,IAAmC;IAEnC,MAAMC,WAAoC,CAAC;IAC3C,KAAK,MAAMf,cAAcR,kBAAkBkB,MAAM,GAAI;QACnDK,QAAQ,CAACf,WAAWG,GAAG,CAAC,GAAGH,WAAWe,QAAQ,CAACD,KAAK;IACtD;IACA,OAAOC;AACT"}
|