@aglyn/aglyn 1.0.0-beta.218 → 1.0.0-beta.220
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -12
- package/src/lib/app-utils/actions.d.ts +26 -433
- package/src/lib/app-utils/actions.js +108 -633
- package/src/lib/app-utils/actions.js.map +1 -1
- package/src/lib/app-utils/activity-presenter.d.ts +3 -3
- package/src/lib/app-utils/activity-presenter.js +36 -52
- package/src/lib/app-utils/activity-presenter.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +1 -1
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/analytics-provider.d.ts +182 -0
- package/src/lib/app-utils/analytics-provider.js +133 -0
- package/src/lib/app-utils/analytics-provider.js.map +1 -0
- package/src/lib/app-utils/api-plugins.d.ts +46 -0
- package/src/lib/app-utils/api-plugins.js +19 -0
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
- package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
- package/src/lib/app-utils/artifact-overrides.js.map +1 -0
- package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
- package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
- package/src/lib/app-utils/artifact-provenance.js.map +1 -0
- package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
- package/src/lib/app-utils/binding-token-catalog.js +17 -41
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
- package/src/lib/app-utils/brand-support-line.d.ts +36 -0
- package/src/lib/app-utils/brand-support-line.js +38 -0
- package/src/lib/app-utils/brand-support-line.js.map +1 -0
- package/src/lib/app-utils/collection-entries.d.ts +2 -2
- package/src/lib/app-utils/collection-entries.js.map +1 -1
- package/src/lib/app-utils/consent-group-change.d.ts +4 -0
- package/src/lib/app-utils/consent-group-change.js.map +1 -1
- package/src/lib/app-utils/consent-groups.d.ts +1 -1
- package/src/lib/app-utils/consent-groups.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +15 -42
- package/src/lib/app-utils/console-routes.js +10 -39
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +24 -2
- package/src/lib/app-utils/contacts.js +21 -5
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/container-membership.d.ts +93 -0
- package/src/lib/app-utils/container-membership.js +165 -0
- package/src/lib/app-utils/container-membership.js.map +1 -0
- package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
- package/src/lib/app-utils/crm-task-reminders.js +32 -11
- package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +24 -4
- package/src/lib/app-utils/crm.js +23 -0
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
- package/src/lib/app-utils/cross-pool-desync.js +47 -0
- package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
- package/src/lib/app-utils/csv-upload.d.ts +29 -0
- package/src/lib/app-utils/csv-upload.js +29 -0
- package/src/lib/app-utils/csv-upload.js.map +1 -0
- package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
- package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
- package/src/lib/app-utils/csv.js.map +1 -0
- package/src/lib/app-utils/dataset-models.d.ts +5 -5
- package/src/lib/app-utils/dataset-models.js +19 -2
- package/src/lib/app-utils/dataset-models.js.map +1 -1
- package/src/lib/app-utils/datasets.d.ts +65 -30
- package/src/lib/app-utils/datasets.js +74 -24
- package/src/lib/app-utils/datasets.js.map +1 -1
- package/src/lib/app-utils/docs-deflection.d.ts +6 -6
- package/src/lib/app-utils/docs-deflection.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +14 -8
- package/src/lib/app-utils/docs-help.generated.js +23 -6
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-index.generated.js +570 -346
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
- package/src/lib/app-utils/docs-retrieval.js +14 -10
- package/src/lib/app-utils/docs-retrieval.js.map +1 -1
- package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
- package/src/lib/app-utils/duplicate-resource.js +7 -4
- package/src/lib/app-utils/duplicate-resource.js.map +1 -1
- package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
- package/src/lib/app-utils/dynamic-list-rule.js +3 -3
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
- package/src/lib/app-utils/element-ui.d.ts +1 -1
- package/src/lib/app-utils/element-ui.js +1 -1
- package/src/lib/app-utils/element-ui.js.map +1 -1
- package/src/lib/app-utils/email-starter-policy.js +3 -3
- package/src/lib/app-utils/email-starter-policy.js.map +1 -1
- package/src/lib/app-utils/email-text.d.ts +80 -0
- package/src/lib/app-utils/email-text.js +189 -0
- package/src/lib/app-utils/email-text.js.map +1 -0
- package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
- package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
- package/src/lib/app-utils/enrollment-basis.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +75 -6
- package/src/lib/app-utils/error-beacon.js +160 -21
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
- package/src/lib/app-utils/expand-repeatables.js +14 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.js +2 -2
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +2 -3
- package/src/lib/app-utils/health-report.js +8 -6
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/host-events.d.ts +82 -0
- package/src/lib/app-utils/host-events.js +62 -0
- package/src/lib/app-utils/host-events.js.map +1 -0
- package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
- package/src/lib/app-utils/inbound-mail-domain.js +31 -0
- package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +11 -0
- package/src/lib/app-utils/lockdown.js +33 -3
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/margin-utilization.d.ts +4 -69
- package/src/lib/app-utils/margin-utilization.js +82 -69
- package/src/lib/app-utils/margin-utilization.js.map +1 -1
- package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
- package/src/lib/app-utils/member-email-aliases.js +29 -0
- package/src/lib/app-utils/member-email-aliases.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
- package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/node-interactions.d.ts +7 -7
- package/src/lib/app-utils/node-interactions.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +147 -47
- package/src/lib/app-utils/notifications.js +182 -44
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.d.ts +15 -1
- package/src/lib/app-utils/operator-alerts.js +47 -10
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/payments-configured.d.ts +8 -21
- package/src/lib/app-utils/payments-configured.js +8 -23
- package/src/lib/app-utils/payments-configured.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +207 -306
- package/src/lib/app-utils/plan-entitlements.js +345 -528
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/platform-brand.d.ts +10 -0
- package/src/lib/app-utils/platform-brand.js +14 -3
- package/src/lib/app-utils/platform-brand.js.map +1 -1
- package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
- package/src/lib/app-utils/platform-marketing-consent.js +6 -5
- package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
- package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
- package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
- package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +14 -5
- package/src/lib/app-utils/release-flags.js +21 -125
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/repeat-query.d.ts +56 -0
- package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
- package/src/lib/app-utils/repeat-query.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +9 -0
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/retained-refusals.d.ts +73 -0
- package/src/lib/app-utils/retained-refusals.js +186 -0
- package/src/lib/app-utils/retained-refusals.js.map +1 -0
- package/src/lib/app-utils/scope-tokens.d.ts +27 -0
- package/src/lib/app-utils/scope-tokens.js +61 -0
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +3 -3
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.js +1 -1
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +132 -0
- package/src/lib/app-utils/seo-audit.js +246 -0
- package/src/lib/app-utils/seo-audit.js.map +1 -0
- package/src/lib/app-utils/seo-keywords.d.ts +48 -0
- package/src/lib/app-utils/seo-keywords.js +60 -0
- package/src/lib/app-utils/seo-keywords.js.map +1 -0
- package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
- package/src/lib/app-utils/seo-page-facts.js +155 -0
- package/src/lib/app-utils/seo-page-facts.js.map +1 -0
- package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
- package/src/lib/app-utils/seo-site-scan.js +162 -0
- package/src/lib/app-utils/seo-site-scan.js.map +1 -0
- package/src/lib/app-utils/seo-title-variables.js +1 -1
- package/src/lib/app-utils/seo-title-variables.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +13 -18
- package/src/lib/app-utils/server.js +33 -51
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-interactions.d.ts +501 -0
- package/src/lib/app-utils/site-interactions.js +554 -0
- package/src/lib/app-utils/site-interactions.js.map +1 -0
- package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
- package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +2 -2
- package/src/lib/app-utils/site-theme.js.map +1 -0
- package/src/lib/app-utils/sitemap.d.ts +4 -3
- package/src/lib/app-utils/sitemap.js +9 -7
- package/src/lib/app-utils/sitemap.js.map +1 -1
- package/src/lib/app-utils/stale-build-error.d.ts +51 -0
- package/src/lib/app-utils/stale-build-error.js +100 -1
- package/src/lib/app-utils/stale-build-error.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
- package/src/lib/app-utils/starter-template-nodes.js +129 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
- package/src/lib/app-utils/starter-templates.d.ts +11 -34
- package/src/lib/app-utils/starter-templates.js +19 -230
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
- package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
- package/src/lib/app-utils/subscription-topics.js.map +1 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
- package/src/lib/app-utils/theme-library.d.ts +10 -3
- package/src/lib/app-utils/theme-library.js +13 -4
- package/src/lib/app-utils/theme-library.js.map +1 -1
- package/src/lib/app-utils/usage-budget.d.ts +73 -188
- package/src/lib/app-utils/usage-budget.js +58 -194
- package/src/lib/app-utils/usage-budget.js.map +1 -1
- package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
- package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
- package/src/lib/app-utils/utm-attribution.js.map +1 -0
- package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
- package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
- package/src/lib/app-utils/utm-forwarding.js.map +1 -0
- package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +25 -17
- package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +38 -30
- package/src/lib/app-utils/utm-touch.js.map +1 -0
- package/src/lib/app-utils/variables.d.ts +9 -9
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/video-object.js +6 -5
- package/src/lib/app-utils/video-object.js.map +1 -1
- package/src/lib/app-utils/visitor-consent.d.ts +41 -164
- package/src/lib/app-utils/visitor-consent.js +66 -277
- package/src/lib/app-utils/visitor-consent.js.map +1 -1
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
- package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
- package/src/lib/app-utils/web-vitals-rum.js +31 -63
- package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
- package/src/lib/app-utils/where-used.d.ts +11 -6
- package/src/lib/app-utils/where-used.js +3 -8
- package/src/lib/app-utils/where-used.js.map +1 -1
- package/src/lib/foundation/definitions/components.types.d.ts +1 -1
- package/src/lib/foundation/definitions/components.types.js +1 -1
- package/src/lib/foundation/definitions/components.types.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +11 -137
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +1 -3
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +11 -5
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
- package/src/lib/plugin-manager/besigner-documents.js +61 -0
- package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
- package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
- package/src/lib/plugin-manager/computed-variables.js +74 -0
- package/src/lib/plugin-manager/computed-variables.js.map +1 -0
- package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
- package/src/lib/plugin-manager/custom-fields.js +47 -1
- package/src/lib/plugin-manager/custom-fields.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +372 -99
- package/src/lib/plugin-manager/feature-plugins.js +126 -51
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +134 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +840 -10
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
- package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-containers.js +27 -0
- package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
- package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-dependents.js +75 -0
- package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
- package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-distribution.js +45 -0
- package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
- package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
- package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
- package/src/lib/plugin-manager/plugin-record-index.js +51 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
- package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-record-written.js +42 -0
- package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-site-export.js +40 -0
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
- package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +319 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +186 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
- package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
- package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
- package/src/lib/plugin-manager/record-captured-contact.js +10 -32
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
- package/src/lib/plugin-manager/repeat-rows.js +108 -0
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
- package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
- package/src/lib/plugin-manager/submission-record-target.js +125 -0
- package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
- package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
- package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
- package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
- package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
- package/src/lib/plugin-manager/video-embed-provider.js +165 -0
- package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
- package/src/lib/types/nodes.d.ts +2 -2
- package/src/lib/types/nodes.js.map +1 -1
- package/src/lib/app-utils/assist-credits.d.ts +0 -482
- package/src/lib/app-utils/assist-credits.js +0 -509
- package/src/lib/app-utils/assist-credits.js.map +0 -1
- package/src/lib/app-utils/campaign-attribution.js.map +0 -1
- package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
- package/src/lib/app-utils/campaign-membership.d.ts +0 -121
- package/src/lib/app-utils/campaign-membership.js +0 -189
- package/src/lib/app-utils/campaign-membership.js.map +0 -1
- package/src/lib/app-utils/campaign-touch.js.map +0 -1
- package/src/lib/app-utils/console-record-links.d.ts +0 -163
- package/src/lib/app-utils/console-record-links.js +0 -180
- package/src/lib/app-utils/console-record-links.js.map +0 -1
- package/src/lib/app-utils/contact-holder.d.ts +0 -68
- package/src/lib/app-utils/contact-holder.js +0 -129
- package/src/lib/app-utils/contact-holder.js.map +0 -1
- package/src/lib/app-utils/contact-merge.d.ts +0 -136
- package/src/lib/app-utils/contact-merge.js +0 -412
- package/src/lib/app-utils/contact-merge.js.map +0 -1
- package/src/lib/app-utils/crm-booking.d.ts +0 -75
- package/src/lib/app-utils/crm-booking.js +0 -133
- package/src/lib/app-utils/crm-booking.js.map +0 -1
- package/src/lib/app-utils/crm-csv.d.ts +0 -239
- package/src/lib/app-utils/crm-csv.js +0 -467
- package/src/lib/app-utils/crm-csv.js.map +0 -1
- package/src/lib/app-utils/crm-digest.d.ts +0 -185
- package/src/lib/app-utils/crm-digest.js +0 -272
- package/src/lib/app-utils/crm-digest.js.map +0 -1
- package/src/lib/app-utils/crm-inbound.d.ts +0 -245
- package/src/lib/app-utils/crm-inbound.js +0 -407
- package/src/lib/app-utils/crm-inbound.js.map +0 -1
- package/src/lib/app-utils/crm-reports.d.ts +0 -479
- package/src/lib/app-utils/crm-reports.js +0 -680
- package/src/lib/app-utils/crm-reports.js.map +0 -1
- package/src/lib/app-utils/dataset-csv.js.map +0 -1
- package/src/lib/app-utils/dataset-query.d.ts +0 -57
- package/src/lib/app-utils/dataset-query.js.map +0 -1
- package/src/lib/app-utils/email-topics.js.map +0 -1
- package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
- package/src/lib/app-utils/form-dataset-binding.js +0 -74
- package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
- package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
- package/src/lib/app-utils/list-import.d.ts +0 -158
- package/src/lib/app-utils/list-import.js +0 -332
- package/src/lib/app-utils/list-import.js.map +0 -1
- package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
- package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
- package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
- package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
- package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
- package/src/lib/app-utils/marketplace-theme.js.map +0 -1
- package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
- package/src/lib/app-utils/marketplace-update-state.js +0 -132
- package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
- package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
- package/src/lib/app-utils/publisher-agreement.js +0 -321
- package/src/lib/app-utils/publisher-agreement.js.map +0 -1
- package/src/lib/app-utils/wistia-embed.d.ts +0 -104
- package/src/lib/app-utils/wistia-embed.js +0 -138
- package/src/lib/app-utils/wistia-embed.js.map +0 -1
- package/src/lib/app-utils/workflows.d.ts +0 -136
- package/src/lib/app-utils/workflows.js +0 -346
- package/src/lib/app-utils/workflows.js.map +0 -1
- package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
- package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
- package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
- package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
- package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
- package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
*/ const providers = new Map();
|
|
17
|
+
const listeners = new Set();
|
|
18
|
+
let snapshot = [];
|
|
19
|
+
let lastGrants = null;
|
|
20
|
+
function publish() {
|
|
21
|
+
snapshot = [
|
|
22
|
+
...providers.values()
|
|
23
|
+
];
|
|
24
|
+
for (const listener of [
|
|
25
|
+
...listeners
|
|
26
|
+
]){
|
|
27
|
+
try {
|
|
28
|
+
listener();
|
|
29
|
+
} catch (unused) {
|
|
30
|
+
// One listener cannot stop the others hearing about the provider.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Registers `pluginId`'s adapter. The same plugin registering again replaces
|
|
36
|
+
* its adapter. If a consent answer was already given in this document, the
|
|
37
|
+
* adapter is told it before anything else can reach it.
|
|
38
|
+
*/ export function registerAnalyticsProvider(pluginId, provider) {
|
|
39
|
+
providers.set(pluginId, provider);
|
|
40
|
+
if (lastGrants) {
|
|
41
|
+
try {
|
|
42
|
+
provider.applyConsent(lastGrants);
|
|
43
|
+
} catch (unused) {
|
|
44
|
+
// An adapter that throws on its own consent call is one we cannot
|
|
45
|
+
// silence; the cookie sweep still stands.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
publish();
|
|
49
|
+
}
|
|
50
|
+
/** The registered adapters, in registration order. A stable array. */ export function analyticsProviders() {
|
|
51
|
+
return snapshot;
|
|
52
|
+
}
|
|
53
|
+
/** Hears every registration. Answers the unsubscribe. */ export function subscribeAnalyticsProviders(listener) {
|
|
54
|
+
listeners.add(listener);
|
|
55
|
+
return ()=>{
|
|
56
|
+
listeners.delete(listener);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Tell every resident tag the visitor's answer, and answer the ids acted on.
|
|
61
|
+
* Advertising is clamped to analytics: a refusal withdraws both.
|
|
62
|
+
*/ export function applyAnalyticsConsent(grants) {
|
|
63
|
+
const analytics = grants.analytics === true;
|
|
64
|
+
const clamped = {
|
|
65
|
+
analytics,
|
|
66
|
+
advertising: analytics && grants.advertising === true
|
|
67
|
+
};
|
|
68
|
+
lastGrants = clamped;
|
|
69
|
+
const ids = [];
|
|
70
|
+
for (const provider of snapshot){
|
|
71
|
+
try {
|
|
72
|
+
ids.push(...provider.applyConsent(clamped));
|
|
73
|
+
} catch (unused) {
|
|
74
|
+
// See `registerAnalyticsProvider`.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return ids;
|
|
78
|
+
}
|
|
79
|
+
/** Whether any registered adapter has a tag resident that can take an event. */ export function analyticsTagResident() {
|
|
80
|
+
return snapshot.some((provider)=>{
|
|
81
|
+
try {
|
|
82
|
+
return provider.resident();
|
|
83
|
+
} catch (unused) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Hand one event to every resident tag. Answers whether any took it, so a
|
|
90
|
+
* caller that may hold an event while a tag is still loading knows to.
|
|
91
|
+
*/ export function sendAnalyticsProviderEvent(name, params, options) {
|
|
92
|
+
let sent = false;
|
|
93
|
+
for (const provider of snapshot){
|
|
94
|
+
try {
|
|
95
|
+
if (!provider.resident()) continue;
|
|
96
|
+
provider.sendEvent(name, params, options);
|
|
97
|
+
sent = true;
|
|
98
|
+
} catch (unused) {
|
|
99
|
+
// Analytics never breaks the page.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return sent;
|
|
103
|
+
}
|
|
104
|
+
const loading = new Map();
|
|
105
|
+
/**
|
|
106
|
+
* Fetch and register each adapter once per document. Idempotent and safe to
|
|
107
|
+
* call during render: it starts the fetch, never waits for it, and a loader
|
|
108
|
+
* that fails leaves that provider unregistered — the vendor's tag then never
|
|
109
|
+
* mounts, which is the outcome a failed vendor library has anyway.
|
|
110
|
+
*/ export function loadAnalyticsProviders(loaders) {
|
|
111
|
+
return Promise.all(loaders.map((loader)=>{
|
|
112
|
+
let pending = loading.get(loader.pluginId);
|
|
113
|
+
if (!pending) {
|
|
114
|
+
pending = loader.load().then((module)=>{
|
|
115
|
+
if (module == null ? void 0 : module.analyticsProvider) {
|
|
116
|
+
registerAnalyticsProvider(loader.pluginId, module.analyticsProvider);
|
|
117
|
+
}
|
|
118
|
+
}).catch(()=>{
|
|
119
|
+
// No adapter, no tag — see above.
|
|
120
|
+
});
|
|
121
|
+
loading.set(loader.pluginId, pending);
|
|
122
|
+
}
|
|
123
|
+
return pending;
|
|
124
|
+
})).then(()=>undefined);
|
|
125
|
+
}
|
|
126
|
+
/** Test seam — forgets every adapter, load and consent answer. */ export function resetAnalyticsProviders() {
|
|
127
|
+
providers.clear();
|
|
128
|
+
loading.clear();
|
|
129
|
+
lastGrants = null;
|
|
130
|
+
publish();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=analytics-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/analytics-provider.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { VisitorConsentHost } from './visitor-consent'\n\n/**\n * Third-party analytics tags, behind one contract (AGL-3080).\n *\n * A site can send its visitors' measurement to an analytics vendor it chose:\n * a measurement id or a tag container, saved in the site's analytics\n * settings. The platform owns everything around that tag — whether this\n * visitor may be measured at all (`visitor-consent.ts`), the consent banner,\n * the first-party pageview beacon that meters the site (`analytics-beacon.ts`,\n * which is not a tag and never goes through here) — and names no vendor. A\n * plugin's ADAPTER is what knows a vendor: what its tag looks like on the\n * page, how a resident tag is told the visitor's answer changed, and how an\n * event reaches it.\n *\n * ## Declared, then loaded where it is used\n *\n * Two questions, answered by two different mechanisms, because their readers\n * cannot wait for the same thing:\n *\n * - \"Does this site run a tag, so is there anything to ask its visitors?\" is\n * asked synchronously, during render, by the consent gate on the published\n * page and by the console's banner card and preview. A registry one of\n * those had not filled yet would answer \"no tag\" — no banner, and nothing to\n * gate — so the answer is COMPILED: the plugin declares which of the site's\n * analytics settings it mounts a tag for (`analyticsProvider.settings` in\n * `plugins.config.json`), and `hostConfiguresAnalyticsTag` in\n * `visitor-consent.ts` reads that list. With no provider declared, no setting configures a tag, and a site\n * with nothing that could mount asks its visitors nothing.\n * - Everything else — mounting the tag, telling it the answer changed,\n * handing it an event — happens after the adapter has loaded. The adapter\n * is a chunk of its own, reached through each app's generated\n * `plugins.analytics.generated.ts` and fetched only by a document that uses\n * it: a published page whose site configures a tag, and the console, whose\n * own measurement tag the consent controls answer to. A page whose site\n * configures none never fetches it.\n *\n * ## What an unloaded adapter means\n *\n * A tag is mounted only by its adapter, so until the adapter has loaded there\n * is no tag of its to silence or to send to, and nothing is lost by the wait:\n * events find no resident tag and drop, exactly as they do for a visitor who\n * has not granted.\n *\n * A tag this document did not mount — the console's, which its analytics SDK\n * injects — can be resident before the adapter arrives. So the last answer\n * {@link applyAnalyticsConsent} was given is kept, and an adapter that\n * registers later is handed it at once: a withdrawal made in that window\n * reaches the tag the moment something can speak to it.\n */\n\n/**\n * What a provider declares about itself, compiled from `plugins.config.json`.\n * `settings` are keys of the site's `analytics` settings; each is validated\n * by the pattern `ANALYTICS_SETTING_PATTERNS` in `visitor-consent.ts` holds\n * for it.\n */\nexport interface AnalyticsProviderDeclaration {\n /** The plugin that provides the adapter. */\n pluginId: string\n /** The analytics settings this provider mounts a tag for. */\n settings: readonly string[]\n}\n\n/** The visitor's answer, in the platform's two categories. */\nexport interface AnalyticsGrants {\n analytics: boolean\n advertising: boolean\n}\n\n/**\n * One tag on a published page: an inline boot that runs first, then the\n * vendor's library. The page renders the pair in order, under the consent\n * gate and with the request's nonce, as `${id}-init` and `${id}-src`.\n *\n * Both strings land in the document. `boot` is inline script, so an adapter\n * builds it from constants and format-checked ids only.\n */\nexport interface AnalyticsTagMount {\n /** Element id prefix. Stable: specs and the CSP report read it. */\n id: string\n /** Inline script that runs before the library. */\n boot: string\n /** The library URL. */\n src: string\n /**\n * A marker for the library this pair loads, for an advertising vendor that\n * rides the same library instead of fetching it again (`sharesLibrary` in\n * `advertising-tags.ts`).\n */\n library?: string\n}\n\n/** What the page tells an adapter about the pageview it mounts tags for. */\nexport interface AnalyticsMountOptions {\n /**\n * Whether the site runs the platform's consent machinery. When it does, the\n * tag is told the visitor's answer before it measures anything; when the\n * host runs a consent solution of its own, that solution tells it.\n */\n consentRequired: boolean\n /** Whether this visitor granted advertising storage on this site. */\n advertising: boolean\n}\n\nexport interface AnalyticsEventOptions {\n /**\n * Address only the measurement destinations of the resident tag, not any\n * advertising destination sharing its library. Set for events that no\n * advertising report reads, such as Core Web Vitals.\n */\n measurementOnly?: boolean\n}\n\n/** A vendor's adapter. */\nexport interface AnalyticsProvider {\n /**\n * The tags a granted pageview mounts for `host`, in order; empty when the\n * host configures none of this provider's settings, or configures them\n * malformed. Called only once the page's consent gate has said yes.\n */\n mounts(\n host: VisitorConsentHost | null | undefined,\n options: AnalyticsMountOptions,\n ): readonly AnalyticsTagMount[]\n /**\n * Make every resident tag of this vendor agree with `grants`, and answer\n * the ids it acted on. A page cannot unload a script, so a withdrawal in\n * the middle of a pageview has to be told to the tag that is already there.\n */\n applyConsent(grants: AnalyticsGrants): readonly string[]\n /** Whether a tag of this vendor is resident and can take an event now. */\n resident(): boolean\n /** Hand one event to the resident tag. Never throws. */\n sendEvent(\n name: string,\n params: Record<string, unknown>,\n options?: AnalyticsEventOptions,\n ): void\n}\n\n/** An adapter module, as an app's generated manifest loads it. */\nexport interface AnalyticsProviderModule {\n analyticsProvider: AnalyticsProvider\n}\n\n/** One row of an app's generated `plugins.analytics.generated.ts`. */\nexport interface AnalyticsProviderLoader {\n pluginId: string\n load: () => Promise<AnalyticsProviderModule>\n}\n\nconst providers = new Map<string, AnalyticsProvider>()\nconst listeners = new Set<() => void>()\nlet snapshot: readonly AnalyticsProvider[] = []\nlet lastGrants: AnalyticsGrants | null = null\n\nfunction publish(): void {\n snapshot = [...providers.values()]\n for (const listener of [...listeners]) {\n try {\n listener()\n } catch {\n // One listener cannot stop the others hearing about the provider.\n }\n }\n}\n\n/**\n * Registers `pluginId`'s adapter. The same plugin registering again replaces\n * its adapter. If a consent answer was already given in this document, the\n * adapter is told it before anything else can reach it.\n */\nexport function registerAnalyticsProvider(\n pluginId: string,\n provider: AnalyticsProvider,\n): void {\n providers.set(pluginId, provider)\n if (lastGrants) {\n try {\n provider.applyConsent(lastGrants)\n } catch {\n // An adapter that throws on its own consent call is one we cannot\n // silence; the cookie sweep still stands.\n }\n }\n publish()\n}\n\n/** The registered adapters, in registration order. A stable array. */\nexport function analyticsProviders(): readonly AnalyticsProvider[] {\n return snapshot\n}\n\n/** Hears every registration. Answers the unsubscribe. */\nexport function subscribeAnalyticsProviders(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\n/**\n * Tell every resident tag the visitor's answer, and answer the ids acted on.\n * Advertising is clamped to analytics: a refusal withdraws both.\n */\nexport function applyAnalyticsConsent(grants: AnalyticsGrants): string[] {\n const analytics = grants.analytics === true\n const clamped: AnalyticsGrants = {\n analytics,\n advertising: analytics && grants.advertising === true,\n }\n lastGrants = clamped\n const ids: string[] = []\n for (const provider of snapshot) {\n try {\n ids.push(...provider.applyConsent(clamped))\n } catch {\n // See `registerAnalyticsProvider`.\n }\n }\n return ids\n}\n\n/** Whether any registered adapter has a tag resident that can take an event. */\nexport function analyticsTagResident(): boolean {\n return snapshot.some((provider) => {\n try {\n return provider.resident()\n } catch {\n return false\n }\n })\n}\n\n/**\n * Hand one event to every resident tag. Answers whether any took it, so a\n * caller that may hold an event while a tag is still loading knows to.\n */\nexport function sendAnalyticsProviderEvent(\n name: string,\n params: Record<string, unknown>,\n options?: AnalyticsEventOptions,\n): boolean {\n let sent = false\n for (const provider of snapshot) {\n try {\n if (!provider.resident()) continue\n provider.sendEvent(name, params, options)\n sent = true\n } catch {\n // Analytics never breaks the page.\n }\n }\n return sent\n}\n\nconst loading = new Map<string, Promise<void>>()\n\n/**\n * Fetch and register each adapter once per document. Idempotent and safe to\n * call during render: it starts the fetch, never waits for it, and a loader\n * that fails leaves that provider unregistered — the vendor's tag then never\n * mounts, which is the outcome a failed vendor library has anyway.\n */\nexport function loadAnalyticsProviders(\n loaders: readonly AnalyticsProviderLoader[],\n): Promise<void> {\n return Promise.all(\n loaders.map((loader) => {\n let pending = loading.get(loader.pluginId)\n if (!pending) {\n pending = loader\n .load()\n .then((module) => {\n if (module?.analyticsProvider) {\n registerAnalyticsProvider(loader.pluginId, module.analyticsProvider)\n }\n })\n .catch(() => {\n // No adapter, no tag — see above.\n })\n loading.set(loader.pluginId, pending)\n }\n return pending\n }),\n ).then((): void => undefined)\n}\n\n/** Test seam — forgets every adapter, load and consent answer. */\nexport function resetAnalyticsProviders(): void {\n providers.clear()\n loading.clear()\n lastGrants = null\n publish()\n}\n"],"names":["providers","Map","listeners","Set","snapshot","lastGrants","publish","values","listener","registerAnalyticsProvider","pluginId","provider","set","applyConsent","analyticsProviders","subscribeAnalyticsProviders","add","delete","applyAnalyticsConsent","grants","analytics","clamped","advertising","ids","push","analyticsTagResident","some","resident","sendAnalyticsProviderEvent","name","params","options","sent","sendEvent","loading","loadAnalyticsProviders","loaders","Promise","all","map","loader","pending","get","load","then","module","analyticsProvider","catch","undefined","resetAnalyticsProviders","clear"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GA0JD,MAAMA,YAAY,IAAIC;AACtB,MAAMC,YAAY,IAAIC;AACtB,IAAIC,WAAyC,EAAE;AAC/C,IAAIC,aAAqC;AAEzC,SAASC;IACPF,WAAW;WAAIJ,UAAUO,MAAM;KAAG;IAClC,KAAK,MAAMC,YAAY;WAAIN;KAAU,CAAE;QACrC,IAAI;YACFM;QACF,EAAE,eAAM;QACN,kEAAkE;QACpE;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASC,0BACdC,QAAgB,EAChBC,QAA2B;IAE3BX,UAAUY,GAAG,CAACF,UAAUC;IACxB,IAAIN,YAAY;QACd,IAAI;YACFM,SAASE,YAAY,CAACR;QACxB,EAAE,eAAM;QACN,kEAAkE;QAClE,0CAA0C;QAC5C;IACF;IACAC;AACF;AAEA,oEAAoE,GACpE,OAAO,SAASQ;IACd,OAAOV;AACT;AAEA,uDAAuD,GACvD,OAAO,SAASW,4BAA4BP,QAAoB;IAC9DN,UAAUc,GAAG,CAACR;IACd,OAAO;QACLN,UAAUe,MAAM,CAACT;IACnB;AACF;AAEA;;;CAGC,GACD,OAAO,SAASU,sBAAsBC,MAAuB;IAC3D,MAAMC,YAAYD,OAAOC,SAAS,KAAK;IACvC,MAAMC,UAA2B;QAC/BD;QACAE,aAAaF,aAAaD,OAAOG,WAAW,KAAK;IACnD;IACAjB,aAAagB;IACb,MAAME,MAAgB,EAAE;IACxB,KAAK,MAAMZ,YAAYP,SAAU;QAC/B,IAAI;YACFmB,IAAIC,IAAI,IAAIb,SAASE,YAAY,CAACQ;QACpC,EAAE,eAAM;QACN,mCAAmC;QACrC;IACF;IACA,OAAOE;AACT;AAEA,8EAA8E,GAC9E,OAAO,SAASE;IACd,OAAOrB,SAASsB,IAAI,CAAC,CAACf;QACpB,IAAI;YACF,OAAOA,SAASgB,QAAQ;QAC1B,EAAE,eAAM;YACN,OAAO;QACT;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,SAASC,2BACdC,IAAY,EACZC,MAA+B,EAC/BC,OAA+B;IAE/B,IAAIC,OAAO;IACX,KAAK,MAAMrB,YAAYP,SAAU;QAC/B,IAAI;YACF,IAAI,CAACO,SAASgB,QAAQ,IAAI;YAC1BhB,SAASsB,SAAS,CAACJ,MAAMC,QAAQC;YACjCC,OAAO;QACT,EAAE,eAAM;QACN,mCAAmC;QACrC;IACF;IACA,OAAOA;AACT;AAEA,MAAME,UAAU,IAAIjC;AAEpB;;;;;CAKC,GACD,OAAO,SAASkC,uBACdC,OAA2C;IAE3C,OAAOC,QAAQC,GAAG,CAChBF,QAAQG,GAAG,CAAC,CAACC;QACX,IAAIC,UAAUP,QAAQQ,GAAG,CAACF,OAAO9B,QAAQ;QACzC,IAAI,CAAC+B,SAAS;YACZA,UAAUD,OACPG,IAAI,GACJC,IAAI,CAAC,CAACC;gBACL,IAAIA,0BAAAA,OAAQC,iBAAiB,EAAE;oBAC7BrC,0BAA0B+B,OAAO9B,QAAQ,EAAEmC,OAAOC,iBAAiB;gBACrE;YACF,GACCC,KAAK,CAAC;YACL,kCAAkC;YACpC;YACFb,QAAQtB,GAAG,CAAC4B,OAAO9B,QAAQ,EAAE+B;QAC/B;QACA,OAAOA;IACT,IACAG,IAAI,CAAC,IAAYI;AACrB;AAEA,gEAAgE,GAChE,OAAO,SAASC;IACdjD,UAAUkD,KAAK;IACfhB,QAAQgB,KAAK;IACb7C,aAAa;IACbC;AACF"}
|
|
@@ -137,6 +137,39 @@ export interface PluginApiRouteOptions {
|
|
|
137
137
|
* authenticates the link itself, by a signature it verifies.
|
|
138
138
|
*/
|
|
139
139
|
recipientLink?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* The route is called by a MACHINE that proves itself with a credential
|
|
142
|
+
* the route verifies — a scheduler's shared secret, a provider's signed
|
|
143
|
+
* webhook — and names no site and no member (AGL-3080).
|
|
144
|
+
*
|
|
145
|
+
* Such a request has no subject the dispatcher could gate on before the
|
|
146
|
+
* route has verified it: a sweep spans every organization, and a webhook's
|
|
147
|
+
* workspace is inside a payload only a verified signature makes worth
|
|
148
|
+
* reading. Gated anonymously, a plugin on a partial rollout would refuse
|
|
149
|
+
* every delivery. So both dispatchers skip their per-site enablement and
|
|
150
|
+
* release gates for it, and — because a chunked sweep posts from one
|
|
151
|
+
* address, and a provider retries in bursts — their write limit and their
|
|
152
|
+
* cross-origin check (`isMachinePluginApiPath`); lockdown still applies.
|
|
153
|
+
* The flag grants nothing by itself: the route authenticates its caller,
|
|
154
|
+
* and judges the plugin's release and plan for each organization it
|
|
155
|
+
* resolves from what it verified.
|
|
156
|
+
*/
|
|
157
|
+
machine?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* The route hands a workspace the records a plugin keeps for it — a
|
|
160
|
+
* data-portability export — which the workspace is owed whether or not the
|
|
161
|
+
* plugin is switched on or released for it now (AGL-3080).
|
|
162
|
+
*
|
|
163
|
+
* Exporting the people a workspace holds is an obligation, not a feature:
|
|
164
|
+
* a workspace that switched the plugin off, or a plugin paused by its
|
|
165
|
+
* kill switch, still has to be able to take its data away. So both
|
|
166
|
+
* dispatchers skip their per-site enablement and release gates for it —
|
|
167
|
+
* and nothing else: lockdown, the email-verification gate and the rate
|
|
168
|
+
* limit still apply. The flag grants nothing by itself: the route
|
|
169
|
+
* authenticates the member, and asks the plugin's release and plan itself
|
|
170
|
+
* of anything it serves beyond what the workspace is owed.
|
|
171
|
+
*/
|
|
172
|
+
portability?: boolean;
|
|
140
173
|
/**
|
|
141
174
|
* The route OPENS A CARD PAYMENT for whoever calls it: a Checkout Session,
|
|
142
175
|
* a PaymentIntent or a SetupIntent (AGL-3363).
|
|
@@ -194,6 +227,19 @@ export declare function resolvePluginApiMatch(path: string): PluginApiMatch | un
|
|
|
194
227
|
* before their enablement and release gates (AGL-2981).
|
|
195
228
|
*/
|
|
196
229
|
export declare function isPluginRecipientLinkRoute(path: string): boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Whether a request path resolves to a route registered as a
|
|
232
|
+
* {@link PluginApiRouteOptions.machine} caller's — what both dispatchers ask
|
|
233
|
+
* before their enablement and release gates, and the write limits before
|
|
234
|
+
* they count (AGL-3080).
|
|
235
|
+
*/
|
|
236
|
+
export declare function isPluginMachineRoute(path: string): boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Whether a request path resolves to a route registered as a
|
|
239
|
+
* {@link PluginApiRouteOptions.portability} export — what both dispatchers
|
|
240
|
+
* ask before their enablement and release gates (AGL-3080).
|
|
241
|
+
*/
|
|
242
|
+
export declare function isPluginPortabilityRoute(path: string): boolean;
|
|
197
243
|
/**
|
|
198
244
|
* Whether a request path resolves to a route registered with
|
|
199
245
|
* {@link PluginApiRouteOptions.cardPayment} — what the tenant dispatcher
|
|
@@ -157,6 +157,25 @@ export function unregisterPluginApiRoute(path) {
|
|
|
157
157
|
const matched = matchRegisteredApiKey(path);
|
|
158
158
|
return matched ? ((_apiRouteOptions_get = apiRouteOptions.get(matched.key)) == null ? void 0 : _apiRouteOptions_get.recipientLink) === true : false;
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Whether a request path resolves to a route registered as a
|
|
162
|
+
* {@link PluginApiRouteOptions.machine} caller's — what both dispatchers ask
|
|
163
|
+
* before their enablement and release gates, and the write limits before
|
|
164
|
+
* they count (AGL-3080).
|
|
165
|
+
*/ export function isPluginMachineRoute(path) {
|
|
166
|
+
var _apiRouteOptions_get;
|
|
167
|
+
const matched = matchRegisteredApiKey(path);
|
|
168
|
+
return matched ? ((_apiRouteOptions_get = apiRouteOptions.get(matched.key)) == null ? void 0 : _apiRouteOptions_get.machine) === true : false;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Whether a request path resolves to a route registered as a
|
|
172
|
+
* {@link PluginApiRouteOptions.portability} export — what both dispatchers
|
|
173
|
+
* ask before their enablement and release gates (AGL-3080).
|
|
174
|
+
*/ export function isPluginPortabilityRoute(path) {
|
|
175
|
+
var _apiRouteOptions_get;
|
|
176
|
+
const matched = matchRegisteredApiKey(path);
|
|
177
|
+
return matched ? ((_apiRouteOptions_get = apiRouteOptions.get(matched.key)) == null ? void 0 : _apiRouteOptions_get.portability) === true : false;
|
|
178
|
+
}
|
|
160
179
|
/**
|
|
161
180
|
* Whether a request path resolves to a route registered with
|
|
162
181
|
* {@link PluginApiRouteOptions.cardPayment} — what the tenant dispatcher
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/api-plugins.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 * Server half of the plugin pattern (AGL-396): the API-route registry, the\n * server counterpart to the ConsoleExtension registry. A feature plugin\n * registers request handlers here from its `/server` entry point (never its\n * client barrel, so firebase-admin and other server-only deps stay out of\n * the browser bundle). The app ships one catch-all dispatcher route per\n * Next app that resolves a request path to a registered handler — so moving\n * a feature's API into its plugin needs no new app route and keeps the same\n * URL. Reference implementation: events-calendar `events/list` (AGL-396).\n *\n * The request/response shapes are structural (not `next` types) so plugins\n * stay framework-light; `NextApiRequest`/`NextApiResponse` satisfy them, so\n * the dispatcher passes Next's objects straight through.\n */\n\nexport interface PluginApiRequest {\n method?: string\n query: Partial<Record<string, string | string[]>>\n // `any` mirrors NextApiRequest.body (parsed JSON/form/text); handlers\n // validate it themselves, as they did as Next routes.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n body: any\n /**\n * The unparsed request text, for handlers that verify signatures over the\n * exact payload (Stripe/Svix webhooks registered as plugin API paths).\n * UTF-8 re-encoding of the text is byte-identical for valid UTF-8 JSON.\n */\n rawBody?: string\n headers: Partial<Record<string, string | string[]>>\n cookies: Partial<Record<string, string>>\n /**\n * Node socket — the client-address fallback (`remoteAddress`).\n *\n * On the console's Pages Router this is the real transport peer. Behind the\n * App Router adapter there is no socket, and `api-adapter.ts` fills it from\n * the shared client-address reader so a handler falling back to it gets the\n * same trusted hop rather than a caller-supplied one.\n */\n socket: { remoteAddress?: string }\n}\n\nexport interface PluginApiResponse {\n status(code: number): PluginApiResponse\n json(body: unknown): void\n send(body: unknown): void\n setHeader(name: string, value: string | number | readonly string[]): void\n redirect(url: string): void\n redirect(status: number, url: string): void\n end(): void\n}\n\nexport type PluginApiHandler = (\n req: PluginApiRequest,\n res: PluginApiResponse,\n) => void | Promise<void>\n\n/**\n * A handler in the Web shape (AGL-2939): the dispatcher's own `Request`\n * in, a `Response` out — with a streamed body when the door streams, as\n * the assistant's chat and a job's event feed do. `params` carries the\n * dispatcher's path segments and any `:name` segment the registered path\n * declared.\n */\nexport type PluginWebApiHandler = (\n request: Request,\n context: { params: Record<string, string | string[]> },\n) => Response | Promise<Response>\n\n/** What a route registers: the (req, res) shape, or a Web handler. */\nexport type PluginApiRoute = PluginApiHandler | { web: PluginWebApiHandler }\n\n/** A registered route matched to a request path, with its `:name` segments filled. */\nexport interface PluginApiMatch {\n route: PluginApiRoute\n params: Record<string, string>\n}\n\n/**\n * Who a request is FOR, as far as the dispatchers' release gate is concerned\n * (AGL-2978).\n *\n * The gate buckets a rollout and reads per-org overrides on an org id, and a\n * dispatcher learns that id from the site a request names (`hostId`). Two\n * kinds of request name no site and still belong to one organization:\n *\n * - a request to an ORGANIZATION-level surface (`ConsoleExtension.orgNavItems`,\n * AGL-2974), which names its org rather than a site;\n * - a request that arrives with no bearer token by design — a provider's\n * OAuth redirect back to the platform, which carries only what the platform\n * signed into it.\n *\n * Without a subject the gate treats both as anonymous, so a plugin released to\n * one organization by override refused that organization's own requests, and\n * a staff member previewing a dark plugin could not complete a redirect their\n * own session had started.\n */\nexport interface PluginApiRequestSubject {\n /** The organization: the rollout bucket and the override key. */\n orgId: string | null\n /**\n * The account the request acts for, read ONLY when the request carries no\n * bearer token — a token speaks for itself. A route may name one only when\n * it verified a signature binding the uid to the request, because the gate\n * lets a staff account named here preview a released-off plugin.\n */\n uid?: string | null\n}\n\n/** Reads a request's subject; answers `null` when the request names none. */\nexport type PluginApiSubjectResolver = (\n request: Request,\n) => PluginApiRequestSubject | null | Promise<PluginApiRequestSubject | null>\n\n/** What a registration may say about its route beyond the handler. */\nexport interface PluginApiRouteOptions {\n /**\n * How a request to this route names its subject when it names no site.\n * Consulted by both dispatchers before their release gate, and only for a\n * request with no `hostId`: a named site always decides the org.\n */\n subject?: PluginApiSubjectResolver\n /**\n * The route answers a link the platform MAILED to somebody — an\n * unsubscribe in a message's `List-Unsubscribe` header — rather than a door\n * a member uses (AGL-2981).\n *\n * Such a link has to keep working whether or not its plugin is released,\n * or switched on, for the workspace NOW. A recipient's way out does not\n * close when a rollout is paused or a workspace turns the plugin off:\n * CAN-SPAM holds an opt-out open for thirty days after the send, and an\n * unsubscribe that 404s the day a kill switch is flipped is one nobody can\n * use. So both dispatchers skip their per-site enablement and release\n * gates for it — and nothing else: lockdown and the rate limit still\n * apply. The flag says nothing about who may act, so the route\n * authenticates the link itself, by a signature it verifies.\n */\n recipientLink?: boolean\n /**\n * The route OPENS A CARD PAYMENT for whoever calls it: a Checkout Session,\n * a PaymentIntent or a SetupIntent (AGL-3363).\n *\n * The tenant dispatcher holds such a route to the card-testing counters\n * (`card-payment-velocity.ts`) on top of its ordinary write limit, so a\n * plugin's payment door is covered by declaring itself here rather than by\n * carrying a limiter of its own. A console door (a signed-in merchant's\n * POS or draft order) does not declare it: a register ringing up a queue\n * is not a card tester. The 3-D Secure sweep\n * (`card-payments-request-3ds.spec.ts`) classifies every file that creates\n * a payment, so a new visitor door cannot skip the declaration.\n */\n cardPayment?: boolean\n}\n\n/** Leading/trailing slashes stripped so '/events/list' and 'events/list' key alike. */\nfunction normalizeApiPath(path: string): string {\n return path.replace(/^\\/+|\\/+$/g, '')\n}\n\nconst apiRoutes = new Map<string, PluginApiRoute>()\nconst apiRouteOwners = new Map<string, string>()\nconst apiRouteOptions = new Map<string, PluginApiRouteOptions>()\n\n/**\n * The \"currently registering plugin\" marker moved to a leaf module\n * (`registering-plugin`) so `site-page-hooks` can read it too — this file is\n * only reachable through the `/server` entry, and importing it from the shared\n * plugin-manager barrel formed a cycle (AGL-1289). Re-exported here so the\n * plugin loader's existing import keeps working unchanged.\n */\nimport { getRegisteringPluginId } from './registering-plugin'\n\nexport {\n setRegisteringPluginId,\n getRegisteringPluginId,\n} from './registering-plugin'\n\n/**\n * Registers a plugin API handler at a host-relative path (e.g.\n * 'events/list'), served by the app dispatcher at `/api/events/list`.\n *\n * Idempotent by path FOR ITS OWNER — re-registration by the plugin that\n * holds the path replaces the previous handler, which is what a hot reload,\n * a second surface, and a repeated init all depend on.\n *\n * A different plugin is REFUSED (AGL-2484). This map is process-global and\n * was last-writer-wins on the path alone, while Aglyn's own commission-taking\n * handlers sit at `marketplace/checkout` and `commerce/checkout` — and the\n * tenant dispatcher calls `ensureRemoteServerBundles()` after the first-party\n * loader has run, so a remote bundle registering either path was simply the\n * later writer. Getting there needs `PLUGIN_REMOTE_SERVER=enabled` (off by\n * default), realm trust and a valid signature, so this is the last of several\n * doors rather than the only one; it is also the one the registry could close\n * by itself, because it already recorded who registered what.\n *\n * Anonymous registration — no loader marker — is an IDENTITY here, not a\n * wildcard: it may replace itself and nothing else may take it.\n */\nexport function registerPluginApiRoute(\n path: string,\n handler: PluginApiRoute,\n options?: PluginApiRouteOptions,\n): void {\n const key = normalizeApiPath(path)\n const owner = getRegisteringPluginId() ?? ANONYMOUS_OWNER\n const incumbent = apiRouteOwners.get(key)\n if (incumbent !== undefined && incumbent !== owner) {\n // Loud, because the silent version of this is a payment route quietly\n // answering from someone else's code. Refusing is the safe direction:\n // the incumbent keeps serving.\n console.error(\n `[plugins] refused API route \"${key}\" to \"${owner}\": already ` +\n `registered by \"${incumbent}\"`,\n )\n return\n }\n apiRoutes.set(key, handler)\n apiRouteOwners.set(key, owner)\n // Replaced with the handler, never merged: a re-registration that drops its\n // subject resolver must not keep answering with the previous one.\n if (options) apiRouteOptions.set(key, options)\n else apiRouteOptions.delete(key)\n}\n\n/** The marker for a registration made outside any loader context. */\nconst ANONYMOUS_OWNER = ''\n\n/** The plugin that registered a path, for per-org enablement gating. */\nexport function pluginIdForRegisteredApiPath(path: string): string | undefined {\n const owner = apiRouteOwners.get(normalizeApiPath(path))\n // An anonymous registration has no plugin to gate on, and this has always\n // answered `undefined` for one — the sentinel must not leak out as an id.\n return owner === undefined || owner === ANONYMOUS_OWNER ? undefined : owner\n}\n\nexport function unregisterPluginApiRoute(path: string): void {\n const key = normalizeApiPath(path)\n apiRoutes.delete(key)\n // Release the CLAIM too, or the refusal above outlives the route and the\n // path can never be re-registered for the life of the process.\n apiRouteOwners.delete(key)\n apiRouteOptions.delete(key)\n}\n\n/**\n * The registered key a request path resolves to, with its `:name` segments.\n *\n * An exact registration wins; otherwise a registration whose path carries\n * `:name` segments matches segment for segment. One matcher for the route and\n * for its options, so a request can never be answered by one registration and\n * gated with another's subject.\n */\nfunction matchRegisteredApiKey(\n path: string,\n): { key: string; params: Record<string, string> } | undefined {\n const key = normalizeApiPath(path)\n if (apiRoutes.has(key)) return { key, params: {} }\n const segments = key.split('/')\n for (const registered of apiRoutes.keys()) {\n if (!registered.includes(':')) continue\n const pattern = registered.split('/')\n if (pattern.length !== segments.length) continue\n const params: Record<string, string> = {}\n let matched = true\n for (let index = 0; index < pattern.length; index += 1) {\n const part = pattern[index]\n if (part.startsWith(':') && segments[index]) {\n params[part.slice(1)] = segments[index]\n } else if (part !== segments[index]) {\n matched = false\n break\n }\n }\n if (matched) return { key: registered, params }\n }\n return undefined\n}\n\n/**\n * The route for a request path, or undefined when nothing is registered.\n *\n * An exact registration wins; otherwise a registration whose path carries\n * `:name` segments matches segment for segment (`ai/jobs/:jobId/cancel`\n * answers `ai/jobs/abc/cancel`), and the named segments come back as\n * `params`. Patterns are tried in registration order and a static segment\n * beats a named one only by being registered exactly.\n */\nexport function resolvePluginApiMatch(path: string): PluginApiMatch | undefined {\n const matched = matchRegisteredApiKey(path)\n if (!matched) return undefined\n const route = apiRoutes.get(matched.key)\n return route ? { route, params: matched.params } : undefined\n}\n\n/**\n * Whether a request path resolves to a route registered as a\n * {@link PluginApiRouteOptions.recipientLink} — what both dispatchers ask\n * before their enablement and release gates (AGL-2981).\n */\nexport function isPluginRecipientLinkRoute(path: string): boolean {\n const matched = matchRegisteredApiKey(path)\n return matched ? apiRouteOptions.get(matched.key)?.recipientLink === true : false\n}\n\n/**\n * Whether a request path resolves to a route registered with\n * {@link PluginApiRouteOptions.cardPayment} — what the tenant dispatcher\n * asks before its card-testing counters (AGL-3363).\n */\nexport function isPluginCardPaymentRoute(path: string): boolean {\n const matched = matchRegisteredApiKey(path)\n return matched ? apiRouteOptions.get(matched.key)?.cardPayment === true : false\n}\n\n/** An id a subject may carry: a non-empty path segment, not a path. */\nconst SUBJECT_ID = /^[^/\\s]{1,128}$/\n\nconst subjectId = (value: unknown): string | null =>\n typeof value === 'string' && SUBJECT_ID.test(value) ? value : null\n\n/**\n * The subject a request to a registered route names, or `null` (AGL-2978).\n *\n * `null` when the route declared no resolver, when the resolver answered\n * nothing, and when it THREW: a resolver that cannot read its request has\n * named no subject, and the gate then treats the request as anonymous — the\n * conservative direction. The resolver reads a clone, so the handler still\n * receives the untouched body. Ids are held to a plain path segment, so a\n * resolver cannot hand the gate a path to read overrides from.\n */\nexport async function resolvePluginApiRequestSubject(\n path: string,\n request: Request,\n): Promise<PluginApiRequestSubject | null> {\n const matched = matchRegisteredApiKey(path)\n const resolve = matched ? apiRouteOptions.get(matched.key)?.subject : undefined\n if (!resolve) return null\n try {\n const subject = await resolve(request.clone())\n if (!subject) return null\n const orgId = subjectId(subject.orgId)\n const uid = subjectId(subject.uid)\n if (!orgId && !uid) return null\n return { orgId, ...(uid ? { uid } : {}) }\n } catch {\n return null\n }\n}\n\n/**\n * The legacy (node-style) handler at a request path, or undefined when\n * nothing is registered there or the route is a web handler — a web route\n * is reached through `resolvePluginApiMatch` and `runPluginApiMatch`, since\n * it takes the `Request` and the filled `:name` segments, not a `req`/`res`\n * pair.\n */\nexport function resolvePluginApiRoute(path: string): PluginApiHandler | undefined {\n const route = resolvePluginApiMatch(path)?.route\n return typeof route === 'function' ? route : undefined\n}\n\n/**\n * Runs a matched route against the dispatcher's request: a Web handler is\n * called as it is, and a (req, res) handler goes through the adapter the\n * caller supplies — the adapter lives on the `/server` entry, which this\n * module may not import.\n */\nexport async function runPluginApiMatch(\n match: PluginApiMatch,\n request: Request,\n params: Record<string, string | string[]>,\n runLegacy: (\n handler: PluginApiHandler,\n request: Request,\n params: Record<string, string | string[]>,\n ) => Promise<Response>,\n): Promise<Response> {\n const merged = { ...params, ...match.params }\n const route = match.route as { web?: PluginWebApiHandler }\n if (typeof route === 'object' && route !== null && typeof route.web === 'function') {\n return route.web(request, { params: merged })\n }\n return runLegacy(match.route as PluginApiHandler, request, merged)\n}\n\n/** Registered paths, for diagnostics. */\nexport function listPluginApiRoutes(): string[] {\n return Array.from(apiRoutes.keys())\n}\n"],"names":["normalizeApiPath","path","replace","apiRoutes","Map","apiRouteOwners","apiRouteOptions","getRegisteringPluginId","setRegisteringPluginId","registerPluginApiRoute","handler","options","key","owner","ANONYMOUS_OWNER","incumbent","get","undefined","console","error","set","delete","pluginIdForRegisteredApiPath","unregisterPluginApiRoute","matchRegisteredApiKey","has","params","segments","split","registered","keys","includes","pattern","length","matched","index","part","startsWith","slice","resolvePluginApiMatch","route","isPluginRecipientLinkRoute","recipientLink","isPluginCardPaymentRoute","cardPayment","SUBJECT_ID","subjectId","value","test","resolvePluginApiRequestSubject","request","resolve","subject","clone","orgId","uid","resolvePluginApiRoute","runPluginApiMatch","match","runLegacy","merged","web","listPluginApiRoutes","Array","from"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;CAaC;AA4ID,qFAAqF,GACrF,SAASA,iBAAiBC,IAAY;IACpC,OAAOA,KAAKC,OAAO,CAAC,cAAc;AACpC;AAEA,MAAMC,YAAY,IAAIC;AACtB,MAAMC,iBAAiB,IAAID;AAC3B,MAAME,kBAAkB,IAAIF;AAE5B;;;;;;CAMC,GACD,SAASG,sBAAsB,QAAQ,0BAAsB;AAE7D,SACEC,sBAAsB,EACtBD,sBAAsB,QACjB,0BAAsB;AAE7B;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASE,uBACdR,IAAY,EACZS,OAAuB,EACvBC,OAA+B;QAGjBJ;IADd,MAAMK,MAAMZ,iBAAiBC;IAC7B,MAAMY,SAAQN,0BAAAA,oCAAAA,0BAA4BO;IAC1C,MAAMC,YAAYV,eAAeW,GAAG,CAACJ;IACrC,IAAIG,cAAcE,aAAaF,cAAcF,OAAO;QAClD,sEAAsE;QACtE,sEAAsE;QACtE,+BAA+B;QAC/BK,QAAQC,KAAK,CACX,CAAC,6BAA6B,EAAEP,IAAI,MAAM,EAAEC,MAAM,WAAW,CAAC,GAC5D,CAAC,eAAe,EAAEE,UAAU,CAAC,CAAC;QAElC;IACF;IACAZ,UAAUiB,GAAG,CAACR,KAAKF;IACnBL,eAAee,GAAG,CAACR,KAAKC;IACxB,4EAA4E;IAC5E,kEAAkE;IAClE,IAAIF,SAASL,gBAAgBc,GAAG,CAACR,KAAKD;SACjCL,gBAAgBe,MAAM,CAACT;AAC9B;AAEA,mEAAmE,GACnE,MAAME,kBAAkB;AAExB,sEAAsE,GACtE,OAAO,SAASQ,6BAA6BrB,IAAY;IACvD,MAAMY,QAAQR,eAAeW,GAAG,CAAChB,iBAAiBC;IAClD,0EAA0E;IAC1E,0EAA0E;IAC1E,OAAOY,UAAUI,aAAaJ,UAAUC,kBAAkBG,YAAYJ;AACxE;AAEA,OAAO,SAASU,yBAAyBtB,IAAY;IACnD,MAAMW,MAAMZ,iBAAiBC;IAC7BE,UAAUkB,MAAM,CAACT;IACjB,yEAAyE;IACzE,+DAA+D;IAC/DP,eAAegB,MAAM,CAACT;IACtBN,gBAAgBe,MAAM,CAACT;AACzB;AAEA;;;;;;;CAOC,GACD,SAASY,sBACPvB,IAAY;IAEZ,MAAMW,MAAMZ,iBAAiBC;IAC7B,IAAIE,UAAUsB,GAAG,CAACb,MAAM,OAAO;QAAEA;QAAKc,QAAQ,CAAC;IAAE;IACjD,MAAMC,WAAWf,IAAIgB,KAAK,CAAC;IAC3B,KAAK,MAAMC,cAAc1B,UAAU2B,IAAI,GAAI;QACzC,IAAI,CAACD,WAAWE,QAAQ,CAAC,MAAM;QAC/B,MAAMC,UAAUH,WAAWD,KAAK,CAAC;QACjC,IAAII,QAAQC,MAAM,KAAKN,SAASM,MAAM,EAAE;QACxC,MAAMP,SAAiC,CAAC;QACxC,IAAIQ,UAAU;QACd,IAAK,IAAIC,QAAQ,GAAGA,QAAQH,QAAQC,MAAM,EAAEE,SAAS,EAAG;YACtD,MAAMC,OAAOJ,OAAO,CAACG,MAAM;YAC3B,IAAIC,KAAKC,UAAU,CAAC,QAAQV,QAAQ,CAACQ,MAAM,EAAE;gBAC3CT,MAAM,CAACU,KAAKE,KAAK,CAAC,GAAG,GAAGX,QAAQ,CAACQ,MAAM;YACzC,OAAO,IAAIC,SAAST,QAAQ,CAACQ,MAAM,EAAE;gBACnCD,UAAU;gBACV;YACF;QACF;QACA,IAAIA,SAAS,OAAO;YAAEtB,KAAKiB;YAAYH;QAAO;IAChD;IACA,OAAOT;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASsB,sBAAsBtC,IAAY;IAChD,MAAMiC,UAAUV,sBAAsBvB;IACtC,IAAI,CAACiC,SAAS,OAAOjB;IACrB,MAAMuB,QAAQrC,UAAUa,GAAG,CAACkB,QAAQtB,GAAG;IACvC,OAAO4B,QAAQ;QAAEA;QAAOd,QAAQQ,QAAQR,MAAM;IAAC,IAAIT;AACrD;AAEA;;;;CAIC,GACD,OAAO,SAASwB,2BAA2BxC,IAAY;QAEpCK;IADjB,MAAM4B,UAAUV,sBAAsBvB;IACtC,OAAOiC,UAAU5B,EAAAA,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkCoC,aAAa,MAAK,OAAO;AAC9E;AAEA;;;;CAIC,GACD,OAAO,SAASC,yBAAyB1C,IAAY;QAElCK;IADjB,MAAM4B,UAAUV,sBAAsBvB;IACtC,OAAOiC,UAAU5B,EAAAA,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkCsC,WAAW,MAAK,OAAO;AAC5E;AAEA,qEAAqE,GACrE,MAAMC,aAAa;AAEnB,MAAMC,YAAY,CAACC,QACjB,OAAOA,UAAU,YAAYF,WAAWG,IAAI,CAACD,SAASA,QAAQ;AAEhE;;;;;;;;;CASC,GACD,OAAO,eAAeE,+BACpBhD,IAAY,EACZiD,OAAgB;QAGU5C;IAD1B,MAAM4B,UAAUV,sBAAsBvB;IACtC,MAAMkD,UAAUjB,WAAU5B,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkC8C,OAAO,GAAGnC;IACtE,IAAI,CAACkC,SAAS,OAAO;IACrB,IAAI;QACF,MAAMC,UAAU,MAAMD,QAAQD,QAAQG,KAAK;QAC3C,IAAI,CAACD,SAAS,OAAO;QACrB,MAAME,QAAQR,UAAUM,QAAQE,KAAK;QACrC,MAAMC,MAAMT,UAAUM,QAAQG,GAAG;QACjC,IAAI,CAACD,SAAS,CAACC,KAAK,OAAO;QAC3B,OAAO;YAAED;WAAWC,MAAM;YAAEA;QAAI,IAAI,CAAC;IACvC,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,sBAAsBvD,IAAY;QAClCsC;IAAd,MAAMC,SAAQD,yBAAAA,sBAAsBtC,0BAAtBsC,uBAA6BC,KAAK;IAChD,OAAO,OAAOA,UAAU,aAAaA,QAAQvB;AAC/C;AAEA;;;;;CAKC,GACD,OAAO,eAAewC,kBACpBC,KAAqB,EACrBR,OAAgB,EAChBxB,MAAyC,EACzCiC,SAIsB;IAEtB,MAAMC,SAAS,aAAKlC,QAAWgC,MAAMhC,MAAM;IAC3C,MAAMc,QAAQkB,MAAMlB,KAAK;IACzB,IAAI,OAAOA,UAAU,YAAYA,UAAU,QAAQ,OAAOA,MAAMqB,GAAG,KAAK,YAAY;QAClF,OAAOrB,MAAMqB,GAAG,CAACX,SAAS;YAAExB,QAAQkC;QAAO;IAC7C;IACA,OAAOD,UAAUD,MAAMlB,KAAK,EAAsBU,SAASU;AAC7D;AAEA,uCAAuC,GACvC,OAAO,SAASE;IACd,OAAOC,MAAMC,IAAI,CAAC7D,UAAU2B,IAAI;AAClC"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/api-plugins.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 * Server half of the plugin pattern (AGL-396): the API-route registry, the\n * server counterpart to the ConsoleExtension registry. A feature plugin\n * registers request handlers here from its `/server` entry point (never its\n * client barrel, so firebase-admin and other server-only deps stay out of\n * the browser bundle). The app ships one catch-all dispatcher route per\n * Next app that resolves a request path to a registered handler — so moving\n * a feature's API into its plugin needs no new app route and keeps the same\n * URL. Reference implementation: events-calendar `events/list` (AGL-396).\n *\n * The request/response shapes are structural (not `next` types) so plugins\n * stay framework-light; `NextApiRequest`/`NextApiResponse` satisfy them, so\n * the dispatcher passes Next's objects straight through.\n */\n\nexport interface PluginApiRequest {\n method?: string\n query: Partial<Record<string, string | string[]>>\n // `any` mirrors NextApiRequest.body (parsed JSON/form/text); handlers\n // validate it themselves, as they did as Next routes.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n body: any\n /**\n * The unparsed request text, for handlers that verify signatures over the\n * exact payload (Stripe/Svix webhooks registered as plugin API paths).\n * UTF-8 re-encoding of the text is byte-identical for valid UTF-8 JSON.\n */\n rawBody?: string\n headers: Partial<Record<string, string | string[]>>\n cookies: Partial<Record<string, string>>\n /**\n * Node socket — the client-address fallback (`remoteAddress`).\n *\n * On the console's Pages Router this is the real transport peer. Behind the\n * App Router adapter there is no socket, and `api-adapter.ts` fills it from\n * the shared client-address reader so a handler falling back to it gets the\n * same trusted hop rather than a caller-supplied one.\n */\n socket: { remoteAddress?: string }\n}\n\nexport interface PluginApiResponse {\n status(code: number): PluginApiResponse\n json(body: unknown): void\n send(body: unknown): void\n setHeader(name: string, value: string | number | readonly string[]): void\n redirect(url: string): void\n redirect(status: number, url: string): void\n end(): void\n}\n\nexport type PluginApiHandler = (\n req: PluginApiRequest,\n res: PluginApiResponse,\n) => void | Promise<void>\n\n/**\n * A handler in the Web shape (AGL-2939): the dispatcher's own `Request`\n * in, a `Response` out — with a streamed body when the door streams, as\n * the assistant's chat and a job's event feed do. `params` carries the\n * dispatcher's path segments and any `:name` segment the registered path\n * declared.\n */\nexport type PluginWebApiHandler = (\n request: Request,\n context: { params: Record<string, string | string[]> },\n) => Response | Promise<Response>\n\n/** What a route registers: the (req, res) shape, or a Web handler. */\nexport type PluginApiRoute = PluginApiHandler | { web: PluginWebApiHandler }\n\n/** A registered route matched to a request path, with its `:name` segments filled. */\nexport interface PluginApiMatch {\n route: PluginApiRoute\n params: Record<string, string>\n}\n\n/**\n * Who a request is FOR, as far as the dispatchers' release gate is concerned\n * (AGL-2978).\n *\n * The gate buckets a rollout and reads per-org overrides on an org id, and a\n * dispatcher learns that id from the site a request names (`hostId`). Two\n * kinds of request name no site and still belong to one organization:\n *\n * - a request to an ORGANIZATION-level surface (`ConsoleExtension.orgNavItems`,\n * AGL-2974), which names its org rather than a site;\n * - a request that arrives with no bearer token by design — a provider's\n * OAuth redirect back to the platform, which carries only what the platform\n * signed into it.\n *\n * Without a subject the gate treats both as anonymous, so a plugin released to\n * one organization by override refused that organization's own requests, and\n * a staff member previewing a dark plugin could not complete a redirect their\n * own session had started.\n */\nexport interface PluginApiRequestSubject {\n /** The organization: the rollout bucket and the override key. */\n orgId: string | null\n /**\n * The account the request acts for, read ONLY when the request carries no\n * bearer token — a token speaks for itself. A route may name one only when\n * it verified a signature binding the uid to the request, because the gate\n * lets a staff account named here preview a released-off plugin.\n */\n uid?: string | null\n}\n\n/** Reads a request's subject; answers `null` when the request names none. */\nexport type PluginApiSubjectResolver = (\n request: Request,\n) => PluginApiRequestSubject | null | Promise<PluginApiRequestSubject | null>\n\n/** What a registration may say about its route beyond the handler. */\nexport interface PluginApiRouteOptions {\n /**\n * How a request to this route names its subject when it names no site.\n * Consulted by both dispatchers before their release gate, and only for a\n * request with no `hostId`: a named site always decides the org.\n */\n subject?: PluginApiSubjectResolver\n /**\n * The route answers a link the platform MAILED to somebody — an\n * unsubscribe in a message's `List-Unsubscribe` header — rather than a door\n * a member uses (AGL-2981).\n *\n * Such a link has to keep working whether or not its plugin is released,\n * or switched on, for the workspace NOW. A recipient's way out does not\n * close when a rollout is paused or a workspace turns the plugin off:\n * CAN-SPAM holds an opt-out open for thirty days after the send, and an\n * unsubscribe that 404s the day a kill switch is flipped is one nobody can\n * use. So both dispatchers skip their per-site enablement and release\n * gates for it — and nothing else: lockdown and the rate limit still\n * apply. The flag says nothing about who may act, so the route\n * authenticates the link itself, by a signature it verifies.\n */\n recipientLink?: boolean\n /**\n * The route is called by a MACHINE that proves itself with a credential\n * the route verifies — a scheduler's shared secret, a provider's signed\n * webhook — and names no site and no member (AGL-3080).\n *\n * Such a request has no subject the dispatcher could gate on before the\n * route has verified it: a sweep spans every organization, and a webhook's\n * workspace is inside a payload only a verified signature makes worth\n * reading. Gated anonymously, a plugin on a partial rollout would refuse\n * every delivery. So both dispatchers skip their per-site enablement and\n * release gates for it, and — because a chunked sweep posts from one\n * address, and a provider retries in bursts — their write limit and their\n * cross-origin check (`isMachinePluginApiPath`); lockdown still applies.\n * The flag grants nothing by itself: the route authenticates its caller,\n * and judges the plugin's release and plan for each organization it\n * resolves from what it verified.\n */\n machine?: boolean\n /**\n * The route hands a workspace the records a plugin keeps for it — a\n * data-portability export — which the workspace is owed whether or not the\n * plugin is switched on or released for it now (AGL-3080).\n *\n * Exporting the people a workspace holds is an obligation, not a feature:\n * a workspace that switched the plugin off, or a plugin paused by its\n * kill switch, still has to be able to take its data away. So both\n * dispatchers skip their per-site enablement and release gates for it —\n * and nothing else: lockdown, the email-verification gate and the rate\n * limit still apply. The flag grants nothing by itself: the route\n * authenticates the member, and asks the plugin's release and plan itself\n * of anything it serves beyond what the workspace is owed.\n */\n portability?: boolean\n /**\n * The route OPENS A CARD PAYMENT for whoever calls it: a Checkout Session,\n * a PaymentIntent or a SetupIntent (AGL-3363).\n *\n * The tenant dispatcher holds such a route to the card-testing counters\n * (`card-payment-velocity.ts`) on top of its ordinary write limit, so a\n * plugin's payment door is covered by declaring itself here rather than by\n * carrying a limiter of its own. A console door (a signed-in merchant's\n * POS or draft order) does not declare it: a register ringing up a queue\n * is not a card tester. The 3-D Secure sweep\n * (`card-payments-request-3ds.spec.ts`) classifies every file that creates\n * a payment, so a new visitor door cannot skip the declaration.\n */\n cardPayment?: boolean\n}\n\n/** Leading/trailing slashes stripped so '/events/list' and 'events/list' key alike. */\nfunction normalizeApiPath(path: string): string {\n return path.replace(/^\\/+|\\/+$/g, '')\n}\n\nconst apiRoutes = new Map<string, PluginApiRoute>()\nconst apiRouteOwners = new Map<string, string>()\nconst apiRouteOptions = new Map<string, PluginApiRouteOptions>()\n\n/**\n * The \"currently registering plugin\" marker moved to a leaf module\n * (`registering-plugin`) so `site-page-hooks` can read it too — this file is\n * only reachable through the `/server` entry, and importing it from the shared\n * plugin-manager barrel formed a cycle (AGL-1289). Re-exported here so the\n * plugin loader's existing import keeps working unchanged.\n */\nimport { getRegisteringPluginId } from './registering-plugin'\n\nexport {\n setRegisteringPluginId,\n getRegisteringPluginId,\n} from './registering-plugin'\n\n/**\n * Registers a plugin API handler at a host-relative path (e.g.\n * 'events/list'), served by the app dispatcher at `/api/events/list`.\n *\n * Idempotent by path FOR ITS OWNER — re-registration by the plugin that\n * holds the path replaces the previous handler, which is what a hot reload,\n * a second surface, and a repeated init all depend on.\n *\n * A different plugin is REFUSED (AGL-2484). This map is process-global and\n * was last-writer-wins on the path alone, while Aglyn's own commission-taking\n * handlers sit at `marketplace/checkout` and `commerce/checkout` — and the\n * tenant dispatcher calls `ensureRemoteServerBundles()` after the first-party\n * loader has run, so a remote bundle registering either path was simply the\n * later writer. Getting there needs `PLUGIN_REMOTE_SERVER=enabled` (off by\n * default), realm trust and a valid signature, so this is the last of several\n * doors rather than the only one; it is also the one the registry could close\n * by itself, because it already recorded who registered what.\n *\n * Anonymous registration — no loader marker — is an IDENTITY here, not a\n * wildcard: it may replace itself and nothing else may take it.\n */\nexport function registerPluginApiRoute(\n path: string,\n handler: PluginApiRoute,\n options?: PluginApiRouteOptions,\n): void {\n const key = normalizeApiPath(path)\n const owner = getRegisteringPluginId() ?? ANONYMOUS_OWNER\n const incumbent = apiRouteOwners.get(key)\n if (incumbent !== undefined && incumbent !== owner) {\n // Loud, because the silent version of this is a payment route quietly\n // answering from someone else's code. Refusing is the safe direction:\n // the incumbent keeps serving.\n console.error(\n `[plugins] refused API route \"${key}\" to \"${owner}\": already ` +\n `registered by \"${incumbent}\"`,\n )\n return\n }\n apiRoutes.set(key, handler)\n apiRouteOwners.set(key, owner)\n // Replaced with the handler, never merged: a re-registration that drops its\n // subject resolver must not keep answering with the previous one.\n if (options) apiRouteOptions.set(key, options)\n else apiRouteOptions.delete(key)\n}\n\n/** The marker for a registration made outside any loader context. */\nconst ANONYMOUS_OWNER = ''\n\n/** The plugin that registered a path, for per-org enablement gating. */\nexport function pluginIdForRegisteredApiPath(path: string): string | undefined {\n const owner = apiRouteOwners.get(normalizeApiPath(path))\n // An anonymous registration has no plugin to gate on, and this has always\n // answered `undefined` for one — the sentinel must not leak out as an id.\n return owner === undefined || owner === ANONYMOUS_OWNER ? undefined : owner\n}\n\nexport function unregisterPluginApiRoute(path: string): void {\n const key = normalizeApiPath(path)\n apiRoutes.delete(key)\n // Release the CLAIM too, or the refusal above outlives the route and the\n // path can never be re-registered for the life of the process.\n apiRouteOwners.delete(key)\n apiRouteOptions.delete(key)\n}\n\n/**\n * The registered key a request path resolves to, with its `:name` segments.\n *\n * An exact registration wins; otherwise a registration whose path carries\n * `:name` segments matches segment for segment. One matcher for the route and\n * for its options, so a request can never be answered by one registration and\n * gated with another's subject.\n */\nfunction matchRegisteredApiKey(\n path: string,\n): { key: string; params: Record<string, string> } | undefined {\n const key = normalizeApiPath(path)\n if (apiRoutes.has(key)) return { key, params: {} }\n const segments = key.split('/')\n for (const registered of apiRoutes.keys()) {\n if (!registered.includes(':')) continue\n const pattern = registered.split('/')\n if (pattern.length !== segments.length) continue\n const params: Record<string, string> = {}\n let matched = true\n for (let index = 0; index < pattern.length; index += 1) {\n const part = pattern[index]\n if (part.startsWith(':') && segments[index]) {\n params[part.slice(1)] = segments[index]\n } else if (part !== segments[index]) {\n matched = false\n break\n }\n }\n if (matched) return { key: registered, params }\n }\n return undefined\n}\n\n/**\n * The route for a request path, or undefined when nothing is registered.\n *\n * An exact registration wins; otherwise a registration whose path carries\n * `:name` segments matches segment for segment (`ai/jobs/:jobId/cancel`\n * answers `ai/jobs/abc/cancel`), and the named segments come back as\n * `params`. Patterns are tried in registration order and a static segment\n * beats a named one only by being registered exactly.\n */\nexport function resolvePluginApiMatch(path: string): PluginApiMatch | undefined {\n const matched = matchRegisteredApiKey(path)\n if (!matched) return undefined\n const route = apiRoutes.get(matched.key)\n return route ? { route, params: matched.params } : undefined\n}\n\n/**\n * Whether a request path resolves to a route registered as a\n * {@link PluginApiRouteOptions.recipientLink} — what both dispatchers ask\n * before their enablement and release gates (AGL-2981).\n */\nexport function isPluginRecipientLinkRoute(path: string): boolean {\n const matched = matchRegisteredApiKey(path)\n return matched ? apiRouteOptions.get(matched.key)?.recipientLink === true : false\n}\n\n/**\n * Whether a request path resolves to a route registered as a\n * {@link PluginApiRouteOptions.machine} caller's — what both dispatchers ask\n * before their enablement and release gates, and the write limits before\n * they count (AGL-3080).\n */\nexport function isPluginMachineRoute(path: string): boolean {\n const matched = matchRegisteredApiKey(path)\n return matched ? apiRouteOptions.get(matched.key)?.machine === true : false\n}\n\n/**\n * Whether a request path resolves to a route registered as a\n * {@link PluginApiRouteOptions.portability} export — what both dispatchers\n * ask before their enablement and release gates (AGL-3080).\n */\nexport function isPluginPortabilityRoute(path: string): boolean {\n const matched = matchRegisteredApiKey(path)\n return matched ? apiRouteOptions.get(matched.key)?.portability === true : false\n}\n\n/**\n * Whether a request path resolves to a route registered with\n * {@link PluginApiRouteOptions.cardPayment} — what the tenant dispatcher\n * asks before its card-testing counters (AGL-3363).\n */\nexport function isPluginCardPaymentRoute(path: string): boolean {\n const matched = matchRegisteredApiKey(path)\n return matched ? apiRouteOptions.get(matched.key)?.cardPayment === true : false\n}\n\n/** An id a subject may carry: a non-empty path segment, not a path. */\nconst SUBJECT_ID = /^[^/\\s]{1,128}$/\n\nconst subjectId = (value: unknown): string | null =>\n typeof value === 'string' && SUBJECT_ID.test(value) ? value : null\n\n/**\n * The subject a request to a registered route names, or `null` (AGL-2978).\n *\n * `null` when the route declared no resolver, when the resolver answered\n * nothing, and when it THREW: a resolver that cannot read its request has\n * named no subject, and the gate then treats the request as anonymous — the\n * conservative direction. The resolver reads a clone, so the handler still\n * receives the untouched body. Ids are held to a plain path segment, so a\n * resolver cannot hand the gate a path to read overrides from.\n */\nexport async function resolvePluginApiRequestSubject(\n path: string,\n request: Request,\n): Promise<PluginApiRequestSubject | null> {\n const matched = matchRegisteredApiKey(path)\n const resolve = matched ? apiRouteOptions.get(matched.key)?.subject : undefined\n if (!resolve) return null\n try {\n const subject = await resolve(request.clone())\n if (!subject) return null\n const orgId = subjectId(subject.orgId)\n const uid = subjectId(subject.uid)\n if (!orgId && !uid) return null\n return { orgId, ...(uid ? { uid } : {}) }\n } catch {\n return null\n }\n}\n\n/**\n * The legacy (node-style) handler at a request path, or undefined when\n * nothing is registered there or the route is a web handler — a web route\n * is reached through `resolvePluginApiMatch` and `runPluginApiMatch`, since\n * it takes the `Request` and the filled `:name` segments, not a `req`/`res`\n * pair.\n */\nexport function resolvePluginApiRoute(path: string): PluginApiHandler | undefined {\n const route = resolvePluginApiMatch(path)?.route\n return typeof route === 'function' ? route : undefined\n}\n\n/**\n * Runs a matched route against the dispatcher's request: a Web handler is\n * called as it is, and a (req, res) handler goes through the adapter the\n * caller supplies — the adapter lives on the `/server` entry, which this\n * module may not import.\n */\nexport async function runPluginApiMatch(\n match: PluginApiMatch,\n request: Request,\n params: Record<string, string | string[]>,\n runLegacy: (\n handler: PluginApiHandler,\n request: Request,\n params: Record<string, string | string[]>,\n ) => Promise<Response>,\n): Promise<Response> {\n const merged = { ...params, ...match.params }\n const route = match.route as { web?: PluginWebApiHandler }\n if (typeof route === 'object' && route !== null && typeof route.web === 'function') {\n return route.web(request, { params: merged })\n }\n return runLegacy(match.route as PluginApiHandler, request, merged)\n}\n\n/** Registered paths, for diagnostics. */\nexport function listPluginApiRoutes(): string[] {\n return Array.from(apiRoutes.keys())\n}\n"],"names":["normalizeApiPath","path","replace","apiRoutes","Map","apiRouteOwners","apiRouteOptions","getRegisteringPluginId","setRegisteringPluginId","registerPluginApiRoute","handler","options","key","owner","ANONYMOUS_OWNER","incumbent","get","undefined","console","error","set","delete","pluginIdForRegisteredApiPath","unregisterPluginApiRoute","matchRegisteredApiKey","has","params","segments","split","registered","keys","includes","pattern","length","matched","index","part","startsWith","slice","resolvePluginApiMatch","route","isPluginRecipientLinkRoute","recipientLink","isPluginMachineRoute","machine","isPluginPortabilityRoute","portability","isPluginCardPaymentRoute","cardPayment","SUBJECT_ID","subjectId","value","test","resolvePluginApiRequestSubject","request","resolve","subject","clone","orgId","uid","resolvePluginApiRoute","runPluginApiMatch","match","runLegacy","merged","web","listPluginApiRoutes","Array","from"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;CAaC;AA6KD,qFAAqF,GACrF,SAASA,iBAAiBC,IAAY;IACpC,OAAOA,KAAKC,OAAO,CAAC,cAAc;AACpC;AAEA,MAAMC,YAAY,IAAIC;AACtB,MAAMC,iBAAiB,IAAID;AAC3B,MAAME,kBAAkB,IAAIF;AAE5B;;;;;;CAMC,GACD,SAASG,sBAAsB,QAAQ,0BAAsB;AAE7D,SACEC,sBAAsB,EACtBD,sBAAsB,QACjB,0BAAsB;AAE7B;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASE,uBACdR,IAAY,EACZS,OAAuB,EACvBC,OAA+B;QAGjBJ;IADd,MAAMK,MAAMZ,iBAAiBC;IAC7B,MAAMY,SAAQN,0BAAAA,oCAAAA,0BAA4BO;IAC1C,MAAMC,YAAYV,eAAeW,GAAG,CAACJ;IACrC,IAAIG,cAAcE,aAAaF,cAAcF,OAAO;QAClD,sEAAsE;QACtE,sEAAsE;QACtE,+BAA+B;QAC/BK,QAAQC,KAAK,CACX,CAAC,6BAA6B,EAAEP,IAAI,MAAM,EAAEC,MAAM,WAAW,CAAC,GAC5D,CAAC,eAAe,EAAEE,UAAU,CAAC,CAAC;QAElC;IACF;IACAZ,UAAUiB,GAAG,CAACR,KAAKF;IACnBL,eAAee,GAAG,CAACR,KAAKC;IACxB,4EAA4E;IAC5E,kEAAkE;IAClE,IAAIF,SAASL,gBAAgBc,GAAG,CAACR,KAAKD;SACjCL,gBAAgBe,MAAM,CAACT;AAC9B;AAEA,mEAAmE,GACnE,MAAME,kBAAkB;AAExB,sEAAsE,GACtE,OAAO,SAASQ,6BAA6BrB,IAAY;IACvD,MAAMY,QAAQR,eAAeW,GAAG,CAAChB,iBAAiBC;IAClD,0EAA0E;IAC1E,0EAA0E;IAC1E,OAAOY,UAAUI,aAAaJ,UAAUC,kBAAkBG,YAAYJ;AACxE;AAEA,OAAO,SAASU,yBAAyBtB,IAAY;IACnD,MAAMW,MAAMZ,iBAAiBC;IAC7BE,UAAUkB,MAAM,CAACT;IACjB,yEAAyE;IACzE,+DAA+D;IAC/DP,eAAegB,MAAM,CAACT;IACtBN,gBAAgBe,MAAM,CAACT;AACzB;AAEA;;;;;;;CAOC,GACD,SAASY,sBACPvB,IAAY;IAEZ,MAAMW,MAAMZ,iBAAiBC;IAC7B,IAAIE,UAAUsB,GAAG,CAACb,MAAM,OAAO;QAAEA;QAAKc,QAAQ,CAAC;IAAE;IACjD,MAAMC,WAAWf,IAAIgB,KAAK,CAAC;IAC3B,KAAK,MAAMC,cAAc1B,UAAU2B,IAAI,GAAI;QACzC,IAAI,CAACD,WAAWE,QAAQ,CAAC,MAAM;QAC/B,MAAMC,UAAUH,WAAWD,KAAK,CAAC;QACjC,IAAII,QAAQC,MAAM,KAAKN,SAASM,MAAM,EAAE;QACxC,MAAMP,SAAiC,CAAC;QACxC,IAAIQ,UAAU;QACd,IAAK,IAAIC,QAAQ,GAAGA,QAAQH,QAAQC,MAAM,EAAEE,SAAS,EAAG;YACtD,MAAMC,OAAOJ,OAAO,CAACG,MAAM;YAC3B,IAAIC,KAAKC,UAAU,CAAC,QAAQV,QAAQ,CAACQ,MAAM,EAAE;gBAC3CT,MAAM,CAACU,KAAKE,KAAK,CAAC,GAAG,GAAGX,QAAQ,CAACQ,MAAM;YACzC,OAAO,IAAIC,SAAST,QAAQ,CAACQ,MAAM,EAAE;gBACnCD,UAAU;gBACV;YACF;QACF;QACA,IAAIA,SAAS,OAAO;YAAEtB,KAAKiB;YAAYH;QAAO;IAChD;IACA,OAAOT;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASsB,sBAAsBtC,IAAY;IAChD,MAAMiC,UAAUV,sBAAsBvB;IACtC,IAAI,CAACiC,SAAS,OAAOjB;IACrB,MAAMuB,QAAQrC,UAAUa,GAAG,CAACkB,QAAQtB,GAAG;IACvC,OAAO4B,QAAQ;QAAEA;QAAOd,QAAQQ,QAAQR,MAAM;IAAC,IAAIT;AACrD;AAEA;;;;CAIC,GACD,OAAO,SAASwB,2BAA2BxC,IAAY;QAEpCK;IADjB,MAAM4B,UAAUV,sBAAsBvB;IACtC,OAAOiC,UAAU5B,EAAAA,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkCoC,aAAa,MAAK,OAAO;AAC9E;AAEA;;;;;CAKC,GACD,OAAO,SAASC,qBAAqB1C,IAAY;QAE9BK;IADjB,MAAM4B,UAAUV,sBAAsBvB;IACtC,OAAOiC,UAAU5B,EAAAA,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkCsC,OAAO,MAAK,OAAO;AACxE;AAEA;;;;CAIC,GACD,OAAO,SAASC,yBAAyB5C,IAAY;QAElCK;IADjB,MAAM4B,UAAUV,sBAAsBvB;IACtC,OAAOiC,UAAU5B,EAAAA,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkCwC,WAAW,MAAK,OAAO;AAC5E;AAEA;;;;CAIC,GACD,OAAO,SAASC,yBAAyB9C,IAAY;QAElCK;IADjB,MAAM4B,UAAUV,sBAAsBvB;IACtC,OAAOiC,UAAU5B,EAAAA,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkC0C,WAAW,MAAK,OAAO;AAC5E;AAEA,qEAAqE,GACrE,MAAMC,aAAa;AAEnB,MAAMC,YAAY,CAACC,QACjB,OAAOA,UAAU,YAAYF,WAAWG,IAAI,CAACD,SAASA,QAAQ;AAEhE;;;;;;;;;CASC,GACD,OAAO,eAAeE,+BACpBpD,IAAY,EACZqD,OAAgB;QAGUhD;IAD1B,MAAM4B,UAAUV,sBAAsBvB;IACtC,MAAMsD,UAAUrB,WAAU5B,uBAAAA,gBAAgBU,GAAG,CAACkB,QAAQtB,GAAG,sBAA/BN,qBAAkCkD,OAAO,GAAGvC;IACtE,IAAI,CAACsC,SAAS,OAAO;IACrB,IAAI;QACF,MAAMC,UAAU,MAAMD,QAAQD,QAAQG,KAAK;QAC3C,IAAI,CAACD,SAAS,OAAO;QACrB,MAAME,QAAQR,UAAUM,QAAQE,KAAK;QACrC,MAAMC,MAAMT,UAAUM,QAAQG,GAAG;QACjC,IAAI,CAACD,SAAS,CAACC,KAAK,OAAO;QAC3B,OAAO;YAAED;WAAWC,MAAM;YAAEA;QAAI,IAAI,CAAC;IACvC,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,sBAAsB3D,IAAY;QAClCsC;IAAd,MAAMC,SAAQD,yBAAAA,sBAAsBtC,0BAAtBsC,uBAA6BC,KAAK;IAChD,OAAO,OAAOA,UAAU,aAAaA,QAAQvB;AAC/C;AAEA;;;;;CAKC,GACD,OAAO,eAAe4C,kBACpBC,KAAqB,EACrBR,OAAgB,EAChB5B,MAAyC,EACzCqC,SAIsB;IAEtB,MAAMC,SAAS,aAAKtC,QAAWoC,MAAMpC,MAAM;IAC3C,MAAMc,QAAQsB,MAAMtB,KAAK;IACzB,IAAI,OAAOA,UAAU,YAAYA,UAAU,QAAQ,OAAOA,MAAMyB,GAAG,KAAK,YAAY;QAClF,OAAOzB,MAAMyB,GAAG,CAACX,SAAS;YAAE5B,QAAQsC;QAAO;IAC7C;IACA,OAAOD,UAAUD,MAAMtB,KAAK,EAAsBc,SAASU;AAC7D;AAEA,uCAAuC,GACvC,OAAO,SAASE;IACd,OAAOC,MAAMC,IAAI,CAACjE,UAAU2B,IAAI;AAClC"}
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type ResolvedProvenance } from './
|
|
17
|
+
import { type ResolvedProvenance } from './artifact-provenance';
|
|
18
18
|
/**
|
|
19
19
|
* The override layer (AGL-1019) — own the patch, never the copy.
|
|
20
20
|
*
|
|
21
|
-
* Customising
|
|
21
|
+
* Customising an installed artifact and updating it are only compatible if
|
|
22
22
|
* they are two different documents. Today they are one: you change a theme's
|
|
23
23
|
* primary colour and that edits the theme, so the publisher's v2 can be taken
|
|
24
24
|
* only by discarding your colour, or refused only by missing their fix.
|
|
@@ -34,10 +34,13 @@ import { type ResolvedProvenance } from './marketplace-provenance';
|
|
|
34
34
|
* * a conflict is narrow: only where the patch touches a path the update also
|
|
35
35
|
* changed, which the marketplace plugin's three-way update plan already names.
|
|
36
36
|
*
|
|
37
|
-
* Pure and content-shaped
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* Pure and content-shaped: it knows nothing about Firestore, artifact types
|
|
38
|
+
* or who installed the artifact, so one resolver serves a theme, a node tree
|
|
39
|
+
* and a plugin settings map, and every rule below is testable without a
|
|
40
|
+
* database. The platform's rather than the installer's (AGL-3080), because the
|
|
41
|
+
* readers are: the site's theme resolution, the theme editor and its library.
|
|
42
|
+
* The three-way update plan that writes a new base under a patch is the
|
|
43
|
+
* installer's own (the marketplace's `model/artifact-merge.ts`).
|
|
41
44
|
*
|
|
42
45
|
* ## Which "base" a reader resolves against
|
|
43
46
|
*
|
|
@@ -14,11 +14,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import { stableStringify } from "./
|
|
17
|
+
*/ import { stableStringify } from "./artifact-provenance.js";
|
|
18
18
|
/**
|
|
19
19
|
* The override layer (AGL-1019) — own the patch, never the copy.
|
|
20
20
|
*
|
|
21
|
-
* Customising
|
|
21
|
+
* Customising an installed artifact and updating it are only compatible if
|
|
22
22
|
* they are two different documents. Today they are one: you change a theme's
|
|
23
23
|
* primary colour and that edits the theme, so the publisher's v2 can be taken
|
|
24
24
|
* only by discarding your colour, or refused only by missing their fix.
|
|
@@ -34,10 +34,13 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
34
34
|
* * a conflict is narrow: only where the patch touches a path the update also
|
|
35
35
|
* changed, which the marketplace plugin's three-way update plan already names.
|
|
36
36
|
*
|
|
37
|
-
* Pure and content-shaped
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* Pure and content-shaped: it knows nothing about Firestore, artifact types
|
|
38
|
+
* or who installed the artifact, so one resolver serves a theme, a node tree
|
|
39
|
+
* and a plugin settings map, and every rule below is testable without a
|
|
40
|
+
* database. The platform's rather than the installer's (AGL-3080), because the
|
|
41
|
+
* readers are: the site's theme resolution, the theme editor and its library.
|
|
42
|
+
* The three-way update plan that writes a new base under a patch is the
|
|
43
|
+
* installer's own (the marketplace's `model/artifact-merge.ts`).
|
|
41
44
|
*
|
|
42
45
|
* ## Which "base" a reader resolves against
|
|
43
46
|
*
|
|
@@ -459,4 +462,4 @@ function resolveKeyedArray(base, patch) {
|
|
|
459
462
|
return cursor;
|
|
460
463
|
}
|
|
461
464
|
|
|
462
|
-
//# sourceMappingURL=
|
|
465
|
+
//# sourceMappingURL=artifact-overrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/artifact-overrides.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n stableStringify,\n type ResolvedProvenance,\n} from './artifact-provenance'\n\n/**\n * The override layer (AGL-1019) — own the patch, never the copy.\n *\n * Customising an installed artifact and updating it are only compatible if\n * they are two different documents. Today they are one: you change a theme's\n * primary colour and that edits the theme, so the publisher's v2 can be taken\n * only by discarding your colour, or refused only by missing their fix.\n *\n * Here the publisher's version stays untouched as the **base** — AGL-1015\n * already stores it, content-addressed and immutable, in\n * `marketplaceArtifactBases/{sha256}` — the user's changes are a **sparse\n * patch** stored beside it, and readers resolve `base ⊕ patch`. Then:\n *\n * * updating replaces the base wholesale and the patch still applies;\n * * \"what did I change\" IS the patch, with no diffing at read time;\n * * \"reset to the publisher's version\" is deleting the patch;\n * * a conflict is narrow: only where the patch touches a path the update also\n * changed, which the marketplace plugin's three-way update plan already names.\n *\n * Pure and content-shaped: it knows nothing about Firestore, artifact types\n * or who installed the artifact, so one resolver serves a theme, a node tree\n * and a plugin settings map, and every rule below is testable without a\n * database. The platform's rather than the installer's (AGL-3080), because the\n * readers are: the site's theme resolution, the theme editor and its library.\n * The three-way update plan that writes a new base under a patch is the\n * installer's own (the marketplace's `model/artifact-merge.ts`).\n *\n * ## Which \"base\" a reader resolves against\n *\n * The installed document's OWN content fields — whatever the install route\n * wrote — are the base at read time, and {@link resolveArtifactContent} takes\n * them directly. `marketplaceArtifactBases` is not that: it is closed to\n * clients in both directions (a base can hold paid content, and its key is on\n * the installed document, so a readable collection would hand out any listing's\n * content by hash). It is the immutable snapshot the server diffs and resets\n * against, nothing more.\n *\n * The two agree because an update writes the publisher's new version onto the\n * document and stamps a base at that same hash in one operation — which is the\n * property that makes \"never mutate the vendored artifact\" enforceable rather\n * than merely intended. The moment a user edit lands in those content fields\n * instead of in the override, both the diff and this resolver are wrong.\n *\n * ## Two rules that are load-bearing\n *\n * **Deletion needs a sentinel, and it is not `null`.** A sparse patch says\n * nothing about paths it omits, so \"omit it to remove it\" is unavailable —\n * omission already means \"inherit\". `null` cannot serve either: it is a real\n * value in this codebase's content, and the besigner's `null`-means-cleared\n * convention is what took every `/product/*` page down in AGL-1226. So removal\n * is spelled with {@link OVERRIDE_DELETE}, a value nothing else produces.\n *\n * **Arrays are never merged by index.** An insert at position 0 shifts every\n * element after it, so index-wise merging reports — and then writes — a change\n * to every one of them. Arrays therefore replace wholesale by default, which is\n * also how {@link planArtifactUpdate} compares them. Where elements carry a\n * stable identity the patch may instead use the keyed form\n * ({@link keyedArrayPatch}) and edit one element by key, so a publisher who\n * inserts an element does not collide with a user who edited a different one.\n */\n\n/**\n * The removal sentinel: a patch value of exactly this string removes the path\n * from the resolved output.\n *\n * A string rather than a wrapper object because a patch travels through\n * Firestore, JSON responses and React state unchanged, and a scalar survives\n * all three without anyone needing to know it is special. The cost is that an\n * artifact whose real content is this exact string cannot be expressed; that is\n * a deliberate trade for a sentinel that cannot be produced by accident, and\n * {@link diffOverride} is the only thing that emits it.\n *\n * Note this is a field *value*, never a field *name* — Firestore rejects field\n * names matching `__.*__`, and this deliberately has no trailing underscores so\n * that a careless refactor into a key still fails loudly rather than silently.\n */\nexport const OVERRIDE_DELETE = '__aglynOverrideDelete'\n\n/**\n * The field on an installed artifact document that holds its override.\n *\n * Beside the base, never inside it. The document's content fields keep holding\n * the publisher's version verbatim, which is what makes an update a wholesale\n * replacement of those fields and nothing else. Writing the user's edits into\n * the content instead is precisely the state this issue exists to leave.\n */\nexport const ARTIFACT_OVERRIDE_FIELD = 'overrides'\n\n/** The stored override, as written under {@link ARTIFACT_OVERRIDE_FIELD}. */\nexport interface ArtifactOverride {\n /** The sparse patch. Resolved over the artifact's content at read time. */\n patch: unknown\n /**\n * The base the patch was authored against — `installedFrom.sha256`.\n *\n * Recorded, not enforced: a patch stays valid across an update by design, and\n * the only thing this answers is \"which publisher version was on screen when\n * these edits were made\", which is what a conflict report needs to be honest.\n */\n baseSha256: string | null\n updatedAt?: unknown\n updatedBy?: string | null\n}\n\n/**\n * A patch node that edits an array BY KEY instead of replacing it.\n *\n * Only ever produced for arrays whose elements are objects carrying a unique\n * value under one identity field, because that is the only case where \"the\n * third element\" and \"the element the publisher shipped third\" are different\n * questions with the same answer.\n */\nexport interface KeyedArrayPatch {\n /** Discriminator. Present only on this form. */\n keyedBy: string\n /** Per-key sub-patches; a value of {@link OVERRIDE_DELETE} removes the element. */\n entries: Record<string, unknown>\n /**\n * The full key order, when the user reordered the array.\n *\n * Absent when they did not — and absence is the point: an array with no\n * `order` lets a publisher insert an element and have it appear, while an\n * array with one pins the order the user chose. Keys present in the base but\n * missing from `order` keep their relative position at the end, so a stale\n * `order` never drops an element.\n */\n order?: string[]\n}\n\n/** Identity fields tried, in order, when deciding whether an array is keyed. */\nconst KEY_FIELDS = ['id', 'key', 'slug', 'name'] as const\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value != null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n (Object.getPrototypeOf(value) === Object.prototype ||\n Object.getPrototypeOf(value) === null)\n\nconst same = (a: unknown, b: unknown): boolean =>\n stableStringify(a) === stableStringify(b)\n\nexport const isDeleteSentinel = (value: unknown): boolean =>\n value === OVERRIDE_DELETE\n\nexport function isKeyedArrayPatch(value: unknown): value is KeyedArrayPatch {\n return (\n isPlainObject(value) &&\n typeof value['keyedBy'] === 'string' &&\n isPlainObject(value['entries'])\n )\n}\n\n/**\n * The identity field this array is keyed by, or null when it is not keyed.\n *\n * Every element must be a plain object carrying the same field, and every value\n * must be a non-empty string that appears once. A single duplicate or missing\n * key makes the whole array unkeyed rather than partly keyed — a patch that can\n * address only some elements is worse than one that addresses none, because the\n * gaps are invisible at the call site.\n */\nexport function arrayKeyField(value: unknown): string | null {\n if (!Array.isArray(value) || !value.length) return null\n if (!value.every(isPlainObject)) return null\n for (const field of KEY_FIELDS) {\n const keys = value.map((entry) => (entry as Record<string, unknown>)[field])\n if (!keys.every((key) => typeof key === 'string' && key !== '')) continue\n if (new Set(keys as string[]).size !== keys.length) continue\n return field\n }\n return null\n}\n\n/** Builds a keyed-array patch, dropping it entirely when it says nothing. */\nfunction keyedArrayPatch(\n keyedBy: string,\n entries: Record<string, unknown>,\n order: string[] | undefined,\n): KeyedArrayPatch | undefined {\n if (!Object.keys(entries).length && !order) return undefined\n return { keyedBy, entries, ...(order ? { order } : {}) }\n}\n\n/**\n * `base ⊕ patch` — the resolved view every reader sees.\n *\n * Deep-merges plain objects; everything else the patch names replaces what the\n * base holds. The base is never mutated: each level is copied on write, so a\n * caller can resolve the same base against two patches and get two answers.\n *\n * A patch that names a path the base does not have simply adds it. That is what\n * makes the same function serve \"override a publisher's field\" and \"set a field\n * the publisher never shipped\", and it is why a resolved artifact can be richer\n * than its base rather than only a recolouring of it.\n */\nexport function resolveOverride<T = unknown>(base: unknown, patch: unknown): T {\n return resolve(base, patch) as T\n}\n\nfunction resolve(base: unknown, patch: unknown): unknown {\n if (patch === undefined) return base\n if (isDeleteSentinel(patch)) return undefined\n\n if (isKeyedArrayPatch(patch)) return resolveKeyedArray(base, patch)\n\n if (isPlainObject(patch) && isPlainObject(base)) {\n const merged: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n const resolved = resolve(merged[key], value)\n if (resolved === undefined) delete merged[key]\n else merged[key] = resolved\n }\n return merged\n }\n\n // A patch object over a non-object base still applies — the patch wins, and\n // its own delete sentinels are honoured so that `{a: DELETE}` over `null`\n // resolves to `{}` rather than smuggling the sentinel into the output.\n if (isPlainObject(patch)) return resolve({}, patch)\n\n return patch\n}\n\nfunction resolveKeyedArray(base: unknown, patch: KeyedArrayPatch): unknown[] {\n const source = Array.isArray(base) ? base : []\n const keyOf = (entry: unknown): string | undefined => {\n const key = isPlainObject(entry) ? entry[patch.keyedBy] : undefined\n return typeof key === 'string' && key !== '' ? key : undefined\n }\n\n const resolved: Array<{ key: string | undefined; value: unknown }> = []\n for (const entry of source) {\n const key = keyOf(entry)\n // An unkeyed element in an otherwise keyed array is unaddressable, so it is\n // carried through untouched rather than dropped: the patch never claimed to\n // describe it.\n if (key === undefined || !(key in patch.entries)) {\n resolved.push({ key, value: entry })\n continue\n }\n const value = resolve(entry, patch.entries[key])\n if (value !== undefined) resolved.push({ key, value })\n }\n\n // Entries the base does not hold are additions, appended in patch order.\n const present = new Set(resolved.map((entry) => entry.key))\n for (const [key, value] of Object.entries(patch.entries)) {\n if (present.has(key) || isDeleteSentinel(value)) continue\n const added = resolve(undefined, value)\n if (added !== undefined) resolved.push({ key, value: added })\n }\n\n if (!patch.order) return resolved.map((entry) => entry.value)\n\n // Ordered: the listed keys first, in the order given, then everything the\n // order did not mention in its existing relative position. A key in `order`\n // that no longer exists is skipped rather than inserted as a hole.\n const rank = new Map(patch.order.map((key, index) => [key, index]))\n return resolved\n .map((entry, index) => ({\n ...entry,\n rank: entry.key != null && rank.has(entry.key)\n ? (rank.get(entry.key) as number)\n : patch.order!.length + index,\n }))\n .sort((a, b) => a.rank - b.rank)\n .map((entry) => entry.value)\n}\n\n/**\n * \"What did I change\" — the sparse patch that turns `base` into `edited`.\n *\n * The inverse of {@link resolveOverride}, and held to it:\n * `resolveOverride(base, diffOverride(base, edited))` equals `edited` for every\n * shape this codebase stores. That round trip is the module's central property\n * and the thing its tests exist to defend, because every consumer downstream —\n * themes, theme overrides, host variables, plugin settings — inherits it.\n *\n * Returns `undefined` when nothing changed, so an unedited artifact stores no\n * override at all rather than an empty husk that reads as \"customised\".\n */\nexport function diffOverride(base: unknown, edited: unknown): unknown {\n if (same(base, edited)) return undefined\n if (edited === undefined) return OVERRIDE_DELETE\n\n if (isPlainObject(base) && isPlainObject(edited)) {\n const patch: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(edited)) {\n const child = diffOverride(base[key], value)\n if (child !== undefined) patch[key] = child\n }\n for (const key of Object.keys(base)) {\n if (!(key in edited)) patch[key] = OVERRIDE_DELETE\n }\n return patch\n }\n\n if (Array.isArray(base) && Array.isArray(edited)) {\n const keyed = diffKeyedArray(base, edited)\n if (keyed) return keyed\n }\n\n return edited\n}\n\n/**\n * A per-element patch for two keyed arrays, or null when they are not both\n * keyed by the same field — in which case the caller falls back to replacing\n * the array wholesale, which is always correct if sometimes coarse.\n */\nfunction diffKeyedArray(\n base: unknown[],\n edited: unknown[],\n): KeyedArrayPatch | undefined | null {\n const field = arrayKeyField(base)\n if (!field || arrayKeyField(edited) !== field) return null\n\n const keyOf = (entry: unknown) =>\n String((entry as Record<string, unknown>)[field])\n const baseByKey = new Map(base.map((entry) => [keyOf(entry), entry]))\n const editedKeys = edited.map(keyOf)\n\n const entries: Record<string, unknown> = {}\n for (const entry of edited) {\n const key = keyOf(entry)\n const child = diffOverride(baseByKey.get(key), entry)\n if (child !== undefined) entries[key] = child\n }\n for (const key of baseByKey.keys()) {\n if (!editedKeys.includes(key)) entries[key] = OVERRIDE_DELETE\n }\n\n // Order is stored only when the resolver would not already produce it —\n // surviving elements in the base's order, then additions in the order they\n // appear. So the common case (a value edit, or an element appended) stores no\n // order and leaves the publisher free to insert elements of their own, while\n // a genuine reorder or a mid-list insert is pinned.\n const natural = [\n ...base.map(keyOf).filter((key) => editedKeys.includes(key)),\n ...editedKeys.filter((key) => !baseByKey.has(key)),\n ]\n const reordered = !same(natural, editedKeys)\n\n return keyedArrayPatch(field, entries, reordered ? editedKeys : undefined)\n}\n\n/**\n * Every path the patch touches, dotted, for \"what did I change\" in a UI and for\n * pairing an override against an update plan's conflicts — whose paths\n * {@link planArtifactUpdate} spells the same way.\n *\n * A keyed array contributes `field[key]` segments rather than indices, because\n * an index is exactly the thing that stops meaning the same element the moment\n * the publisher inserts one.\n */\nexport function overridePaths(patch: unknown, prefix = ''): string[] {\n if (patch === undefined) return []\n if (isKeyedArrayPatch(patch)) {\n return Object.entries(patch.entries).flatMap(([key, value]) =>\n overridePaths(value, `${prefix}[${key}]`),\n )\n }\n if (isPlainObject(patch) && !isDeleteSentinel(patch)) {\n const keys = Object.keys(patch)\n if (keys.length) {\n return keys.flatMap((key) =>\n overridePaths(patch[key], prefix ? `${prefix}.${key}` : key),\n )\n }\n }\n return prefix ? [prefix] : []\n}\n\n/**\n * True when the patch says nothing — the artifact is the publisher's, as shipped.\n *\n * `undefined` is the only absence. A `null` is a VALUE the user chose, and\n * calling it empty here would silently discard \"set this field to null\" on\n * every save — the same conflation between \"cleared\" and \"absent\" that took\n * every `/product/*` page down in AGL-1226.\n */\nexport function isEmptyOverride(patch: unknown): boolean {\n if (patch === undefined) return true\n if (isKeyedArrayPatch(patch)) {\n return !patch.order && Object.values(patch.entries).every(isEmptyOverride)\n }\n if (isDeleteSentinel(patch)) return false\n if (isPlainObject(patch)) return Object.values(patch).every(isEmptyOverride)\n return false\n}\n\n/**\n * Is this shape storable as a whole-artifact override?\n *\n * A patch is a *sparse* description of edits to a structured artifact, so its\n * top level is an object or a keyed array and nothing else. A stored scalar —\n * above all a top-level `null` — would resolve the entire artifact away, which\n * is never something a user did on purpose and always something a half-written\n * or hand-edited field can produce. Nested nulls stay values; only the root is\n * constrained, and constraining it is what keeps a junk field from blanking a\n * live site.\n */\nfunction isStorablePatch(patch: unknown): boolean {\n return (\n (isKeyedArrayPatch(patch) || isPlainObject(patch)) && !isEmptyOverride(patch)\n )\n}\n\nexport interface OverrideEligibility {\n ok: boolean\n /** Why not, in the words the surface refusing it should use. */\n reason?: string\n}\n\n/**\n * Can this artifact hold an override at all? (AGL-1015's provenance, answered.)\n *\n * An override is a patch over the publisher's version, so an artifact with no\n * recorded base has nothing to be a patch *of*. Storing one anyway would\n * produce a patch against whatever the document happened to hold — including\n * the user's own earlier edits — and the first update would resolve it against\n * a base it was never written for. Saying so plainly is the whole point of the\n * `updatable` flag; this is the sentence to put next to the disabled control.\n */\nexport function canOverrideArtifact(\n provenance: ResolvedProvenance | null | undefined,\n): OverrideEligibility {\n if (!provenance || provenance.state === 'unknown') {\n return {\n ok: false,\n reason:\n 'This was not installed from the marketplace, so there is no ' +\n 'publisher’s version to keep your changes separate from. Edit it ' +\n 'directly — it is already yours.',\n }\n }\n if (!provenance.updatable) {\n return {\n ok: false,\n reason:\n 'This was installed before update tracking recorded an original, so ' +\n 'there is nothing to layer your changes over. Re-installing it from ' +\n 'the listing records one, and your changes can be kept separately ' +\n 'from then on.',\n }\n }\n return { ok: true }\n}\n\n/**\n * The value to write under {@link ARTIFACT_OVERRIDE_FIELD} — or `null`, meaning\n * \"reset to the publisher's version\", when the patch says nothing.\n *\n * ## Write this WHOLESALE. Never with `merge: true`.\n *\n * Firestore's `merge: true` deep-merges maps, and a patch is a map, so merging\n * a new patch onto an old one takes their union: every path the user has ever\n * overridden stays overridden, and removing an override becomes impossible\n * through the one write that looks like it should do it. `merge: true` on the\n * *document* is fine and expected — it is this *field* that must be replaced,\n * which is what passing this value under a `FieldPath` to `update`, or under\n * `set(..., {mergeFields})`, does:\n *\n * ```ts\n * await ref.set(\n * { [ARTIFACT_OVERRIDE_FIELD]: overrideWriteValue(patch, sha256), updatedAt },\n * { mergeFields: [ARTIFACT_OVERRIDE_FIELD, 'updatedAt'] },\n * )\n * ```\n *\n * The `null` for an empty patch is deliberate over a field delete: a reader\n * that sees `null` knows the override was considered and is empty, which reads\n * the same as absent and needs no second code path.\n */\nexport function overrideWriteValue(\n patch: unknown,\n baseSha256: string | null,\n meta?: { updatedAt?: unknown; updatedBy?: string | null },\n): ArtifactOverride | null {\n if (!isStorablePatch(patch)) return null\n return {\n patch,\n baseSha256,\n ...(meta?.updatedAt !== undefined ? { updatedAt: meta.updatedAt } : {}),\n ...(meta?.updatedBy !== undefined ? { updatedBy: meta.updatedBy } : {}),\n }\n}\n\n/**\n * Reads a stored override off a document, tolerating the junk a client-writable\n * field can hold.\n *\n * Returns `undefined` for anything that is not a well-formed override, so a\n * corrupt field resolves to the publisher's version rather than throwing on a\n * page that would otherwise render.\n */\nexport function readArtifactOverride(\n doc: Record<string, unknown> | null | undefined,\n): ArtifactOverride | undefined {\n const stored = doc?.[ARTIFACT_OVERRIDE_FIELD]\n if (!isPlainObject(stored)) return undefined\n if (!isStorablePatch(stored['patch'])) return undefined\n const sha = stored['baseSha256']\n return {\n patch: stored['patch'],\n baseSha256: typeof sha === 'string' && sha ? sha : null,\n ...(stored['updatedAt'] !== undefined ? { updatedAt: stored['updatedAt'] } : {}),\n ...(typeof stored['updatedBy'] === 'string'\n ? { updatedBy: stored['updatedBy'] }\n : {}),\n }\n}\n\n/**\n * The one call a reader makes: the publisher's content with this document's\n * override resolved over it.\n *\n * `content` is the artifact's own fields — whatever shape the install route\n * wrote, unchanged — so a caller never has to know whether an override exists.\n */\nexport function resolveArtifactContent<T = unknown>(\n content: unknown,\n doc: Record<string, unknown> | null | undefined,\n): T {\n const override = readArtifactOverride(doc)\n return (override ? resolveOverride(content, override.patch) : content) as T\n}\n\n/**\n * The paths where an incoming update collides with what the user overrode.\n *\n * Narrow by construction, which is the payoff the whole layer was built for: a\n * patch touching four paths can conflict in at most four places no matter how\n * much of the artifact the publisher rewrote, so the question put to the user is\n * \"the publisher also changed your heading colour — whose wins?\" rather than\n * \"this update rewrites 300 nodes, continue?\".\n *\n * A path whose incoming value already equals what the override sets is not a\n * conflict — the publisher adopted the user's change, and there is nothing to\n * ask about.\n */\nexport function overrideConflicts(\n base: unknown,\n patch: unknown,\n incoming: unknown,\n): string[] {\n const overridden = resolveOverride(base, patch)\n return overridePaths(patch).filter((path) => {\n const baseValue = readPath(base, path)\n const incomingValue = readPath(incoming, path)\n if (same(baseValue, incomingValue)) return false\n return !same(readPath(overridden, path), incomingValue)\n })\n}\n\n/** Reads a dotted path, understanding the `field[key]` segments keyed arrays use. */\nfunction readPath(value: unknown, path: string): unknown {\n let cursor: unknown = value\n for (const segment of path.match(/[^.[\\]]+/g) ?? []) {\n if (Array.isArray(cursor)) {\n const field = arrayKeyField(cursor)\n cursor = field\n ? cursor.find(\n (entry) => (entry as Record<string, unknown>)[field] === segment,\n )\n : undefined\n continue\n }\n if (!isPlainObject(cursor)) return undefined\n cursor = cursor[segment]\n }\n return cursor\n}\n"],"names":["stableStringify","OVERRIDE_DELETE","ARTIFACT_OVERRIDE_FIELD","KEY_FIELDS","isPlainObject","value","Array","isArray","Object","getPrototypeOf","prototype","same","a","b","isDeleteSentinel","isKeyedArrayPatch","arrayKeyField","length","every","field","keys","map","entry","key","Set","size","keyedArrayPatch","keyedBy","entries","order","undefined","resolveOverride","base","patch","resolve","resolveKeyedArray","merged","resolved","source","keyOf","push","present","has","added","rank","Map","index","get","sort","diffOverride","edited","child","keyed","diffKeyedArray","String","baseByKey","editedKeys","includes","natural","filter","reordered","overridePaths","prefix","flatMap","isEmptyOverride","values","isStorablePatch","canOverrideArtifact","provenance","state","ok","reason","updatable","overrideWriteValue","baseSha256","meta","updatedAt","updatedBy","readArtifactOverride","doc","stored","sha","resolveArtifactContent","content","override","overrideConflicts","incoming","overridden","path","baseValue","readPath","incomingValue","cursor","segment","match","find"],"mappings":"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"}
|
|
@@ -15,15 +15,17 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* The kinds of content a workspace can INSTALL rather than author (AGL-654).
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* Installed content is a platform concept with more readers than writers:
|
|
21
|
+
* whatever installs a component, a theme or an email template stamps where it
|
|
22
|
+
* came from (below), and the theme editor, a template's page, the site's theme
|
|
23
|
+
* resolution and the AI plugin all read that stamp without knowing who wrote
|
|
24
|
+
* it. So the contract is core's and names no installer (AGL-3080). The
|
|
25
|
+
* marketplace, which is today's only installer, re-exports the union under the
|
|
26
|
+
* name its publish routes use, `MarketplaceArtifactType`.
|
|
25
27
|
*/
|
|
26
|
-
export type
|
|
28
|
+
export type InstallableArtifactType = 'component' | 'template' | 'plugin' | 'layout' | 'datasetSchema' | 'emailTemplate'
|
|
27
29
|
/**
|
|
28
30
|
* A campaign email design, installed as a new `kind: 'email'` screen.
|
|
29
31
|
*
|
|
@@ -67,7 +69,7 @@ export interface InstalledFrom {
|
|
|
67
69
|
version: string | null;
|
|
68
70
|
/** Content hash of the base snapshot — the key into {@link ARTIFACT_BASE_COLLECTION}. */
|
|
69
71
|
sha256: string | null;
|
|
70
|
-
artifactType:
|
|
72
|
+
artifactType: InstallableArtifactType;
|
|
71
73
|
installedAt: unknown;
|
|
72
74
|
/** The publishing org (`listing.profileId`, org-owned since AGL-652). */
|
|
73
75
|
publisherOrgId: string | null;
|
|
@@ -100,7 +102,7 @@ export interface ResolvedProvenance {
|
|
|
100
102
|
listingId: string | null;
|
|
101
103
|
version: string | null;
|
|
102
104
|
sha256: string | null;
|
|
103
|
-
artifactType:
|
|
105
|
+
artifactType: InstallableArtifactType | null;
|
|
104
106
|
publisherOrgId: string | null;
|
|
105
107
|
/**
|
|
106
108
|
* A base snapshot exists, so this artifact can be diffed and safely updated.
|
|
@@ -138,7 +140,7 @@ export declare function resolveProvenance(doc: {
|
|
|
138
140
|
profileId?: string;
|
|
139
141
|
version?: unknown;
|
|
140
142
|
sha256?: string;
|
|
141
|
-
} | null | undefined, fallbackArtifactType?:
|
|
143
|
+
} | null | undefined, fallbackArtifactType?: InstallableArtifactType): ResolvedProvenance;
|
|
142
144
|
/**
|
|
143
145
|
* Deterministic JSON with object keys in sorted order, so the same content
|
|
144
146
|
* hashes to the same id no matter what order Firestore handed the fields back.
|
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ /**
|
|
17
|
-
*
|
|
17
|
+
* The kinds of content a workspace can INSTALL rather than author (AGL-654).
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Installed content is a platform concept with more readers than writers:
|
|
20
|
+
* whatever installs a component, a theme or an email template stamps where it
|
|
21
|
+
* came from (below), and the theme editor, a template's page, the site's theme
|
|
22
|
+
* resolution and the AI plugin all read that stamp without knowing who wrote
|
|
23
|
+
* it. So the contract is core's and names no installer (AGL-3080). The
|
|
24
|
+
* marketplace, which is today's only installer, re-exports the union under the
|
|
25
|
+
* name its publish routes use, `MarketplaceArtifactType`.
|
|
24
26
|
*/ /**
|
|
25
27
|
* Install provenance (AGL-1015): where an installed artifact came from, at
|
|
26
28
|
* what version, and what it looked like when it arrived.
|
|
@@ -133,4 +135,4 @@ function asVersion(value) {
|
|
|
133
135
|
return `{${entries.join(',')}}`;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
//# sourceMappingURL=
|
|
138
|
+
//# sourceMappingURL=artifact-provenance.js.map
|