@aglyn/aglyn 1.0.0-beta.217 → 1.0.0-beta.219
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 +14 -14
- package/src/lib/app-utils/actions.d.ts +25 -374
- package/src/lib/app-utils/actions.js +101 -561
- 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/admin-audit-index.js +2 -2
- package/src/lib/app-utils/admin-audit-index.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +11 -2
- package/src/lib/app-utils/analytics-events.js +11 -2
- 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/assist-credits.js +2 -1
- package/src/lib/app-utils/assist-credits.js.map +1 -1
- 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/binding-tokens.js +1 -1
- package/src/lib/app-utils/binding-tokens.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/campaign-touch.d.ts +5 -5
- package/src/lib/app-utils/campaign-touch.js +8 -8
- package/src/lib/app-utils/campaign-touch.js.map +1 -1
- 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/console-record-links.d.ts +10 -89
- package/src/lib/app-utils/console-record-links.js +3 -107
- package/src/lib/app-utils/console-record-links.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +16 -7
- package/src/lib/app-utils/console-routes.js +9 -3
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +23 -1
- package/src/lib/app-utils/contacts.js +21 -5
- package/src/lib/app-utils/contacts.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-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 -11
- package/src/lib/app-utils/datasets.js +74 -11
- 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 +12 -6
- package/src/lib/app-utils/docs-help.generated.js +21 -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 +514 -318
- 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.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/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 +10 -10
- package/src/lib/app-utils/expand-repeatables.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/lockdown.d.ts +33 -1
- package/src/lib/app-utils/lockdown.js +45 -4
- 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/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 +78 -47
- package/src/lib/app-utils/notifications.js +63 -44
- package/src/lib/app-utils/notifications.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 +43 -128
- package/src/lib/app-utils/plan-entitlements.js +145 -304
- 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/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/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-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.d.ts +15 -0
- package/src/lib/app-utils/screen-route.js +21 -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 +6 -9
- package/src/lib/app-utils/server.js +17 -27
- 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} +5 -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-templates.d.ts +20 -0
- package/src/lib/app-utils/starter-templates.js +49 -0
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- 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/{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/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/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/canvas-manager/canvas-manager.js +3 -3
- package/src/lib/canvas-manager/canvas-manager.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 +18 -135
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +27 -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 +292 -20
- package/src/lib/plugin-manager/feature-plugins.js +105 -17
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
- package/src/lib/plugin-manager/first-party-plugins.generated.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-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-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-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-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-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-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-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 +107 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
- package/src/lib/plugin-manager/plugin-usage-axes.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/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/campaign-attribution.js.map +0 -1
- package/src/lib/app-utils/campaign-forwarding.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-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/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-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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { VisitorConsentHost } from './visitor-consent';
|
|
18
|
+
/**
|
|
19
|
+
* Third-party analytics tags, behind one contract (AGL-3080).
|
|
20
|
+
*
|
|
21
|
+
* A site can send its visitors' measurement to an analytics vendor it chose:
|
|
22
|
+
* a measurement id or a tag container, saved in the site's analytics
|
|
23
|
+
* settings. The platform owns everything around that tag — whether this
|
|
24
|
+
* visitor may be measured at all (`visitor-consent.ts`), the consent banner,
|
|
25
|
+
* the first-party pageview beacon that meters the site (`analytics-beacon.ts`,
|
|
26
|
+
* which is not a tag and never goes through here) — and names no vendor. A
|
|
27
|
+
* plugin's ADAPTER is what knows a vendor: what its tag looks like on the
|
|
28
|
+
* page, how a resident tag is told the visitor's answer changed, and how an
|
|
29
|
+
* event reaches it.
|
|
30
|
+
*
|
|
31
|
+
* ## Declared, then loaded where it is used
|
|
32
|
+
*
|
|
33
|
+
* Two questions, answered by two different mechanisms, because their readers
|
|
34
|
+
* cannot wait for the same thing:
|
|
35
|
+
*
|
|
36
|
+
* - "Does this site run a tag, so is there anything to ask its visitors?" is
|
|
37
|
+
* asked synchronously, during render, by the consent gate on the published
|
|
38
|
+
* page and by the console's banner card and preview. A registry one of
|
|
39
|
+
* those had not filled yet would answer "no tag" — no banner, and nothing to
|
|
40
|
+
* gate — so the answer is COMPILED: the plugin declares which of the site's
|
|
41
|
+
* analytics settings it mounts a tag for (`analyticsProvider.settings` in
|
|
42
|
+
* `plugins.config.json`), and `hostConfiguresAnalyticsTag` in
|
|
43
|
+
* `visitor-consent.ts` reads that list. With no provider declared, no setting configures a tag, and a site
|
|
44
|
+
* with nothing that could mount asks its visitors nothing.
|
|
45
|
+
* - Everything else — mounting the tag, telling it the answer changed,
|
|
46
|
+
* handing it an event — happens after the adapter has loaded. The adapter
|
|
47
|
+
* is a chunk of its own, reached through each app's generated
|
|
48
|
+
* `plugins.analytics.generated.ts` and fetched only by a document that uses
|
|
49
|
+
* it: a published page whose site configures a tag, and the console, whose
|
|
50
|
+
* own measurement tag the consent controls answer to. A page whose site
|
|
51
|
+
* configures none never fetches it.
|
|
52
|
+
*
|
|
53
|
+
* ## What an unloaded adapter means
|
|
54
|
+
*
|
|
55
|
+
* A tag is mounted only by its adapter, so until the adapter has loaded there
|
|
56
|
+
* is no tag of its to silence or to send to, and nothing is lost by the wait:
|
|
57
|
+
* events find no resident tag and drop, exactly as they do for a visitor who
|
|
58
|
+
* has not granted.
|
|
59
|
+
*
|
|
60
|
+
* A tag this document did not mount — the console's, which its analytics SDK
|
|
61
|
+
* injects — can be resident before the adapter arrives. So the last answer
|
|
62
|
+
* {@link applyAnalyticsConsent} was given is kept, and an adapter that
|
|
63
|
+
* registers later is handed it at once: a withdrawal made in that window
|
|
64
|
+
* reaches the tag the moment something can speak to it.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* What a provider declares about itself, compiled from `plugins.config.json`.
|
|
68
|
+
* `settings` are keys of the site's `analytics` settings; each is validated
|
|
69
|
+
* by the pattern `ANALYTICS_SETTING_PATTERNS` in `visitor-consent.ts` holds
|
|
70
|
+
* for it.
|
|
71
|
+
*/
|
|
72
|
+
export interface AnalyticsProviderDeclaration {
|
|
73
|
+
/** The plugin that provides the adapter. */
|
|
74
|
+
pluginId: string;
|
|
75
|
+
/** The analytics settings this provider mounts a tag for. */
|
|
76
|
+
settings: readonly string[];
|
|
77
|
+
}
|
|
78
|
+
/** The visitor's answer, in the platform's two categories. */
|
|
79
|
+
export interface AnalyticsGrants {
|
|
80
|
+
analytics: boolean;
|
|
81
|
+
advertising: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One tag on a published page: an inline boot that runs first, then the
|
|
85
|
+
* vendor's library. The page renders the pair in order, under the consent
|
|
86
|
+
* gate and with the request's nonce, as `${id}-init` and `${id}-src`.
|
|
87
|
+
*
|
|
88
|
+
* Both strings land in the document. `boot` is inline script, so an adapter
|
|
89
|
+
* builds it from constants and format-checked ids only.
|
|
90
|
+
*/
|
|
91
|
+
export interface AnalyticsTagMount {
|
|
92
|
+
/** Element id prefix. Stable: specs and the CSP report read it. */
|
|
93
|
+
id: string;
|
|
94
|
+
/** Inline script that runs before the library. */
|
|
95
|
+
boot: string;
|
|
96
|
+
/** The library URL. */
|
|
97
|
+
src: string;
|
|
98
|
+
/**
|
|
99
|
+
* A marker for the library this pair loads, for an advertising vendor that
|
|
100
|
+
* rides the same library instead of fetching it again (`sharesLibrary` in
|
|
101
|
+
* `advertising-tags.ts`).
|
|
102
|
+
*/
|
|
103
|
+
library?: string;
|
|
104
|
+
}
|
|
105
|
+
/** What the page tells an adapter about the pageview it mounts tags for. */
|
|
106
|
+
export interface AnalyticsMountOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Whether the site runs the platform's consent machinery. When it does, the
|
|
109
|
+
* tag is told the visitor's answer before it measures anything; when the
|
|
110
|
+
* host runs a consent solution of its own, that solution tells it.
|
|
111
|
+
*/
|
|
112
|
+
consentRequired: boolean;
|
|
113
|
+
/** Whether this visitor granted advertising storage on this site. */
|
|
114
|
+
advertising: boolean;
|
|
115
|
+
}
|
|
116
|
+
export interface AnalyticsEventOptions {
|
|
117
|
+
/**
|
|
118
|
+
* Address only the measurement destinations of the resident tag, not any
|
|
119
|
+
* advertising destination sharing its library. Set for events that no
|
|
120
|
+
* advertising report reads, such as Core Web Vitals.
|
|
121
|
+
*/
|
|
122
|
+
measurementOnly?: boolean;
|
|
123
|
+
}
|
|
124
|
+
/** A vendor's adapter. */
|
|
125
|
+
export interface AnalyticsProvider {
|
|
126
|
+
/**
|
|
127
|
+
* The tags a granted pageview mounts for `host`, in order; empty when the
|
|
128
|
+
* host configures none of this provider's settings, or configures them
|
|
129
|
+
* malformed. Called only once the page's consent gate has said yes.
|
|
130
|
+
*/
|
|
131
|
+
mounts(host: VisitorConsentHost | null | undefined, options: AnalyticsMountOptions): readonly AnalyticsTagMount[];
|
|
132
|
+
/**
|
|
133
|
+
* Make every resident tag of this vendor agree with `grants`, and answer
|
|
134
|
+
* the ids it acted on. A page cannot unload a script, so a withdrawal in
|
|
135
|
+
* the middle of a pageview has to be told to the tag that is already there.
|
|
136
|
+
*/
|
|
137
|
+
applyConsent(grants: AnalyticsGrants): readonly string[];
|
|
138
|
+
/** Whether a tag of this vendor is resident and can take an event now. */
|
|
139
|
+
resident(): boolean;
|
|
140
|
+
/** Hand one event to the resident tag. Never throws. */
|
|
141
|
+
sendEvent(name: string, params: Record<string, unknown>, options?: AnalyticsEventOptions): void;
|
|
142
|
+
}
|
|
143
|
+
/** An adapter module, as an app's generated manifest loads it. */
|
|
144
|
+
export interface AnalyticsProviderModule {
|
|
145
|
+
analyticsProvider: AnalyticsProvider;
|
|
146
|
+
}
|
|
147
|
+
/** One row of an app's generated `plugins.analytics.generated.ts`. */
|
|
148
|
+
export interface AnalyticsProviderLoader {
|
|
149
|
+
pluginId: string;
|
|
150
|
+
load: () => Promise<AnalyticsProviderModule>;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Registers `pluginId`'s adapter. The same plugin registering again replaces
|
|
154
|
+
* its adapter. If a consent answer was already given in this document, the
|
|
155
|
+
* adapter is told it before anything else can reach it.
|
|
156
|
+
*/
|
|
157
|
+
export declare function registerAnalyticsProvider(pluginId: string, provider: AnalyticsProvider): void;
|
|
158
|
+
/** The registered adapters, in registration order. A stable array. */
|
|
159
|
+
export declare function analyticsProviders(): readonly AnalyticsProvider[];
|
|
160
|
+
/** Hears every registration. Answers the unsubscribe. */
|
|
161
|
+
export declare function subscribeAnalyticsProviders(listener: () => void): () => void;
|
|
162
|
+
/**
|
|
163
|
+
* Tell every resident tag the visitor's answer, and answer the ids acted on.
|
|
164
|
+
* Advertising is clamped to analytics: a refusal withdraws both.
|
|
165
|
+
*/
|
|
166
|
+
export declare function applyAnalyticsConsent(grants: AnalyticsGrants): string[];
|
|
167
|
+
/** Whether any registered adapter has a tag resident that can take an event. */
|
|
168
|
+
export declare function analyticsTagResident(): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Hand one event to every resident tag. Answers whether any took it, so a
|
|
171
|
+
* caller that may hold an event while a tag is still loading knows to.
|
|
172
|
+
*/
|
|
173
|
+
export declare function sendAnalyticsProviderEvent(name: string, params: Record<string, unknown>, options?: AnalyticsEventOptions): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Fetch and register each adapter once per document. Idempotent and safe to
|
|
176
|
+
* call during render: it starts the fetch, never waits for it, and a loader
|
|
177
|
+
* that fails leaves that provider unregistered — the vendor's tag then never
|
|
178
|
+
* mounts, which is the outcome a failed vendor library has anyway.
|
|
179
|
+
*/
|
|
180
|
+
export declare function loadAnalyticsProviders(loaders: readonly AnalyticsProviderLoader[]): Promise<void>;
|
|
181
|
+
/** Test seam — forgets every adapter, load and consent answer. */
|
|
182
|
+
export declare function resetAnalyticsProviders(): void;
|
|
@@ -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":"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"}
|
|
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":"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"}
|
|
@@ -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
|