@aglyn/aglyn 1.0.0-beta.233 → 1.0.0-beta.235

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.
Files changed (95) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/activity-labels.d.ts +117 -0
  3. package/src/lib/app-utils/activity-labels.js +343 -0
  4. package/src/lib/app-utils/activity-labels.js.map +1 -0
  5. package/src/lib/app-utils/admin-audit-index.d.ts +11 -0
  6. package/src/lib/app-utils/admin-audit-index.js +12 -1
  7. package/src/lib/app-utils/admin-audit-index.js.map +1 -1
  8. package/src/lib/app-utils/advertising-consent.d.ts +111 -0
  9. package/src/lib/app-utils/advertising-consent.js +216 -0
  10. package/src/lib/app-utils/advertising-consent.js.map +1 -0
  11. package/src/lib/app-utils/advertising-events.d.ts +98 -0
  12. package/src/lib/app-utils/advertising-events.js +340 -0
  13. package/src/lib/app-utils/advertising-events.js.map +1 -0
  14. package/src/lib/app-utils/advertising-tag-mounts.js +15 -8
  15. package/src/lib/app-utils/advertising-tag-mounts.js.map +1 -1
  16. package/src/lib/app-utils/advertising-tags.d.ts +54 -8
  17. package/src/lib/app-utils/advertising-tags.js +126 -28
  18. package/src/lib/app-utils/advertising-tags.js.map +1 -1
  19. package/src/lib/app-utils/analytics-events.d.ts +15 -86
  20. package/src/lib/app-utils/analytics-events.js +21 -6
  21. package/src/lib/app-utils/analytics-events.js.map +1 -1
  22. package/src/lib/app-utils/consent-banner-ui.d.ts +14 -0
  23. package/src/lib/app-utils/consent-banner-ui.js +23 -2
  24. package/src/lib/app-utils/consent-banner-ui.js.map +1 -1
  25. package/src/lib/app-utils/docs-help-section-excerpt-text.d.ts +14 -0
  26. package/src/lib/app-utils/docs-help-section-excerpt-text.js +31 -0
  27. package/src/lib/app-utils/docs-help-section-excerpt-text.js.map +1 -0
  28. package/src/lib/app-utils/docs-help-section-excerpt.d.ts +13 -0
  29. package/src/lib/app-utils/docs-help-section-excerpt.js +37 -0
  30. package/src/lib/app-utils/docs-help-section-excerpt.js.map +1 -0
  31. package/src/lib/app-utils/docs-help-sections.generated.d.ts +31 -0
  32. package/src/lib/app-utils/docs-help-sections.generated.js +660 -0
  33. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -0
  34. package/src/lib/app-utils/docs-help.d.ts +16 -4
  35. package/src/lib/app-utils/docs-help.generated.d.ts +171 -34
  36. package/src/lib/app-utils/docs-help.generated.js +1104 -3
  37. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  38. package/src/lib/app-utils/docs-help.js +25 -6
  39. package/src/lib/app-utils/docs-help.js.map +1 -1
  40. package/src/lib/app-utils/docs-index.generated.js +1008 -91
  41. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  42. package/src/lib/app-utils/health-report.js +12 -0
  43. package/src/lib/app-utils/health-report.js.map +1 -1
  44. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
  45. package/src/lib/app-utils/plugin-release-flags.generated.js +7 -1
  46. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
  47. package/src/lib/app-utils/realm-host-surface.generated.js +1 -0
  48. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  49. package/src/lib/app-utils/variables.d.ts +2 -0
  50. package/src/lib/app-utils/variables.js +3 -0
  51. package/src/lib/app-utils/variables.js.map +1 -1
  52. package/src/lib/app-utils/visitor-consent.d.ts +34 -0
  53. package/src/lib/app-utils/visitor-consent.js +49 -2
  54. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  55. package/src/lib/app-utils/where-used-summary.d.ts +41 -0
  56. package/src/lib/app-utils/where-used-summary.js +33 -0
  57. package/src/lib/app-utils/where-used-summary.js.map +1 -0
  58. package/src/lib/app-utils/where-used.d.ts +2 -20
  59. package/src/lib/app-utils/where-used.js +13 -12
  60. package/src/lib/app-utils/where-used.js.map +1 -1
  61. package/src/lib/foundation/definitions/platform.types.d.ts +33 -0
  62. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  63. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +7 -0
  64. package/src/lib/plugin-manager/first-party-plugins.generated.js +166 -2
  65. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  66. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +51 -0
  67. package/src/lib/plugin-manager/plugin-activity-actions.js +43 -0
  68. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  69. package/src/lib/plugin-manager/plugin-advertising-conversions.d.ts +84 -0
  70. package/src/lib/plugin-manager/plugin-advertising-conversions.js +86 -0
  71. package/src/lib/plugin-manager/plugin-advertising-conversions.js.map +1 -0
  72. package/src/lib/plugin-manager/plugin-config.d.ts +23 -0
  73. package/src/lib/plugin-manager/plugin-config.js.map +1 -1
  74. package/src/lib/plugin-manager/plugin-local-deliveries.d.ts +161 -0
  75. package/src/lib/plugin-manager/plugin-local-deliveries.js +46 -0
  76. package/src/lib/plugin-manager/plugin-local-deliveries.js.map +1 -0
  77. package/src/lib/plugin-manager/plugin-media-ingest.d.ts +87 -0
  78. package/src/lib/plugin-manager/plugin-media-ingest.js +34 -0
  79. package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -0
  80. package/src/lib/plugin-manager/plugin-order-email-copies.d.ts +93 -0
  81. package/src/lib/plugin-manager/plugin-order-email-copies.js +104 -0
  82. package/src/lib/plugin-manager/plugin-order-email-copies.js.map +1 -0
  83. package/src/lib/plugin-manager/plugin-shipment-records.d.ts +7 -0
  84. package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -1
  85. package/src/lib/plugin-manager/plugin-site-csp.d.ts +58 -0
  86. package/src/lib/plugin-manager/plugin-site-csp.js +103 -0
  87. package/src/lib/plugin-manager/plugin-site-csp.js.map +1 -0
  88. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +1 -0
  89. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  90. package/src/lib/plugin-manager/site-page-hooks.d.ts +13 -0
  91. package/src/lib/plugin-manager/site-page-hooks.js +31 -0
  92. package/src/lib/plugin-manager/site-page-hooks.js.map +1 -1
  93. package/src/lib/plugin-manager/stock-photo-provider.d.ts +141 -0
  94. package/src/lib/plugin-manager/stock-photo-provider.js +59 -0
  95. package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-shipment-records.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport type { PluginShippingAddress } from './plugin-shipping-rates'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * What was sold and shipped, kept by the plugin that sold it (AGL-3612).\n *\n * A plugin that buys a carrier label needs three things from the seller of\n * the goods, and must not read the seller's documents for any of them: what\n * a record holds that still has to ship (lines, weights, the address), a way\n * to write a shipment back onto it once the label exists, and a way to tell\n * it what the carrier says has happened since. The seller registers\n * {@link PluginShipmentRecords}; the label buyer asks it. The seller's own\n * transition rules, its timeline and the emails its customers get all stay\n * inside the seller's write, so a label bought here and a parcel shipped by\n * hand land the same way.\n *\n * The words are a seller's and a carrier's, never one catalog's: a record is\n * anything with lines that ship — an order, a rental, a subscription box.\n *\n * ## Shipments announced\n *\n * A seller also announces each shipment it records, however it was recorded\n * — a label bought here, or a carrier and number typed by hand — so a plugin\n * that follows parcels can start following that one. Listeners run after the\n * write; one that throws is logged and never undoes it.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-shipment-records`); it is not in the\n * barrel.\n */\n\n/** One line of a record, as a shipper needs it. */\nexport interface PluginShipmentLine {\n /** The line's position on the record: the id a write names it by. */\n lineIndex: number\n name: string\n sku?: string\n quantity: number\n /** Units of the line not yet on any shipment. */\n quantityUnshipped: number\n /** Per unit, in the record's currency. */\n unitValueCents: number\n /** Per unit; absent when the seller does not know it. */\n weightGrams?: number\n lengthCm?: number\n widthCm?: number\n heightCm?: number\n /** Harmonized System code, for customs. */\n hsCode?: string\n /** ISO-3166 alpha-2 country the goods were made in, for customs. */\n originCountry?: string\n}\n\n/** A shipment already on the record. */\nexport interface PluginRecordedShipment {\n id: string\n carrier?: string\n trackingNumber?: string\n trackingUrl?: string\n /** The label it was shipped under, when a label buyer wrote it. */\n labelRef?: string\n lineIndexes: number[]\n atMs: number\n}\n\n/** A record with lines that ship. */\nexport interface PluginShippableRecord {\n hostId: string\n recordId: string\n /** What a person calls it: `#1042`. */\n displayRef: string\n /** The seller's own status word, shown as is. */\n status: string\n /** Whether the seller would accept a shipment on it now. */\n shippable: boolean\n /** ISO-4217, lower case. */\n currency: string\n shipTo?: PluginShippingAddress\n customerEmail?: string\n lines: PluginShipmentLine[]\n shipments: PluginRecordedShipment[]\n /** The rate the customer chose at checkout, when it was a carrier's. */\n chosenService?: { serviceKey?: string; label?: string; amountCents?: number }\n createdAtMs?: number\n /**\n * Whether the record was paid in a payment provider's test mode (AGL-3634):\n * no money moved, so nothing real may be sent for it. Absent when the\n * seller does not know, which reads as live.\n */\n testMode?: boolean\n}\n\n/** A shipment to write. */\nexport interface PluginShipmentWrite {\n hostId: string\n recordId: string\n /** What is in the parcel; absent means every unit not yet shipped. */\n lines?: Array<{ lineIndex: number; quantity: number }>\n carrier: string\n trackingNumber: string\n trackingUrl?: string\n /** The label's file, an https URL the seller may show beside the shipment. */\n labelUrl?: string\n labelRef?: string\n /** The member who bought the label, for the record's timeline. */\n actorUid?: string\n}\n\n/**\n * What a write did. `already` is a success: the same label was written\n * before, so a retry cannot add a second shipment.\n */\nexport type PluginShipmentWriteOutcome =\n | { outcome: 'recorded'; shipmentId: string }\n | { outcome: 'already'; shipmentId?: string }\n | { outcome: 'no_such_record' }\n /** The seller's rules refused; `from` is the status that refused. */\n | { outcome: 'blocked'; from: string; reason?: string }\n\n/** Where a parcel is, in a carrier-neutral word. */\nexport type PluginTrackingStatus =\n | 'pre_transit'\n | 'in_transit'\n | 'out_for_delivery'\n | 'delivered'\n | 'exception'\n | 'returned'\n\nexport const PLUGIN_TRACKING_STATUSES: readonly PluginTrackingStatus[] = [\n 'pre_transit',\n 'in_transit',\n 'out_for_delivery',\n 'delivered',\n 'exception',\n 'returned',\n]\n\n/** What a carrier says happened to one parcel. */\nexport interface PluginTrackingUpdate {\n hostId: string\n recordId: string\n trackingNumber: string\n status: PluginTrackingStatus\n /** The carrier's own words for the event. */\n detail?: string\n atMs: number\n}\n\nexport type PluginTrackingOutcome =\n | { outcome: 'recorded' }\n /** The record already says this, so nothing was written. */\n | { outcome: 'unchanged' }\n | { outcome: 'no_such_record' }\n | { outcome: 'no_such_shipment' }\n\n/** A place a site ships from. */\nexport interface PluginShipFromAddress {\n id: string\n name: string\n address: PluginShippingAddress\n}\n\nexport interface PluginShipmentRecords {\n /** One record, or `null` when the site has none by that id. */\n read(hostId: string, recordId: string): Promise<PluginShippableRecord | null>\n /** Writes a shipment under the seller's own rules, once per label. */\n recordShipment(write: PluginShipmentWrite): Promise<PluginShipmentWriteOutcome>\n /** Records what the carrier says; `delivered` moves the record on as the seller does. */\n recordTracking(update: PluginTrackingUpdate): Promise<PluginTrackingOutcome>\n /** The addresses the site keeps for where it ships from. */\n shipFromAddresses(hostId: string): Promise<PluginShipFromAddress[]>\n}\n\nexport const PLUGIN_SHIPMENT_RECORDS =\n definePluginServiceContract<PluginShipmentRecords>('core.shipment-records', {\n multiple: false,\n })\n\n/** Registers the seller. A second plugin is refused naming both. */\nexport function registerPluginShipmentRecords(\n records: PluginShipmentRecords,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_SHIPMENT_RECORDS, records, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The seller, or `null` when no plugin registered one. */\nexport function pluginShipmentRecords(): PluginShipmentRecords | null {\n return resolvePluginServices(PLUGIN_SHIPMENT_RECORDS)[0]?.impl ?? null\n}\n\n/** A shipment a seller recorded, by any door. */\nexport interface PluginShipmentAnnouncement {\n hostId: string\n recordId: string\n shipmentId: string\n carrier?: string\n trackingNumber?: string\n /** Present when a label buyer wrote it; a listener that bought it already follows it. */\n labelRef?: string\n}\n\nexport type PluginShipmentListener = (\n announcement: PluginShipmentAnnouncement,\n) => void | Promise<void>\n\nconst PLUGIN_SHIPMENT_LISTENERS = definePluginServiceContract<PluginShipmentListener>(\n 'core.shipment-listeners',\n { multiple: true },\n)\n\n/** Joins the listeners. Re-registering under the same plugin replaces its own. */\nexport function registerPluginShipmentListener(\n listener: PluginShipmentListener,\n options?: { pluginId?: string },\n): void {\n const pluginId = getRegisteringPluginId() ?? options?.pluginId\n registerPluginService(PLUGIN_SHIPMENT_LISTENERS, listener, {\n ...(pluginId ? { pluginId } : {}),\n })\n}\n\n/**\n * Tells every listener about a shipment the seller recorded. Awaited, each\n * isolated: a listener that throws is logged and the rest still hear it.\n */\nexport async function announcePluginShipment(\n announcement: PluginShipmentAnnouncement,\n): Promise<void> {\n for (const entry of resolvePluginServices(PLUGIN_SHIPMENT_LISTENERS)) {\n try {\n await entry.impl(announcement)\n } catch (error) {\n console.error(\n `[shipments] listener \"${entry.pluginId}\" failed for ${announcement.hostId}/${announcement.recordId}`,\n error,\n )\n }\n }\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_TRACKING_STATUSES","PLUGIN_SHIPMENT_RECORDS","multiple","registerPluginShipmentRecords","records","options","pluginId","pluginShipmentRecords","impl","PLUGIN_SHIPMENT_LISTENERS","registerPluginShipmentListener","listener","announcePluginShipment","announcement","entry","error","console","hostId","recordId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAExE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA+H1B,OAAO,MAAMC,2BAA4D;IACvE;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAsCD,OAAO,MAAMC,0BACXJ,4BAAmD,yBAAyB;IAC1EK,UAAU;AACZ,GAAE;AAEJ,kEAAkE,GAClE,OAAO,SAASC,8BACdC,OAA8B,EAC9BC,OAA+B;IAE/BP,sBAAsBG,yBAAyBG,SAAS,aAClDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yDAAyD,GACzD,OAAO,SAASC;;QACPR;IAAP,gBAAOA,0BAAAA,sBAAsBE,wBAAwB,CAAC,EAAE,qBAAjDF,wBAAmDS,IAAI,mBAAI;AACpE;AAiBA,MAAMC,4BAA4BZ,4BAChC,2BACA;IAAEK,UAAU;AAAK;AAGnB,gFAAgF,GAChF,OAAO,SAASQ,+BACdC,QAAgC,EAChCN,OAA+B;QAEdT;IAAjB,MAAMU,YAAWV,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASC,QAAQ;IAC9DR,sBAAsBW,2BAA2BE,UAAU,aACrDL,WAAW;QAAEA;IAAS,IAAI,CAAC;AAEnC;AAEA;;;CAGC,GACD,OAAO,eAAeM,uBACpBC,YAAwC;IAExC,KAAK,MAAMC,SAASf,sBAAsBU,2BAA4B;QACpE,IAAI;YACF,MAAMK,MAAMN,IAAI,CAACK;QACnB,EAAE,OAAOE,OAAO;YACdC,QAAQD,KAAK,CACX,CAAC,sBAAsB,EAAED,MAAMR,QAAQ,CAAC,aAAa,EAAEO,aAAaI,MAAM,CAAC,CAAC,EAAEJ,aAAaK,QAAQ,EAAE,EACrGH;QAEJ;IACF;AACF"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-shipment-records.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport type { PluginShippingAddress } from './plugin-shipping-rates'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * What was sold and shipped, kept by the plugin that sold it (AGL-3612).\n *\n * A plugin that buys a carrier label needs three things from the seller of\n * the goods, and must not read the seller's documents for any of them: what\n * a record holds that still has to ship (lines, weights, the address), a way\n * to write a shipment back onto it once the label exists, and a way to tell\n * it what the carrier says has happened since. The seller registers\n * {@link PluginShipmentRecords}; the label buyer asks it. The seller's own\n * transition rules, its timeline and the emails its customers get all stay\n * inside the seller's write, so a label bought here and a parcel shipped by\n * hand land the same way.\n *\n * The words are a seller's and a carrier's, never one catalog's: a record is\n * anything with lines that ship — an order, a rental, a subscription box.\n *\n * ## Shipments announced\n *\n * A seller also announces each shipment it records, however it was recorded\n * — a label bought here, or a carrier and number typed by hand — so a plugin\n * that follows parcels can start following that one. Listeners run after the\n * write; one that throws is logged and never undoes it.\n *\n * Import this module by its own subpath\n * (`@aglyn/aglyn/plugin-manager/plugin-shipment-records`); it is not in the\n * barrel.\n */\n\n/** One line of a record, as a shipper needs it. */\nexport interface PluginShipmentLine {\n /** The line's position on the record: the id a write names it by. */\n lineIndex: number\n name: string\n sku?: string\n quantity: number\n /** Units of the line not yet on any shipment. */\n quantityUnshipped: number\n /** Per unit, in the record's currency. */\n unitValueCents: number\n /** Per unit; absent when the seller does not know it. */\n weightGrams?: number\n lengthCm?: number\n widthCm?: number\n heightCm?: number\n /** Harmonized System code, for customs. */\n hsCode?: string\n /** ISO-3166 alpha-2 country the goods were made in, for customs. */\n originCountry?: string\n}\n\n/** A shipment already on the record. */\nexport interface PluginRecordedShipment {\n id: string\n carrier?: string\n trackingNumber?: string\n trackingUrl?: string\n /** The label it was shipped under, when a label buyer wrote it. */\n labelRef?: string\n lineIndexes: number[]\n atMs: number\n}\n\n/** A record with lines that ship. */\nexport interface PluginShippableRecord {\n hostId: string\n recordId: string\n /** What a person calls it: `#1042`. */\n displayRef: string\n /** The seller's own status word, shown as is. */\n status: string\n /** Whether the seller would accept a shipment on it now. */\n shippable: boolean\n /** ISO-4217, lower case. */\n currency: string\n shipTo?: PluginShippingAddress\n customerEmail?: string\n lines: PluginShipmentLine[]\n shipments: PluginRecordedShipment[]\n /** The rate the customer chose at checkout, when it was a carrier's. */\n chosenService?: { serviceKey?: string; label?: string; amountCents?: number }\n createdAtMs?: number\n /**\n * Whether the record was paid in a payment provider's test mode (AGL-3634):\n * no money moved, so nothing real may be sent for it. Absent when the\n * seller does not know, which reads as live.\n */\n testMode?: boolean\n}\n\n/** A shipment to write. */\nexport interface PluginShipmentWrite {\n hostId: string\n recordId: string\n /** What is in the parcel; absent means every unit not yet shipped. */\n lines?: Array<{ lineIndex: number; quantity: number }>\n carrier: string\n trackingNumber: string\n trackingUrl?: string\n /** The label's file, an https URL the seller may show beside the shipment. */\n labelUrl?: string\n labelRef?: string\n /**\n * What the label cost the seller, in integer cents of the record's\n * currency (AGL-3693); absent when the label was bought in another\n * currency or the cost is not known. A marketplace that pays the seller\n * back for shipping (Faire's `maker_cost_cents`) is told it.\n */\n labelCostCents?: number\n /** The member who bought the label, for the record's timeline. */\n actorUid?: string\n}\n\n/**\n * What a write did. `already` is a success: the same label was written\n * before, so a retry cannot add a second shipment.\n */\nexport type PluginShipmentWriteOutcome =\n | { outcome: 'recorded'; shipmentId: string }\n | { outcome: 'already'; shipmentId?: string }\n | { outcome: 'no_such_record' }\n /** The seller's rules refused; `from` is the status that refused. */\n | { outcome: 'blocked'; from: string; reason?: string }\n\n/** Where a parcel is, in a carrier-neutral word. */\nexport type PluginTrackingStatus =\n | 'pre_transit'\n | 'in_transit'\n | 'out_for_delivery'\n | 'delivered'\n | 'exception'\n | 'returned'\n\nexport const PLUGIN_TRACKING_STATUSES: readonly PluginTrackingStatus[] = [\n 'pre_transit',\n 'in_transit',\n 'out_for_delivery',\n 'delivered',\n 'exception',\n 'returned',\n]\n\n/** What a carrier says happened to one parcel. */\nexport interface PluginTrackingUpdate {\n hostId: string\n recordId: string\n trackingNumber: string\n status: PluginTrackingStatus\n /** The carrier's own words for the event. */\n detail?: string\n atMs: number\n}\n\nexport type PluginTrackingOutcome =\n | { outcome: 'recorded' }\n /** The record already says this, so nothing was written. */\n | { outcome: 'unchanged' }\n | { outcome: 'no_such_record' }\n | { outcome: 'no_such_shipment' }\n\n/** A place a site ships from. */\nexport interface PluginShipFromAddress {\n id: string\n name: string\n address: PluginShippingAddress\n}\n\nexport interface PluginShipmentRecords {\n /** One record, or `null` when the site has none by that id. */\n read(hostId: string, recordId: string): Promise<PluginShippableRecord | null>\n /** Writes a shipment under the seller's own rules, once per label. */\n recordShipment(write: PluginShipmentWrite): Promise<PluginShipmentWriteOutcome>\n /** Records what the carrier says; `delivered` moves the record on as the seller does. */\n recordTracking(update: PluginTrackingUpdate): Promise<PluginTrackingOutcome>\n /** The addresses the site keeps for where it ships from. */\n shipFromAddresses(hostId: string): Promise<PluginShipFromAddress[]>\n}\n\nexport const PLUGIN_SHIPMENT_RECORDS =\n definePluginServiceContract<PluginShipmentRecords>('core.shipment-records', {\n multiple: false,\n })\n\n/** Registers the seller. A second plugin is refused naming both. */\nexport function registerPluginShipmentRecords(\n records: PluginShipmentRecords,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_SHIPMENT_RECORDS, records, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The seller, or `null` when no plugin registered one. */\nexport function pluginShipmentRecords(): PluginShipmentRecords | null {\n return resolvePluginServices(PLUGIN_SHIPMENT_RECORDS)[0]?.impl ?? null\n}\n\n/** A shipment a seller recorded, by any door. */\nexport interface PluginShipmentAnnouncement {\n hostId: string\n recordId: string\n shipmentId: string\n carrier?: string\n trackingNumber?: string\n /** Present when a label buyer wrote it; a listener that bought it already follows it. */\n labelRef?: string\n}\n\nexport type PluginShipmentListener = (\n announcement: PluginShipmentAnnouncement,\n) => void | Promise<void>\n\nconst PLUGIN_SHIPMENT_LISTENERS = definePluginServiceContract<PluginShipmentListener>(\n 'core.shipment-listeners',\n { multiple: true },\n)\n\n/** Joins the listeners. Re-registering under the same plugin replaces its own. */\nexport function registerPluginShipmentListener(\n listener: PluginShipmentListener,\n options?: { pluginId?: string },\n): void {\n const pluginId = getRegisteringPluginId() ?? options?.pluginId\n registerPluginService(PLUGIN_SHIPMENT_LISTENERS, listener, {\n ...(pluginId ? { pluginId } : {}),\n })\n}\n\n/**\n * Tells every listener about a shipment the seller recorded. Awaited, each\n * isolated: a listener that throws is logged and the rest still hear it.\n */\nexport async function announcePluginShipment(\n announcement: PluginShipmentAnnouncement,\n): Promise<void> {\n for (const entry of resolvePluginServices(PLUGIN_SHIPMENT_LISTENERS)) {\n try {\n await entry.impl(announcement)\n } catch (error) {\n console.error(\n `[shipments] listener \"${entry.pluginId}\" failed for ${announcement.hostId}/${announcement.recordId}`,\n error,\n )\n }\n }\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_TRACKING_STATUSES","PLUGIN_SHIPMENT_RECORDS","multiple","registerPluginShipmentRecords","records","options","pluginId","pluginShipmentRecords","impl","PLUGIN_SHIPMENT_LISTENERS","registerPluginShipmentListener","listener","announcePluginShipment","announcement","entry","error","console","hostId","recordId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAExE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAsI1B,OAAO,MAAMC,2BAA4D;IACvE;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAsCD,OAAO,MAAMC,0BACXJ,4BAAmD,yBAAyB;IAC1EK,UAAU;AACZ,GAAE;AAEJ,kEAAkE,GAClE,OAAO,SAASC,8BACdC,OAA8B,EAC9BC,OAA+B;IAE/BP,sBAAsBG,yBAAyBG,SAAS,aAClDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,yDAAyD,GACzD,OAAO,SAASC;;QACPR;IAAP,gBAAOA,0BAAAA,sBAAsBE,wBAAwB,CAAC,EAAE,qBAAjDF,wBAAmDS,IAAI,mBAAI;AACpE;AAiBA,MAAMC,4BAA4BZ,4BAChC,2BACA;IAAEK,UAAU;AAAK;AAGnB,gFAAgF,GAChF,OAAO,SAASQ,+BACdC,QAAgC,EAChCN,OAA+B;QAEdT;IAAjB,MAAMU,YAAWV,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASC,QAAQ;IAC9DR,sBAAsBW,2BAA2BE,UAAU,aACrDL,WAAW;QAAEA;IAAS,IAAI,CAAC;AAEnC;AAEA;;;CAGC,GACD,OAAO,eAAeM,uBACpBC,YAAwC;IAExC,KAAK,MAAMC,SAASf,sBAAsBU,2BAA4B;QACpE,IAAI;YACF,MAAMK,MAAMN,IAAI,CAACK;QACnB,EAAE,OAAOE,OAAO;YACdC,QAAQD,KAAK,CACX,CAAC,sBAAsB,EAAED,MAAMR,QAAQ,CAAC,aAAa,EAAEO,aAAaI,MAAM,CAAC,CAAC,EAAEJ,aAAaK,QAAQ,EAAE,EACrGH;QAEJ;IACF;AACF"}
