@aglyn/aglyn 1.0.0-beta.161 → 1.0.0-beta.163
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 +10 -10
- package/src/lib/app-utils/campaign-membership.d.ts +12 -2
- package/src/lib/app-utils/campaign-membership.js +13 -2
- package/src/lib/app-utils/campaign-membership.js.map +1 -1
- package/src/lib/app-utils/collection-entry-date.d.ts +25 -17
- package/src/lib/app-utils/collection-entry-date.js +55 -20
- package/src/lib/app-utils/collection-entry-date.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +2 -0
- package/src/lib/app-utils/console-routes.js +22 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +8 -0
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +21 -0
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-help.generated.js +2 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +9 -9
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/email-state.d.ts +57 -0
- package/src/lib/app-utils/email-state.js +136 -0
- package/src/lib/app-utils/email-state.js.map +1 -0
- package/src/lib/app-utils/notifications.d.ts +58 -0
- package/src/lib/app-utils/notifications.js +62 -8
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +1 -0
- package/src/lib/app-utils/server.js +3 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +16 -0
- package/src/lib/foundation/definitions/platform.types.js +1 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/plugin-membership-detach.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-membership-detach.js +102 -0
- package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +85 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +53 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +22 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* A plugin's share of taking a CONTAINER away (AGL-3254).
|
|
19
|
+
*
|
|
20
|
+
* A container — a campaign is the first — is a document other records join
|
|
21
|
+
* by naming it in a membership array on their OWN documents
|
|
22
|
+
* (`app-utils/campaign-membership.ts` says why the edge lives on the
|
|
23
|
+
* member). When the container is removed, its owner clears the id off every
|
|
24
|
+
* member it can name. A plugin that keeps members of its own where that
|
|
25
|
+
* walk does not reach — records under the org, in collections the owner
|
|
26
|
+
* does not know — registers a detacher here, and the removal runs every
|
|
27
|
+
* detacher with the container's id, the membership field it is held in,
|
|
28
|
+
* the site and the org, so the plugin can clear its own records.
|
|
29
|
+
*
|
|
30
|
+
* Detachers run BEFORE the container is deleted, for the reason the owner's
|
|
31
|
+
* own passes do: a run that stops between them leaves records already out
|
|
32
|
+
* of the container, which every surface draws correctly, while deleting
|
|
33
|
+
* first would leave a plugin's record naming an id nothing can resolve. A
|
|
34
|
+
* detacher that reports `remaining` holds the deletion, exactly as the
|
|
35
|
+
* owner's own pass does when the container is on more records than one
|
|
36
|
+
* request can clear.
|
|
37
|
+
*
|
|
38
|
+
* Detachers run in registration order and are ISOLATED: a throw is logged
|
|
39
|
+
* against its plugin, recorded as `null`, and does not stop the next one.
|
|
40
|
+
* The owner reads a `null` as "this plugin's records may still name the
|
|
41
|
+
* container" and refuses to remove it, which is the direction a failure
|
|
42
|
+
* here has to fall.
|
|
43
|
+
*
|
|
44
|
+
* Registered from a plugin's declarations — `consoleServerDeclarations`,
|
|
45
|
+
* since a container is removed from the console — so the detacher is in
|
|
46
|
+
* place in a process that never loaded the plugin's API surface. The core
|
|
47
|
+
* never imports a plugin; this is how a removal one plugin performs reaches
|
|
48
|
+
* another plugin's records.
|
|
49
|
+
*/ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
50
|
+
const registrations = [];
|
|
51
|
+
/**
|
|
52
|
+
* Registers a plugin's membership detacher. Owner = the loader's marker
|
|
53
|
+
* inside a register fn, else `options.pluginId`; a detacher with neither
|
|
54
|
+
* throws. One detacher per plugin: registering again replaces the plugin's
|
|
55
|
+
* earlier detacher in place, so a module evaluated twice does not run twice.
|
|
56
|
+
*/ export function registerPluginMembershipDetacher(detacher, options) {
|
|
57
|
+
var _ref, _getRegisteringPluginId;
|
|
58
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
59
|
+
if (!pluginId) {
|
|
60
|
+
throw new Error('a plugin membership detacher was registered with no owner: pass ' + '{ pluginId } when registering outside a plugin register fn');
|
|
61
|
+
}
|
|
62
|
+
const index = registrations.findIndex((entry)=>entry.pluginId === pluginId);
|
|
63
|
+
if (index >= 0) registrations[index] = {
|
|
64
|
+
pluginId,
|
|
65
|
+
detacher
|
|
66
|
+
};
|
|
67
|
+
else registrations.push({
|
|
68
|
+
pluginId,
|
|
69
|
+
detacher
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Runs every detacher, in registration order, and answers each plugin's
|
|
74
|
+
* report by plugin id: the detacher's own report, or `null` for one that
|
|
75
|
+
* threw. Never throws.
|
|
76
|
+
*/ export async function runPluginMembershipDetachers(request) {
|
|
77
|
+
const reports = {};
|
|
78
|
+
for (const { pluginId, detacher } of [
|
|
79
|
+
...registrations
|
|
80
|
+
]){
|
|
81
|
+
try {
|
|
82
|
+
var _ref;
|
|
83
|
+
const report = await detacher(_extends({}, request));
|
|
84
|
+
reports[pluginId] = {
|
|
85
|
+
detached: Math.max(0, Math.floor(Number((_ref = report == null ? void 0 : report.detached) != null ? _ref : 0)) || 0),
|
|
86
|
+
remaining: (report == null ? void 0 : report.remaining) === true
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
reports[pluginId] = null;
|
|
90
|
+
console.error(`[plugins] ${pluginId} failed to detach ${request.field} ${request.id} on host ${request.hostId}`, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return reports;
|
|
94
|
+
}
|
|
95
|
+
/** The plugins with a detacher in place, in registration order. Only for specs and the guards. */ export function listPluginMembershipDetachers() {
|
|
96
|
+
return registrations.map((entry)=>entry.pluginId);
|
|
97
|
+
}
|
|
98
|
+
/** Test seam: forget every detacher. */ export function resetPluginMembershipDetachersForTests() {
|
|
99
|
+
registrations.length = 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=plugin-membership-detach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-membership-detach.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 * A plugin's share of taking a CONTAINER away (AGL-3254).\n *\n * A container — a campaign is the first — is a document other records join\n * by naming it in a membership array on their OWN documents\n * (`app-utils/campaign-membership.ts` says why the edge lives on the\n * member). When the container is removed, its owner clears the id off every\n * member it can name. A plugin that keeps members of its own where that\n * walk does not reach — records under the org, in collections the owner\n * does not know — registers a detacher here, and the removal runs every\n * detacher with the container's id, the membership field it is held in,\n * the site and the org, so the plugin can clear its own records.\n *\n * Detachers run BEFORE the container is deleted, for the reason the owner's\n * own passes do: a run that stops between them leaves records already out\n * of the container, which every surface draws correctly, while deleting\n * first would leave a plugin's record naming an id nothing can resolve. A\n * detacher that reports `remaining` holds the deletion, exactly as the\n * owner's own pass does when the container is on more records than one\n * request can clear.\n *\n * Detachers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one.\n * The owner reads a `null` as \"this plugin's records may still name the\n * container\" and refuses to remove it, which is the direction a failure\n * here has to fall.\n *\n * Registered from a plugin's declarations — `consoleServerDeclarations`,\n * since a container is removed from the console — so the detacher is in\n * place in a process that never loaded the plugin's API surface. The core\n * never imports a plugin; this is how a removal one plugin performs reaches\n * another plugin's records.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** The container being removed, and where its members may be. */\nexport interface PluginMembershipDetachRequest {\n /** The site the container belongs to. */\n hostId: string\n /** The site's organization, or `''` for a site with no org behind it. */\n orgId: string\n /** The membership field a member holds the container's id in. */\n field: string\n /** The container's document id — what the field names. */\n id: string\n}\n\n/**\n * What a detacher reports: how many of its records it took the container\n * off, and whether any were left for a second run. Counts, never content.\n */\nexport interface PluginMembershipDetachReport {\n detached: number\n remaining: boolean\n}\n\nexport type PluginMembershipDetacher = (\n request: PluginMembershipDetachRequest,\n) => Promise<PluginMembershipDetachReport>\n\ninterface Registration {\n pluginId: string\n detacher: PluginMembershipDetacher\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's membership detacher. Owner = the loader's marker\n * inside a register fn, else `options.pluginId`; a detacher with neither\n * throws. One detacher per plugin: registering again replaces the plugin's\n * earlier detacher in place, so a module evaluated twice does not run twice.\n */\nexport function registerPluginMembershipDetacher(\n detacher: PluginMembershipDetacher,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin membership detacher was registered with no owner: pass ' +\n '{ pluginId } when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, detacher }\n else registrations.push({ pluginId, detacher })\n}\n\n/**\n * Runs every detacher, in registration order, and answers each plugin's\n * report by plugin id: the detacher's own report, or `null` for one that\n * threw. Never throws.\n */\nexport async function runPluginMembershipDetachers(\n request: PluginMembershipDetachRequest,\n): Promise<Record<string, PluginMembershipDetachReport | null>> {\n const reports: Record<string, PluginMembershipDetachReport | null> = {}\n for (const { pluginId, detacher } of [...registrations]) {\n try {\n const report = await detacher({ ...request })\n reports[pluginId] = {\n detached: Math.max(0, Math.floor(Number(report?.detached ?? 0)) || 0),\n remaining: report?.remaining === true,\n }\n } catch (error) {\n reports[pluginId] = null\n console.error(\n `[plugins] ${pluginId} failed to detach ${request.field} ${request.id} on host ${request.hostId}`,\n error,\n )\n }\n }\n return reports\n}\n\n/** The plugins with a detacher in place, in registration order. Only for specs and the guards. */\nexport function listPluginMembershipDetachers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every detacher. */\nexport function resetPluginMembershipDetachersForTests(): void {\n registrations.length = 0\n}\n"],"names":["getRegisteringPluginId","registrations","registerPluginMembershipDetacher","detacher","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginMembershipDetachers","request","reports","report","detached","Math","max","floor","Number","remaining","error","console","field","id","hostId","listPluginMembershipDetachers","map","resetPluginMembershipDetachersForTests","length"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAgCxE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,iCACdC,QAAkC,EAClCC,OAA+B;QAEbJ,MAAAA;IAAlB,MAAMK,WAAW,EAACL,QAAAA,0BAAAA,oCAAAA,0BAA4BI,2BAAAA,QAASC,QAAQ,YAA7CL,OAAiD,IAAIM,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,qEACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAS;SACvDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAS;AAC/C;AAEA;;;;CAIC,GACD,OAAO,eAAeS,6BACpBC,OAAsC;IAEtC,MAAMC,UAA+D,CAAC;IACtE,KAAK,MAAM,EAAET,QAAQ,EAAEF,QAAQ,EAAE,IAAI;WAAIF;KAAc,CAAE;QACvD,IAAI;;YACF,MAAMc,SAAS,MAAMZ,SAAS,aAAKU;YACnCC,OAAO,CAACT,SAAS,GAAG;gBAClBW,UAAUC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,eAAOL,0BAAAA,OAAQC,QAAQ,mBAAI,OAAO;gBACnEK,WAAWN,CAAAA,0BAAAA,OAAQM,SAAS,MAAK;YACnC;QACF,EAAE,OAAOC,OAAO;YACdR,OAAO,CAACT,SAAS,GAAG;YACpBkB,QAAQD,KAAK,CACX,CAAC,UAAU,EAAEjB,SAAS,kBAAkB,EAAEQ,QAAQW,KAAK,CAAC,CAAC,EAAEX,QAAQY,EAAE,CAAC,SAAS,EAAEZ,QAAQa,MAAM,EAAE,EACjGJ;QAEJ;IACF;IACA,OAAOR;AACT;AAEA,gGAAgG,GAChG,OAAO,SAASa;IACd,OAAO1B,cAAc2B,GAAG,CAAC,CAAClB,QAAUA,MAAML,QAAQ;AACpD;AAEA,sCAAsC,GACtC,OAAO,SAASwB;IACd5B,cAAc6B,MAAM,GAAG;AACzB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { EmailState } from '../app-utils/email-state';
|
|
18
|
+
/**
|
|
19
|
+
* The verdict on an address, written onto the records that carry it
|
|
20
|
+
* (AGL-3245).
|
|
21
|
+
*
|
|
22
|
+
* The senders keep their lists — the platform's suppression list, a site's
|
|
23
|
+
* suppressions, an organization's do-not-contact list — and those are what
|
|
24
|
+
* a send consults. The record a person reads is the record system's: a
|
|
25
|
+
* lead, a contact, whatever the plugin that keeps the workspace's records
|
|
26
|
+
* calls them. A sender must not write that plugin's documents itself, nor
|
|
27
|
+
* import it; so the record system registers a WRITER here, and a sender
|
|
28
|
+
* that filed a verdict on a list says so through it.
|
|
29
|
+
*
|
|
30
|
+
* The writer finds every record the address is — the plugin knows its own
|
|
31
|
+
* storage and its own index — and stamps `emailState` on each, keeping the
|
|
32
|
+
* stronger verdict (`nextEmailState`). A single-implementation contract: a
|
|
33
|
+
* workspace keeps one record system.
|
|
34
|
+
*
|
|
35
|
+
* ## The caller proves who is asking
|
|
36
|
+
*
|
|
37
|
+
* Like the timeline seam, the registry authenticates nobody. The caller
|
|
38
|
+
* decides, in its own terms and before it asks, that the verdict is the
|
|
39
|
+
* workspace's to write: an address it sent to, a list it wrote.
|
|
40
|
+
*/
|
|
41
|
+
export interface PluginRecordEmailStateRequest {
|
|
42
|
+
/** The organization whose records are stamped. */
|
|
43
|
+
orgId?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Or the site the send left from, when the caller knows only that — a
|
|
46
|
+
* campaign webhook, an unsubscribe link. The writer reads the
|
|
47
|
+
* organization off it.
|
|
48
|
+
*/
|
|
49
|
+
hostId?: string;
|
|
50
|
+
email: string;
|
|
51
|
+
state: EmailState;
|
|
52
|
+
/** Write the state whatever the record holds — a release. */
|
|
53
|
+
force?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface PluginRecordEmailStateReport {
|
|
56
|
+
/** Records whose state moved. */
|
|
57
|
+
records: number;
|
|
58
|
+
}
|
|
59
|
+
export interface PluginRecordEmailStateWriter {
|
|
60
|
+
/** Never throws: a list is the control, the stamp is what a person reads. */
|
|
61
|
+
stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>;
|
|
62
|
+
}
|
|
63
|
+
export declare const PLUGIN_RECORD_EMAIL_STATE: import("./plugin-services").PluginServiceContract<PluginRecordEmailStateWriter>;
|
|
64
|
+
/**
|
|
65
|
+
* Registers the workspace's record email-state writer. The owner is the
|
|
66
|
+
* loader's marker when a register fn is running, else `options.pluginId`;
|
|
67
|
+
* with neither the registration throws, and a second plugin's writer is
|
|
68
|
+
* refused naming both — the incumbent keeps serving.
|
|
69
|
+
*/
|
|
70
|
+
export declare function registerPluginRecordEmailStateWriter(writer: PluginRecordEmailStateWriter, options?: {
|
|
71
|
+
pluginId?: string;
|
|
72
|
+
}): void;
|
|
73
|
+
export interface ResolvedPluginRecordEmailStateWriter {
|
|
74
|
+
/** The plugin that keeps the records. */
|
|
75
|
+
pluginId: string;
|
|
76
|
+
writer: PluginRecordEmailStateWriter;
|
|
77
|
+
}
|
|
78
|
+
/** The writer, with its owner, or `null` when no plugin keeps records here. */
|
|
79
|
+
export declare function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmailStateWriter | null;
|
|
80
|
+
/**
|
|
81
|
+
* Stamps the verdict through whichever plugin keeps the records, or answers
|
|
82
|
+
* `null` when none does. Never throws: every caller is a sender for whom
|
|
83
|
+
* the stamp is a courtesy beside the list it already wrote.
|
|
84
|
+
*/
|
|
85
|
+
export declare function stampRecordEmailState(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport | null>;
|
|
@@ -0,0 +1,53 @@
|
|
|
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, resolvePluginServices } from "./plugin-services.js";
|
|
18
|
+
export const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract('core.record-email-state', {
|
|
19
|
+
multiple: false
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Registers the workspace's record email-state writer. The owner is the
|
|
23
|
+
* loader's marker when a register fn is running, else `options.pluginId`;
|
|
24
|
+
* with neither the registration throws, and a second plugin's writer is
|
|
25
|
+
* refused naming both — the incumbent keeps serving.
|
|
26
|
+
*/ export function registerPluginRecordEmailStateWriter(writer, options) {
|
|
27
|
+
registerPluginService(PLUGIN_RECORD_EMAIL_STATE, writer, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
28
|
+
pluginId: options.pluginId
|
|
29
|
+
} : {}));
|
|
30
|
+
}
|
|
31
|
+
/** The writer, with its owner, or `null` when no plugin keeps records here. */ export function pluginRecordEmailStateWriter() {
|
|
32
|
+
const entry = resolvePluginServices(PLUGIN_RECORD_EMAIL_STATE)[0];
|
|
33
|
+
return entry ? {
|
|
34
|
+
pluginId: entry.pluginId,
|
|
35
|
+
writer: entry.impl
|
|
36
|
+
} : null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Stamps the verdict through whichever plugin keeps the records, or answers
|
|
40
|
+
* `null` when none does. Never throws: every caller is a sender for whom
|
|
41
|
+
* the stamp is a courtesy beside the list it already wrote.
|
|
42
|
+
*/ export async function stampRecordEmailState(request) {
|
|
43
|
+
const resolved = pluginRecordEmailStateWriter();
|
|
44
|
+
if (!resolved) return null;
|
|
45
|
+
try {
|
|
46
|
+
return await resolved.writer.stamp(request);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('[record-email-state] the record system could not stamp the address', error);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=plugin-record-email-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-email-state.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 { EmailState } from '../app-utils/email-state'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The verdict on an address, written onto the records that carry it\n * (AGL-3245).\n *\n * The senders keep their lists — the platform's suppression list, a site's\n * suppressions, an organization's do-not-contact list — and those are what\n * a send consults. The record a person reads is the record system's: a\n * lead, a contact, whatever the plugin that keeps the workspace's records\n * calls them. A sender must not write that plugin's documents itself, nor\n * import it; so the record system registers a WRITER here, and a sender\n * that filed a verdict on a list says so through it.\n *\n * The writer finds every record the address is — the plugin knows its own\n * storage and its own index — and stamps `emailState` on each, keeping the\n * stronger verdict (`nextEmailState`). A single-implementation contract: a\n * workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like the timeline seam, the registry authenticates nobody. The caller\n * decides, in its own terms and before it asks, that the verdict is the\n * workspace's to write: an address it sent to, a list it wrote.\n */\n\nexport interface PluginRecordEmailStateRequest {\n /** The organization whose records are stamped. */\n orgId?: string\n /**\n * Or the site the send left from, when the caller knows only that — a\n * campaign webhook, an unsubscribe link. The writer reads the\n * organization off it.\n */\n hostId?: string\n email: string\n state: EmailState\n /** Write the state whatever the record holds — a release. */\n force?: boolean\n}\n\nexport interface PluginRecordEmailStateReport {\n /** Records whose state moved. */\n records: number\n}\n\nexport interface PluginRecordEmailStateWriter {\n /** Never throws: a list is the control, the stamp is what a person reads. */\n stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>\n}\n\nexport const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract<PluginRecordEmailStateWriter>(\n 'core.record-email-state',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record email-state writer. The owner is the\n * loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws, and a second plugin's writer is\n * refused naming both — the incumbent keeps serving.\n */\nexport function registerPluginRecordEmailStateWriter(\n writer: PluginRecordEmailStateWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_EMAIL_STATE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordEmailStateWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordEmailStateWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmailStateWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_EMAIL_STATE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Stamps the verdict through whichever plugin keeps the records, or answers\n * `null` when none does. Never throws: every caller is a sender for whom\n * the stamp is a courtesy beside the list it already wrote.\n */\nexport async function stampRecordEmailState(\n request: PluginRecordEmailStateRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved) return null\n try {\n return await resolved.writer.stamp(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not stamp the address', error)\n return null\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_EMAIL_STATE","multiple","registerPluginRecordEmailStateWriter","writer","options","pluginId","pluginRecordEmailStateWriter","entry","impl","stampRecordEmailState","request","resolved","stamp","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAmD1B,OAAO,MAAMC,4BAA4BH,4BACvC,2BACA;IAAEI,UAAU;AAAM,GACnB;AAED;;;;;CAKC,GACD,OAAO,SAASC,qCACdC,MAAoC,EACpCC,OAA+B;IAE/BN,sBAAsBE,2BAA2BG,QAAQ,aACnDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAQA,6EAA6E,GAC7E,OAAO,SAASC;IACd,MAAMC,QAAQR,sBAAsBC,0BAA0B,CAAC,EAAE;IACjE,OAAOO,QAAQ;QAAEF,UAAUE,MAAMF,QAAQ;QAAEF,QAAQI,MAAMC,IAAI;IAAC,IAAI;AACpE;AAEA;;;;CAIC,GACD,OAAO,eAAeC,sBACpBC,OAAsC;IAEtC,MAAMC,WAAWL;IACjB,IAAI,CAACK,UAAU,OAAO;IACtB,IAAI;QACF,OAAO,MAAMA,SAASR,MAAM,CAACS,KAAK,CAACF;IACrC,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,sEAAsEA;QACpF,OAAO;IACT;AACF"}
|
|
@@ -131,9 +131,31 @@ export type PluginRecordWrite = {
|
|
|
131
131
|
/** Customer-safe: a caller may show or log it as it stands. */
|
|
132
132
|
error: string;
|
|
133
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* What became of an email a plugin filed (AGL-3245): the receiving server
|
|
136
|
+
* bounced it, or the recipient reported it. Addressed by the `Message-ID`
|
|
137
|
+
* the send was filed under, so the verdict lands on the send's own entry
|
|
138
|
+
* and the record reads Sent, then Bounced.
|
|
139
|
+
*/
|
|
140
|
+
export interface PluginRecordDeliveryRequest {
|
|
141
|
+
orgId: string;
|
|
142
|
+
/** The `Message-ID` header of the send, angle brackets included. */
|
|
143
|
+
messageId: string;
|
|
144
|
+
state: 'bounced' | 'complained';
|
|
145
|
+
/** When it happened, epoch ms. */
|
|
146
|
+
atMs: number;
|
|
147
|
+
/** What the server said, scrubbed of the address; the owner bounds it. */
|
|
148
|
+
detail?: string | null;
|
|
149
|
+
}
|
|
134
150
|
export interface PluginRecordTimelineWriter {
|
|
135
151
|
logActivity(request: PluginRecordActivityRequest): Promise<PluginRecordWrite>;
|
|
136
152
|
createTask(request: PluginRecordTaskRequest): Promise<PluginRecordWrite>;
|
|
153
|
+
/**
|
|
154
|
+
* Marks a filed email bounced or reported (AGL-3245). Optional: a record
|
|
155
|
+
* system that keeps no delivery state answers nothing, and the caller's
|
|
156
|
+
* entry stands as filed.
|
|
157
|
+
*/
|
|
158
|
+
recordEmailDelivery?(request: PluginRecordDeliveryRequest): Promise<PluginRecordWrite>;
|
|
137
159
|
}
|
|
138
160
|
export declare const PLUGIN_RECORD_TIMELINE: import("./plugin-services").PluginServiceContract<PluginRecordTimelineWriter>;
|
|
139
161
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-timeline.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 * An entry one plugin files on a record another plugin keeps (AGL-2981).\n *\n * A person, a company and a deal are records of the workspace's record\n * system, and what happened with them — an email sent, a reply received, a\n * call to make — belongs on their timeline, whichever plugin it happened in.\n * A plugin that sends mail or books a meeting must not write the record\n * system's documents itself: it would restate the owner's rules — where an\n * entry lives, who may see it, how a copy of the same email is told apart,\n * how many entries a record may carry — and drift from them the day the\n * owner changes one. Nor may it import the owner, which the package map\n * forbids.\n *\n * So the record system registers a WRITER here, from its server surfaces,\n * and a caller asks for it. Every rule stays the owner's:\n *\n * - the entry is stamped with the scope a record made on the site would\n * carry, so it is visible to exactly who sees the record;\n * - an EMAIL is filed once per `Message-ID`, under the id the owner files a\n * copy of the same message by through any other door — a copy the rep\n * forwarded to a capture address is the same entry, not a second one;\n * - any other entry, and every task, is filed once per the caller's own\n * `dedupeKey`, so a caller that runs again finds what it wrote rather\n * than writing it twice;\n * - a record whose log is full, or a workspace whose plan carries no record\n * system, is refused — answered, never thrown.\n *\n * A single-implementation contract: a workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like `plugin-resource-drafts`, the registry authenticates nobody. The\n * caller decides, in its own terms and before it asks, that the entry is\n * the workspace's to write — that the record is one it acted on, for the\n * member it acted for.\n */\n\n/** The record an entry is filed on: any of the four, as the owner links them. */\nexport interface PluginRecordLink {\n contactId?: string\n companyId?: string\n dealId?: string\n /**\n * A lead not yet converted (AGL-3234) — the record system's own id for\n * it, addressed under `hostId`. A sequence's emails and tasks land on the\n * lead's page while the person is one, and follow the lead to the contact\n * it becomes.\n */\n leadId?: string\n}\n\n/** Where an entry is filed, and by which plugin. */\nexport interface PluginRecordEntryContext {\n orgId: string\n /** The site whose scope the entry carries: as if a member made it there. */\n hostId: string\n link: PluginRecordLink\n /** The plugin filing it, recorded on the entry. */\n sourcePluginId: string\n}\n\n/** What an activity is, in the record system's own words. */\nexport type PluginRecordActivityKind = 'email' | 'call' | 'meeting' | 'note' | 'other'\n\n/** The message an `email` activity is about. */\nexport interface PluginRecordEmail {\n /** `outbound` for one the workspace wrote; `inbound` for one written to it. */\n direction: 'outbound' | 'inbound'\n subject: string\n /** The address on the message's From. */\n from: string\n /** The address it was written to, when one is worth showing. */\n to: string | null\n /** The `Message-ID` header, angle brackets included: what files it once. */\n messageId: string\n /** The `Message-ID` it answered, when it answered one. */\n inReplyTo?: string | null\n}\n\nexport interface PluginRecordActivityRequest extends PluginRecordEntryContext {\n kind: PluginRecordActivityKind\n /** When it happened, epoch ms. */\n atMs: number\n /** Plain text: an email's excerpt, a note's words. The owner bounds it. */\n body: string\n /** The member it is by, or `''` for none. */\n byUid: string\n byName?: string | null\n /** Required for an `email`, and read only for one. */\n email?: PluginRecordEmail | null\n /** Required for every kind but `email`: the caller's id for this entry. */\n dedupeKey?: string | null\n}\n\n/** What a task is, in the record system's own words. */\nexport type PluginRecordTaskKind = 'call' | 'email' | 'meeting' | 'todo'\n\nexport interface PluginRecordTaskRequest extends PluginRecordEntryContext {\n /** The caller's id for this task: the same key never makes a second task. */\n dedupeKey: string\n title: string\n notes?: string | null\n kind: PluginRecordTaskKind\n /** When it is due, epoch ms. */\n dueAtMs: number\n /** The member it is for, or `''` for nobody. */\n assigneeUid: string\n /** The member who made it happen, or `''` when nobody did. */\n createdByUid: string\n}\n\n/**\n * The owner's answer. `created: false` is the entry a previous write — or a\n * copy through another door — already filed, under the same `id`.\n */\nexport type PluginRecordWrite =\n | { ok: true; id: string; created: boolean }\n | {\n ok: false\n status: 400 | 403 | 404 | 409\n /** Customer-safe: a caller may show or log it as it stands. */\n error: string\n }\n\nexport interface PluginRecordTimelineWriter {\n logActivity(request: PluginRecordActivityRequest): Promise<PluginRecordWrite>\n createTask(request: PluginRecordTaskRequest): Promise<PluginRecordWrite>\n}\n\nexport const PLUGIN_RECORD_TIMELINE = definePluginServiceContract<PluginRecordTimelineWriter>(\n 'core.record-timeline',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record timeline writer. The owner is the loader's\n * marker when a register fn is running, else `options.pluginId`; with neither\n * the registration throws, and a second plugin's writer is refused naming\n * both — the incumbent keeps serving.\n */\nexport function registerPluginRecordTimelineWriter(\n writer: PluginRecordTimelineWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_TIMELINE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordTimelineWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordTimelineWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordTimelineWriter(): ResolvedPluginRecordTimelineWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_TIMELINE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_TIMELINE","multiple","registerPluginRecordTimelineWriter","writer","options","pluginId","pluginRecordTimelineWriter","entry","impl"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-timeline.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 * An entry one plugin files on a record another plugin keeps (AGL-2981).\n *\n * A person, a company and a deal are records of the workspace's record\n * system, and what happened with them — an email sent, a reply received, a\n * call to make — belongs on their timeline, whichever plugin it happened in.\n * A plugin that sends mail or books a meeting must not write the record\n * system's documents itself: it would restate the owner's rules — where an\n * entry lives, who may see it, how a copy of the same email is told apart,\n * how many entries a record may carry — and drift from them the day the\n * owner changes one. Nor may it import the owner, which the package map\n * forbids.\n *\n * So the record system registers a WRITER here, from its server surfaces,\n * and a caller asks for it. Every rule stays the owner's:\n *\n * - the entry is stamped with the scope a record made on the site would\n * carry, so it is visible to exactly who sees the record;\n * - an EMAIL is filed once per `Message-ID`, under the id the owner files a\n * copy of the same message by through any other door — a copy the rep\n * forwarded to a capture address is the same entry, not a second one;\n * - any other entry, and every task, is filed once per the caller's own\n * `dedupeKey`, so a caller that runs again finds what it wrote rather\n * than writing it twice;\n * - a record whose log is full, or a workspace whose plan carries no record\n * system, is refused — answered, never thrown.\n *\n * A single-implementation contract: a workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like `plugin-resource-drafts`, the registry authenticates nobody. The\n * caller decides, in its own terms and before it asks, that the entry is\n * the workspace's to write — that the record is one it acted on, for the\n * member it acted for.\n */\n\n/** The record an entry is filed on: any of the four, as the owner links them. */\nexport interface PluginRecordLink {\n contactId?: string\n companyId?: string\n dealId?: string\n /**\n * A lead not yet converted (AGL-3234) — the record system's own id for\n * it, addressed under `hostId`. A sequence's emails and tasks land on the\n * lead's page while the person is one, and follow the lead to the contact\n * it becomes.\n */\n leadId?: string\n}\n\n/** Where an entry is filed, and by which plugin. */\nexport interface PluginRecordEntryContext {\n orgId: string\n /** The site whose scope the entry carries: as if a member made it there. */\n hostId: string\n link: PluginRecordLink\n /** The plugin filing it, recorded on the entry. */\n sourcePluginId: string\n}\n\n/** What an activity is, in the record system's own words. */\nexport type PluginRecordActivityKind = 'email' | 'call' | 'meeting' | 'note' | 'other'\n\n/** The message an `email` activity is about. */\nexport interface PluginRecordEmail {\n /** `outbound` for one the workspace wrote; `inbound` for one written to it. */\n direction: 'outbound' | 'inbound'\n subject: string\n /** The address on the message's From. */\n from: string\n /** The address it was written to, when one is worth showing. */\n to: string | null\n /** The `Message-ID` header, angle brackets included: what files it once. */\n messageId: string\n /** The `Message-ID` it answered, when it answered one. */\n inReplyTo?: string | null\n}\n\nexport interface PluginRecordActivityRequest extends PluginRecordEntryContext {\n kind: PluginRecordActivityKind\n /** When it happened, epoch ms. */\n atMs: number\n /** Plain text: an email's excerpt, a note's words. The owner bounds it. */\n body: string\n /** The member it is by, or `''` for none. */\n byUid: string\n byName?: string | null\n /** Required for an `email`, and read only for one. */\n email?: PluginRecordEmail | null\n /** Required for every kind but `email`: the caller's id for this entry. */\n dedupeKey?: string | null\n}\n\n/** What a task is, in the record system's own words. */\nexport type PluginRecordTaskKind = 'call' | 'email' | 'meeting' | 'todo'\n\nexport interface PluginRecordTaskRequest extends PluginRecordEntryContext {\n /** The caller's id for this task: the same key never makes a second task. */\n dedupeKey: string\n title: string\n notes?: string | null\n kind: PluginRecordTaskKind\n /** When it is due, epoch ms. */\n dueAtMs: number\n /** The member it is for, or `''` for nobody. */\n assigneeUid: string\n /** The member who made it happen, or `''` when nobody did. */\n createdByUid: string\n}\n\n/**\n * The owner's answer. `created: false` is the entry a previous write — or a\n * copy through another door — already filed, under the same `id`.\n */\nexport type PluginRecordWrite =\n | { ok: true; id: string; created: boolean }\n | {\n ok: false\n status: 400 | 403 | 404 | 409\n /** Customer-safe: a caller may show or log it as it stands. */\n error: string\n }\n\n/**\n * What became of an email a plugin filed (AGL-3245): the receiving server\n * bounced it, or the recipient reported it. Addressed by the `Message-ID`\n * the send was filed under, so the verdict lands on the send's own entry\n * and the record reads Sent, then Bounced.\n */\nexport interface PluginRecordDeliveryRequest {\n orgId: string\n /** The `Message-ID` header of the send, angle brackets included. */\n messageId: string\n state: 'bounced' | 'complained'\n /** When it happened, epoch ms. */\n atMs: number\n /** What the server said, scrubbed of the address; the owner bounds it. */\n detail?: string | null\n}\n\nexport interface PluginRecordTimelineWriter {\n logActivity(request: PluginRecordActivityRequest): Promise<PluginRecordWrite>\n createTask(request: PluginRecordTaskRequest): Promise<PluginRecordWrite>\n /**\n * Marks a filed email bounced or reported (AGL-3245). Optional: a record\n * system that keeps no delivery state answers nothing, and the caller's\n * entry stands as filed.\n */\n recordEmailDelivery?(request: PluginRecordDeliveryRequest): Promise<PluginRecordWrite>\n}\n\nexport const PLUGIN_RECORD_TIMELINE = definePluginServiceContract<PluginRecordTimelineWriter>(\n 'core.record-timeline',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record timeline writer. The owner is the loader's\n * marker when a register fn is running, else `options.pluginId`; with neither\n * the registration throws, and a second plugin's writer is refused naming\n * both — the incumbent keeps serving.\n */\nexport function registerPluginRecordTimelineWriter(\n writer: PluginRecordTimelineWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_TIMELINE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordTimelineWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordTimelineWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordTimelineWriter(): ResolvedPluginRecordTimelineWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_TIMELINE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_TIMELINE","multiple","registerPluginRecordTimelineWriter","writer","options","pluginId","pluginRecordTimelineWriter","entry","impl"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA0J1B,OAAO,MAAMC,yBAAyBH,4BACpC,wBACA;IAAEI,UAAU;AAAM,GACnB;AAED;;;;;CAKC,GACD,OAAO,SAASC,mCACdC,MAAkC,EAClCC,OAA+B;IAE/BN,sBAAsBE,wBAAwBG,QAAQ,aAChDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAQA,6EAA6E,GAC7E,OAAO,SAASC;IACd,MAAMC,QAAQR,sBAAsBC,uBAAuB,CAAC,EAAE;IAC9D,OAAOO,QAAQ;QAAEF,UAAUE,MAAMF,QAAQ;QAAEF,QAAQI,MAAMC,IAAI;IAAC,IAAI;AACpE"}
|