@aglyn/aglyn 1.0.0-beta.293 → 1.0.0
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/crm-kinds.d.ts +7 -1
- package/src/lib/app-utils/crm-kinds.js +11 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +5 -1
- package/src/lib/app-utils/crm.js +51 -4
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +4 -3
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- 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 +24 -17
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/screen-version-publish.d.ts +71 -0
- package/src/lib/app-utils/screen-version-publish.js +82 -0
- package/src/lib/app-utils/screen-version-publish.js.map +1 -0
- package/src/lib/app-utils/server.d.ts +1 -0
- package/src/lib/app-utils/server.js +1 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/version-draft.d.ts +61 -0
- package/src/lib/app-utils/version-draft.js +53 -0
- package/src/lib/app-utils/version-draft.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +14 -0
- package/src/lib/plugin-manager/plugin-jobs.js +30 -7
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +5 -2
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
|
@@ -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 and the company they work for are records of the workspace's\n * record system, and what happened with them — an email sent, a reply\n * received, a call to make — belongs on their timeline, whichever plugin it\n * 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 * - a caller that knows a record only as the owner handed it over — a kind\n * and an id carried on a link — or knows only the person's address is\n * answered with the record the owner finds for it, on that site, or\n * refused with a 404 when it finds none.\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/**\n * The record an entry is filed on: the person (a contact, or a lead not yet\n * one) and the company they work for, as the owner links them — or, from a\n * caller that holds no id of the owner's, how to find the record.\n *\n * The ids win where given, and are filed on as named. Otherwise `record`,\n * then `email`: the owner looks the record up as the entry's site sees it,\n * links whatever it groups that record under itself, and refuses the entry\n * with a 404 when it finds nothing.\n */\nexport interface PluginRecordLink {\n contactId?: string\n companyId?: 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 * A record the owner handed the caller, by its kind and id in the owner's\n * own words, and carried back unread (AGL-2660): the record a booking link\n * was dropped from, through the zone the owner drew it in. A kind the\n * owner does not keep, or a record the site cannot see, is passed over for\n * `email` rather than refused.\n */\n record?: { kind: string; id: string } | null\n /**\n * The person at this address, as the entry's site knows them: what the\n * entry lands on when nothing above names a record the owner finds — a\n * booking taken off the widget cold.\n */\n email?: string | null\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 /**\n * The member it is for, `''` for nobody, or `null` for whoever holds the\n * record it lands on — the caller that found the record by `record` or\n * `email` does not know who that is.\n */\n assigneeUid: string | null\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\n/**\n * An email a plugin is about to send to a person (AGL-3080), offered to the\n * record system BEFORE it goes so the entry it files can follow the\n * message's delivery: the record system answers with the provider tags the\n * message has to carry for the delivery webhook to find the entry, and with\n * how to file it once the provider has accepted it.\n *\n * Only a message addressed to the person `link` names — the person an\n * automation's event is about — earns an entry: an alert routed to the\n * merchant's own inbox would put that inbox on a customer's history.\n */\nexport interface PluginRecordEmailPrepareRequest {\n orgId: string\n /** The site the message goes out as: the entry carries its scope. */\n hostId: string\n /** The address the message is sent to, raw. */\n to: string\n /** The person the sender means: by id where it holds one, else by address. */\n link: Pick<PluginRecordLink, 'contactId' | 'email'>\n /**\n * The organization's billing document, when the caller already read it —\n * the record system's plan gate and scope read it; absent, the owner reads\n * it itself.\n */\n org?: unknown\n}\n\n/** The message as it was sent, for the entry the record system files. */\nexport interface PluginRecordEmailSent {\n subject: string\n body: string\n to: string\n /** When it was accepted, epoch ms; now when absent. */\n atMs?: number\n /** The sender's own id for what sent it — an automation's id. */\n sourceRef: string\n}\n\n/** What the record system answered a prepared email with. */\nexport interface PluginRecordPreparedEmail {\n /** Provider tags the message must carry for its delivery to reach the entry. */\n tags: ReadonlyArray<{ name: string; value: string }>\n /** Files the entry, once the provider accepted the message. Never throws. */\n file(sent: PluginRecordEmailSent): Promise<void>\n}\n\n/**\n * A delivery event the provider reported for a message that carried tags\n * (AGL-2615, AGL-3080), handed over by the webhook that heard it. The record\n * system filed the entry the tags name when the message was prepared\n * (`prepareEmail`, or its own send), so only it can read them: the webhook\n * hands every tag the message carried and the event in the delivery log's\n * own vocabulary, and the record system moves the entry the tags name, if\n * any, to that state.\n */\nexport interface PluginRecordTaggedDeliveryRequest {\n /** Every tag the message carried, by name, as the provider echoed them. */\n tags: Readonly<Record<string, string>>\n /** The normalized event — `delivered`, `opened`, `clicked`, `bounced`, `complained`. */\n event: string\n /** When the provider says it happened, epoch ms. */\n atMs: number\n}\n\nexport interface PluginRecordTimelineWriter {\n logActivity(request: PluginRecordActivityRequest): Promise<PluginRecordWrite>\n createTask(request: PluginRecordTaskRequest): Promise<PluginRecordWrite>\n /**\n * Prepares the entry an email to a person will earn (AGL-3080). Optional:\n * a record system that files no sent mail answers nothing. `null` for a\n * message that earns no entry. Never throws.\n */\n prepareEmail?(request: PluginRecordEmailPrepareRequest): Promise<PluginRecordPreparedEmail | null>\n /**\n * Moves the entry a delivered message's tags name to the event's state\n * (AGL-3080). Optional: a record system that files no sent mail answers\n * nothing. Tags that name no entry of its own are its to ignore. Never\n * throws.\n */\n recordTaggedDelivery?(request: PluginRecordTaggedDeliveryRequest): Promise<void>\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\n/**\n * Asks whichever plugin keeps records to prepare the entry an email to a\n * person will earn, or answers `null` when none does, the one that does\n * files no sent mail, or this message earns no entry. Never throws: the\n * entry is bookkeeping beside a send, and a lookup that failed must not\n * become a message that never left.\n */\nexport async function preparePluginRecordEmail(\n request: PluginRecordEmailPrepareRequest,\n): Promise<PluginRecordPreparedEmail | null> {\n const resolved = pluginRecordTimelineWriter()\n if (!resolved?.writer.prepareEmail) return null\n try {\n return await resolved.writer.prepareEmail(request)\n } catch (error) {\n console.error('[record-timeline] the record system could not prepare an email entry', error)\n return null\n }\n}\n\n/**\n * Hands a delivery event to whichever plugin keeps records, for the entry the\n * message's tags name (AGL-3080). Does nothing when no plugin keeps records or\n * the one that does files no sent mail. Never throws: the webhook answers the\n * provider whatever happens here.\n */\nexport async function recordPluginTaggedEmailDelivery(\n request: PluginRecordTaggedDeliveryRequest,\n): Promise<void> {\n const resolved = pluginRecordTimelineWriter()\n if (!resolved?.writer.recordTaggedDelivery) return\n try {\n await resolved.writer.recordTaggedDelivery(request)\n } catch (error) {\n console.error('[record-timeline] the record system could not record a delivery', error)\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_TIMELINE","multiple","registerPluginRecordTimelineWriter","writer","options","pluginId","pluginRecordTimelineWriter","entry","impl","preparePluginRecordEmail","request","resolved","prepareEmail","error","console","recordPluginTaggedEmailDelivery","recordTaggedDelivery"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAsQ1B,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;AAEA;;;;;;CAMC,GACD,OAAO,eAAeC,yBACpBC,OAAwC;IAExC,MAAMC,WAAWL;IACjB,IAAI,EAACK,4BAAAA,SAAUR,MAAM,CAACS,YAAY,GAAE,OAAO;IAC3C,IAAI;QACF,OAAO,MAAMD,SAASR,MAAM,CAACS,YAAY,CAACF;IAC5C,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,wEAAwEA;QACtF,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeE,gCACpBL,OAA0C;IAE1C,MAAMC,WAAWL;IACjB,IAAI,EAACK,4BAAAA,SAAUR,MAAM,CAACa,oBAAoB,GAAE;IAC5C,IAAI;QACF,MAAML,SAASR,MAAM,CAACa,oBAAoB,CAACN;IAC7C,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,mEAAmEA;IACnF;AACF"}
|
|
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 and the company they work for are records of the workspace's\n * record system, and what happened with them — an email sent, a reply\n * received, a call to make — belongs on their timeline, whichever plugin it\n * 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 * - a caller that knows a record only as the owner handed it over — a kind\n * and an id carried on a link — or knows only the person's address is\n * answered with the record the owner finds for it, on that site, or\n * refused with a 404 when it finds none.\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/**\n * The record an entry is filed on: the person (a contact, or a lead not yet\n * one) and the company they work for, as the owner links them — or, from a\n * caller that holds no id of the owner's, how to find the record.\n *\n * The ids win where given, and are filed on as named. Otherwise `record`,\n * then `email`: the owner looks the record up as the entry's site sees it,\n * links whatever it groups that record under itself, and refuses the entry\n * with a 404 when it finds nothing.\n */\nexport interface PluginRecordLink {\n contactId?: string\n companyId?: 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 * A record the owner handed the caller, by its kind and id in the owner's\n * own words, and carried back unread (AGL-2660): the record a booking link\n * was dropped from, through the zone the owner drew it in. A kind the\n * owner does not keep, or a record the site cannot see, is passed over for\n * `email` rather than refused.\n */\n record?: { kind: string; id: string } | null\n /**\n * The person at this address, as the entry's site knows them: what the\n * entry lands on when nothing above names a record the owner finds — a\n * booking taken off the widget cold.\n */\n email?: string | null\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/**\n * What an activity is, in the record system's own words — a message kind\n * (`text`, `whatsapp`, …) for a plugin that carries one on that channel.\n */\nexport type PluginRecordActivityKind =\n | 'email'\n | 'call'\n | 'text'\n | 'whatsapp'\n | 'messenger'\n | 'instagram'\n | 'linkedin'\n | 'meeting'\n | 'note'\n | '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 /**\n * The member it is for, `''` for nobody, or `null` for whoever holds the\n * record it lands on — the caller that found the record by `record` or\n * `email` does not know who that is.\n */\n assigneeUid: string | null\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\n/**\n * An email a plugin is about to send to a person (AGL-3080), offered to the\n * record system BEFORE it goes so the entry it files can follow the\n * message's delivery: the record system answers with the provider tags the\n * message has to carry for the delivery webhook to find the entry, and with\n * how to file it once the provider has accepted it.\n *\n * Only a message addressed to the person `link` names — the person an\n * automation's event is about — earns an entry: an alert routed to the\n * merchant's own inbox would put that inbox on a customer's history.\n */\nexport interface PluginRecordEmailPrepareRequest {\n orgId: string\n /** The site the message goes out as: the entry carries its scope. */\n hostId: string\n /** The address the message is sent to, raw. */\n to: string\n /** The person the sender means: by id where it holds one, else by address. */\n link: Pick<PluginRecordLink, 'contactId' | 'email'>\n /**\n * The organization's billing document, when the caller already read it —\n * the record system's plan gate and scope read it; absent, the owner reads\n * it itself.\n */\n org?: unknown\n}\n\n/** The message as it was sent, for the entry the record system files. */\nexport interface PluginRecordEmailSent {\n subject: string\n body: string\n to: string\n /** When it was accepted, epoch ms; now when absent. */\n atMs?: number\n /** The sender's own id for what sent it — an automation's id. */\n sourceRef: string\n}\n\n/** What the record system answered a prepared email with. */\nexport interface PluginRecordPreparedEmail {\n /** Provider tags the message must carry for its delivery to reach the entry. */\n tags: ReadonlyArray<{ name: string; value: string }>\n /** Files the entry, once the provider accepted the message. Never throws. */\n file(sent: PluginRecordEmailSent): Promise<void>\n}\n\n/**\n * A delivery event the provider reported for a message that carried tags\n * (AGL-2615, AGL-3080), handed over by the webhook that heard it. The record\n * system filed the entry the tags name when the message was prepared\n * (`prepareEmail`, or its own send), so only it can read them: the webhook\n * hands every tag the message carried and the event in the delivery log's\n * own vocabulary, and the record system moves the entry the tags name, if\n * any, to that state.\n */\nexport interface PluginRecordTaggedDeliveryRequest {\n /** Every tag the message carried, by name, as the provider echoed them. */\n tags: Readonly<Record<string, string>>\n /** The normalized event — `delivered`, `opened`, `clicked`, `bounced`, `complained`. */\n event: string\n /** When the provider says it happened, epoch ms. */\n atMs: number\n}\n\nexport interface PluginRecordTimelineWriter {\n logActivity(request: PluginRecordActivityRequest): Promise<PluginRecordWrite>\n createTask(request: PluginRecordTaskRequest): Promise<PluginRecordWrite>\n /**\n * Prepares the entry an email to a person will earn (AGL-3080). Optional:\n * a record system that files no sent mail answers nothing. `null` for a\n * message that earns no entry. Never throws.\n */\n prepareEmail?(request: PluginRecordEmailPrepareRequest): Promise<PluginRecordPreparedEmail | null>\n /**\n * Moves the entry a delivered message's tags name to the event's state\n * (AGL-3080). Optional: a record system that files no sent mail answers\n * nothing. Tags that name no entry of its own are its to ignore. Never\n * throws.\n */\n recordTaggedDelivery?(request: PluginRecordTaggedDeliveryRequest): Promise<void>\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\n/**\n * Asks whichever plugin keeps records to prepare the entry an email to a\n * person will earn, or answers `null` when none does, the one that does\n * files no sent mail, or this message earns no entry. Never throws: the\n * entry is bookkeeping beside a send, and a lookup that failed must not\n * become a message that never left.\n */\nexport async function preparePluginRecordEmail(\n request: PluginRecordEmailPrepareRequest,\n): Promise<PluginRecordPreparedEmail | null> {\n const resolved = pluginRecordTimelineWriter()\n if (!resolved?.writer.prepareEmail) return null\n try {\n return await resolved.writer.prepareEmail(request)\n } catch (error) {\n console.error('[record-timeline] the record system could not prepare an email entry', error)\n return null\n }\n}\n\n/**\n * Hands a delivery event to whichever plugin keeps records, for the entry the\n * message's tags name (AGL-3080). Does nothing when no plugin keeps records or\n * the one that does files no sent mail. Never throws: the webhook answers the\n * provider whatever happens here.\n */\nexport async function recordPluginTaggedEmailDelivery(\n request: PluginRecordTaggedDeliveryRequest,\n): Promise<void> {\n const resolved = pluginRecordTimelineWriter()\n if (!resolved?.writer.recordTaggedDelivery) return\n try {\n await resolved.writer.recordTaggedDelivery(request)\n } catch (error) {\n console.error('[record-timeline] the record system could not record a delivery', error)\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_TIMELINE","multiple","registerPluginRecordTimelineWriter","writer","options","pluginId","pluginRecordTimelineWriter","entry","impl","preparePluginRecordEmail","request","resolved","prepareEmail","error","console","recordPluginTaggedEmailDelivery","recordTaggedDelivery"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAmR1B,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;AAEA;;;;;;CAMC,GACD,OAAO,eAAeC,yBACpBC,OAAwC;IAExC,MAAMC,WAAWL;IACjB,IAAI,EAACK,4BAAAA,SAAUR,MAAM,CAACS,YAAY,GAAE,OAAO;IAC3C,IAAI;QACF,OAAO,MAAMD,SAASR,MAAM,CAACS,YAAY,CAACF;IAC5C,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,wEAAwEA;QACtF,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeE,gCACpBL,OAA0C;IAE1C,MAAMC,WAAWL;IACjB,IAAI,EAACK,4BAAAA,SAAUR,MAAM,CAACa,oBAAoB,GAAE;IAC5C,IAAI;QACF,MAAML,SAASR,MAAM,CAACa,oBAAoB,CAACN;IAC7C,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,mEAAmEA;IACnF;AACF"}
|