@@ -0,0 +1,58 @@
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
+ /** The directives a plugin may widen; the enforced, owner-widenable ones. */
18
+ export declare const PLUGIN_SITE_CSP_DIRECTIVES: readonly ["connect", "frame", "img", "media", "font"];
19
+ export type PluginSiteCspDirective = (typeof PLUGIN_SITE_CSP_DIRECTIVES)[number];
20
+ export type PluginSiteCspHosts = Partial<Record<PluginSiteCspDirective, readonly string[]>>;
21
+ /** One plugin's declaration, as the generator compiles it. */
22
+ export interface PluginSiteCspDeclaration {
23
+ pluginId: string;
24
+ /** The settings field that must be exactly `true` for anything to be admitted. */
25
+ switchField: string;
26
+ /**
27
+ * The settings field whose value names the variant. Absent for a plugin
28
+ * with one set of hosts, which declares a single `default` variant.
29
+ */
30
+ variantField?: string;
31
+ /** A settings field that must hold a non-empty string, or nothing loads. */
32
+ requiredField?: string;
33
+ /** The hosts each variant needs, by directive. */
34
+ variants: Readonly<Record<string, PluginSiteCspHosts>>;
35
+ }
36
+ /** Every first-party declaration, compiled from `plugins.config.json`. */
37
+ export declare function pluginSiteCspDeclarations(): readonly PluginSiteCspDeclaration[];
38
+ /**
39
+ * The hosts one plugin's stored settings admit, or none.
40
+ *
41
+ * Fails CLOSED on every malformed input: a switch that is not exactly `true`,
42
+ * an unknown variant, an empty required field. The settings document is
43
+ * written through the plugin's own route, but a reader of a policy input
44
+ * trusts nothing it did not parse.
45
+ */
46
+ export declare function pluginSiteCspHostsFor(declaration: PluginSiteCspDeclaration, settings: Record<string, unknown> | null | undefined): PluginSiteCspHosts;
47
+ /**
48
+ * The union of what every declared plugin the site runs admits, by
49
+ * directive, de-duplicated and in declaration order.
50
+ *
51
+ * `settingsByPlugin` holds the stored settings document of each plugin the
52
+ * caller read; a plugin missing from `enabledPluginIds` admits nothing even
53
+ * when its settings are present, because a switched-off plugin loads nothing
54
+ * on the page.
55
+ */
56
+ export declare function pluginSiteCspHosts(enabledPluginIds: readonly string[], settingsByPlugin: Readonly<Record<string, Record<string, unknown> | null | undefined>>, declared?: readonly PluginSiteCspDeclaration[]): Record<PluginSiteCspDirective, string[]>;
57
+ /** The declared plugins among a site's enabled set: the settings worth reading. */
58
+ export declare function pluginSiteCspPluginsToRead(enabledPluginIds: readonly string[], declared?: readonly PluginSiteCspDeclaration[]): string[];
@@ -0,0 +1,103 @@
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
+ * WHAT A PLUGIN'S SITE FEATURE NEEDS THE PUBLISHED PAGE'S POLICY TO ADMIT
18
+ * (AGL-3698).
19
+ *
20
+ * The tenant enforces `connect-src`, `frame-src`, `img-src`, `media-src` and
21
+ * `font-src` (AGL-1152). A plugin that loads a vendor's widget on a site —
22
+ * the merchant's own live chat is the first — needs that vendor's hosts in
23
+ * those directives, and only on the sites that switched the feature on:
24
+ * pinning them for every site would make every published page's policy
25
+ * describe a vendor most of them never load.
26
+ *
27
+ * So a plugin DECLARES the hosts in `plugins.config.json` (`siteCsp`), keyed
28
+ * by the value of one field of its site settings document
29
+ * (`hosts/{hostId}/pluginSettings/{pluginId}`), and switched by another. The
30
+ * generator compiles the declaration into `first-party-plugins.generated.ts`,
31
+ * and the tenant's lockdown verdict — the route that already hands the
32
+ * middleware every per-site list — reads the settings document of each
33
+ * declared plugin the site runs and adds the chosen variant's hosts. Core
34
+ * names no vendor and no plugin.
35
+ *
36
+ * The hosts ride the owner-list path in `security-origins.js`, so they are
37
+ * held to the same parse as an owner's own entries: a bare hostname or one
38
+ * leading `*.`, made `https://`. A `wss://` connection to an admitted host is
39
+ * covered by its `https://` source (CSP Level 3 scheme matching).
40
+ */ import { PLUGIN_SITE_CSP_DECLARED } from "./first-party-plugins.generated.js";
41
+ /** The directives a plugin may widen; the enforced, owner-widenable ones. */ export const PLUGIN_SITE_CSP_DIRECTIVES = [
42
+ 'connect',
43
+ 'frame',
44
+ 'img',
45
+ 'media',
46
+ 'font'
47
+ ];
48
+ /** Every first-party declaration, compiled from `plugins.config.json`. */ export function pluginSiteCspDeclarations() {
49
+ return PLUGIN_SITE_CSP_DECLARED;
50
+ }
51
+ /**
52
+ * The hosts one plugin's stored settings admit, or none.
53
+ *
54
+ * Fails CLOSED on every malformed input: a switch that is not exactly `true`,
55
+ * an unknown variant, an empty required field. The settings document is
56
+ * written through the plugin's own route, but a reader of a policy input
57
+ * trusts nothing it did not parse.
58
+ */ export function pluginSiteCspHostsFor(declaration, settings) {
59
+ var _declaration_variants_variant;
60
+ if (!settings || settings[declaration.switchField] !== true) return {};
61
+ if (declaration.requiredField) {
62
+ const required = settings[declaration.requiredField];
63
+ if (typeof required !== 'string' || !required.trim()) return {};
64
+ }
65
+ const variant = declaration.variantField ? settings[declaration.variantField] : 'default';
66
+ if (typeof variant !== 'string') return {};
67
+ if (!Object.prototype.hasOwnProperty.call(declaration.variants, variant)) {
68
+ return {};
69
+ }
70
+ return (_declaration_variants_variant = declaration.variants[variant]) != null ? _declaration_variants_variant : {};
71
+ }
72
+ /**
73
+ * The union of what every declared plugin the site runs admits, by
74
+ * directive, de-duplicated and in declaration order.
75
+ *
76
+ * `settingsByPlugin` holds the stored settings document of each plugin the
77
+ * caller read; a plugin missing from `enabledPluginIds` admits nothing even
78
+ * when its settings are present, because a switched-off plugin loads nothing
79
+ * on the page.
80
+ */ export function pluginSiteCspHosts(enabledPluginIds, settingsByPlugin, declared = PLUGIN_SITE_CSP_DECLARED) {
81
+ const out = Object.fromEntries(PLUGIN_SITE_CSP_DIRECTIVES.map((directive)=>[
82
+ directive,
83
+ []
84
+ ]));
85
+ const enabled = new Set(enabledPluginIds);
86
+ for (const declaration of declared){
87
+ if (!enabled.has(declaration.pluginId)) continue;
88
+ const hosts = pluginSiteCspHostsFor(declaration, settingsByPlugin[declaration.pluginId]);
89
+ for (const directive of PLUGIN_SITE_CSP_DIRECTIVES){
90
+ var _hosts_directive;
91
+ for (const host of (_hosts_directive = hosts[directive]) != null ? _hosts_directive : []){
92
+ if (!out[directive].includes(host)) out[directive].push(host);
93
+ }
94
+ }
95
+ }
96
+ return out;
97
+ }
98
+ /** The declared plugins among a site's enabled set: the settings worth reading. */ export function pluginSiteCspPluginsToRead(enabledPluginIds, declared = PLUGIN_SITE_CSP_DECLARED) {
99
+ const enabled = new Set(enabledPluginIds);
100
+ return declared.map((declaration)=>declaration.pluginId).filter((pluginId)=>enabled.has(pluginId));
101
+ }
102
+
103
+ //# sourceMappingURL=plugin-site-csp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-site-csp.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 * WHAT A PLUGIN'S SITE FEATURE NEEDS THE PUBLISHED PAGE'S POLICY TO ADMIT\n * (AGL-3698).\n *\n * The tenant enforces `connect-src`, `frame-src`, `img-src`, `media-src` and\n * `font-src` (AGL-1152). A plugin that loads a vendor's widget on a site —\n * the merchant's own live chat is the first — needs that vendor's hosts in\n * those directives, and only on the sites that switched the feature on:\n * pinning them for every site would make every published page's policy\n * describe a vendor most of them never load.\n *\n * So a plugin DECLARES the hosts in `plugins.config.json` (`siteCsp`), keyed\n * by the value of one field of its site settings document\n * (`hosts/{hostId}/pluginSettings/{pluginId}`), and switched by another. The\n * generator compiles the declaration into `first-party-plugins.generated.ts`,\n * and the tenant's lockdown verdict — the route that already hands the\n * middleware every per-site list — reads the settings document of each\n * declared plugin the site runs and adds the chosen variant's hosts. Core\n * names no vendor and no plugin.\n *\n * The hosts ride the owner-list path in `security-origins.js`, so they are\n * held to the same parse as an owner's own entries: a bare hostname or one\n * leading `*.`, made `https://`. A `wss://` connection to an admitted host is\n * covered by its `https://` source (CSP Level 3 scheme matching).\n */\n\nimport { PLUGIN_SITE_CSP_DECLARED } from './first-party-plugins.generated'\n\n/** The directives a plugin may widen; the enforced, owner-widenable ones. */\nexport const PLUGIN_SITE_CSP_DIRECTIVES = [\n 'connect',\n 'frame',\n 'img',\n 'media',\n 'font',\n] as const\n\nexport type PluginSiteCspDirective = (typeof PLUGIN_SITE_CSP_DIRECTIVES)[number]\n\nexport type PluginSiteCspHosts = Partial<\n Record<PluginSiteCspDirective, readonly string[]>\n>\n\n/** One plugin's declaration, as the generator compiles it. */\nexport interface PluginSiteCspDeclaration {\n pluginId: string\n /** The settings field that must be exactly `true` for anything to be admitted. */\n switchField: string\n /**\n * The settings field whose value names the variant. Absent for a plugin\n * with one set of hosts, which declares a single `default` variant.\n */\n variantField?: string\n /** A settings field that must hold a non-empty string, or nothing loads. */\n requiredField?: string\n /** The hosts each variant needs, by directive. */\n variants: Readonly<Record<string, PluginSiteCspHosts>>\n}\n\n/** Every first-party declaration, compiled from `plugins.config.json`. */\nexport function pluginSiteCspDeclarations(): readonly PluginSiteCspDeclaration[] {\n return PLUGIN_SITE_CSP_DECLARED\n}\n\n/**\n * The hosts one plugin's stored settings admit, or none.\n *\n * Fails CLOSED on every malformed input: a switch that is not exactly `true`,\n * an unknown variant, an empty required field. The settings document is\n * written through the plugin's own route, but a reader of a policy input\n * trusts nothing it did not parse.\n */\nexport function pluginSiteCspHostsFor(\n declaration: PluginSiteCspDeclaration,\n settings: Record<string, unknown> | null | undefined,\n): PluginSiteCspHosts {\n if (!settings || settings[declaration.switchField] !== true) return {}\n if (declaration.requiredField) {\n const required = settings[declaration.requiredField]\n if (typeof required !== 'string' || !required.trim()) return {}\n }\n const variant = declaration.variantField\n ? settings[declaration.variantField]\n : 'default'\n if (typeof variant !== 'string') return {}\n if (!Object.prototype.hasOwnProperty.call(declaration.variants, variant)) {\n return {}\n }\n return declaration.variants[variant] ?? {}\n}\n\n/**\n * The union of what every declared plugin the site runs admits, by\n * directive, de-duplicated and in declaration order.\n *\n * `settingsByPlugin` holds the stored settings document of each plugin the\n * caller read; a plugin missing from `enabledPluginIds` admits nothing even\n * when its settings are present, because a switched-off plugin loads nothing\n * on the page.\n */\nexport function pluginSiteCspHosts(\n enabledPluginIds: readonly string[],\n settingsByPlugin: Readonly<Record<string, Record<string, unknown> | null | undefined>>,\n declared: readonly PluginSiteCspDeclaration[] = PLUGIN_SITE_CSP_DECLARED,\n): Record<PluginSiteCspDirective, string[]> {\n const out = Object.fromEntries(\n PLUGIN_SITE_CSP_DIRECTIVES.map((directive) => [directive, [] as string[]]),\n ) as Record<PluginSiteCspDirective, string[]>\n const enabled = new Set(enabledPluginIds)\n for (const declaration of declared) {\n if (!enabled.has(declaration.pluginId)) continue\n const hosts = pluginSiteCspHostsFor(\n declaration,\n settingsByPlugin[declaration.pluginId],\n )\n for (const directive of PLUGIN_SITE_CSP_DIRECTIVES) {\n for (const host of hosts[directive] ?? []) {\n if (!out[directive].includes(host)) out[directive].push(host)\n }\n }\n }\n return out\n}\n\n/** The declared plugins among a site's enabled set: the settings worth reading. */\nexport function pluginSiteCspPluginsToRead(\n enabledPluginIds: readonly string[],\n declared: readonly PluginSiteCspDeclaration[] = PLUGIN_SITE_CSP_DECLARED,\n): string[] {\n const enabled = new Set(enabledPluginIds)\n return declared\n .map((declaration) => declaration.pluginId)\n .filter((pluginId) => enabled.has(pluginId))\n}\n"],"names":["PLUGIN_SITE_CSP_DECLARED","PLUGIN_SITE_CSP_DIRECTIVES","pluginSiteCspDeclarations","pluginSiteCspHostsFor","declaration","settings","switchField","requiredField","required","trim","variant","variantField","Object","prototype","hasOwnProperty","call","variants","pluginSiteCspHosts","enabledPluginIds","settingsByPlugin","declared","out","fromEntries","map","directive","enabled","Set","has","pluginId","hosts","host","includes","push","pluginSiteCspPluginsToRead","filter"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GAED,SAASA,wBAAwB,QAAQ,qCAAiC;AAE1E,2EAA2E,GAC3E,OAAO,MAAMC,6BAA6B;IACxC;IACA;IACA;IACA;IACA;CACD,CAAS;AAwBV,wEAAwE,GACxE,OAAO,SAASC;IACd,OAAOF;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASG,sBACdC,WAAqC,EACrCC,QAAoD;QAc7CD;IAZP,IAAI,CAACC,YAAYA,QAAQ,CAACD,YAAYE,WAAW,CAAC,KAAK,MAAM,OAAO,CAAC;IACrE,IAAIF,YAAYG,aAAa,EAAE;QAC7B,MAAMC,WAAWH,QAAQ,CAACD,YAAYG,aAAa,CAAC;QACpD,IAAI,OAAOC,aAAa,YAAY,CAACA,SAASC,IAAI,IAAI,OAAO,CAAC;IAChE;IACA,MAAMC,UAAUN,YAAYO,YAAY,GACpCN,QAAQ,CAACD,YAAYO,YAAY,CAAC,GAClC;IACJ,IAAI,OAAOD,YAAY,UAAU,OAAO,CAAC;IACzC,IAAI,CAACE,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACX,YAAYY,QAAQ,EAAEN,UAAU;QACxE,OAAO,CAAC;IACV;IACA,QAAON,gCAAAA,YAAYY,QAAQ,CAACN,QAAQ,YAA7BN,gCAAiC,CAAC;AAC3C;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASa,mBACdC,gBAAmC,EACnCC,gBAAsF,EACtFC,WAAgDpB,wBAAwB;IAExE,MAAMqB,MAAMT,OAAOU,WAAW,CAC5BrB,2BAA2BsB,GAAG,CAAC,CAACC,YAAc;YAACA;YAAW,EAAE;SAAa;IAE3E,MAAMC,UAAU,IAAIC,IAAIR;IACxB,KAAK,MAAMd,eAAegB,SAAU;QAClC,IAAI,CAACK,QAAQE,GAAG,CAACvB,YAAYwB,QAAQ,GAAG;QACxC,MAAMC,QAAQ1B,sBACZC,aACAe,gBAAgB,CAACf,YAAYwB,QAAQ,CAAC;QAExC,KAAK,MAAMJ,aAAavB,2BAA4B;gBAC/B4B;YAAnB,KAAK,MAAMC,SAAQD,mBAAAA,KAAK,CAACL,UAAU,YAAhBK,mBAAoB,EAAE,CAAE;gBACzC,IAAI,CAACR,GAAG,CAACG,UAAU,CAACO,QAAQ,CAACD,OAAOT,GAAG,CAACG,UAAU,CAACQ,IAAI,CAACF;YAC1D;QACF;IACF;IACA,OAAOT;AACT;AAEA,iFAAiF,GACjF,OAAO,SAASY,2BACdf,gBAAmC,EACnCE,WAAgDpB,wBAAwB;IAExE,MAAMyB,UAAU,IAAIC,IAAIR;IACxB,OAAOE,SACJG,GAAG,CAAC,CAACnB,cAAgBA,YAAYwB,QAAQ,EACzCM,MAAM,CAAC,CAACN,WAAaH,QAAQE,GAAG,CAACC;AACtC"}
@@ -69,6 +69,7 @@ export const AGLYN_HOST_SURFACE = Object.freeze({
69
69
  formatVariableValue: m7.formatVariableValue,
70
70
  functionGlobals: m7.functionGlobals,
71
71
  hasBindings: m7.hasBindings,
72
+ isVariableName: m7.isVariableName,
72
73
  resolveBindings: m7.resolveBindings,
73
74
  resolveNodesBindings: m7.resolveNodesBindings,
74
75
  COMPONENT_CATEGORY_ORDER: m8.COMPONENT_CATEGORY_ORDER,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/realm-host-aglyn.generated.ts"],"sourcesContent":["/**\n * GENERATED FILE — do not edit. Regenerate with:\n * node tools/scripts/generate-realm-host-exports.mjs\n *\n * The realm plugin surface of `@aglyn/aglyn` (AGL-3392): what a signed\n * bundle reads from core. Imported only by the lazily loaded host module.\n */\n/* eslint-disable */\n\nimport * as m0 from \"../aglyn\"\nimport * as m1 from \"../app-utils/binding-tokens\"\nimport * as m2 from \"../app-utils/enabled-plugins-context\"\nimport * as m3 from \"../app-utils/functions\"\nimport * as m4 from \"../app-utils/node-identity\"\nimport * as m5 from \"../app-utils/screen-link-context-value\"\nimport * as m6 from \"../app-utils/site-context\"\nimport * as m7 from \"../app-utils/variables\"\nimport * as m8 from \"../foundation/constants/components\"\nimport * as m9 from \"../foundation/constants/shared\"\nimport * as m10 from \"../foundation/definitions/components.types\"\nimport * as m11 from \"./feature-plugins\"\nimport * as m12 from \"../types/nodes\"\n\nexport const AGLYN_HOST_SURFACE: Readonly<Record<string, unknown>> = Object.freeze({\n Aglyn: m0.Aglyn,\n aglyn: m0.aglyn,\n canvas: m0.canvas,\n components: m0.components,\n emitter: m0.emitter,\n logger: m0.logger,\n plugins: m0.plugins,\n FUNCTION_TOKEN_PATTERN: m1.FUNCTION_TOKEN_PATTERN,\n MISSING_BINDING_LABEL: m1.MISSING_BINDING_LABEL,\n NAME_TOKEN_PATTERN: m1.NAME_TOKEN_PATTERN,\n VARIABLE_ID_TOKEN_PATTERN: m1.VARIABLE_ID_TOKEN_PATTERN,\n bindingTokenNeedsDeep: m1.bindingTokenNeedsDeep,\n displayBindingTokens: m1.displayBindingTokens,\n editableBindingTokens: m1.editableBindingTokens,\n formatFunctionIdToken: m1.formatFunctionIdToken,\n formatVariableIdToken: m1.formatVariableIdToken,\n keyById: m1.keyById,\n nodesReferenceBinding: m1.nodesReferenceBinding,\n normalizeBindingTokens: m1.normalizeBindingTokens,\n rewriteBindingTokensDeep: m1.rewriteBindingTokensDeep,\n textReferencesBinding: m1.textReferencesBinding,\n CATEGORY_REQUIRED_CAPABILITY: m2.CATEGORY_REQUIRED_CAPABILITY,\n EnabledPluginsContext: m2.EnabledPluginsContext,\n isCategoryCapabilityEnabled: m2.isCategoryCapabilityEnabled,\n isFromEnabledPlugin: m2.isFromEnabledPlugin,\n isSwitchedOffForRenderedSite: m2.isSwitchedOffForRenderedSite,\n useEnabledPlugins: m2.useEnabledPlugins,\n FUNCTION_BUILTIN_NAMES: m3.FUNCTION_BUILTIN_NAMES,\n FUNCTION_MAX_OPERATIONS: m3.FUNCTION_MAX_OPERATIONS,\n evaluateExpression: m3.evaluateExpression,\n evaluateHostFunction: m3.evaluateHostFunction,\n expressionIdentifiers: m3.expressionIdentifiers,\n expressionSyntaxError: m3.expressionSyntaxError,\n formatFunctionParameterOptions: m3.formatFunctionParameterOptions,\n functionReferencedNames: m3.functionReferencedNames,\n parseFunctionParameterOptions: m3.parseFunctionParameterOptions,\n NodeIdentityContext: m4.NodeIdentityContext,\n useNodeId: m4.useNodeId,\n ScreenLinkContext: m5.ScreenLinkContext,\n PREVIEW_REFUSED_STATUS: m6.PREVIEW_REFUSED_STATUS,\n PREVIEW_WRITE_BLOCKED_EVENT: m6.PREVIEW_WRITE_BLOCKED_EVENT,\n SiteContext: m6.SiteContext,\n isPreviewRefusal: m6.isPreviewRefusal,\n useSite: m6.useSite,\n useSiteFetch: m6.useSiteFetch,\n HOST_VARIABLE_TYPE_LABELS: m7.HOST_VARIABLE_TYPE_LABELS,\n VARIABLE_NAME_PATTERN: m7.VARIABLE_NAME_PATTERN,\n attachFunctionDefinitions: m7.attachFunctionDefinitions,\n formatVariableValue: m7.formatVariableValue,\n functionGlobals: m7.functionGlobals,\n hasBindings: m7.hasBindings,\n resolveBindings: m7.resolveBindings,\n resolveNodesBindings: m7.resolveNodesBindings,\n COMPONENT_CATEGORY_ORDER: m8.COMPONENT_CATEGORY_ORDER,\n ComponentCategory: m8.ComponentCategory,\n REUSABLE_COMPONENT_CATEGORY: m8.REUSABLE_COMPONENT_CATEGORY,\n REUSABLE_EMAIL_BLOCK_CATEGORY: m8.REUSABLE_EMAIL_BLOCK_CATEGORY,\n FEATURE_FLAG: m9.FEATURE_FLAG,\n RICH_TEXT_COMMANDS: m9.RICH_TEXT_COMMANDS,\n FieldComponentType: m10.FieldComponentType,\n FieldValidatorType: m10.FieldValidatorType,\n LinealDirectiveFlag: m10.LinealDirectiveFlag,\n CONSOLE_HOST_GATED_WIDGET_SLOTS: m11.CONSOLE_HOST_GATED_WIDGET_SLOTS,\n CONSOLE_SEARCH_LOAD_POINT: m11.CONSOLE_SEARCH_LOAD_POINT,\n CONSOLE_STAFF_WIDGET_SLOTS: m11.CONSOLE_STAFF_WIDGET_SLOTS,\n CONSOLE_WIDGET_SLOTS: m11.CONSOLE_WIDGET_SLOTS,\n MUI_BUNDLE_ID: m11.MUI_BUNDLE_ID,\n THEME_PRESETS_LOAD_POINT: m11.THEME_PRESETS_LOAD_POINT,\n defineUiFeatureBundle: m11.defineUiFeatureBundle,\n isConsoleHostGatedWidgetSlot: m11.isConsoleHostGatedWidgetSlot,\n isConsoleStaffWidgetSlot: m11.isConsoleStaffWidgetSlot,\n listConsoleExtensions: m11.listConsoleExtensions,\n listConsoleNavItems: m11.listConsoleNavItems,\n listConsoleOrgNavItems: m11.listConsoleOrgNavItems,\n listConsoleProviders: m11.listConsoleProviders,\n listConsolePublicPages: m11.listConsolePublicPages,\n listConsoleSearchSources: m11.listConsoleSearchSources,\n listConsoleStaffPages: m11.listConsoleStaffPages,\n listConsoleThemePresets: m11.listConsoleThemePresets,\n listConsoleWidgets: m11.listConsoleWidgets,\n normalizeConsolePublicPath: m11.normalizeConsolePublicPath,\n registerConsoleExtension: m11.registerConsoleExtension,\n resolveConsoleOrgPluginPage: m11.resolveConsoleOrgPluginPage,\n resolveConsolePluginPage: m11.resolveConsolePluginPage,\n resolveConsolePublicPage: m11.resolveConsolePublicPage,\n resolveConsoleStaffPage: m11.resolveConsoleStaffPage,\n unregisterConsoleExtension: m11.unregisterConsoleExtension,\n NodeType: m12.NodeType,\n})\n"],"names":["m0","m1","m2","m3","m4","m5","m6","m7","m8","m9","m10","m11","m12","AGLYN_HOST_SURFACE","Object","freeze","Aglyn","aglyn","canvas","components","emitter","logger","plugins","FUNCTION_TOKEN_PATTERN","MISSING_BINDING_LABEL","NAME_TOKEN_PATTERN","VARIABLE_ID_TOKEN_PATTERN","bindingTokenNeedsDeep","displayBindingTokens","editableBindingTokens","formatFunctionIdToken","formatVariableIdToken","keyById","nodesReferenceBinding","normalizeBindingTokens","rewriteBindingTokensDeep","textReferencesBinding","CATEGORY_REQUIRED_CAPABILITY","EnabledPluginsContext","isCategoryCapabilityEnabled","isFromEnabledPlugin","isSwitchedOffForRenderedSite","useEnabledPlugins","FUNCTION_BUILTIN_NAMES","FUNCTION_MAX_OPERATIONS","evaluateExpression","evaluateHostFunction","expressionIdentifiers","expressionSyntaxError","formatFunctionParameterOptions","functionReferencedNames","parseFunctionParameterOptions","NodeIdentityContext","useNodeId","ScreenLinkContext","PREVIEW_REFUSED_STATUS","PREVIEW_WRITE_BLOCKED_EVENT","SiteContext","isPreviewRefusal","useSite","useSiteFetch","HOST_VARIABLE_TYPE_LABELS","VARIABLE_NAME_PATTERN","attachFunctionDefinitions","formatVariableValue","functionGlobals","hasBindings","resolveBindings","resolveNodesBindings","COMPONENT_CATEGORY_ORDER","ComponentCategory","REUSABLE_COMPONENT_CATEGORY","REUSABLE_EMAIL_BLOCK_CATEGORY","FEATURE_FLAG","RICH_TEXT_COMMANDS","FieldComponentType","FieldValidatorType","LinealDirectiveFlag","CONSOLE_HOST_GATED_WIDGET_SLOTS","CONSOLE_SEARCH_LOAD_POINT","CONSOLE_STAFF_WIDGET_SLOTS","CONSOLE_WIDGET_SLOTS","MUI_BUNDLE_ID","THEME_PRESETS_LOAD_POINT","defineUiFeatureBundle","isConsoleHostGatedWidgetSlot","isConsoleStaffWidgetSlot","listConsoleExtensions","listConsoleNavItems","listConsoleOrgNavItems","listConsoleProviders","listConsolePublicPages","listConsoleSearchSources","listConsoleStaffPages","listConsoleThemePresets","listConsoleWidgets","normalizeConsolePublicPath","registerConsoleExtension","resolveConsoleOrgPluginPage","resolveConsolePluginPage","resolveConsolePublicPage","resolveConsoleStaffPage","unregisterConsoleExtension","NodeType"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/realm-host-aglyn.generated.ts"],"sourcesContent":["/**\n * GENERATED FILE — do not edit. Regenerate with:\n * node tools/scripts/generate-realm-host-exports.mjs\n *\n * The realm plugin surface of `@aglyn/aglyn` (AGL-3392): what a signed\n * bundle reads from core. Imported only by the lazily loaded host module.\n */\n/* eslint-disable */\n\nimport * as m0 from \"../aglyn\"\nimport * as m1 from \"../app-utils/binding-tokens\"\nimport * as m2 from \"../app-utils/enabled-plugins-context\"\nimport * as m3 from \"../app-utils/functions\"\nimport * as m4 from \"../app-utils/node-identity\"\nimport * as m5 from \"../app-utils/screen-link-context-value\"\nimport * as m6 from \"../app-utils/site-context\"\nimport * as m7 from \"../app-utils/variables\"\nimport * as m8 from \"../foundation/constants/components\"\nimport * as m9 from \"../foundation/constants/shared\"\nimport * as m10 from \"../foundation/definitions/components.types\"\nimport * as m11 from \"./feature-plugins\"\nimport * as m12 from \"../types/nodes\"\n\nexport const AGLYN_HOST_SURFACE: Readonly<Record<string, unknown>> = Object.freeze({\n Aglyn: m0.Aglyn,\n aglyn: m0.aglyn,\n canvas: m0.canvas,\n components: m0.components,\n emitter: m0.emitter,\n logger: m0.logger,\n plugins: m0.plugins,\n FUNCTION_TOKEN_PATTERN: m1.FUNCTION_TOKEN_PATTERN,\n MISSING_BINDING_LABEL: m1.MISSING_BINDING_LABEL,\n NAME_TOKEN_PATTERN: m1.NAME_TOKEN_PATTERN,\n VARIABLE_ID_TOKEN_PATTERN: m1.VARIABLE_ID_TOKEN_PATTERN,\n bindingTokenNeedsDeep: m1.bindingTokenNeedsDeep,\n displayBindingTokens: m1.displayBindingTokens,\n editableBindingTokens: m1.editableBindingTokens,\n formatFunctionIdToken: m1.formatFunctionIdToken,\n formatVariableIdToken: m1.formatVariableIdToken,\n keyById: m1.keyById,\n nodesReferenceBinding: m1.nodesReferenceBinding,\n normalizeBindingTokens: m1.normalizeBindingTokens,\n rewriteBindingTokensDeep: m1.rewriteBindingTokensDeep,\n textReferencesBinding: m1.textReferencesBinding,\n CATEGORY_REQUIRED_CAPABILITY: m2.CATEGORY_REQUIRED_CAPABILITY,\n EnabledPluginsContext: m2.EnabledPluginsContext,\n isCategoryCapabilityEnabled: m2.isCategoryCapabilityEnabled,\n isFromEnabledPlugin: m2.isFromEnabledPlugin,\n isSwitchedOffForRenderedSite: m2.isSwitchedOffForRenderedSite,\n useEnabledPlugins: m2.useEnabledPlugins,\n FUNCTION_BUILTIN_NAMES: m3.FUNCTION_BUILTIN_NAMES,\n FUNCTION_MAX_OPERATIONS: m3.FUNCTION_MAX_OPERATIONS,\n evaluateExpression: m3.evaluateExpression,\n evaluateHostFunction: m3.evaluateHostFunction,\n expressionIdentifiers: m3.expressionIdentifiers,\n expressionSyntaxError: m3.expressionSyntaxError,\n formatFunctionParameterOptions: m3.formatFunctionParameterOptions,\n functionReferencedNames: m3.functionReferencedNames,\n parseFunctionParameterOptions: m3.parseFunctionParameterOptions,\n NodeIdentityContext: m4.NodeIdentityContext,\n useNodeId: m4.useNodeId,\n ScreenLinkContext: m5.ScreenLinkContext,\n PREVIEW_REFUSED_STATUS: m6.PREVIEW_REFUSED_STATUS,\n PREVIEW_WRITE_BLOCKED_EVENT: m6.PREVIEW_WRITE_BLOCKED_EVENT,\n SiteContext: m6.SiteContext,\n isPreviewRefusal: m6.isPreviewRefusal,\n useSite: m6.useSite,\n useSiteFetch: m6.useSiteFetch,\n HOST_VARIABLE_TYPE_LABELS: m7.HOST_VARIABLE_TYPE_LABELS,\n VARIABLE_NAME_PATTERN: m7.VARIABLE_NAME_PATTERN,\n attachFunctionDefinitions: m7.attachFunctionDefinitions,\n formatVariableValue: m7.formatVariableValue,\n functionGlobals: m7.functionGlobals,\n hasBindings: m7.hasBindings,\n isVariableName: m7.isVariableName,\n resolveBindings: m7.resolveBindings,\n resolveNodesBindings: m7.resolveNodesBindings,\n COMPONENT_CATEGORY_ORDER: m8.COMPONENT_CATEGORY_ORDER,\n ComponentCategory: m8.ComponentCategory,\n REUSABLE_COMPONENT_CATEGORY: m8.REUSABLE_COMPONENT_CATEGORY,\n REUSABLE_EMAIL_BLOCK_CATEGORY: m8.REUSABLE_EMAIL_BLOCK_CATEGORY,\n FEATURE_FLAG: m9.FEATURE_FLAG,\n RICH_TEXT_COMMANDS: m9.RICH_TEXT_COMMANDS,\n FieldComponentType: m10.FieldComponentType,\n FieldValidatorType: m10.FieldValidatorType,\n LinealDirectiveFlag: m10.LinealDirectiveFlag,\n CONSOLE_HOST_GATED_WIDGET_SLOTS: m11.CONSOLE_HOST_GATED_WIDGET_SLOTS,\n CONSOLE_SEARCH_LOAD_POINT: m11.CONSOLE_SEARCH_LOAD_POINT,\n CONSOLE_STAFF_WIDGET_SLOTS: m11.CONSOLE_STAFF_WIDGET_SLOTS,\n CONSOLE_WIDGET_SLOTS: m11.CONSOLE_WIDGET_SLOTS,\n MUI_BUNDLE_ID: m11.MUI_BUNDLE_ID,\n THEME_PRESETS_LOAD_POINT: m11.THEME_PRESETS_LOAD_POINT,\n defineUiFeatureBundle: m11.defineUiFeatureBundle,\n isConsoleHostGatedWidgetSlot: m11.isConsoleHostGatedWidgetSlot,\n isConsoleStaffWidgetSlot: m11.isConsoleStaffWidgetSlot,\n listConsoleExtensions: m11.listConsoleExtensions,\n listConsoleNavItems: m11.listConsoleNavItems,\n listConsoleOrgNavItems: m11.listConsoleOrgNavItems,\n listConsoleProviders: m11.listConsoleProviders,\n listConsolePublicPages: m11.listConsolePublicPages,\n listConsoleSearchSources: m11.listConsoleSearchSources,\n listConsoleStaffPages: m11.listConsoleStaffPages,\n listConsoleThemePresets: m11.listConsoleThemePresets,\n listConsoleWidgets: m11.listConsoleWidgets,\n normalizeConsolePublicPath: m11.normalizeConsolePublicPath,\n registerConsoleExtension: m11.registerConsoleExtension,\n resolveConsoleOrgPluginPage: m11.resolveConsoleOrgPluginPage,\n resolveConsolePluginPage: m11.resolveConsolePluginPage,\n resolveConsolePublicPage: m11.resolveConsolePublicPage,\n resolveConsoleStaffPage: m11.resolveConsoleStaffPage,\n unregisterConsoleExtension: m11.unregisterConsoleExtension,\n NodeType: m12.NodeType,\n})\n"],"names":["m0","m1","m2","m3","m4","m5","m6","m7","m8","m9","m10","m11","m12","AGLYN_HOST_SURFACE","Object","freeze","Aglyn","aglyn","canvas","components","emitter","logger","plugins","FUNCTION_TOKEN_PATTERN","MISSING_BINDING_LABEL","NAME_TOKEN_PATTERN","VARIABLE_ID_TOKEN_PATTERN","bindingTokenNeedsDeep","displayBindingTokens","editableBindingTokens","formatFunctionIdToken","formatVariableIdToken","keyById","nodesReferenceBinding","normalizeBindingTokens","rewriteBindingTokensDeep","textReferencesBinding","CATEGORY_REQUIRED_CAPABILITY","EnabledPluginsContext","isCategoryCapabilityEnabled","isFromEnabledPlugin","isSwitchedOffForRenderedSite","useEnabledPlugins","FUNCTION_BUILTIN_NAMES","FUNCTION_MAX_OPERATIONS","evaluateExpression","evaluateHostFunction","expressionIdentifiers","expressionSyntaxError","formatFunctionParameterOptions","functionReferencedNames","parseFunctionParameterOptions","NodeIdentityContext","useNodeId","ScreenLinkContext","PREVIEW_REFUSED_STATUS","PREVIEW_WRITE_BLOCKED_EVENT","SiteContext","isPreviewRefusal","useSite","useSiteFetch","HOST_VARIABLE_TYPE_LABELS","VARIABLE_NAME_PATTERN","attachFunctionDefinitions","formatVariableValue","functionGlobals","hasBindings","isVariableName","resolveBindings","resolveNodesBindings","COMPONENT_CATEGORY_ORDER","ComponentCategory","REUSABLE_COMPONENT_CATEGORY","REUSABLE_EMAIL_BLOCK_CATEGORY","FEATURE_FLAG","RICH_TEXT_COMMANDS","FieldComponentType","FieldValidatorType","LinealDirectiveFlag","CONSOLE_HOST_GATED_WIDGET_SLOTS","CONSOLE_SEARCH_LOAD_POINT","CONSOLE_STAFF_WIDGET_SLOTS","CONSOLE_WIDGET_SLOTS","MUI_BUNDLE_ID","THEME_PRESETS_LOAD_POINT","defineUiFeatureBundle","isConsoleHostGatedWidgetSlot","isConsoleStaffWidgetSlot","listConsoleExtensions","listConsoleNavItems","listConsoleOrgNavItems","listConsoleProviders","listConsolePublicPages","listConsoleSearchSources","listConsoleStaffPages","listConsoleThemePresets","listConsoleWidgets","normalizeConsolePublicPath","registerConsoleExtension","resolveConsoleOrgPluginPage","resolveConsolePluginPage","resolveConsolePublicPage","resolveConsoleStaffPage","unregisterConsoleExtension","NodeType"],"mappings":"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"}
