@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,194 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /**
|
|
18
|
+
* WHAT A PLUGIN EARNED THE OPERATOR, ON THE STAFF REVENUE REPORT (AGL-3080).
|
|
19
|
+
*
|
|
20
|
+
* The operator's revenue report states what the deployment actually kept in a
|
|
21
|
+
* period. Its own subscription invoices are platform billing and the report
|
|
22
|
+
* folds them itself. A plugin that sells through the platform's account — a
|
|
23
|
+
* storefront's application fee, a marketplace's commission — earns the
|
|
24
|
+
* operator a take the report cannot compute without knowing that plugin's
|
|
25
|
+
* storage and its fee rules, and it has no business restating either.
|
|
26
|
+
*
|
|
27
|
+
* So the division is: the REPORT is the platform's — the period, the two
|
|
28
|
+
* bases, the gap, the sweep ceiling, the read budget for names and the page.
|
|
29
|
+
* What a plugin's sales earned, net of what is not the operator's, and who
|
|
30
|
+
* produced it, are the plugin's. A source answers one period with a
|
|
31
|
+
* {@link RevenueSourceAnswer}: its earned line, its gross-to-net lines, its
|
|
32
|
+
* qualifications, its attribution tables and its own settled figures.
|
|
33
|
+
*
|
|
34
|
+
* ## ⛔ AN UNREAD SOURCE IS NEVER A ZERO
|
|
35
|
+
*
|
|
36
|
+
* A source that answered nothing reads, on a total, exactly like one that
|
|
37
|
+
* earned nothing, and a revenue total quoted short is a figure somebody
|
|
38
|
+
* repeats. So, as the tax return's sources do:
|
|
39
|
+
*
|
|
40
|
+
* - every plugin that earns through the platform declares
|
|
41
|
+
* `"revenueSource": true` in `plugins.config.json`, compiled into
|
|
42
|
+
* {@link PLUGIN_REVENUE_SOURCES}; a declared plugin with no registration, a
|
|
43
|
+
* source that threw and a malformed answer each come back
|
|
44
|
+
* `outcome: 'refused'`, and the page says whose earnings are missing;
|
|
45
|
+
* - a source whose own read could not run answers with `failure` and figures
|
|
46
|
+
* it does NOT claim, and one whose sweep stopped at the ceiling answers
|
|
47
|
+
* `truncated` — the page names both rather than quoting the total.
|
|
48
|
+
*
|
|
49
|
+
* {@link readRevenueSources} never throws and never drops a declared source.
|
|
50
|
+
* The reader is the staff revenue route, which awaits `ensureAll` on the
|
|
51
|
+
* `consoleApi` surface before it asks.
|
|
52
|
+
*/ import { PLUGIN_REVENUE_SOURCES } from "./first-party-plugins.generated.js";
|
|
53
|
+
import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
|
|
54
|
+
export { PLUGIN_REVENUE_SOURCES };
|
|
55
|
+
/** Every source answers; one per plugin. */ export const REVENUE_SOURCES = definePluginServiceContract('core.revenue-sources', {
|
|
56
|
+
multiple: true
|
|
57
|
+
});
|
|
58
|
+
/** Installs a plugin's source; registering again replaces it. */ export function registerRevenueSource(source, options) {
|
|
59
|
+
if (typeof (source == null ? void 0 : source.read) !== 'function') {
|
|
60
|
+
throw new Error('a revenue source needs a read function');
|
|
61
|
+
}
|
|
62
|
+
registerPluginService(REVENUE_SOURCES, source, _extends({}, (options == null ? void 0 : options.pluginId) ? {
|
|
63
|
+
pluginId: options.pluginId
|
|
64
|
+
} : {}));
|
|
65
|
+
}
|
|
66
|
+
/** The plugins with a registered source, in the order they answer. */ export function listRevenueSources() {
|
|
67
|
+
return resolvePluginServices(REVENUE_SOURCES).map((entry)=>entry.pluginId);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Group, sort and cap — every attribution table's shared half.
|
|
71
|
+
*
|
|
72
|
+
* A row whose key is missing is NOT dropped. It is grouped under an explicit
|
|
73
|
+
* unattributed key, because a sale nobody can attribute still happened and
|
|
74
|
+
* dropping it would make the rows sum below the total — the one property that
|
|
75
|
+
* makes any of these tables worth reading. The cap carries its remainder as
|
|
76
|
+
* FIGURES, so the table stays a complete accounting even when it is not a
|
|
77
|
+
* complete list.
|
|
78
|
+
*/ export function groupRevenueAttribution(entries, limit, unattributedLabel) {
|
|
79
|
+
const byKey = new Map();
|
|
80
|
+
for (const entry of entries != null ? entries : []){
|
|
81
|
+
var _byKey_get, _entry_detail;
|
|
82
|
+
const key = entry.key || unattributedLabel;
|
|
83
|
+
const row = (_byKey_get = byKey.get(key)) != null ? _byKey_get : {
|
|
84
|
+
key,
|
|
85
|
+
name: key === unattributedLabel ? unattributedLabel : key,
|
|
86
|
+
detail: (_entry_detail = entry.detail) != null ? _entry_detail : '',
|
|
87
|
+
gainCents: 0,
|
|
88
|
+
lossCents: 0,
|
|
89
|
+
count: 0
|
|
90
|
+
};
|
|
91
|
+
row.gainCents += entry.gain;
|
|
92
|
+
row.lossCents += entry.loss;
|
|
93
|
+
row.count += 1;
|
|
94
|
+
if (!row.detail && entry.detail) row.detail = entry.detail;
|
|
95
|
+
byKey.set(key, row);
|
|
96
|
+
}
|
|
97
|
+
const rows = [
|
|
98
|
+
...byKey.values()
|
|
99
|
+
].sort((a, b)=>b.gainCents - a.gainCents || b.lossCents - a.lossCents || a.key.localeCompare(b.key));
|
|
100
|
+
const kept = rows.slice(0, Math.max(0, limit));
|
|
101
|
+
const dropped = rows.slice(Math.max(0, limit));
|
|
102
|
+
return {
|
|
103
|
+
rows: kept,
|
|
104
|
+
omittedRows: dropped.length,
|
|
105
|
+
omittedGainCents: dropped.reduce((sum, row)=>sum + row.gainCents, 0),
|
|
106
|
+
omittedLossCents: dropped.reduce((sum, row)=>sum + row.lossCents, 0)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const isText = (value)=>typeof value === 'string' && value.trim().length > 0;
|
|
110
|
+
/** What is wrong with an answer, or null when it can be printed. */ function malformed(answer) {
|
|
111
|
+
var _answer_earned, _answer_earned1;
|
|
112
|
+
if (!answer || typeof answer !== 'object') return 'no report';
|
|
113
|
+
if (!isText(answer.id) || !isText(answer.name)) return 'a report with no id or name';
|
|
114
|
+
const cents = (_answer_earned = answer.earned) == null ? void 0 : _answer_earned.cents;
|
|
115
|
+
if (typeof cents !== 'number' || !Number.isFinite(cents)) return 'no earned figure';
|
|
116
|
+
if (!isText((_answer_earned1 = answer.earned) == null ? void 0 : _answer_earned1.label)) return 'an earned figure with no label';
|
|
117
|
+
if (typeof answer.truncated !== 'boolean') return 'no truncation flag';
|
|
118
|
+
for (const key of [
|
|
119
|
+
'grossToNet',
|
|
120
|
+
'notes',
|
|
121
|
+
'attribution'
|
|
122
|
+
]){
|
|
123
|
+
if (!Array.isArray(answer[key])) return `no ${key}`;
|
|
124
|
+
}
|
|
125
|
+
const badLine = answer.grossToNet.find((line)=>typeof (line == null ? void 0 : line.cents) !== 'number' || !Number.isFinite(line.cents));
|
|
126
|
+
if (badLine) return 'a gross-to-net line with no figure';
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Every source's answer for one period — declared sources first, in the
|
|
131
|
+
* compiled order, then any other registered source.
|
|
132
|
+
*
|
|
133
|
+
* NEVER THROWS and never drops a declared source. `declared` is the compiled
|
|
134
|
+
* {@link PLUGIN_REVENUE_SOURCES} unless a spec passes its own.
|
|
135
|
+
*/ export async function readRevenueSources(request, declared = PLUGIN_REVENUE_SOURCES) {
|
|
136
|
+
const registered = resolvePluginServices(REVENUE_SOURCES);
|
|
137
|
+
const order = [
|
|
138
|
+
...declared,
|
|
139
|
+
...registered.map((entry)=>entry.pluginId).filter((pluginId)=>!declared.includes(pluginId))
|
|
140
|
+
].filter((pluginId, index, all)=>all.indexOf(pluginId) === index);
|
|
141
|
+
const answers = await Promise.all(order.map(async (pluginId)=>{
|
|
142
|
+
var _registered_find;
|
|
143
|
+
const source = (_registered_find = registered.find((entry)=>entry.pluginId === pluginId)) == null ? void 0 : _registered_find.impl;
|
|
144
|
+
if (!source) {
|
|
145
|
+
console.error(`[revenue] "${pluginId}" declares a revenue source and none is ` + 'registered in this process; its earnings cannot be read.');
|
|
146
|
+
return {
|
|
147
|
+
outcome: 'refused',
|
|
148
|
+
pluginId,
|
|
149
|
+
id: pluginId,
|
|
150
|
+
reason: `The “${pluginId}” plugin earns through the platform and registered ` + 'nothing to read its earnings with.'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const answer = await source.read(request);
|
|
155
|
+
const problem = malformed(answer);
|
|
156
|
+
if (problem) {
|
|
157
|
+
return {
|
|
158
|
+
outcome: 'refused',
|
|
159
|
+
pluginId,
|
|
160
|
+
id: pluginId,
|
|
161
|
+
reason: `The “${pluginId}” plugin answered ${problem}.`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return _extends({}, answer, {
|
|
165
|
+
outcome: 'answered',
|
|
166
|
+
pluginId
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`[revenue] "${pluginId}" failed to read its earnings`, error);
|
|
170
|
+
return {
|
|
171
|
+
outcome: 'refused',
|
|
172
|
+
pluginId,
|
|
173
|
+
id: pluginId,
|
|
174
|
+
reason: `The “${pluginId}” plugin could not read its earnings for this period.`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}));
|
|
178
|
+
const seen = new Set();
|
|
179
|
+
return answers.map((section)=>{
|
|
180
|
+
if (section.outcome !== 'answered') return section;
|
|
181
|
+
if (!seen.has(section.id)) {
|
|
182
|
+
seen.add(section.id);
|
|
183
|
+
return section;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
outcome: 'refused',
|
|
187
|
+
pluginId: section.pluginId,
|
|
188
|
+
id: section.pluginId,
|
|
189
|
+
reason: `The “${section.pluginId}” plugin answered as “${section.id}”, which ` + 'another source already is.'
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=plugin-revenue-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-revenue-sources.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WHAT A PLUGIN EARNED THE OPERATOR, ON THE STAFF REVENUE REPORT (AGL-3080).\n *\n * The operator's revenue report states what the deployment actually kept in a\n * period. Its own subscription invoices are platform billing and the report\n * folds them itself. A plugin that sells through the platform's account — a\n * storefront's application fee, a marketplace's commission — earns the\n * operator a take the report cannot compute without knowing that plugin's\n * storage and its fee rules, and it has no business restating either.\n *\n * So the division is: the REPORT is the platform's — the period, the two\n * bases, the gap, the sweep ceiling, the read budget for names and the page.\n * What a plugin's sales earned, net of what is not the operator's, and who\n * produced it, are the plugin's. A source answers one period with a\n * {@link RevenueSourceAnswer}: its earned line, its gross-to-net lines, its\n * qualifications, its attribution tables and its own settled figures.\n *\n * ## ⛔ AN UNREAD SOURCE IS NEVER A ZERO\n *\n * A source that answered nothing reads, on a total, exactly like one that\n * earned nothing, and a revenue total quoted short is a figure somebody\n * repeats. So, as the tax return's sources do:\n *\n * - every plugin that earns through the platform declares\n * `\"revenueSource\": true` in `plugins.config.json`, compiled into\n * {@link PLUGIN_REVENUE_SOURCES}; a declared plugin with no registration, a\n * source that threw and a malformed answer each come back\n * `outcome: 'refused'`, and the page says whose earnings are missing;\n * - a source whose own read could not run answers with `failure` and figures\n * it does NOT claim, and one whose sweep stopped at the ceiling answers\n * `truncated` — the page names both rather than quoting the total.\n *\n * {@link readRevenueSources} never throws and never drops a declared source.\n * The reader is the staff revenue route, which awaits `ensureAll` on the\n * `consoleApi` surface before it asks.\n */\n\nimport { PLUGIN_REVENUE_SOURCES } from './first-party-plugins.generated'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\nexport { PLUGIN_REVENUE_SOURCES }\n\n/**\n * Pages a query to exhaustion under the operator's ceiling. The ceiling is the\n * platform's policy about its own request, so a source is handed the sweep\n * rather than choosing one; `truncated` means the ceiling stopped it.\n */\nexport type RevenueSweep = <Doc>(\n query: unknown,\n orderField: unknown,\n) => Promise<{ docs: Doc[]; truncated: boolean }>\n\n/** One attributed row: who produced a gain, and the loss beside it. */\nexport interface RevenueAttributionRow {\n key: string\n /** Display name, filled in by a bounded lookup; the id until then. */\n name: string\n /** Secondary identity — plugin id, publisher, subdomain. */\n detail: string\n /** Revenue earned, net of its own reversals. */\n gainCents: number\n /** Money handed back. Already deducted from `gainCents`. */\n lossCents: number\n count: number\n}\n\n/** A capped attribution: shown rows plus the remainder, as figures. */\nexport interface RevenueAttribution {\n rows: RevenueAttributionRow[]\n omittedRows: number\n omittedGainCents: number\n omittedLossCents: number\n}\n\n/** What the report asks every source, once per period. */\nexport interface RevenueSourceRequest {\n /** `2026-08` or `2026-Q3`, echoed for the record. */\n period: string\n /** Half-open UTC bounds of the period. */\n start: Date\n end: Date\n /** Rows per attribution table the page shows; the rest is carried as figures. */\n attributionLimit: number\n sweep: RevenueSweep\n /**\n * Display names for organizations — which the report has already read, so\n * naming a publisher costs nothing.\n */\n orgNames(orgIds: readonly string[]): Promise<ReadonlyMap<string, string>>\n /**\n * Names the rows a source will SHOW from a collection's documents, in place:\n * `name` from `nameField`, `detail` from `detailField`, and `(deleted)` for a\n * document that is gone. Called AFTER capping, so the read is bounded by what\n * is displayed rather than by how many sales the period holds.\n */\n nameRows(\n rows: RevenueAttributionRow[],\n from: { collection: string; nameField: string; detailField: string },\n ): Promise<void>\n}\n\n/** A line of the report's gross-versus-net reconciliation. */\nexport interface RevenueSourceLine {\n label: string\n cents: number\n /** Subtracted on the way from gross to what the operator kept. */\n deduction: boolean\n /** Whose money it is — the sentence that says why it is or is not revenue. */\n note: string\n}\n\n/** A table of who produced a source's earnings. */\nexport interface RevenueAttributionTable extends RevenueAttribution {\n id: string\n heading: string\n /** The column naming what a row is: `Listing`, `Storefront`. */\n unit: string\n /** The column counting what was summed: `Sales`, `Orders`. */\n countLabel: string\n empty: string\n /** Draw the rows as ranked bars above the table, saying this when there are none. */\n chartEmpty?: string\n}\n\n/** What a source answers for one period. */\nexport interface RevenueSourceAnswer {\n /** Stable key, unique on the report. */\n id: string\n /** How the report names it when its read stopped short: `marketplace`. */\n name: string\n /** Its line of \"where the money came from\", net of everything not the operator's. */\n earned: { label: string; cents: number; note: string }\n grossToNet: RevenueSourceLine[]\n /** Qualifications worth stating beside the figures. Only those that apply. */\n notes: Array<{ label: string; tone: 'warning' | 'neutral' }>\n /** Each sums to `earned`. */\n attribution: RevenueAttributionTable[]\n /** A sentence under the source's attribution tables. */\n attributionNote?: string\n /** The sweep stopped at the ceiling: every figure is a LOWER BOUND. */\n truncated: boolean\n /**\n * The source's read could not run at all, so its figures are not counted —\n * never a cap, which is a different state with a different remedy.\n */\n failure: { title: string; detail: string } | null\n /** The source's own settled figures, carried whole for the audit. */\n summary: unknown\n}\n\n/** One source on the report: its answer, or why there is none. */\nexport type RevenueSection =\n | ({ outcome: 'answered'; pluginId: string } & RevenueSourceAnswer)\n | {\n outcome: 'refused'\n pluginId: string\n /** The plugin id: a refused source never said what it calls itself. */\n id: string\n reason: string\n }\n\nexport interface RevenueSource {\n /** This plugin's earnings for one period. May throw: the report refuses it. */\n read(request: RevenueSourceRequest): Promise<RevenueSourceAnswer>\n}\n\n/** Every source answers; one per plugin. */\nexport const REVENUE_SOURCES = definePluginServiceContract<RevenueSource>(\n 'core.revenue-sources',\n { multiple: true },\n)\n\n/** Installs a plugin's source; registering again replaces it. */\nexport function registerRevenueSource(\n source: RevenueSource,\n options?: { pluginId?: string },\n): void {\n if (typeof source?.read !== 'function') {\n throw new Error('a revenue source needs a read function')\n }\n registerPluginService(REVENUE_SOURCES, source, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** The plugins with a registered source, in the order they answer. */\nexport function listRevenueSources(): string[] {\n return resolvePluginServices(REVENUE_SOURCES).map((entry) => entry.pluginId)\n}\n\n/**\n * Group, sort and cap — every attribution table's shared half.\n *\n * A row whose key is missing is NOT dropped. It is grouped under an explicit\n * unattributed key, because a sale nobody can attribute still happened and\n * dropping it would make the rows sum below the total — the one property that\n * makes any of these tables worth reading. The cap carries its remainder as\n * FIGURES, so the table stays a complete accounting even when it is not a\n * complete list.\n */\nexport function groupRevenueAttribution(\n entries: ReadonlyArray<{ key: string; detail: string; gain: number; loss: number }>,\n limit: number,\n unattributedLabel: string,\n): RevenueAttribution {\n const byKey = new Map<string, RevenueAttributionRow>()\n for (const entry of entries ?? []) {\n const key = entry.key || unattributedLabel\n const row = byKey.get(key) ?? {\n key,\n name: key === unattributedLabel ? unattributedLabel : key,\n detail: entry.detail ?? '',\n gainCents: 0,\n lossCents: 0,\n count: 0,\n }\n row.gainCents += entry.gain\n row.lossCents += entry.loss\n row.count += 1\n if (!row.detail && entry.detail) row.detail = entry.detail\n byKey.set(key, row)\n }\n const rows = [...byKey.values()].sort(\n (a, b) =>\n b.gainCents - a.gainCents ||\n b.lossCents - a.lossCents ||\n a.key.localeCompare(b.key),\n )\n const kept = rows.slice(0, Math.max(0, limit))\n const dropped = rows.slice(Math.max(0, limit))\n return {\n rows: kept,\n omittedRows: dropped.length,\n omittedGainCents: dropped.reduce((sum, row) => sum + row.gainCents, 0),\n omittedLossCents: dropped.reduce((sum, row) => sum + row.lossCents, 0),\n }\n}\n\nconst isText = (value: unknown): value is string =>\n typeof value === 'string' && value.trim().length > 0\n\n/** What is wrong with an answer, or null when it can be printed. */\nfunction malformed(answer: RevenueSourceAnswer): string | null {\n if (!answer || typeof answer !== 'object') return 'no report'\n if (!isText(answer.id) || !isText(answer.name)) return 'a report with no id or name'\n const cents = answer.earned?.cents\n if (typeof cents !== 'number' || !Number.isFinite(cents)) return 'no earned figure'\n if (!isText(answer.earned?.label)) return 'an earned figure with no label'\n if (typeof answer.truncated !== 'boolean') return 'no truncation flag'\n for (const key of ['grossToNet', 'notes', 'attribution'] as const) {\n if (!Array.isArray(answer[key])) return `no ${key}`\n }\n const badLine = answer.grossToNet.find(\n (line) => typeof line?.cents !== 'number' || !Number.isFinite(line.cents),\n )\n if (badLine) return 'a gross-to-net line with no figure'\n return null\n}\n\n/**\n * Every source's answer for one period — declared sources first, in the\n * compiled order, then any other registered source.\n *\n * NEVER THROWS and never drops a declared source. `declared` is the compiled\n * {@link PLUGIN_REVENUE_SOURCES} unless a spec passes its own.\n */\nexport async function readRevenueSources(\n request: RevenueSourceRequest,\n declared: readonly string[] = PLUGIN_REVENUE_SOURCES,\n): Promise<RevenueSection[]> {\n const registered = resolvePluginServices(REVENUE_SOURCES)\n const order = [\n ...declared,\n ...registered\n .map((entry) => entry.pluginId)\n .filter((pluginId) => !declared.includes(pluginId)),\n ].filter((pluginId, index, all) => all.indexOf(pluginId) === index)\n\n const answers = await Promise.all(\n order.map(async (pluginId): Promise<RevenueSection> => {\n const source = registered.find((entry) => entry.pluginId === pluginId)?.impl\n if (!source) {\n console.error(\n `[revenue] \"${pluginId}\" declares a revenue source and none is ` +\n 'registered in this process; its earnings cannot be read.',\n )\n return {\n outcome: 'refused',\n pluginId,\n id: pluginId,\n reason:\n `The “${pluginId}” plugin earns through the platform and registered ` +\n 'nothing to read its earnings with.',\n }\n }\n try {\n const answer = await source.read(request)\n const problem = malformed(answer)\n if (problem) {\n return {\n outcome: 'refused',\n pluginId,\n id: pluginId,\n reason: `The “${pluginId}” plugin answered ${problem}.`,\n }\n }\n return { ...answer, outcome: 'answered', pluginId }\n } catch (error) {\n console.error(`[revenue] \"${pluginId}\" failed to read its earnings`, error)\n return {\n outcome: 'refused',\n pluginId,\n id: pluginId,\n reason: `The “${pluginId}” plugin could not read its earnings for this period.`,\n }\n }\n }),\n )\n\n const seen = new Set<string>()\n return answers.map((section) => {\n if (section.outcome !== 'answered') return section\n if (!seen.has(section.id)) {\n seen.add(section.id)\n return section\n }\n return {\n outcome: 'refused',\n pluginId: section.pluginId,\n id: section.pluginId,\n reason:\n `The “${section.pluginId}” plugin answered as “${section.id}”, which ` +\n 'another source already is.',\n }\n })\n}\n"],"names":["PLUGIN_REVENUE_SOURCES","definePluginServiceContract","registerPluginService","resolvePluginServices","REVENUE_SOURCES","multiple","registerRevenueSource","source","options","read","Error","pluginId","listRevenueSources","map","entry","groupRevenueAttribution","entries","limit","unattributedLabel","byKey","Map","key","row","get","name","detail","gainCents","lossCents","count","gain","loss","set","rows","values","sort","a","b","localeCompare","kept","slice","Math","max","dropped","omittedRows","length","omittedGainCents","reduce","sum","omittedLossCents","isText","value","trim","malformed","answer","id","cents","earned","Number","isFinite","label","truncated","Array","isArray","badLine","grossToNet","find","line","readRevenueSources","request","declared","registered","order","filter","includes","index","all","indexOf","answers","Promise","impl","console","error","outcome","reason","problem","seen","Set","section","has","add"],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A plugin's SECTION of the whole-site backup (AGL-3080): what it carries in
|
|
19
|
+
* the bundle and restores from it, when a host collection's `siteExport`
|
|
20
|
+
* cannot say so.
|
|
21
|
+
*
|
|
22
|
+
* A plain host collection is copied by the export and restore routes
|
|
23
|
+
* themselves (`plugin-site-export.ts`). Some plugin data is not that shape:
|
|
24
|
+
* it lives under the ORGANIZATION and is narrowed to the site being backed up,
|
|
25
|
+
* it carries a subcollection, a restore has to validate it against its own
|
|
26
|
+
* model and derive what the model implies, and its count is sold as an add-on
|
|
27
|
+
* rather than read off the plan. Only the plugin knows any of that. So the
|
|
28
|
+
* plugin declares the section and answers for it: the export asks it for the
|
|
29
|
+
* site's share, and the restore asks it — before the first write — whether
|
|
30
|
+
* the bundle would cross what the organization has paid for, then hands it the
|
|
31
|
+
* bundle's items and the restore's writer.
|
|
32
|
+
*
|
|
33
|
+
* ## Declared, then registered — and an absent section is refused
|
|
34
|
+
*
|
|
35
|
+
* The plugin declares the section in `plugins.config.json`
|
|
36
|
+
* (`siteBundleSections`: the bundle key and the most items it carries), which
|
|
37
|
+
* the generator compiles, and registers the answer from its
|
|
38
|
+
* `serverDeclarations` entry. A backup that silently lacks a section is worse
|
|
39
|
+
* than a failed one — the file looks whole until the day it is restored — so
|
|
40
|
+
* a section declared and not registered is not read as "nothing to carry":
|
|
41
|
+
* {@link resolveSiteBundleSections} runs the app's declarations step again,
|
|
42
|
+
* and throws if the section is still missing, which fails the export or the
|
|
43
|
+
* restore out loud.
|
|
44
|
+
*
|
|
45
|
+
* ## What the restore owns
|
|
46
|
+
*
|
|
47
|
+
* The section writes through {@link SiteBundleImportRequest.write}, never
|
|
48
|
+
* with a writer of its own: the restore's chunked batches, its document count
|
|
49
|
+
* and its all-or-nothing refusal are the restore's, and a write that went
|
|
50
|
+
* around them would land after a refusal or be missing from the total. The
|
|
51
|
+
* restore's own stamps (`createdAt`, `updatedAt`) come from
|
|
52
|
+
* {@link SiteBundleImportRequest.stamps}, so a restored document is dated the
|
|
53
|
+
* way every other one is.
|
|
54
|
+
*
|
|
55
|
+
* Reached by its own subpath, never the barrel: its readers are the two
|
|
56
|
+
* console routes and the plugins that answer them.
|
|
57
|
+
*/
|
|
58
|
+
/** A section as declared: its key in the bundle and the most items it carries. */
|
|
59
|
+
export interface PluginSiteBundleSectionDeclaration {
|
|
60
|
+
/** The bundle's top-level key for this section's items, e.g. `datasets`. */
|
|
61
|
+
key: string;
|
|
62
|
+
/** The most items one bundle carries; a restore reads no more. */
|
|
63
|
+
limit: number;
|
|
64
|
+
}
|
|
65
|
+
/** A declaration with the plugin that made it. */
|
|
66
|
+
export type ResolvedPluginSiteBundleSectionDeclaration = PluginSiteBundleSectionDeclaration & {
|
|
67
|
+
pluginId: string;
|
|
68
|
+
};
|
|
69
|
+
/** One item of a section, as the bundle carries it: a document with its `$id`. */
|
|
70
|
+
export type SiteBundleItem = Record<string, any>;
|
|
71
|
+
/** Whose backup this is. */
|
|
72
|
+
export interface SiteBundleRequest {
|
|
73
|
+
hostId: string;
|
|
74
|
+
/**
|
|
75
|
+
* The site's organization, or `null` for a site with none — which holds no
|
|
76
|
+
* organization data, a known-empty answer rather than a failure.
|
|
77
|
+
*/
|
|
78
|
+
orgId: string | null;
|
|
79
|
+
/** The section's declared limit: the most items one bundle carries. */
|
|
80
|
+
limit: number;
|
|
81
|
+
}
|
|
82
|
+
/** The restore's question: this bundle's items, for this site. */
|
|
83
|
+
export interface SiteBundleRestoreRequest extends SiteBundleRequest {
|
|
84
|
+
/** The organization's document, for the plan and add-ons a count is met against. */
|
|
85
|
+
org: unknown;
|
|
86
|
+
/** The bundle's items under the section's key, already capped at its declared limit. */
|
|
87
|
+
items: readonly SiteBundleItem[];
|
|
88
|
+
}
|
|
89
|
+
/** What the restore hands a section to write with. */
|
|
90
|
+
export interface SiteBundleImportRequest extends SiteBundleRestoreRequest {
|
|
91
|
+
/**
|
|
92
|
+
* Writes one document, whole (`merge: false`), by its full path
|
|
93
|
+
* (`orgs/{orgId}/…`), on the restore's batches and counted in its total.
|
|
94
|
+
*/
|
|
95
|
+
write(documentPath: string, data: Record<string, unknown>): Promise<void>;
|
|
96
|
+
/** The restore's own stamps for a document it writes: its creation and update times. */
|
|
97
|
+
stamps(): Record<string, unknown>;
|
|
98
|
+
/**
|
|
99
|
+
* Loads every plugin's console server surface, for a section that checks
|
|
100
|
+
* what it restores against something other plugins register there (a
|
|
101
|
+
* custom field type). Costs a load, so a section asks only when it needs to.
|
|
102
|
+
*/
|
|
103
|
+
loadPluginSurfaces(): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/** A row the restore reports without refusing: a document restored as it was, and why it is worth a look. */
|
|
106
|
+
export type SiteBundleReportRow = Record<string, unknown>;
|
|
107
|
+
/** A plugin's answers for one section. */
|
|
108
|
+
export interface PluginSiteBundleSection {
|
|
109
|
+
/** The site's share, read whole or not at all: throw rather than return a short list. */
|
|
110
|
+
export(request: SiteBundleRequest): Promise<SiteBundleItem[]>;
|
|
111
|
+
/**
|
|
112
|
+
* Before the restore writes anything: the sentence it answers 403 with when
|
|
113
|
+
* restoring these items would cross what the organization may hold, or
|
|
114
|
+
* `null`. A bundle refused here writes nothing at all.
|
|
115
|
+
*/
|
|
116
|
+
refusal?(request: SiteBundleRestoreRequest): Promise<string | null>;
|
|
117
|
+
/** Writes the items back through `request.write`; answers the rows worth reporting. */
|
|
118
|
+
import(request: SiteBundleImportRequest): Promise<readonly SiteBundleReportRow[]>;
|
|
119
|
+
}
|
|
120
|
+
/** Every declared section, in config order — the order the bundle carries them. */
|
|
121
|
+
export declare function listDeclaredSiteBundleSections(): readonly ResolvedPluginSiteBundleSectionDeclaration[];
|
|
122
|
+
/**
|
|
123
|
+
* Registers a plugin's answers for a section it declared. The owner is the
|
|
124
|
+
* plugin whose register fn is running, or the `pluginId` passed from a boot
|
|
125
|
+
* declaration. Refused: no owner, a key nobody declared (only a declared
|
|
126
|
+
* section is ever read, so an undeclared one would be carried by nothing),
|
|
127
|
+
* and a key another plugin declared. The same plugin registering again
|
|
128
|
+
* replaces its answers.
|
|
129
|
+
*/
|
|
130
|
+
export declare function registerPluginSiteBundleSection(key: string, section: PluginSiteBundleSection, options?: {
|
|
131
|
+
pluginId?: string;
|
|
132
|
+
}): void;
|
|
133
|
+
/** A declared section with the answers its plugin registered. */
|
|
134
|
+
export type ResolvedSiteBundleSection = ResolvedPluginSiteBundleSectionDeclaration & {
|
|
135
|
+
section: PluginSiteBundleSection;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Every declared section with its answers, in config order. A declared
|
|
139
|
+
* section with nothing registered runs the app's declarations step once and
|
|
140
|
+
* asks again; still missing, this THROWS naming it — an export that carried
|
|
141
|
+
* the rest would be a backup missing a section with nothing to say so.
|
|
142
|
+
*/
|
|
143
|
+
export declare function resolveSiteBundleSections(): Promise<ResolvedSiteBundleSection[]>;
|
|
144
|
+
/** Only for specs: forgets every registered section. */
|
|
145
|
+
export declare function resetSiteBundleSectionsForTests(): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
var _globalScope_SECTIONS_KEY;
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2026 Aglyn LLC
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
19
|
+
import { PLUGIN_SITE_BUNDLE_SECTIONS_DECLARED } from "./first-party-plugins.generated.js";
|
|
20
|
+
import { runPluginDeclarationsRepair } from "./plugin-declarations-repair.js";
|
|
21
|
+
/**
|
|
22
|
+
* One table per process, on `globalThis` (AGL-3412): a plugin registers from
|
|
23
|
+
* its declarations at boot, which Next compiles apart from the routes that
|
|
24
|
+
* read, and a module-scoped map would be filled in one copy and read empty in
|
|
25
|
+
* the other.
|
|
26
|
+
*/ const SECTIONS_KEY = Symbol.for('@aglyn/aglyn:site-bundle-sections');
|
|
27
|
+
const globalScope = globalThis;
|
|
28
|
+
const sections = (_globalScope_SECTIONS_KEY = globalScope[SECTIONS_KEY]) != null ? _globalScope_SECTIONS_KEY : globalScope[SECTIONS_KEY] = new Map();
|
|
29
|
+
/** Every declared section, in config order — the order the bundle carries them. */ export function listDeclaredSiteBundleSections() {
|
|
30
|
+
return PLUGIN_SITE_BUNDLE_SECTIONS_DECLARED;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registers a plugin's answers for a section it declared. The owner is the
|
|
34
|
+
* plugin whose register fn is running, or the `pluginId` passed from a boot
|
|
35
|
+
* declaration. Refused: no owner, a key nobody declared (only a declared
|
|
36
|
+
* section is ever read, so an undeclared one would be carried by nothing),
|
|
37
|
+
* and a key another plugin declared. The same plugin registering again
|
|
38
|
+
* replaces its answers.
|
|
39
|
+
*/ export function registerPluginSiteBundleSection(key, section, options) {
|
|
40
|
+
var _ref, _getRegisteringPluginId;
|
|
41
|
+
const name = key.trim();
|
|
42
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
43
|
+
if (!pluginId) {
|
|
44
|
+
throw new Error(`site bundle section "${name}" registered with no owner: pass { pluginId } ` + 'when registering outside a plugin register fn');
|
|
45
|
+
}
|
|
46
|
+
const declared = listDeclaredSiteBundleSections().find((one)=>one.key === name);
|
|
47
|
+
if (!declared) {
|
|
48
|
+
throw new Error(`site bundle section "${name}" is not declared in plugins.config.json ` + '(siteBundleSections), so no export would ever carry it');
|
|
49
|
+
}
|
|
50
|
+
if (declared.pluginId !== pluginId) {
|
|
51
|
+
throw new Error(`site bundle section "${name}" is declared by "${declared.pluginId}"; ` + `refused "${pluginId}"`);
|
|
52
|
+
}
|
|
53
|
+
sections.set(name, {
|
|
54
|
+
pluginId,
|
|
55
|
+
section
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Every declared section with its answers, in config order. A declared
|
|
60
|
+
* section with nothing registered runs the app's declarations step once and
|
|
61
|
+
* asks again; still missing, this THROWS naming it — an export that carried
|
|
62
|
+
* the rest would be a backup missing a section with nothing to say so.
|
|
63
|
+
*/ export async function resolveSiteBundleSections() {
|
|
64
|
+
const missing = ()=>listDeclaredSiteBundleSections().filter((declared)=>!sections.has(declared.key));
|
|
65
|
+
if (missing().length) await runPluginDeclarationsRepair();
|
|
66
|
+
const absent = missing();
|
|
67
|
+
if (absent.length) {
|
|
68
|
+
throw new Error(`[plugins] site bundle section(s) ${absent.map((one)=>`"${one.key}" (${one.pluginId})`).join(', ')} ` + 'declared and not registered in this process');
|
|
69
|
+
}
|
|
70
|
+
return listDeclaredSiteBundleSections().map((declared)=>_extends({}, declared, {
|
|
71
|
+
section: sections.get(declared.key).section
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
/** Only for specs: forgets every registered section. */ export function resetSiteBundleSectionsForTests() {
|
|
75
|
+
sections.clear();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=plugin-site-bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-site-bundle.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport { PLUGIN_SITE_BUNDLE_SECTIONS_DECLARED } from './first-party-plugins.generated'\nimport { runPluginDeclarationsRepair } from './plugin-declarations-repair'\n\n/**\n * A plugin's SECTION of the whole-site backup (AGL-3080): what it carries in\n * the bundle and restores from it, when a host collection's `siteExport`\n * cannot say so.\n *\n * A plain host collection is copied by the export and restore routes\n * themselves (`plugin-site-export.ts`). Some plugin data is not that shape:\n * it lives under the ORGANIZATION and is narrowed to the site being backed up,\n * it carries a subcollection, a restore has to validate it against its own\n * model and derive what the model implies, and its count is sold as an add-on\n * rather than read off the plan. Only the plugin knows any of that. So the\n * plugin declares the section and answers for it: the export asks it for the\n * site's share, and the restore asks it — before the first write — whether\n * the bundle would cross what the organization has paid for, then hands it the\n * bundle's items and the restore's writer.\n *\n * ## Declared, then registered — and an absent section is refused\n *\n * The plugin declares the section in `plugins.config.json`\n * (`siteBundleSections`: the bundle key and the most items it carries), which\n * the generator compiles, and registers the answer from its\n * `serverDeclarations` entry. A backup that silently lacks a section is worse\n * than a failed one — the file looks whole until the day it is restored — so\n * a section declared and not registered is not read as \"nothing to carry\":\n * {@link resolveSiteBundleSections} runs the app's declarations step again,\n * and throws if the section is still missing, which fails the export or the\n * restore out loud.\n *\n * ## What the restore owns\n *\n * The section writes through {@link SiteBundleImportRequest.write}, never\n * with a writer of its own: the restore's chunked batches, its document count\n * and its all-or-nothing refusal are the restore's, and a write that went\n * around them would land after a refusal or be missing from the total. The\n * restore's own stamps (`createdAt`, `updatedAt`) come from\n * {@link SiteBundleImportRequest.stamps}, so a restored document is dated the\n * way every other one is.\n *\n * Reached by its own subpath, never the barrel: its readers are the two\n * console routes and the plugins that answer them.\n */\n\n/** A section as declared: its key in the bundle and the most items it carries. */\nexport interface PluginSiteBundleSectionDeclaration {\n /** The bundle's top-level key for this section's items, e.g. `datasets`. */\n key: string\n /** The most items one bundle carries; a restore reads no more. */\n limit: number\n}\n\n/** A declaration with the plugin that made it. */\nexport type ResolvedPluginSiteBundleSectionDeclaration =\n PluginSiteBundleSectionDeclaration & { pluginId: string }\n\n/** One item of a section, as the bundle carries it: a document with its `$id`. */\nexport type SiteBundleItem = Record<string, any>\n\n/** Whose backup this is. */\nexport interface SiteBundleRequest {\n hostId: string\n /**\n * The site's organization, or `null` for a site with none — which holds no\n * organization data, a known-empty answer rather than a failure.\n */\n orgId: string | null\n /** The section's declared limit: the most items one bundle carries. */\n limit: number\n}\n\n/** The restore's question: this bundle's items, for this site. */\nexport interface SiteBundleRestoreRequest extends SiteBundleRequest {\n /** The organization's document, for the plan and add-ons a count is met against. */\n org: unknown\n /** The bundle's items under the section's key, already capped at its declared limit. */\n items: readonly SiteBundleItem[]\n}\n\n/** What the restore hands a section to write with. */\nexport interface SiteBundleImportRequest extends SiteBundleRestoreRequest {\n /**\n * Writes one document, whole (`merge: false`), by its full path\n * (`orgs/{orgId}/…`), on the restore's batches and counted in its total.\n */\n write(documentPath: string, data: Record<string, unknown>): Promise<void>\n /** The restore's own stamps for a document it writes: its creation and update times. */\n stamps(): Record<string, unknown>\n /**\n * Loads every plugin's console server surface, for a section that checks\n * what it restores against something other plugins register there (a\n * custom field type). Costs a load, so a section asks only when it needs to.\n */\n loadPluginSurfaces(): Promise<void>\n}\n\n/** A row the restore reports without refusing: a document restored as it was, and why it is worth a look. */\nexport type SiteBundleReportRow = Record<string, unknown>\n\n/** A plugin's answers for one section. */\nexport interface PluginSiteBundleSection {\n /** The site's share, read whole or not at all: throw rather than return a short list. */\n export(request: SiteBundleRequest): Promise<SiteBundleItem[]>\n /**\n * Before the restore writes anything: the sentence it answers 403 with when\n * restoring these items would cross what the organization may hold, or\n * `null`. A bundle refused here writes nothing at all.\n */\n refusal?(request: SiteBundleRestoreRequest): Promise<string | null>\n /** Writes the items back through `request.write`; answers the rows worth reporting. */\n import(request: SiteBundleImportRequest): Promise<readonly SiteBundleReportRow[]>\n}\n\ninterface Registered {\n pluginId: string\n section: PluginSiteBundleSection\n}\n\n/**\n * One table per process, on `globalThis` (AGL-3412): a plugin registers from\n * its declarations at boot, which Next compiles apart from the routes that\n * read, and a module-scoped map would be filled in one copy and read empty in\n * the other.\n */\nconst SECTIONS_KEY = Symbol.for('@aglyn/aglyn:site-bundle-sections')\n\nconst globalScope = globalThis as typeof globalThis & {\n [SECTIONS_KEY]?: Map<string, Registered>\n}\n\nconst sections: Map<string, Registered> =\n globalScope[SECTIONS_KEY] ?? (globalScope[SECTIONS_KEY] = new Map())\n\n/** Every declared section, in config order — the order the bundle carries them. */\nexport function listDeclaredSiteBundleSections(): readonly ResolvedPluginSiteBundleSectionDeclaration[] {\n return PLUGIN_SITE_BUNDLE_SECTIONS_DECLARED\n}\n\n/**\n * Registers a plugin's answers for a section it declared. The owner is the\n * plugin whose register fn is running, or the `pluginId` passed from a boot\n * declaration. Refused: no owner, a key nobody declared (only a declared\n * section is ever read, so an undeclared one would be carried by nothing),\n * and a key another plugin declared. The same plugin registering again\n * replaces its answers.\n */\nexport function registerPluginSiteBundleSection(\n key: string,\n section: PluginSiteBundleSection,\n options?: { pluginId?: string },\n): void {\n const name = key.trim()\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `site bundle section \"${name}\" registered with no owner: pass { pluginId } ` +\n 'when registering outside a plugin register fn',\n )\n }\n const declared = listDeclaredSiteBundleSections().find((one) => one.key === name)\n if (!declared) {\n throw new Error(\n `site bundle section \"${name}\" is not declared in plugins.config.json ` +\n '(siteBundleSections), so no export would ever carry it',\n )\n }\n if (declared.pluginId !== pluginId) {\n throw new Error(\n `site bundle section \"${name}\" is declared by \"${declared.pluginId}\"; ` +\n `refused \"${pluginId}\"`,\n )\n }\n sections.set(name, { pluginId, section })\n}\n\n/** A declared section with the answers its plugin registered. */\nexport type ResolvedSiteBundleSection = ResolvedPluginSiteBundleSectionDeclaration & {\n section: PluginSiteBundleSection\n}\n\n/**\n * Every declared section with its answers, in config order. A declared\n * section with nothing registered runs the app's declarations step once and\n * asks again; still missing, this THROWS naming it — an export that carried\n * the rest would be a backup missing a section with nothing to say so.\n */\nexport async function resolveSiteBundleSections(): Promise<ResolvedSiteBundleSection[]> {\n const missing = () =>\n listDeclaredSiteBundleSections().filter((declared) => !sections.has(declared.key))\n if (missing().length) await runPluginDeclarationsRepair()\n const absent = missing()\n if (absent.length) {\n throw new Error(\n `[plugins] site bundle section(s) ${absent.map((one) => `\"${one.key}\" (${one.pluginId})`).join(', ')} ` +\n 'declared and not registered in this process',\n )\n }\n return listDeclaredSiteBundleSections().map((declared) => ({\n ...declared,\n section: (sections.get(declared.key) as Registered).section,\n }))\n}\n\n/** Only for specs: forgets every registered section. */\nexport function resetSiteBundleSectionsForTests(): void {\n sections.clear()\n}\n"],"names":["globalScope","getRegisteringPluginId","PLUGIN_SITE_BUNDLE_SECTIONS_DECLARED","runPluginDeclarationsRepair","SECTIONS_KEY","Symbol","for","globalThis","sections","Map","listDeclaredSiteBundleSections","registerPluginSiteBundleSection","key","section","options","name","trim","pluginId","Error","declared","find","one","set","resolveSiteBundleSections","missing","filter","has","length","absent","map","join","get","resetSiteBundleSectionsForTests","clear"],"mappings":";IAsJEA;AAtJF;;;;;;;;;;;;;;;CAeC,GAED,SAASC,sBAAsB,QAAQ,qCAAiC;AACxE,SAASC,oCAAoC,QAAQ,qCAAiC;AACtF,SAASC,2BAA2B,QAAQ,kCAA8B;AAsH1E;;;;;CAKC,GACD,MAAMC,eAAeC,OAAOC,GAAG,CAAC;AAEhC,MAAMN,cAAcO;AAIpB,MAAMC,YACJR,4BAAAA,WAAW,CAACI,aAAa,YAAzBJ,4BAA8BA,WAAW,CAACI,aAAa,GAAG,IAAIK;AAEhE,iFAAiF,GACjF,OAAO,SAASC;IACd,OAAOR;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASS,gCACdC,GAAW,EACXC,OAAgC,EAChCC,OAA+B;QAGbb,MAAAA;IADlB,MAAMc,OAAOH,IAAII,IAAI;IACrB,MAAMC,WAAW,EAAChB,QAAAA,0BAAAA,oCAAAA,0BAA4Ba,2BAAAA,QAASG,QAAQ,YAA7ChB,OAAiD,IAAIe,IAAI;IAC3E,IAAI,CAACC,UAAU;QACb,MAAM,IAAIC,MACR,CAAC,qBAAqB,EAAEH,KAAK,8CAA8C,CAAC,GAC1E;IAEN;IACA,MAAMI,WAAWT,iCAAiCU,IAAI,CAAC,CAACC,MAAQA,IAAIT,GAAG,KAAKG;IAC5E,IAAI,CAACI,UAAU;QACb,MAAM,IAAID,MACR,CAAC,qBAAqB,EAAEH,KAAK,yCAAyC,CAAC,GACrE;IAEN;IACA,IAAII,SAASF,QAAQ,KAAKA,UAAU;QAClC,MAAM,IAAIC,MACR,CAAC,qBAAqB,EAAEH,KAAK,kBAAkB,EAAEI,SAASF,QAAQ,CAAC,GAAG,CAAC,GACrE,CAAC,SAAS,EAAEA,SAAS,CAAC,CAAC;IAE7B;IACAT,SAASc,GAAG,CAACP,MAAM;QAAEE;QAAUJ;IAAQ;AACzC;AAOA;;;;;CAKC,GACD,OAAO,eAAeU;IACpB,MAAMC,UAAU,IACdd,iCAAiCe,MAAM,CAAC,CAACN,WAAa,CAACX,SAASkB,GAAG,CAACP,SAASP,GAAG;IAClF,IAAIY,UAAUG,MAAM,EAAE,MAAMxB;IAC5B,MAAMyB,SAASJ;IACf,IAAII,OAAOD,MAAM,EAAE;QACjB,MAAM,IAAIT,MACR,CAAC,iCAAiC,EAAEU,OAAOC,GAAG,CAAC,CAACR,MAAQ,CAAC,CAAC,EAAEA,IAAIT,GAAG,CAAC,GAAG,EAAES,IAAIJ,QAAQ,CAAC,CAAC,CAAC,EAAEa,IAAI,CAAC,MAAM,CAAC,CAAC,GACrG;IAEN;IACA,OAAOpB,iCAAiCmB,GAAG,CAAC,CAACV,WAAc,aACtDA;YACHN,SAAS,AAACL,SAASuB,GAAG,CAACZ,SAASP,GAAG,EAAiBC,OAAO;;AAE/D;AAEA,sDAAsD,GACtD,OAAO,SAASmB;IACdxB,SAASyB,KAAK;AAChB"}
|
|
@@ -27,6 +27,18 @@ export interface PluginSiteCacheRequest {
|
|
|
27
27
|
* `'marketplace listing revoked'`, not `'revoke'`.
|
|
28
28
|
*/
|
|
29
29
|
reason: string;
|
|
30
|
+
/**
|
|
31
|
+
* Narrows a site's drop to the pages that changed: its site-absolute
|
|
32
|
+
* addresses (`/`, `/contact`), keyed by site id. A site named here loses
|
|
33
|
+
* only those pages, and one named with an empty list loses none; a site
|
|
34
|
+
* not named here loses every page it serves.
|
|
35
|
+
*
|
|
36
|
+
* For a change the plugin can place exactly — a document it publishes that
|
|
37
|
+
* renders on the pages that place it — rather than one that reaches every
|
|
38
|
+
* page, like a revoked bundle. The plugin knows which pages; the app knows
|
|
39
|
+
* how to drop one, on every address the site answers at.
|
|
40
|
+
*/
|
|
41
|
+
paths?: Readonly<Record<string, readonly string[]>>;
|
|
30
42
|
}
|
|
31
43
|
export interface PluginSiteCacheResult {
|
|
32
44
|
/** Sites whose cached pages were dropped. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-site-cache.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 * HOW A PLUGIN DROPS A SITE'S CACHED PAGES (AGL-3080).\n *\n * The shell hands this one down rather than up. Every other contract in this\n * directory is a plugin telling the shell something; this is the shell\n * telling plugins it can do something — purge the rendered HTML of a site —\n * because purging it takes the tenant's cache tags and revalidation paths,\n * which live in the app and are not a plugin's to know.\n *\n * ## The case, and why it is a kill switch rather than a nicety\n *\n * A marketplace plugin can be REVOKED. Revoking writes a stop on the\n * version, and the tenant refuses revoked bytes at render time — but a page\n * already rendered and cached does not re-render to find that out. So the\n * revoke has a second half: drop the cached pages of every site running it.\n * Without it a revoked plugin keeps executing in visitors' browsers for as\n * long as the cache holds, which is the whole failure the stop exists to\n * prevent.\n *\n * ## ⛔ AN ABSENT IMPLEMENTATION IS AN ANSWER, AND IT IS `complete: false`\n *\n * The registry is filled by the APP at boot. A process that has not filled\n * it resolves \"no cache to drop\" — indistinguishable, from the inside, from\n * a workspace whose sites were all already fresh. That is the AGL-3025 shape\n * and on this path it means a revoked plugin quietly kept serving.\n *\n * So there is no silent branch. {@link dropPluginSiteCache} always answers a\n * {@link PluginSiteCacheResult}, and `complete` is false whenever the drop\n * did not certainly happen — nothing registered, or the implementation\n * threw. A caller acts on that: the revoke itself must still stand (a plugin\n * left un-revoked because a cache purge failed is strictly worse), but the\n * reader has to be told the purge did not run, and the log has to carry it.\n *\n * ## The split, in one line\n *\n * The plugin knows WHICH sites — that is its own install pins, its own\n * collection. The shell knows HOW to drop one, and owns the fan-out policy:\n * the cap, the concurrency, and saying out loud when a cap truncated the\n * list. Neither half is guessable from the other side.\n */\n\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n} from './plugin-services'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** What the caller wants dropped, and why. */\nexport interface PluginSiteCacheRequest {\n /**\n * The sites whose rendered pages must go. Duplicates are the caller's to\n * avoid or not — the implementation is free to collapse them.\n */\n hostIds: readonly string[]\n /**\n * Why, in a few words, for the fan-out's own logging: a truncated purge\n * is only debuggable if the record says what it was purging for.\n * `'marketplace listing revoked'`, not `'revoke'`.\n */\n reason: string\n}\n\nexport interface PluginSiteCacheResult {\n /** Sites whose cached pages were dropped. */\n dropped: number\n /**\n * Sites deliberately left holding cached pages because the fan-out hit\n * its cap. Not an error — a decision the shell made and logged — but a\n * number a caller may want to surface, because those sites are still\n * serving what was just stopped.\n */\n skipped: number\n /**\n * Whether the drop certainly happened. FALSE when no implementation is\n * registered or one threw, and a caller must not read `dropped: 0` as\n * \"there was nothing to drop\" without checking this first.\n */\n complete: boolean\n}\n\nexport interface PluginSiteCache {\n drop(request: PluginSiteCacheRequest): Promise<PluginSiteCacheResult>\n}\n\n/**\n * One implementation: the app's. Two would be two cache layers disagreeing\n * about whether a page is gone, and the second to register would be the one\n * nobody knew about.\n */\nexport const PLUGIN_SITE_CACHE = definePluginServiceContract<PluginSiteCache>(\n 'core.site-cache',\n { multiple: false },\n)\n\n/**\n * Installs the app's implementation. Called once, at server boot, by the app\n * that owns the tenant's cache.\n *\n * `pluginId` is required from outside a plugin's register fn, as it is for\n * every service: the app passes its own marker, and the contract refuses an\n * anonymous registration rather than accepting one nothing can be traced to.\n */\nexport function registerPluginSiteCache(\n cache: PluginSiteCache,\n options?: { pluginId?: string },\n): void {\n if (typeof cache?.drop !== 'function') {\n throw new Error('a site cache needs a drop function')\n }\n registerPluginService(PLUGIN_SITE_CACHE, cache, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** Whether an implementation is installed — for a caller that wants to say so. */\nexport function hasPluginSiteCache(): boolean {\n return Boolean(resolvePluginService(PLUGIN_SITE_CACHE))\n}\n\n/**\n * Drops the cached pages of the named sites.\n *\n * NEVER THROWS and never silently succeeds: a failure comes back as\n * `complete: false` with the reason logged, because the callers are stop\n * paths that must complete their own work either way. Read `complete`\n * before reading `dropped`.\n */\nexport async function dropPluginSiteCache(\n request: PluginSiteCacheRequest,\n): Promise<PluginSiteCacheResult> {\n const hostIds = [...new Set(request.hostIds.filter(Boolean))]\n if (!hostIds.length) {\n // Nothing asked for is the one case where \"nothing dropped\" is complete\n // on its own terms, whether or not anything is registered.\n return { dropped: 0, skipped: 0, complete: true }\n }\n const cache = resolvePluginService(PLUGIN_SITE_CACHE)\n if (!cache) {\n console.error(\n `[site-cache] no implementation is installed, so ${hostIds.length} ` +\n `site(s) keep their cached pages after: ${request.reason}. The app ` +\n 'registers this at boot; a process that has not is serving stopped ' +\n 'content.',\n )\n return { dropped: 0, skipped: 0, complete: false }\n }\n try {\n return await cache.drop({ ...request, hostIds })\n } catch (error) {\n console.error(\n `[site-cache] the drop failed after: ${request.reason}`,\n error,\n )\n return { dropped: 0, skipped: 0, complete: false }\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","PLUGIN_SITE_CACHE","multiple","registerPluginSiteCache","cache","options","drop","Error","pluginId","hasPluginSiteCache","Boolean","dropPluginSiteCache","request","hostIds","Set","filter","length","dropped","skipped","complete","console","error","reason"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,QACf,uBAAmB;
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-site-cache.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 * HOW A PLUGIN DROPS A SITE'S CACHED PAGES (AGL-3080).\n *\n * The shell hands this one down rather than up. Every other contract in this\n * directory is a plugin telling the shell something; this is the shell\n * telling plugins it can do something — purge the rendered HTML of a site —\n * because purging it takes the tenant's cache tags and revalidation paths,\n * which live in the app and are not a plugin's to know.\n *\n * ## The case, and why it is a kill switch rather than a nicety\n *\n * A marketplace plugin can be REVOKED. Revoking writes a stop on the\n * version, and the tenant refuses revoked bytes at render time — but a page\n * already rendered and cached does not re-render to find that out. So the\n * revoke has a second half: drop the cached pages of every site running it.\n * Without it a revoked plugin keeps executing in visitors' browsers for as\n * long as the cache holds, which is the whole failure the stop exists to\n * prevent.\n *\n * ## ⛔ AN ABSENT IMPLEMENTATION IS AN ANSWER, AND IT IS `complete: false`\n *\n * The registry is filled by the APP at boot. A process that has not filled\n * it resolves \"no cache to drop\" — indistinguishable, from the inside, from\n * a workspace whose sites were all already fresh. That is the AGL-3025 shape\n * and on this path it means a revoked plugin quietly kept serving.\n *\n * So there is no silent branch. {@link dropPluginSiteCache} always answers a\n * {@link PluginSiteCacheResult}, and `complete` is false whenever the drop\n * did not certainly happen — nothing registered, or the implementation\n * threw. A caller acts on that: the revoke itself must still stand (a plugin\n * left un-revoked because a cache purge failed is strictly worse), but the\n * reader has to be told the purge did not run, and the log has to carry it.\n *\n * ## The split, in one line\n *\n * The plugin knows WHICH sites — that is its own install pins, its own\n * collection. The shell knows HOW to drop one, and owns the fan-out policy:\n * the cap, the concurrency, and saying out loud when a cap truncated the\n * list. Neither half is guessable from the other side.\n */\n\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginService,\n} from './plugin-services'\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** What the caller wants dropped, and why. */\nexport interface PluginSiteCacheRequest {\n /**\n * The sites whose rendered pages must go. Duplicates are the caller's to\n * avoid or not — the implementation is free to collapse them.\n */\n hostIds: readonly string[]\n /**\n * Why, in a few words, for the fan-out's own logging: a truncated purge\n * is only debuggable if the record says what it was purging for.\n * `'marketplace listing revoked'`, not `'revoke'`.\n */\n reason: string\n /**\n * Narrows a site's drop to the pages that changed: its site-absolute\n * addresses (`/`, `/contact`), keyed by site id. A site named here loses\n * only those pages, and one named with an empty list loses none; a site\n * not named here loses every page it serves.\n *\n * For a change the plugin can place exactly — a document it publishes that\n * renders on the pages that place it — rather than one that reaches every\n * page, like a revoked bundle. The plugin knows which pages; the app knows\n * how to drop one, on every address the site answers at.\n */\n paths?: Readonly<Record<string, readonly string[]>>\n}\n\nexport interface PluginSiteCacheResult {\n /** Sites whose cached pages were dropped. */\n dropped: number\n /**\n * Sites deliberately left holding cached pages because the fan-out hit\n * its cap. Not an error — a decision the shell made and logged — but a\n * number a caller may want to surface, because those sites are still\n * serving what was just stopped.\n */\n skipped: number\n /**\n * Whether the drop certainly happened. FALSE when no implementation is\n * registered or one threw, and a caller must not read `dropped: 0` as\n * \"there was nothing to drop\" without checking this first.\n */\n complete: boolean\n}\n\nexport interface PluginSiteCache {\n drop(request: PluginSiteCacheRequest): Promise<PluginSiteCacheResult>\n}\n\n/**\n * One implementation: the app's. Two would be two cache layers disagreeing\n * about whether a page is gone, and the second to register would be the one\n * nobody knew about.\n */\nexport const PLUGIN_SITE_CACHE = definePluginServiceContract<PluginSiteCache>(\n 'core.site-cache',\n { multiple: false },\n)\n\n/**\n * Installs the app's implementation. Called once, at server boot, by the app\n * that owns the tenant's cache.\n *\n * `pluginId` is required from outside a plugin's register fn, as it is for\n * every service: the app passes its own marker, and the contract refuses an\n * anonymous registration rather than accepting one nothing can be traced to.\n */\nexport function registerPluginSiteCache(\n cache: PluginSiteCache,\n options?: { pluginId?: string },\n): void {\n if (typeof cache?.drop !== 'function') {\n throw new Error('a site cache needs a drop function')\n }\n registerPluginService(PLUGIN_SITE_CACHE, cache, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\n/** Whether an implementation is installed — for a caller that wants to say so. */\nexport function hasPluginSiteCache(): boolean {\n return Boolean(resolvePluginService(PLUGIN_SITE_CACHE))\n}\n\n/**\n * Drops the cached pages of the named sites.\n *\n * NEVER THROWS and never silently succeeds: a failure comes back as\n * `complete: false` with the reason logged, because the callers are stop\n * paths that must complete their own work either way. Read `complete`\n * before reading `dropped`.\n */\nexport async function dropPluginSiteCache(\n request: PluginSiteCacheRequest,\n): Promise<PluginSiteCacheResult> {\n const hostIds = [...new Set(request.hostIds.filter(Boolean))]\n if (!hostIds.length) {\n // Nothing asked for is the one case where \"nothing dropped\" is complete\n // on its own terms, whether or not anything is registered.\n return { dropped: 0, skipped: 0, complete: true }\n }\n const cache = resolvePluginService(PLUGIN_SITE_CACHE)\n if (!cache) {\n console.error(\n `[site-cache] no implementation is installed, so ${hostIds.length} ` +\n `site(s) keep their cached pages after: ${request.reason}. The app ` +\n 'registers this at boot; a process that has not is serving stopped ' +\n 'content.',\n )\n return { dropped: 0, skipped: 0, complete: false }\n }\n try {\n return await cache.drop({ ...request, hostIds })\n } catch (error) {\n console.error(\n `[site-cache] the drop failed after: ${request.reason}`,\n error,\n )\n return { dropped: 0, skipped: 0, complete: false }\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginService","PLUGIN_SITE_CACHE","multiple","registerPluginSiteCache","cache","options","drop","Error","pluginId","hasPluginSiteCache","Boolean","dropPluginSiteCache","request","hostIds","Set","filter","length","dropped","skipped","complete","console","error","reason"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCC,GAED,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,oBAAoB,QACf,uBAAmB;AAoD1B;;;;CAIC,GACD,OAAO,MAAMC,oBAAoBH,4BAC/B,mBACA;IAAEI,UAAU;AAAM,GACnB;AAED;;;;;;;CAOC,GACD,OAAO,SAASC,wBACdC,KAAsB,EACtBC,OAA+B;IAE/B,IAAI,QAAOD,yBAAAA,MAAOE,IAAI,MAAK,YAAY;QACrC,MAAM,IAAIC,MAAM;IAClB;IACAR,sBAAsBE,mBAAmBG,OAAO,aAC1CC,CAAAA,2BAAAA,QAASG,QAAQ,IAAG;QAAEA,UAAUH,QAAQG,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAEA,gFAAgF,GAChF,OAAO,SAASC;IACd,OAAOC,QAAQV,qBAAqBC;AACtC;AAEA;;;;;;;CAOC,GACD,OAAO,eAAeU,oBACpBC,OAA+B;IAE/B,MAAMC,UAAU;WAAI,IAAIC,IAAIF,QAAQC,OAAO,CAACE,MAAM,CAACL;KAAU;IAC7D,IAAI,CAACG,QAAQG,MAAM,EAAE;QACnB,wEAAwE;QACxE,2DAA2D;QAC3D,OAAO;YAAEC,SAAS;YAAGC,SAAS;YAAGC,UAAU;QAAK;IAClD;IACA,MAAMf,QAAQJ,qBAAqBC;IACnC,IAAI,CAACG,OAAO;QACVgB,QAAQC,KAAK,CACX,CAAC,gDAAgD,EAAER,QAAQG,MAAM,CAAC,CAAC,CAAC,GAClE,CAAC,uCAAuC,EAAEJ,QAAQU,MAAM,CAAC,UAAU,CAAC,GACpE,uEACA;QAEJ,OAAO;YAAEL,SAAS;YAAGC,SAAS;YAAGC,UAAU;QAAM;IACnD;IACA,IAAI;QACF,OAAO,MAAMf,MAAME,IAAI,CAAC,aAAKM;YAASC;;IACxC,EAAE,OAAOQ,OAAO;QACdD,QAAQC,KAAK,CACX,CAAC,oCAAoC,EAAET,QAAQU,MAAM,EAAE,EACvDD;QAEF,OAAO;YAAEJ,SAAS;YAAGC,SAAS;YAAGC,UAAU;QAAM;IACnD;AACF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A plugin's host collection in the platform's whole-site backup (AGL-3080).
|
|
19
|
+
*
|
|
20
|
+
* `/api/hosts/export` writes one JSON bundle of everything designable on a
|
|
21
|
+
* site and `/api/hosts/import` restores it (AGL-163). The platform's own
|
|
22
|
+
* documents — screens, layouts, components, authors, content collections,
|
|
23
|
+
* media — are the routes' to carry. A plugin whose site documents belong in
|
|
24
|
+
* a backup declares so beside the collection, in the `hostCollections` block
|
|
25
|
+
* of `plugins.config.json` (a host collection's `siteExport`), and both
|
|
26
|
+
* routes walk the declarations: the export reads up to `limit` live documents
|
|
27
|
+
* into the bundle under the collection's own name, and the restore writes each
|
|
28
|
+
* one back through `fields` and counts the result against the collection's
|
|
29
|
+
* declared `resource`.
|
|
30
|
+
*
|
|
31
|
+
* ## `fields` is the round trip, not the create
|
|
32
|
+
*
|
|
33
|
+
* A restore writes with `merge: false`, so the list is every key a live
|
|
34
|
+
* document carries — which can be narrower than what a create admits, and is
|
|
35
|
+
* often wider — and a key left off is ERASED from every restored document
|
|
36
|
+
* (AGL-1382). Nothing the restore stamps (`createdAt`, `updatedAt`,
|
|
37
|
+
* `deletedAt`) or scopes (`visibleTo`) is on it; the generator refuses them.
|
|
38
|
+
*
|
|
39
|
+
* ## Compiled, and counted
|
|
40
|
+
*
|
|
41
|
+
* A backup that silently lacks a collection is worse than a failed one: the
|
|
42
|
+
* file looks whole until the day it is restored. So the declarations are
|
|
43
|
+
* compiled, present the moment this module is imported, and a runtime
|
|
44
|
+
* `registerPluginHostCollections` carrying a `siteExport` throws. A restore
|
|
45
|
+
* creates documents with the Admin SDK, past the rules, so the generator also
|
|
46
|
+
* refuses a `siteExport` on a collection with no `resource`: the resource's
|
|
47
|
+
* plan counter or flat platform cap is what the restore is met against.
|
|
48
|
+
*
|
|
49
|
+
* Reached by its own subpath, never the barrel: its readers are the two
|
|
50
|
+
* console routes.
|
|
51
|
+
*/
|
|
52
|
+
export interface PluginHostCollectionSiteExport {
|
|
53
|
+
/** The most live documents one bundle carries; a restore writes no more. */
|
|
54
|
+
limit: number;
|
|
55
|
+
/** The keys a restore writes back. Anything else a bundle carries is dropped. */
|
|
56
|
+
fields: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
/** What a restore of a declared collection is counted against. */
|
|
59
|
+
export type PluginSiteExportCount =
|
|
60
|
+
/** A plan counter, an `OrgEntitlements` key core resolves. */
|
|
61
|
+
{
|
|
62
|
+
quotaKey: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A flat platform cap, resolved from the name the resource declares, or
|
|
66
|
+
* `null` for a name core does not hold — which a restore refuses.
|
|
67
|
+
*/
|
|
68
|
+
| {
|
|
69
|
+
max: number | null;
|
|
70
|
+
};
|
|
71
|
+
/** A declared collection with the plugin that made it and its count. */
|
|
72
|
+
export interface ResolvedPluginSiteExportCollection extends PluginHostCollectionSiteExport {
|
|
73
|
+
pluginId: string;
|
|
74
|
+
/** The subcollection under `hosts/{hostId}`, and its key in the bundle. */
|
|
75
|
+
collection: string;
|
|
76
|
+
/** Plural, for a restore's refusal: "this backup holds 40 {label}". */
|
|
77
|
+
label: string;
|
|
78
|
+
count: PluginSiteExportCount;
|
|
79
|
+
}
|
|
80
|
+
/** Every collection a plugin declares for the site backup, in config order. */
|
|
81
|
+
export declare function listPluginSiteExportCollections(): ResolvedPluginSiteExportCollection[];
|