@aglyn/aglyn 1.0.0-beta.176 → 1.0.0-beta.178

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.
@@ -0,0 +1,103 @@
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 answer to "did this workspace already know this person?"
19
+ * (AGL-3289).
20
+ *
21
+ * The platform asks when a staff member reads where an account came from: a
22
+ * sign-up whose address a sales workspace was already working, or whose name
23
+ * matches somebody on a list, is the answer to "is this the person we have
24
+ * been writing to — on a personal address?". The platform keeps no people of
25
+ * its own, so it cannot answer; any plugin that keeps records about people
26
+ * can, and registers a matcher here. The platform runs every matcher and
27
+ * shows what comes back.
28
+ *
29
+ * Two kinds of answer, and the distinction is the point of the seam:
30
+ *
31
+ * - **`email`** — the record carries the account's address. Certain.
32
+ * - **`name`** — the record's name resembles the account's. A GUESS, which the
33
+ * staff card labels "possible" and never presents as a fact.
34
+ *
35
+ * A matcher leaves out the record the account's own sign-up created: a
36
+ * platform that files every new account into its sales workspace would
37
+ * otherwise answer "yes, we know them" for everybody, which says nothing.
38
+ * What is worth showing is a record that existed BEFORE the account did.
39
+ *
40
+ * Matchers run in registration order and are ISOLATED: a throw is logged
41
+ * against its plugin and reported as that plugin having failed, so a card can
42
+ * say "could not check" rather than showing an absence it did not measure.
43
+ * Nothing here writes; every matcher is a read.
44
+ *
45
+ * Registered from a plugin's server declarations, so the matcher is in place
46
+ * in a process that never loaded the plugin's API surface.
47
+ */ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
48
+ const registrations = [];
49
+ /**
50
+ * Registers a plugin's matcher. Owner = the loader's marker inside a register
51
+ * fn, else `options.pluginId`; a matcher with neither throws. One matcher per
52
+ * plugin: registering again replaces the earlier one in place.
53
+ */ export function registerPluginPersonMatcher(matcher, options) {
54
+ var _ref, _getRegisteringPluginId;
55
+ const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
56
+ if (!pluginId) {
57
+ throw new Error('a plugin person matcher was registered with no owner: pass { pluginId } ' + 'when registering outside a plugin register fn');
58
+ }
59
+ const index = registrations.findIndex((entry)=>entry.pluginId === pluginId);
60
+ if (index >= 0) registrations[index] = {
61
+ pluginId,
62
+ matcher
63
+ };
64
+ else registrations.push({
65
+ pluginId,
66
+ matcher
67
+ });
68
+ }
69
+ /** The plugins with a matcher registered, in registration order. */ export function listPluginPersonMatchers() {
70
+ return registrations.map((entry)=>entry.pluginId);
71
+ }
72
+ /** Test seam — forget every matcher. */ export function resetPluginPersonMatchers() {
73
+ registrations.length = 0;
74
+ }
75
+ /** Runs every matcher. Never throws. */ export async function runPluginPersonMatchers(request) {
76
+ const report = {
77
+ matches: [],
78
+ failed: [],
79
+ asked: []
80
+ };
81
+ for (const { pluginId, matcher } of [
82
+ ...registrations
83
+ ]){
84
+ report.asked.push(pluginId);
85
+ try {
86
+ const found = await matcher(_extends({}, request));
87
+ for (const match of Array.isArray(found) ? found : []){
88
+ report.matches.push(_extends({}, match, {
89
+ pluginId
90
+ }));
91
+ }
92
+ } catch (error) {
93
+ report.failed.push(pluginId);
94
+ // The org, never the person: this line is read by people who were not
95
+ // asked to look the person up.
96
+ console.error(`[plugins] ${pluginId} failed to match a person in org ${request.orgId}`, error);
97
+ }
98
+ }
99
+ report.matches.sort((a, b)=>a.basis === b.basis ? 0 : a.basis === 'email' ? -1 : 1);
100
+ return report;
101
+ }
102
+
103
+ //# sourceMappingURL=plugin-person-matches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-person-matches.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 answer to \"did this workspace already know this person?\"\n * (AGL-3289).\n *\n * The platform asks when a staff member reads where an account came from: a\n * sign-up whose address a sales workspace was already working, or whose name\n * matches somebody on a list, is the answer to \"is this the person we have\n * been writing to — on a personal address?\". The platform keeps no people of\n * its own, so it cannot answer; any plugin that keeps records about people\n * can, and registers a matcher here. The platform runs every matcher and\n * shows what comes back.\n *\n * Two kinds of answer, and the distinction is the point of the seam:\n *\n * - **`email`** — the record carries the account's address. Certain.\n * - **`name`** — the record's name resembles the account's. A GUESS, which the\n * staff card labels \"possible\" and never presents as a fact.\n *\n * A matcher leaves out the record the account's own sign-up created: a\n * platform that files every new account into its sales workspace would\n * otherwise answer \"yes, we know them\" for everybody, which says nothing.\n * What is worth showing is a record that existed BEFORE the account did.\n *\n * Matchers run in registration order and are ISOLATED: a throw is logged\n * against its plugin and reported as that plugin having failed, so a card can\n * say \"could not check\" rather than showing an absence it did not measure.\n * Nothing here writes; every matcher is a read.\n *\n * Registered from a plugin's server declarations, so the matcher is in place\n * in a process that never loaded the plugin's API surface.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** Who to look for, and where. */\nexport interface PluginPersonMatchRequest {\n /** The workspace whose records are searched. */\n orgId: string\n /** Its slug, so a matcher can link to what it found. */\n orgSlug: string | null\n /** The account's address, normalized; null when it has none. */\n email: string | null\n /** The account's name, for the name half; null when it has none. */\n name: string | null\n /**\n * When the account was created. A record the account's own sign-up made at\n * or after this moment is the account, not somebody already known.\n */\n accountCreatedAtMs: number | null\n}\n\n/** One record a matcher found. */\nexport interface PluginPersonMatch {\n /** The record's kind, in the plugin's own words. */\n kind: string\n id: string\n /** What a reader recognizes the record by — a name, else its address. */\n label: string\n email: string | null\n /** `email` is certain; `name` is a resemblance, shown as \"possible\". */\n basis: 'email' | 'name'\n /** When the workspace first knew this record, when it can say. */\n firstSeenAtMs: number | null\n /** How the workspace came to know them, in the plugin's words. */\n sources: string[]\n /** A console path to the record, when the plugin publishes one. */\n href: string | null\n}\n\nexport type PluginPersonMatcher = (\n request: PluginPersonMatchRequest,\n) => Promise<PluginPersonMatch[]>\n\ninterface Registration {\n pluginId: string\n matcher: PluginPersonMatcher\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's matcher. Owner = the loader's marker inside a register\n * fn, else `options.pluginId`; a matcher with neither throws. One matcher per\n * plugin: registering again replaces the earlier one in place.\n */\nexport function registerPluginPersonMatcher(\n matcher: PluginPersonMatcher,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin person matcher was registered with no owner: pass { pluginId } ' +\n '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, matcher }\n else registrations.push({ pluginId, matcher })\n}\n\n/** The plugins with a matcher registered, in registration order. */\nexport function listPluginPersonMatchers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam — forget every matcher. */\nexport function resetPluginPersonMatchers(): void {\n registrations.length = 0\n}\n\n/** What every matcher answered, together. */\nexport interface PluginPersonMatchReport {\n /** Every match, tagged with the plugin that found it; certain ones first. */\n matches: Array<PluginPersonMatch & { pluginId: string }>\n /** Plugins whose matcher threw — their absence from `matches` measures nothing. */\n failed: string[]\n /** Plugins that were asked. None means nothing on this install can answer. */\n asked: string[]\n}\n\n/** Runs every matcher. Never throws. */\nexport async function runPluginPersonMatchers(\n request: PluginPersonMatchRequest,\n): Promise<PluginPersonMatchReport> {\n const report: PluginPersonMatchReport = { matches: [], failed: [], asked: [] }\n for (const { pluginId, matcher } of [...registrations]) {\n report.asked.push(pluginId)\n try {\n const found = await matcher({ ...request })\n for (const match of Array.isArray(found) ? found : []) {\n report.matches.push({ ...match, pluginId })\n }\n } catch (error) {\n report.failed.push(pluginId)\n // The org, never the person: this line is read by people who were not\n // asked to look the person up.\n console.error(`[plugins] ${pluginId} failed to match a person in org ${request.orgId}`, error)\n }\n }\n report.matches.sort((a, b) =>\n a.basis === b.basis ? 0 : a.basis === 'email' ? -1 : 1,\n )\n return report\n}\n"],"names":["getRegisteringPluginId","registrations","registerPluginPersonMatcher","matcher","options","pluginId","trim","Error","index","findIndex","entry","push","listPluginPersonMatchers","map","resetPluginPersonMatchers","length","runPluginPersonMatchers","request","report","matches","failed","asked","found","match","Array","isArray","error","console","orgId","sort","a","b","basis"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AA8CxE,MAAMC,gBAAgC,EAAE;AAExC;;;;CAIC,GACD,OAAO,SAASC,4BACdC,OAA4B,EAC5BC,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,6EACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAQ;SACtDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAQ;AAC9C;AAEA,kEAAkE,GAClE,OAAO,SAASS;IACd,OAAOX,cAAcY,GAAG,CAAC,CAACH,QAAUA,MAAML,QAAQ;AACpD;AAEA,sCAAsC,GACtC,OAAO,SAASS;IACdb,cAAcc,MAAM,GAAG;AACzB;AAYA,sCAAsC,GACtC,OAAO,eAAeC,wBACpBC,OAAiC;IAEjC,MAAMC,SAAkC;QAAEC,SAAS,EAAE;QAAEC,QAAQ,EAAE;QAAEC,OAAO,EAAE;IAAC;IAC7E,KAAK,MAAM,EAAEhB,QAAQ,EAAEF,OAAO,EAAE,IAAI;WAAIF;KAAc,CAAE;QACtDiB,OAAOG,KAAK,CAACV,IAAI,CAACN;QAClB,IAAI;YACF,MAAMiB,QAAQ,MAAMnB,QAAQ,aAAKc;YACjC,KAAK,MAAMM,SAASC,MAAMC,OAAO,CAACH,SAASA,QAAQ,EAAE,CAAE;gBACrDJ,OAAOC,OAAO,CAACR,IAAI,CAAC,aAAKY;oBAAOlB;;YAClC;QACF,EAAE,OAAOqB,OAAO;YACdR,OAAOE,MAAM,CAACT,IAAI,CAACN;YACnB,sEAAsE;YACtE,+BAA+B;YAC/BsB,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAErB,SAAS,iCAAiC,EAAEY,QAAQW,KAAK,EAAE,EAAEF;QAC1F;IACF;IACAR,OAAOC,OAAO,CAACU,IAAI,CAAC,CAACC,GAAGC,IACtBD,EAAEE,KAAK,KAAKD,EAAEC,KAAK,GAAG,IAAIF,EAAEE,KAAK,KAAK,UAAU,CAAC,IAAI;IAEvD,OAAOd;AACT"}