@@ -86,6 +86,19 @@ export declare function registerSitePageResolver(fn: SitePageResolver): void;
86
86
  export declare function registerSitePageEnricher(fn: SitePageEnricher): void;
87
87
  export declare function resolveSiteRedirect(context: SitePageContext): Promise<SiteRedirectAnswer | undefined>;
88
88
  export declare function resolveSitePage(context: SitePageContext): Promise<SitePageAnswer | undefined>;
89
+ /**
90
+ * The page prop an enricher names the vendors it loads on the ANALYTICS
91
+ * grant with (AGL-3698): a feature that loads a third party with the page
92
+ * only for a visitor whose recorded consent grants analytics — a live chat
93
+ * set to load with the page — names it here, so the consent banner the
94
+ * visitor answers names it too. A list of display names, `['Tidio chat']`.
95
+ *
96
+ * The one key enrichers SHARE rather than own: every contribution is joined,
97
+ * not overwritten, so two plugins each naming a vendor both reach the banner.
98
+ */
99
+ export declare const CONSENT_ANALYTICS_VENDORS_PROP = "consentAnalyticsVendors";
100
+ /** The vendor names a page's props carry under {@link CONSENT_ANALYTICS_VENDORS_PROP}. */
101
+ export declare function consentAnalyticsVendorsOf(props: unknown): string[];
89
102
  export interface SitePageEnrichment {
90
103
  /** The merged contributions, spread into the page props as before. */
91
104
  props: Record<string, unknown>;
@@ -57,6 +57,27 @@ export async function resolveSitePage(context) {
57
57
  if (typeof value === 'object') return Object.keys(value).length === 0;
58
58
  return false;
59
59
  }
60
+ /**
61
+ * The page prop an enricher names the vendors it loads on the ANALYTICS
62
+ * grant with (AGL-3698): a feature that loads a third party with the page
63
+ * only for a visitor whose recorded consent grants analytics — a live chat
64
+ * set to load with the page — names it here, so the consent banner the
65
+ * visitor answers names it too. A list of display names, `['Tidio chat']`.
66
+ *
67
+ * The one key enrichers SHARE rather than own: every contribution is joined,
68
+ * not overwritten, so two plugins each naming a vendor both reach the banner.
69
+ */ export const CONSENT_ANALYTICS_VENDORS_PROP = 'consentAnalyticsVendors';
70
+ /** The vendor names a page's props carry under {@link CONSENT_ANALYTICS_VENDORS_PROP}. */ export function consentAnalyticsVendorsOf(props) {
71
+ const value = props == null ? void 0 : props[CONSENT_ANALYTICS_VENDORS_PROP];
72
+ if (!Array.isArray(value)) return [];
73
+ const out = [];
74
+ for (const entry of value){
75
+ if (typeof entry !== 'string') continue;
76
+ const name = entry.trim().slice(0, 60);
77
+ if (name && !out.includes(name)) out.push(name);
78
+ }
79
+ return out;
80
+ }
60
81
  export async function runSitePageEnrichers(context) {
61
82
  const merged = {};
62
83
  const contributors = new Set();
@@ -85,7 +106,17 @@ export async function runSitePageEnrichers(context) {
85
106
  try {
86
107
  if (outcome.status === 'rejected') throw outcome.reason;
87
108
  const result = outcome.value;
109
+ const vendors = [
110
+ ...consentAnalyticsVendorsOf(merged),
111
+ ...consentAnalyticsVendorsOf(result)
112
+ ];
88
113
  Object.assign(merged, result);
114
+ // Joined, not overwritten — see CONSENT_ANALYTICS_VENDORS_PROP.
115
+ if (vendors.length) {
116
+ merged[CONSENT_ANALYTICS_VENDORS_PROP] = consentAnalyticsVendorsOf({
117
+ [CONSENT_ANALYTICS_VENDORS_PROP]: vendors
118
+ });
119
+ }
89
120
  // "Contributed" means produced something with content, not merely
90
121
  // returned a key. Every marketing enricher returns its full key set on
91
122
  // every page — `announcementBar: null`, `experiments: []` — so keying
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/site-page-hooks.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// The leaf module, NOT `api-plugins` — that one is reachable only through the\n// `/server` entry, and importing it from this shared barrel formed a cycle\n// that left the binding missing at runtime (AGL-1289).\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/**\n * Tenant page-composition extension points (AGL-418): plugins hook the\n * site loader (`load-page-data`) from their `/server` entries instead of\n * the app importing plugin domain code. Three kinds, run in this order:\n *\n * 1. REDIRECT RESOLVERS — may answer a request with a redirect before any\n * route resolution (plugins-redirects' rule engine, AGL-155).\n * 2. PAGE RESOLVERS — may fully resolve a path into page props\n * (plugins-commerce's PDP/PLP template composition for /products/* and\n * /collections/*, AGL-299). First non-undefined answer wins.\n * 3. ENRICHERS — contribute extra props onto an already-resolved screen\n * page (plugins-marketing's overlays/experiments/client-automations).\n * Outputs are shallow-merged into the page props in registration order.\n *\n * Handlers run server-side (node runtime) and may use firebase-admin.\n * Errors in enrichers are isolated (a broken plugin never 500s the site);\n * resolver errors propagate because their output IS the page.\n */\n\nexport interface SitePageContext {\n hostId: string\n /** The resolved host doc. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n host: any\n /** The org billing doc (entitlement source); undefined pre-billing. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n org?: any\n /** Request path, '/a/b' form ('/' for the site root). */\n path: string\n slugSegments: string[]\n /**\n * This surface has NO single request path, so `path` above is a\n * placeholder and nothing may be targeted by it (AGL-2511).\n *\n * One surface sets it: the host's designed 404 body. The `not-found`\n * boundary fetches it by host and the response is cached per HOST — one\n * compose a minute however many missing URLs are hit, which is the whole\n * reason a scan of ten thousand dead URLs is cheap. Enriching it per path\n * would key that cache per URL and make the same scan ten thousand\n * composes.\n *\n * An enricher that reads `path` must, when this is set, contribute only\n * what is path-INDEPENDENT and drop the rest — never substitute a plausible\n * path. A site-wide announcement bar is path-independent and shows; an\n * overlay pinned to `/pricing` cannot be evaluated and does not.\n */\n pathUnknown?: boolean\n screenId?: string\n /** The resolved screen doc (enrichers only). */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n screen?: any\n /**\n * The composed node tree for the page — layout applied, versions and\n * references denormalized (enrichers only, AGL-659).\n *\n * An enricher that wants to seed a block's data server-side has to know\n * which blocks are actually on the page and how each one is configured;\n * the screen doc alone does not say, because composition pulls in layout\n * and referenced nodes.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n nodes?: any\n}\n\nexport interface SiteRedirectAnswer {\n destination: string\n statusCode: number\n}\nexport type SiteRedirectResolver = (\n context: SitePageContext,\n) => Promise<SiteRedirectAnswer | undefined> | SiteRedirectAnswer | undefined\n\n/** A fully-composed page (the loader returns it verbatim as `props`). */\nexport type SitePageAnswer = Record<string, unknown>\nexport type SitePageResolver = (\n context: SitePageContext,\n) => Promise<SitePageAnswer | undefined>\n\nexport type SitePageEnricher = (\n context: SitePageContext,\n) => Promise<Record<string, unknown> | undefined>\n\nconst redirectResolvers: SiteRedirectResolver[] = []\nconst pageResolvers: SitePageResolver[] = []\nconst enrichers: SitePageEnricher[] = []\n/**\n * Which plugin registered which enricher (AGL-1289). Recorded from the loader's\n * existing \"currently registering\" marker — the same mechanism that gives\n * `registerPluginApiRoute` its path→plugin ownership — so plugins need no new\n * API and nothing has to be kept in sync by hand.\n */\nconst enricherOwners = new WeakMap<SitePageEnricher, string>()\n\nexport function registerSiteRedirectResolver(fn: SiteRedirectResolver): void {\n redirectResolvers.push(fn)\n}\nexport function registerSitePageResolver(fn: SitePageResolver): void {\n pageResolvers.push(fn)\n}\nexport function registerSitePageEnricher(fn: SitePageEnricher): void {\n enrichers.push(fn)\n const owner = getRegisteringPluginId()\n if (owner) enricherOwners.set(fn, owner)\n}\n\nexport async function resolveSiteRedirect(\n context: SitePageContext,\n): Promise<SiteRedirectAnswer | undefined> {\n for (const resolver of redirectResolvers) {\n const answer = await resolver(context)\n if (answer) return answer\n }\n return undefined\n}\n\nexport async function resolveSitePage(\n context: SitePageContext,\n): Promise<SitePageAnswer | undefined> {\n for (const resolver of pageResolvers) {\n const answer = await resolver(context)\n if (answer) return answer\n }\n return undefined\n}\n\n/** Null/undefined, an empty array or an empty object — nothing to render. */\nfunction isEmptyContribution(value: unknown): boolean {\n if (value == null) return true\n if (Array.isArray(value)) return value.length === 0\n if (typeof value === 'object') return Object.keys(value).length === 0\n return false\n}\n\nexport interface SitePageEnrichment {\n /** The merged contributions, spread into the page props as before. */\n props: Record<string, unknown>\n /** Plugins that returned something non-empty for THIS page. */\n contributors: string[]\n /**\n * True when an enricher produced work but could not be attributed to a\n * plugin — registered outside a plugin's register fn, so the loader's\n * marker was unset. Callers narrowing on `contributors` must treat this as\n * \"cannot narrow\": an unattributed contribution belongs to somebody.\n */\n unattributed: boolean\n}\n\nexport async function runSitePageEnrichers(\n context: SitePageContext,\n): Promise<SitePageEnrichment> {\n const merged: Record<string, unknown> = {}\n const contributors = new Set<string>()\n let unattributed = false\n\n // CONCURRENT, MERGED IN REGISTRATION ORDER (2026-08-26).\n //\n // This was a sequential `for … await`, so every enricher waited on the one\n // before it for no reason: each takes the same immutable `context`, returns\n // its own prop slice, and shares nothing. Production timing put the phase at\n // a flat ~85–120 ms on EVERY render — including pages whose entire loader\n // was 137 ms — because it is a fixed number of round trips, not work that\n // scales with the page.\n //\n // `allSettled` rather than `all`: a broken plugin must drop its slice, never\n // the page, which is the isolation the per-enricher try/catch gave and the\n // plugin docs promise. Results are merged in ARRAY order, which `allSettled`\n // preserves — so two enrichers contributing the same key resolve exactly as\n // they did when the loop was sequential, and `contributors` keeps its\n // insertion order.\n const settled = await Promise.allSettled(\n enrichers.map(async (enricher) => (await enricher(context)) ?? {}),\n )\n\n for (const [index, outcome] of settled.entries()) {\n const enricher = enrichers[index]\n try {\n if (outcome.status === 'rejected') throw outcome.reason\n const result = outcome.value\n Object.assign(merged, result)\n // \"Contributed\" means produced something with content, not merely\n // returned a key. Every marketing enricher returns its full key set on\n // every page — `announcementBar: null`, `experiments: []` — so keying\n // off presence would mark it a contributor everywhere and the\n // attribution would be worth nothing.\n if (Object.values(result).some((value) => !isEmptyContribution(value))) {\n const owner = enricherOwners.get(enricher)\n if (owner) contributors.add(owner)\n else unattributed = true\n }\n } catch (error) {\n // A broken plugin must not take the site down — the page renders\n // without that plugin's contribution.\n console.error('site page enricher failed', error)\n }\n }\n return { props: merged, contributors: [...contributors], unattributed }\n}\n"],"names":["getRegisteringPluginId","redirectResolvers","pageResolvers","enrichers","enricherOwners","WeakMap","registerSiteRedirectResolver","fn","push","registerSitePageResolver","registerSitePageEnricher","owner","set","resolveSiteRedirect","context","resolver","answer","undefined","resolveSitePage","isEmptyContribution","value","Array","isArray","length","Object","keys","runSitePageEnrichers","merged","contributors","Set","unattributed","settled","Promise","allSettled","map","enricher","index","outcome","entries","status","reason","result","assign","values","some","get","add","error","console","props"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,2EAA2E;AAC3E,uDAAuD;AACvD,SAASA,sBAAsB,QAAQ,qCAAiC;AAoFxE,MAAMC,oBAA4C,EAAE;AACpD,MAAMC,gBAAoC,EAAE;AAC5C,MAAMC,YAAgC,EAAE;AACxC;;;;;CAKC,GACD,MAAMC,iBAAiB,IAAIC;AAE3B,OAAO,SAASC,6BAA6BC,EAAwB;IACnEN,kBAAkBO,IAAI,CAACD;AACzB;AACA,OAAO,SAASE,yBAAyBF,EAAoB;IAC3DL,cAAcM,IAAI,CAACD;AACrB;AACA,OAAO,SAASG,yBAAyBH,EAAoB;IAC3DJ,UAAUK,IAAI,CAACD;IACf,MAAMI,QAAQX;IACd,IAAIW,OAAOP,eAAeQ,GAAG,CAACL,IAAII;AACpC;AAEA,OAAO,eAAeE,oBACpBC,OAAwB;IAExB,KAAK,MAAMC,YAAYd,kBAAmB;QACxC,MAAMe,SAAS,MAAMD,SAASD;QAC9B,IAAIE,QAAQ,OAAOA;IACrB;IACA,OAAOC;AACT;AAEA,OAAO,eAAeC,gBACpBJ,OAAwB;IAExB,KAAK,MAAMC,YAAYb,cAAe;QACpC,MAAMc,SAAS,MAAMD,SAASD;QAC9B,IAAIE,QAAQ,OAAOA;IACrB;IACA,OAAOC;AACT;AAEA,2EAA2E,GAC3E,SAASE,oBAAoBC,KAAc;IACzC,IAAIA,SAAS,MAAM,OAAO;IAC1B,IAAIC,MAAMC,OAAO,CAACF,QAAQ,OAAOA,MAAMG,MAAM,KAAK;IAClD,IAAI,OAAOH,UAAU,UAAU,OAAOI,OAAOC,IAAI,CAACL,OAAOG,MAAM,KAAK;IACpE,OAAO;AACT;AAgBA,OAAO,eAAeG,qBACpBZ,OAAwB;IAExB,MAAMa,SAAkC,CAAC;IACzC,MAAMC,eAAe,IAAIC;IACzB,IAAIC,eAAe;IAEnB,yDAAyD;IACzD,EAAE;IACF,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,0EAA0E;IAC1E,wBAAwB;IACxB,EAAE;IACF,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,sEAAsE;IACtE,mBAAmB;IACnB,MAAMC,UAAU,MAAMC,QAAQC,UAAU,CACtC9B,UAAU+B,GAAG,CAAC,OAAOC;YAAc;gBAAA,OAAA,MAAMA,SAASrB,oBAAf,OAA4B,CAAC;;IAGlE,KAAK,MAAM,CAACsB,OAAOC,QAAQ,IAAIN,QAAQO,OAAO,GAAI;QAChD,MAAMH,WAAWhC,SAAS,CAACiC,MAAM;QACjC,IAAI;YACF,IAAIC,QAAQE,MAAM,KAAK,YAAY,MAAMF,QAAQG,MAAM;YACvD,MAAMC,SAASJ,QAAQjB,KAAK;YAC5BI,OAAOkB,MAAM,CAACf,QAAQc;YACtB,kEAAkE;YAClE,uEAAuE;YACvE,sEAAsE;YACtE,8DAA8D;YAC9D,sCAAsC;YACtC,IAAIjB,OAAOmB,MAAM,CAACF,QAAQG,IAAI,CAAC,CAACxB,QAAU,CAACD,oBAAoBC,SAAS;gBACtE,MAAMT,QAAQP,eAAeyC,GAAG,CAACV;gBACjC,IAAIxB,OAAOiB,aAAakB,GAAG,CAACnC;qBACvBmB,eAAe;YACtB;QACF,EAAE,OAAOiB,OAAO;YACd,iEAAiE;YACjE,sCAAsC;YACtCC,QAAQD,KAAK,CAAC,6BAA6BA;QAC7C;IACF;IACA,OAAO;QAAEE,OAAOtB;QAAQC,cAAc;eAAIA;SAAa;QAAEE;IAAa;AACxE"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/site-page-hooks.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// The leaf module, NOT `api-plugins` — that one is reachable only through the\n// `/server` entry, and importing it from this shared barrel formed a cycle\n// that left the binding missing at runtime (AGL-1289).\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/**\n * Tenant page-composition extension points (AGL-418): plugins hook the\n * site loader (`load-page-data`) from their `/server` entries instead of\n * the app importing plugin domain code. Three kinds, run in this order:\n *\n * 1. REDIRECT RESOLVERS — may answer a request with a redirect before any\n * route resolution (plugins-redirects' rule engine, AGL-155).\n * 2. PAGE RESOLVERS — may fully resolve a path into page props\n * (plugins-commerce's PDP/PLP template composition for /products/* and\n * /collections/*, AGL-299). First non-undefined answer wins.\n * 3. ENRICHERS — contribute extra props onto an already-resolved screen\n * page (plugins-marketing's overlays/experiments/client-automations).\n * Outputs are shallow-merged into the page props in registration order.\n *\n * Handlers run server-side (node runtime) and may use firebase-admin.\n * Errors in enrichers are isolated (a broken plugin never 500s the site);\n * resolver errors propagate because their output IS the page.\n */\n\nexport interface SitePageContext {\n hostId: string\n /** The resolved host doc. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n host: any\n /** The org billing doc (entitlement source); undefined pre-billing. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n org?: any\n /** Request path, '/a/b' form ('/' for the site root). */\n path: string\n slugSegments: string[]\n /**\n * This surface has NO single request path, so `path` above is a\n * placeholder and nothing may be targeted by it (AGL-2511).\n *\n * One surface sets it: the host's designed 404 body. The `not-found`\n * boundary fetches it by host and the response is cached per HOST — one\n * compose a minute however many missing URLs are hit, which is the whole\n * reason a scan of ten thousand dead URLs is cheap. Enriching it per path\n * would key that cache per URL and make the same scan ten thousand\n * composes.\n *\n * An enricher that reads `path` must, when this is set, contribute only\n * what is path-INDEPENDENT and drop the rest — never substitute a plausible\n * path. A site-wide announcement bar is path-independent and shows; an\n * overlay pinned to `/pricing` cannot be evaluated and does not.\n */\n pathUnknown?: boolean\n screenId?: string\n /** The resolved screen doc (enrichers only). */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n screen?: any\n /**\n * The composed node tree for the page — layout applied, versions and\n * references denormalized (enrichers only, AGL-659).\n *\n * An enricher that wants to seed a block's data server-side has to know\n * which blocks are actually on the page and how each one is configured;\n * the screen doc alone does not say, because composition pulls in layout\n * and referenced nodes.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n nodes?: any\n}\n\nexport interface SiteRedirectAnswer {\n destination: string\n statusCode: number\n}\nexport type SiteRedirectResolver = (\n context: SitePageContext,\n) => Promise<SiteRedirectAnswer | undefined> | SiteRedirectAnswer | undefined\n\n/** A fully-composed page (the loader returns it verbatim as `props`). */\nexport type SitePageAnswer = Record<string, unknown>\nexport type SitePageResolver = (\n context: SitePageContext,\n) => Promise<SitePageAnswer | undefined>\n\nexport type SitePageEnricher = (\n context: SitePageContext,\n) => Promise<Record<string, unknown> | undefined>\n\nconst redirectResolvers: SiteRedirectResolver[] = []\nconst pageResolvers: SitePageResolver[] = []\nconst enrichers: SitePageEnricher[] = []\n/**\n * Which plugin registered which enricher (AGL-1289). Recorded from the loader's\n * existing \"currently registering\" marker — the same mechanism that gives\n * `registerPluginApiRoute` its path→plugin ownership — so plugins need no new\n * API and nothing has to be kept in sync by hand.\n */\nconst enricherOwners = new WeakMap<SitePageEnricher, string>()\n\nexport function registerSiteRedirectResolver(fn: SiteRedirectResolver): void {\n redirectResolvers.push(fn)\n}\nexport function registerSitePageResolver(fn: SitePageResolver): void {\n pageResolvers.push(fn)\n}\nexport function registerSitePageEnricher(fn: SitePageEnricher): void {\n enrichers.push(fn)\n const owner = getRegisteringPluginId()\n if (owner) enricherOwners.set(fn, owner)\n}\n\nexport async function resolveSiteRedirect(\n context: SitePageContext,\n): Promise<SiteRedirectAnswer | undefined> {\n for (const resolver of redirectResolvers) {\n const answer = await resolver(context)\n if (answer) return answer\n }\n return undefined\n}\n\nexport async function resolveSitePage(\n context: SitePageContext,\n): Promise<SitePageAnswer | undefined> {\n for (const resolver of pageResolvers) {\n const answer = await resolver(context)\n if (answer) return answer\n }\n return undefined\n}\n\n/** Null/undefined, an empty array or an empty object — nothing to render. */\nfunction isEmptyContribution(value: unknown): boolean {\n if (value == null) return true\n if (Array.isArray(value)) return value.length === 0\n if (typeof value === 'object') return Object.keys(value).length === 0\n return false\n}\n\n/**\n * The page prop an enricher names the vendors it loads on the ANALYTICS\n * grant with (AGL-3698): a feature that loads a third party with the page\n * only for a visitor whose recorded consent grants analytics — a live chat\n * set to load with the page — names it here, so the consent banner the\n * visitor answers names it too. A list of display names, `['Tidio chat']`.\n *\n * The one key enrichers SHARE rather than own: every contribution is joined,\n * not overwritten, so two plugins each naming a vendor both reach the banner.\n */\nexport const CONSENT_ANALYTICS_VENDORS_PROP = 'consentAnalyticsVendors'\n\n/** The vendor names a page's props carry under {@link CONSENT_ANALYTICS_VENDORS_PROP}. */\nexport function consentAnalyticsVendorsOf(props: unknown): string[] {\n const value = (props as Record<string, unknown> | null | undefined)?.[\n CONSENT_ANALYTICS_VENDORS_PROP\n ]\n if (!Array.isArray(value)) return []\n const out: string[] = []\n for (const entry of value) {\n if (typeof entry !== 'string') continue\n const name = entry.trim().slice(0, 60)\n if (name && !out.includes(name)) out.push(name)\n }\n return out\n}\n\nexport interface SitePageEnrichment {\n /** The merged contributions, spread into the page props as before. */\n props: Record<string, unknown>\n /** Plugins that returned something non-empty for THIS page. */\n contributors: string[]\n /**\n * True when an enricher produced work but could not be attributed to a\n * plugin — registered outside a plugin's register fn, so the loader's\n * marker was unset. Callers narrowing on `contributors` must treat this as\n * \"cannot narrow\": an unattributed contribution belongs to somebody.\n */\n unattributed: boolean\n}\n\nexport async function runSitePageEnrichers(\n context: SitePageContext,\n): Promise<SitePageEnrichment> {\n const merged: Record<string, unknown> = {}\n const contributors = new Set<string>()\n let unattributed = false\n\n // CONCURRENT, MERGED IN REGISTRATION ORDER (2026-08-26).\n //\n // This was a sequential `for … await`, so every enricher waited on the one\n // before it for no reason: each takes the same immutable `context`, returns\n // its own prop slice, and shares nothing. Production timing put the phase at\n // a flat ~85–120 ms on EVERY render — including pages whose entire loader\n // was 137 ms — because it is a fixed number of round trips, not work that\n // scales with the page.\n //\n // `allSettled` rather than `all`: a broken plugin must drop its slice, never\n // the page, which is the isolation the per-enricher try/catch gave and the\n // plugin docs promise. Results are merged in ARRAY order, which `allSettled`\n // preserves — so two enrichers contributing the same key resolve exactly as\n // they did when the loop was sequential, and `contributors` keeps its\n // insertion order.\n const settled = await Promise.allSettled(\n enrichers.map(async (enricher) => (await enricher(context)) ?? {}),\n )\n\n for (const [index, outcome] of settled.entries()) {\n const enricher = enrichers[index]\n try {\n if (outcome.status === 'rejected') throw outcome.reason\n const result = outcome.value\n const vendors = [\n ...consentAnalyticsVendorsOf(merged),\n ...consentAnalyticsVendorsOf(result),\n ]\n Object.assign(merged, result)\n // Joined, not overwritten — see CONSENT_ANALYTICS_VENDORS_PROP.\n if (vendors.length) {\n merged[CONSENT_ANALYTICS_VENDORS_PROP] = consentAnalyticsVendorsOf({\n [CONSENT_ANALYTICS_VENDORS_PROP]: vendors,\n })\n }\n // \"Contributed\" means produced something with content, not merely\n // returned a key. Every marketing enricher returns its full key set on\n // every page — `announcementBar: null`, `experiments: []` — so keying\n // off presence would mark it a contributor everywhere and the\n // attribution would be worth nothing.\n if (Object.values(result).some((value) => !isEmptyContribution(value))) {\n const owner = enricherOwners.get(enricher)\n if (owner) contributors.add(owner)\n else unattributed = true\n }\n } catch (error) {\n // A broken plugin must not take the site down — the page renders\n // without that plugin's contribution.\n console.error('site page enricher failed', error)\n }\n }\n return { props: merged, contributors: [...contributors], unattributed }\n}\n"],"names":["getRegisteringPluginId","redirectResolvers","pageResolvers","enrichers","enricherOwners","WeakMap","registerSiteRedirectResolver","fn","push","registerSitePageResolver","registerSitePageEnricher","owner","set","resolveSiteRedirect","context","resolver","answer","undefined","resolveSitePage","isEmptyContribution","value","Array","isArray","length","Object","keys","CONSENT_ANALYTICS_VENDORS_PROP","consentAnalyticsVendorsOf","props","out","entry","name","trim","slice","includes","runSitePageEnrichers","merged","contributors","Set","unattributed","settled","Promise","allSettled","map","enricher","index","outcome","entries","status","reason","result","vendors","assign","values","some","get","add","error","console"],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * STOCK PHOTOS (AGL-3660): a library of licensed photos outside this
19
+ * platform that a server process searches and copies from.
20
+ *
21
+ * Core defines the contract and never names a library. A plugin registers a
22
+ * provider from its server declarations, and a caller — the AI site build is
23
+ * the first — asks for {@link stockPhotoProvider} and gets the first one
24
+ * this deployment configured, or `null`. Nothing here stores a photo: a
25
+ * caller that keeps one copies its bytes into the site's own media library
26
+ * (`plugin-media-ingest.ts`), because a page that names a library's own
27
+ * address would hotlink it, and most libraries' terms refuse that.
28
+ *
29
+ * What a provider owes its library's terms — caching searches, its rate
30
+ * limit, the hosts it may fetch from — it keeps inside itself, so a caller
31
+ * cannot get them wrong. What it owes the caller is {@link
32
+ * StockPhotoProvider.credit}: the words and links the asset records about
33
+ * where it came from, and whether the library requires them shown.
34
+ *
35
+ * A provider that is registered and not configured (its key unset) answers
36
+ * as absent, so a deployment without the key behaves as one without the
37
+ * plugin.
38
+ */
39
+ /** The shape a caller wants; a provider maps it onto its own filter. */
40
+ export type StockPhotoOrientation = 'horizontal' | 'vertical' | 'any';
41
+ export interface StockPhotoSearchRequest {
42
+ /** Plain search words, at most {@link STOCK_PHOTO_QUERY_MAX_CHARS}. */
43
+ query: string;
44
+ orientation?: StockPhotoOrientation;
45
+ /** The smallest original a hit may have, in pixels. */
46
+ minWidth?: number;
47
+ minHeight?: number;
48
+ /** A hint that the photo should show people, where the library can filter by it. */
49
+ people?: boolean;
50
+ }
51
+ /** The longest query a caller sends; libraries refuse or truncate longer ones. */
52
+ export declare const STOCK_PHOTO_QUERY_MAX_CHARS = 100;
53
+ /** One photo a search found. */
54
+ export interface StockPhoto {
55
+ /** The registering provider's id. */
56
+ provider: string;
57
+ /** The library's own id for the photo, stable across searches. */
58
+ id: string;
59
+ /** The size of the copy {@link StockPhotoProvider.download} fetches. */
60
+ width: number;
61
+ height: number;
62
+ /** The photo's page at the library, for the credit. */
63
+ pageUrl: string;
64
+ /** The contributor's name as the library shows it. */
65
+ photographer: string;
66
+ /** The contributor's page at the library, when it has one. */
67
+ photographerUrl?: string;
68
+ /** The library's tags, lowercase. */
69
+ tags: string[];
70
+ }
71
+ /** What an asset copied from a library records about where it came from. */
72
+ export interface StockPhotoCredit {
73
+ /** The library's name, as its terms ask it written. */
74
+ providerLabel: string;
75
+ /** The license the photo is used under. */
76
+ license: string;
77
+ licenseUrl: string;
78
+ /** Whether the license requires the credit shown wherever the photo is. */
79
+ attributionRequired: boolean;
80
+ /** A sentence crediting the photo, for the asset's description. */
81
+ text: string;
82
+ }
83
+ export interface StockPhotoSearchResult {
84
+ photos: StockPhoto[];
85
+ /** Whether the answer came from the provider's own cache. */
86
+ cached: boolean;
87
+ }
88
+ export interface StockPhotoDownload {
89
+ bytes: Uint8Array;
90
+ /** `image/jpeg`, `image/png` or `image/webp`. */
91
+ contentType: string;
92
+ }
93
+ export interface StockPhotoProvider {
94
+ /** A stable id, the prefix of every source key: `pixabay`. */
95
+ id: string;
96
+ /** The library's name, in customer copy. */
97
+ label: string;
98
+ /** Whether this deployment holds what the provider needs to search. */
99
+ isConfigured(): boolean;
100
+ /**
101
+ * The photos matching `request`, best first. `null` when the provider
102
+ * cannot ask right now — its rate limit is spent, or the library failed —
103
+ * which a caller treats as "no photo", never as an error to retry.
104
+ */
105
+ search(request: StockPhotoSearchRequest, options?: {
106
+ signal?: AbortSignal;
107
+ }): Promise<StockPhotoSearchResult | null>;
108
+ /**
109
+ * The photo's bytes, fetched only from the library's own image hosts, or
110
+ * `null` when they cannot be had within `maxBytes` and the signal.
111
+ */
112
+ download(photo: StockPhoto, options: {
113
+ maxBytes: number;
114
+ signal?: AbortSignal;
115
+ }): Promise<StockPhotoDownload | null>;
116
+ /** How an asset copied from `photo` credits it. */
117
+ credit(photo: StockPhoto): StockPhotoCredit;
118
+ }
119
+ /**
120
+ * Many plugins may each register a library; a caller is handed the first
121
+ * configured one, highest priority first.
122
+ */
123
+ export declare const STOCK_PHOTO_PROVIDERS: import("./plugin-services").PluginServiceContract<StockPhotoProvider>;
124
+ /** The key an asset copied from a library is found again by: `{provider}:{id}`. */
125
+ export declare function stockPhotoSourceKey(photo: Pick<StockPhoto, 'provider' | 'id'>): string;
126
+ /**
127
+ * Registers a library. The owner is the loader's marker when a register fn
128
+ * is running, else `options.pluginId`; the provider's own id tells two
129
+ * libraries of one plugin apart.
130
+ */
131
+ export declare function registerStockPhotoProvider(provider: StockPhotoProvider, options?: {
132
+ pluginId?: string;
133
+ priority?: number;
134
+ }): void;
135
+ /**
136
+ * The first registered library this deployment configured, or `null`.
137
+ * Synchronous and free of I/O. A provider whose configuration check throws
138
+ * answers as unconfigured: a stock photo is decoration, and its failure
139
+ * leaves the caller's own fallback standing.
140
+ */
141
+ export declare function stockPhotoProvider(): StockPhotoProvider | null;