@aglyn/aglyn 1.0.0-beta.218 → 1.0.0-beta.220
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -12
- package/src/lib/app-utils/actions.d.ts +26 -433
- package/src/lib/app-utils/actions.js +108 -633
- package/src/lib/app-utils/actions.js.map +1 -1
- package/src/lib/app-utils/activity-presenter.d.ts +3 -3
- package/src/lib/app-utils/activity-presenter.js +36 -52
- package/src/lib/app-utils/activity-presenter.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +1 -1
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/analytics-provider.d.ts +182 -0
- package/src/lib/app-utils/analytics-provider.js +133 -0
- package/src/lib/app-utils/analytics-provider.js.map +1 -0
- package/src/lib/app-utils/api-plugins.d.ts +46 -0
- package/src/lib/app-utils/api-plugins.js +19 -0
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
- package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
- package/src/lib/app-utils/artifact-overrides.js.map +1 -0
- package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
- package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
- package/src/lib/app-utils/artifact-provenance.js.map +1 -0
- package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
- package/src/lib/app-utils/binding-token-catalog.js +17 -41
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
- package/src/lib/app-utils/brand-support-line.d.ts +36 -0
- package/src/lib/app-utils/brand-support-line.js +38 -0
- package/src/lib/app-utils/brand-support-line.js.map +1 -0
- package/src/lib/app-utils/collection-entries.d.ts +2 -2
- package/src/lib/app-utils/collection-entries.js.map +1 -1
- package/src/lib/app-utils/consent-group-change.d.ts +4 -0
- package/src/lib/app-utils/consent-group-change.js.map +1 -1
- package/src/lib/app-utils/consent-groups.d.ts +1 -1
- package/src/lib/app-utils/consent-groups.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +15 -42
- package/src/lib/app-utils/console-routes.js +10 -39
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +24 -2
- package/src/lib/app-utils/contacts.js +21 -5
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/container-membership.d.ts +93 -0
- package/src/lib/app-utils/container-membership.js +165 -0
- package/src/lib/app-utils/container-membership.js.map +1 -0
- package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
- package/src/lib/app-utils/crm-task-reminders.js +32 -11
- package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +24 -4
- package/src/lib/app-utils/crm.js +23 -0
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
- package/src/lib/app-utils/cross-pool-desync.js +47 -0
- package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
- package/src/lib/app-utils/csv-upload.d.ts +29 -0
- package/src/lib/app-utils/csv-upload.js +29 -0
- package/src/lib/app-utils/csv-upload.js.map +1 -0
- package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
- package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
- package/src/lib/app-utils/csv.js.map +1 -0
- package/src/lib/app-utils/dataset-models.d.ts +5 -5
- package/src/lib/app-utils/dataset-models.js +19 -2
- package/src/lib/app-utils/dataset-models.js.map +1 -1
- package/src/lib/app-utils/datasets.d.ts +65 -30
- package/src/lib/app-utils/datasets.js +74 -24
- package/src/lib/app-utils/datasets.js.map +1 -1
- package/src/lib/app-utils/docs-deflection.d.ts +6 -6
- package/src/lib/app-utils/docs-deflection.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +14 -8
- package/src/lib/app-utils/docs-help.generated.js +23 -6
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-index.generated.js +570 -346
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
- package/src/lib/app-utils/docs-retrieval.js +14 -10
- package/src/lib/app-utils/docs-retrieval.js.map +1 -1
- package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
- package/src/lib/app-utils/duplicate-resource.js +7 -4
- package/src/lib/app-utils/duplicate-resource.js.map +1 -1
- package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
- package/src/lib/app-utils/dynamic-list-rule.js +3 -3
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
- package/src/lib/app-utils/element-ui.d.ts +1 -1
- package/src/lib/app-utils/element-ui.js +1 -1
- package/src/lib/app-utils/element-ui.js.map +1 -1
- package/src/lib/app-utils/email-starter-policy.js +3 -3
- package/src/lib/app-utils/email-starter-policy.js.map +1 -1
- package/src/lib/app-utils/email-text.d.ts +80 -0
- package/src/lib/app-utils/email-text.js +189 -0
- package/src/lib/app-utils/email-text.js.map +1 -0
- package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
- package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
- package/src/lib/app-utils/enrollment-basis.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +75 -6
- package/src/lib/app-utils/error-beacon.js +160 -21
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
- package/src/lib/app-utils/expand-repeatables.js +14 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.js +2 -2
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +2 -3
- package/src/lib/app-utils/health-report.js +8 -6
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/host-events.d.ts +82 -0
- package/src/lib/app-utils/host-events.js +62 -0
- package/src/lib/app-utils/host-events.js.map +1 -0
- package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
- package/src/lib/app-utils/inbound-mail-domain.js +31 -0
- package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +11 -0
- package/src/lib/app-utils/lockdown.js +33 -3
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/margin-utilization.d.ts +4 -69
- package/src/lib/app-utils/margin-utilization.js +82 -69
- package/src/lib/app-utils/margin-utilization.js.map +1 -1
- package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
- package/src/lib/app-utils/member-email-aliases.js +29 -0
- package/src/lib/app-utils/member-email-aliases.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
- package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/node-interactions.d.ts +7 -7
- package/src/lib/app-utils/node-interactions.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +147 -47
- package/src/lib/app-utils/notifications.js +182 -44
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.d.ts +15 -1
- package/src/lib/app-utils/operator-alerts.js +47 -10
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/payments-configured.d.ts +8 -21
- package/src/lib/app-utils/payments-configured.js +8 -23
- package/src/lib/app-utils/payments-configured.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +207 -306
- package/src/lib/app-utils/plan-entitlements.js +345 -528
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/platform-brand.d.ts +10 -0
- package/src/lib/app-utils/platform-brand.js +14 -3
- package/src/lib/app-utils/platform-brand.js.map +1 -1
- package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
- package/src/lib/app-utils/platform-marketing-consent.js +6 -5
- package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
- package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
- package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
- package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +14 -5
- package/src/lib/app-utils/release-flags.js +21 -125
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/repeat-query.d.ts +56 -0
- package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
- package/src/lib/app-utils/repeat-query.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +9 -0
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/retained-refusals.d.ts +73 -0
- package/src/lib/app-utils/retained-refusals.js +186 -0
- package/src/lib/app-utils/retained-refusals.js.map +1 -0
- package/src/lib/app-utils/scope-tokens.d.ts +27 -0
- package/src/lib/app-utils/scope-tokens.js +61 -0
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +3 -3
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.js +1 -1
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +132 -0
- package/src/lib/app-utils/seo-audit.js +246 -0
- package/src/lib/app-utils/seo-audit.js.map +1 -0
- package/src/lib/app-utils/seo-keywords.d.ts +48 -0
- package/src/lib/app-utils/seo-keywords.js +60 -0
- package/src/lib/app-utils/seo-keywords.js.map +1 -0
- package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
- package/src/lib/app-utils/seo-page-facts.js +155 -0
- package/src/lib/app-utils/seo-page-facts.js.map +1 -0
- package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
- package/src/lib/app-utils/seo-site-scan.js +162 -0
- package/src/lib/app-utils/seo-site-scan.js.map +1 -0
- package/src/lib/app-utils/seo-title-variables.js +1 -1
- package/src/lib/app-utils/seo-title-variables.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +13 -18
- package/src/lib/app-utils/server.js +33 -51
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-interactions.d.ts +501 -0
- package/src/lib/app-utils/site-interactions.js +554 -0
- package/src/lib/app-utils/site-interactions.js.map +1 -0
- package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
- package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +2 -2
- package/src/lib/app-utils/site-theme.js.map +1 -0
- package/src/lib/app-utils/sitemap.d.ts +4 -3
- package/src/lib/app-utils/sitemap.js +9 -7
- package/src/lib/app-utils/sitemap.js.map +1 -1
- package/src/lib/app-utils/stale-build-error.d.ts +51 -0
- package/src/lib/app-utils/stale-build-error.js +100 -1
- package/src/lib/app-utils/stale-build-error.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
- package/src/lib/app-utils/starter-template-nodes.js +129 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
- package/src/lib/app-utils/starter-templates.d.ts +11 -34
- package/src/lib/app-utils/starter-templates.js +19 -230
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
- package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
- package/src/lib/app-utils/subscription-topics.js.map +1 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
- package/src/lib/app-utils/theme-library.d.ts +10 -3
- package/src/lib/app-utils/theme-library.js +13 -4
- package/src/lib/app-utils/theme-library.js.map +1 -1
- package/src/lib/app-utils/usage-budget.d.ts +73 -188
- package/src/lib/app-utils/usage-budget.js +58 -194
- package/src/lib/app-utils/usage-budget.js.map +1 -1
- package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
- package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
- package/src/lib/app-utils/utm-attribution.js.map +1 -0
- package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
- package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
- package/src/lib/app-utils/utm-forwarding.js.map +1 -0
- package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +25 -17
- package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +38 -30
- package/src/lib/app-utils/utm-touch.js.map +1 -0
- package/src/lib/app-utils/variables.d.ts +9 -9
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/video-object.js +6 -5
- package/src/lib/app-utils/video-object.js.map +1 -1
- package/src/lib/app-utils/visitor-consent.d.ts +41 -164
- package/src/lib/app-utils/visitor-consent.js +66 -277
- package/src/lib/app-utils/visitor-consent.js.map +1 -1
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
- package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
- package/src/lib/app-utils/web-vitals-rum.js +31 -63
- package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
- package/src/lib/app-utils/where-used.d.ts +11 -6
- package/src/lib/app-utils/where-used.js +3 -8
- package/src/lib/app-utils/where-used.js.map +1 -1
- package/src/lib/foundation/definitions/components.types.d.ts +1 -1
- package/src/lib/foundation/definitions/components.types.js +1 -1
- package/src/lib/foundation/definitions/components.types.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +11 -137
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +1 -3
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +11 -5
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
- package/src/lib/plugin-manager/besigner-documents.js +61 -0
- package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
- package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
- package/src/lib/plugin-manager/computed-variables.js +74 -0
- package/src/lib/plugin-manager/computed-variables.js.map +1 -0
- package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
- package/src/lib/plugin-manager/custom-fields.js +47 -1
- package/src/lib/plugin-manager/custom-fields.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +372 -99
- package/src/lib/plugin-manager/feature-plugins.js +126 -51
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +134 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +840 -10
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
- package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-containers.js +27 -0
- package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
- package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-dependents.js +75 -0
- package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
- package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-distribution.js +45 -0
- package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
- package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
- package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
- package/src/lib/plugin-manager/plugin-record-index.js +51 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
- package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-record-written.js +42 -0
- package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-site-export.js +40 -0
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
- package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +319 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +186 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
- package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
- package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
- package/src/lib/plugin-manager/record-captured-contact.js +10 -32
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
- package/src/lib/plugin-manager/repeat-rows.js +108 -0
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
- package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
- package/src/lib/plugin-manager/submission-record-target.js +125 -0
- package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
- package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
- package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
- package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
- package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
- package/src/lib/plugin-manager/video-embed-provider.js +165 -0
- package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
- package/src/lib/types/nodes.d.ts +2 -2
- package/src/lib/types/nodes.js.map +1 -1
- package/src/lib/app-utils/assist-credits.d.ts +0 -482
- package/src/lib/app-utils/assist-credits.js +0 -509
- package/src/lib/app-utils/assist-credits.js.map +0 -1
- package/src/lib/app-utils/campaign-attribution.js.map +0 -1
- package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
- package/src/lib/app-utils/campaign-membership.d.ts +0 -121
- package/src/lib/app-utils/campaign-membership.js +0 -189
- package/src/lib/app-utils/campaign-membership.js.map +0 -1
- package/src/lib/app-utils/campaign-touch.js.map +0 -1
- package/src/lib/app-utils/console-record-links.d.ts +0 -163
- package/src/lib/app-utils/console-record-links.js +0 -180
- package/src/lib/app-utils/console-record-links.js.map +0 -1
- package/src/lib/app-utils/contact-holder.d.ts +0 -68
- package/src/lib/app-utils/contact-holder.js +0 -129
- package/src/lib/app-utils/contact-holder.js.map +0 -1
- package/src/lib/app-utils/contact-merge.d.ts +0 -136
- package/src/lib/app-utils/contact-merge.js +0 -412
- package/src/lib/app-utils/contact-merge.js.map +0 -1
- package/src/lib/app-utils/crm-booking.d.ts +0 -75
- package/src/lib/app-utils/crm-booking.js +0 -133
- package/src/lib/app-utils/crm-booking.js.map +0 -1
- package/src/lib/app-utils/crm-csv.d.ts +0 -239
- package/src/lib/app-utils/crm-csv.js +0 -467
- package/src/lib/app-utils/crm-csv.js.map +0 -1
- package/src/lib/app-utils/crm-digest.d.ts +0 -185
- package/src/lib/app-utils/crm-digest.js +0 -272
- package/src/lib/app-utils/crm-digest.js.map +0 -1
- package/src/lib/app-utils/crm-inbound.d.ts +0 -245
- package/src/lib/app-utils/crm-inbound.js +0 -407
- package/src/lib/app-utils/crm-inbound.js.map +0 -1
- package/src/lib/app-utils/crm-reports.d.ts +0 -479
- package/src/lib/app-utils/crm-reports.js +0 -680
- package/src/lib/app-utils/crm-reports.js.map +0 -1
- package/src/lib/app-utils/dataset-csv.js.map +0 -1
- package/src/lib/app-utils/dataset-query.d.ts +0 -57
- package/src/lib/app-utils/dataset-query.js.map +0 -1
- package/src/lib/app-utils/email-topics.js.map +0 -1
- package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
- package/src/lib/app-utils/form-dataset-binding.js +0 -74
- package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
- package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
- package/src/lib/app-utils/list-import.d.ts +0 -158
- package/src/lib/app-utils/list-import.js +0 -332
- package/src/lib/app-utils/list-import.js.map +0 -1
- package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
- package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
- package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
- package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
- package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
- package/src/lib/app-utils/marketplace-theme.js.map +0 -1
- package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
- package/src/lib/app-utils/marketplace-update-state.js +0 -132
- package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
- package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
- package/src/lib/app-utils/publisher-agreement.js +0 -321
- package/src/lib/app-utils/publisher-agreement.js.map +0 -1
- package/src/lib/app-utils/wistia-embed.d.ts +0 -104
- package/src/lib/app-utils/wistia-embed.js +0 -138
- package/src/lib/app-utils/wistia-embed.js.map +0 -1
- package/src/lib/app-utils/workflows.d.ts +0 -136
- package/src/lib/app-utils/workflows.js +0 -346
- package/src/lib/app-utils/workflows.js.map +0 -1
- package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
- package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
- package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
- package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
- package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
- package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
* Where published third-party plugin versions and their kill switches are
|
|
18
|
+
* stored, declared by the plugin that publishes them (AGL-3080).
|
|
19
|
+
*
|
|
20
|
+
* The realm loader is the platform's: it joins a workspace's install pins
|
|
21
|
+
* with the version each pin names, keeps only a version carrying the
|
|
22
|
+
* platform's trust grant, and drops one a kill switch has stopped. The
|
|
23
|
+
* documents it joins against are the distributing plugin's — a version is
|
|
24
|
+
* written under that plugin's listing when it is published, and its kill
|
|
25
|
+
* switch when staff revoke it. So the loader asks this declaration where they
|
|
26
|
+
* are, and names no plugin's collection itself.
|
|
27
|
+
*
|
|
28
|
+
* ## Compiled, never registered
|
|
29
|
+
*
|
|
30
|
+
* Every reader is a security gate on a server path that loads no plugin code:
|
|
31
|
+
* the published page's plugin join, both apps' remote server-bundle loaders,
|
|
32
|
+
* the console's realm gate. A runtime registry one of them had not filled
|
|
33
|
+
* would answer "nowhere" — and "nowhere" must mean "nothing loads", which is
|
|
34
|
+
* only safe because it is also what an unfilled registry would say. That is
|
|
35
|
+
* the AGL-3025 shape one step from the kill switch, so the plugin declares
|
|
36
|
+
* this as data in `plugins.config.json` and the generator compiles it.
|
|
37
|
+
*
|
|
38
|
+
* ## Absent is closed
|
|
39
|
+
*
|
|
40
|
+
* With no distributing plugin, {@link PLUGIN_DISTRIBUTION} is `null`, and
|
|
41
|
+
* every reader fails CLOSED: no version resolves, and every pin reads as
|
|
42
|
+
* revoked. A pin with no store to check it against cannot be vouched for.
|
|
43
|
+
*/ export { PLUGIN_DISTRIBUTION } from "./first-party-plugins.generated.js";
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=plugin-distribution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-distribution.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 * Where published third-party plugin versions and their kill switches are\n * stored, declared by the plugin that publishes them (AGL-3080).\n *\n * The realm loader is the platform's: it joins a workspace's install pins\n * with the version each pin names, keeps only a version carrying the\n * platform's trust grant, and drops one a kill switch has stopped. The\n * documents it joins against are the distributing plugin's — a version is\n * written under that plugin's listing when it is published, and its kill\n * switch when staff revoke it. So the loader asks this declaration where they\n * are, and names no plugin's collection itself.\n *\n * ## Compiled, never registered\n *\n * Every reader is a security gate on a server path that loads no plugin code:\n * the published page's plugin join, both apps' remote server-bundle loaders,\n * the console's realm gate. A runtime registry one of them had not filled\n * would answer \"nowhere\" — and \"nowhere\" must mean \"nothing loads\", which is\n * only safe because it is also what an unfilled registry would say. That is\n * the AGL-3025 shape one step from the kill switch, so the plugin declares\n * this as data in `plugins.config.json` and the generator compiles it.\n *\n * ## Absent is closed\n *\n * With no distributing plugin, {@link PLUGIN_DISTRIBUTION} is `null`, and\n * every reader fails CLOSED: no version resolves, and every pin reads as\n * revoked. A pin with no store to check it against cannot be vouched for.\n */\nexport interface PluginDistribution {\n /** The plugin that declared it. */\n pluginId: string\n /** The top-level collection of listings, keyed by the id an install pins. */\n listings: string\n /** The subcollection of a listing holding one document per version. */\n versions: string\n /** The top-level collection of kill switches, keyed by the listing id. */\n revocations: string\n}\n\nexport { PLUGIN_DISTRIBUTION } from './first-party-plugins.generated'\n"],"names":["PLUGIN_DISTRIBUTION"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GAYD,SAASA,mBAAmB,QAAQ,qCAAiC"}
|
|
@@ -85,6 +85,14 @@ export interface PluginLockdownFeatureDeclaration {
|
|
|
85
85
|
key: string;
|
|
86
86
|
/** The staff checklist's label. */
|
|
87
87
|
label: string;
|
|
88
|
+
/**
|
|
89
|
+
* What the lever is called in the customer's mail when staff pull it for
|
|
90
|
+
* their workspace (`media uploads`, `AI generation`). A noun phrase that
|
|
91
|
+
* reads right mid-sentence ("our team paused media uploads") and after a
|
|
92
|
+
* colon, so lowercase unless it starts with a name or an acronym. The
|
|
93
|
+
* staff `label` never reaches a customer.
|
|
94
|
+
*/
|
|
95
|
+
customerName: string;
|
|
88
96
|
/**
|
|
89
97
|
* Whether a verified staff claim passes the lock. Granted where a staff
|
|
90
98
|
* action aids incident response — one real call proves a provider is back
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-entitlements.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 * Plugin-declared billing and access keys (AGL-2940).\n *\n * A plugin that sells something, gates something, or can be switched off in\n * an incident declares the KEYS here — a seat add-on, a feature flag, a\n * lockdown lever, a permission — and the core catalogs that used to be\n * edited for each one read the declarations instead:\n *\n * - `plan-entitlements` folds every registered seat add-on into\n * `resolveOrgEntitlements` (a quota raised per unit, features switched on)\n * and answers a registered feature's plan defaults;\n * - `lockdown` lists a registered lever on the staff page, carries its label,\n * its staff-bypass rule and its visitor notice, and maps the API paths it\n * names onto it for the plugin dispatcher;\n * - `plugin-permissions` receives the permissions with the owner filled in;\n * - `org-permissions` folds every declared catalog key into the org\n * permission catalog — the role editor, custom roles, per-member overrides\n * and the rules stamp — and `host-permissions` decides the keys that\n * declare host-role defaults for a site collaborator, per site.\n *\n * The NUMBERS stay where they are checked. A seat add-on's price is a row in\n * `PLAN_PRICING`, read by the Stripe wiring and reconciled by\n * `check-pricing-drift`; a declaration names the quota it widens and by how\n * much per plan, and nothing here restates a price.\n *\n * Registration order is deterministic: the catalog order of\n * `FIRST_PARTY_PLUGINS`, then any other id alphabetically. Plugin modules\n * load in parallel, so the order they REGISTER in is whatever the network\n * gave, and a list that followed it would put the staff lockdown checklist\n * in a different order per session.\n */\n\nimport type { HostAccessRole, OrgPlan, OrgRole } from '../foundation'\nimport { FIRST_PARTY_PLUGINS } from './enabled-plugins'\nimport {\n registerPluginPermissions,\n type PluginPermission,\n} from './plugin-permissions'\n\nexport interface PluginSeatAddonDeclaration {\n /** The `org.seatAddons` key the purchase is stored under (`aiAddon`). */\n key: string\n label: string\n /**\n * How many purchased units count. `1` for an org-wide add-on that is\n * bought once, where a doubled webhook item or a hand edit above one is\n * still one purchase; absent to count the stored quantity.\n */\n maxUnits?: number\n /**\n * The quota each unit widens, per plan: the resolved entitlement named by\n * `key` gains `perUnitByPlan[plan] × units`. A plan the add-on is not sold\n * on declares 0.\n */\n quota?: {\n key: string\n perUnitByPlan: Readonly<Record<OrgPlan, number>>\n }\n /** Feature flags a purchased unit switches on. */\n features?: readonly string[]\n}\n\nexport interface PluginFeatureDeclaration {\n /** The `OrgFeatureFlags` key. */\n key: string\n label: string\n /**\n * The plan defaults, when the key is not one the plan tables already\n * carry. Absent means the plan tables decide, which is the case for every\n * key that predates this registry.\n */\n defaultByPlan?: Readonly<Partial<Record<OrgPlan, boolean>>>\n}\n\nexport interface PluginLockdownFeatureDeclaration {\n /** The lever's wire identity (`ai-generate`). */\n key: string\n /** The staff checklist's label. */\n label: string\n /**\n * Whether a verified staff claim passes the lock. Granted where a staff\n * action aids incident response — one real call proves a provider is back\n * — and withheld where the staff action would BE the incident.\n */\n staffBypass: boolean\n /**\n * What a visitor reads while the lever is pulled: what is paused AND what\n * still works. The expected-back window is appended by the notice builder.\n */\n notice: { title: string; body: string }\n /**\n * Plugin API paths the lever gates at the dispatcher: exact paths, and\n * prefixes matched on a segment boundary (`ai/generate` also gates\n * `ai/generate/section`, never `ai/generated-report`).\n */\n apiPaths?: { exact?: readonly string[]; prefixes?: readonly string[] }\n}\n\n/**\n * A key a plugin adds to the org permission CATALOG (AGL-2984): the granular\n * map custom roles and per-member overrides are stored in, that\n * `resolveOrgPermissions` layers and the rules stamp carries, and that the\n * role editor lists. A declared key is stored, layered and resolved exactly\n * like a core key; the declaration supplies what a core key's catalog entry\n * and role table supply.\n */\nexport interface PluginOrgPermissionDeclaration {\n /** The dotted key a custom role and a per-member override store (`reviews.reply`). */\n key: string\n /** The role editor's line for the key, and the word a refusal names. */\n label: string\n /** What the key opens, under the label in the role editor. */\n description: string\n /** Each org role's verdict when no custom role or per-member override sets the key. */\n roleDefaults: Readonly<Record<OrgRole, boolean>>\n /**\n * Each HOST role's verdict, for a key a site collaborator holds per site.\n *\n * A collaborator's membership carries no org standing to refine — it\n * exists only to carry access to named sites — so a key with this field is\n * decided for them on the site a request names: the host role they hold\n * there, refined by the per-site toggle on their member document. A key\n * without it is never held by a collaborator.\n */\n hostRoleDefaults?: Readonly<Record<HostAccessRole, boolean>>\n}\n\nexport interface PluginEntitlementRegistration {\n pluginId: string\n seatAddons?: readonly PluginSeatAddonDeclaration[]\n features?: readonly PluginFeatureDeclaration[]\n lockdownFeatures?: readonly PluginLockdownFeatureDeclaration[]\n /** Per-tier keys a plugin's own pages read (`registerPluginPermissions`). */\n permissions?: readonly Omit<PluginPermission, 'pluginId'>[]\n /** Keys added to the org permission catalog — see {@link PluginOrgPermissionDeclaration}. */\n orgPermissions?: readonly PluginOrgPermissionDeclaration[]\n}\n\nconst registrations = new Map<string, PluginEntitlementRegistration>()\n\n/**\n * Readers of the registrations that keep a derived view in step with them —\n * the org permission catalog rebuilds itself on every registration and\n * reset, because a plugin's declarations can register after a module that\n * imported the catalog was evaluated.\n */\nconst listeners = new Set<() => void>()\n\n/** Calls `listener` after every registration and reset; answers the unsubscribe. */\nexport function subscribePluginEntitlements(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\nfunction notifyListeners(): void {\n for (const listener of [...listeners]) listener()\n}\n\nconst CATALOG_ORDER = new Map(\n FIRST_PARTY_PLUGINS.map((plugin, index) => [plugin.id, index]),\n)\n\nfunction catalogRank(pluginId: string): number {\n return CATALOG_ORDER.get(pluginId) ?? Number.MAX_SAFE_INTEGER\n}\n\nfunction orderedRegistrations(): PluginEntitlementRegistration[] {\n return [...registrations.values()].sort(\n (a, b) =>\n catalogRank(a.pluginId) - catalogRank(b.pluginId) ||\n a.pluginId.localeCompare(b.pluginId),\n )\n}\n\nfunction refuseStolenKeys(\n pluginId: string,\n kind: 'seat add-on' | 'lockdown feature' | 'org permission',\n keys: readonly string[],\n ownerOf: (key: string) => string | undefined,\n): void {\n for (const key of keys) {\n const owner = ownerOf(key)\n if (owner && owner !== pluginId) {\n throw new Error(\n `${kind} \"${key}\" is already declared by \"${owner}\"; refused \"${pluginId}\"`,\n )\n }\n }\n}\n\n/**\n * Idempotent per plugin — re-registration replaces that plugin's\n * declarations. A seat add-on, lockdown or catalog permission key another\n * plugin already owns refuses the whole registration: one key, one owner,\n * or the two declarations disagree about what the key means.\n */\nexport function registerPluginEntitlements(\n registration: PluginEntitlementRegistration,\n): void {\n const pluginId = registration.pluginId.trim()\n if (!pluginId) throw new Error('plugin entitlements need a pluginId')\n refuseStolenKeys(\n pluginId,\n 'seat add-on',\n (registration.seatAddons ?? []).map((entry) => entry.key),\n (key) => pluginSeatAddonOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'lockdown feature',\n (registration.lockdownFeatures ?? []).map((entry) => entry.key),\n (key) => pluginLockdownFeatureOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'org permission',\n (registration.orgPermissions ?? []).map((entry) => entry.key),\n (key) => pluginOrgPermissionOwner(key),\n )\n registrations.set(pluginId, { ...registration, pluginId })\n if (registration.permissions?.length) {\n registerPluginPermissions(\n registration.permissions.map((permission) => ({ ...permission, pluginId })),\n )\n }\n notifyListeners()\n}\n\nfunction pluginSeatAddonOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.seatAddons?.some((addon) => addon.key === key)) return entry.pluginId\n }\n return undefined\n}\n\nfunction pluginLockdownFeatureOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.lockdownFeatures?.some((feature) => feature.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\nfunction pluginOrgPermissionOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.orgPermissions?.some((permission) => permission.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\n/** Every declared catalog permission, in catalog order, with the plugin that declared it. */\nexport function listPluginOrgPermissions(): Array<\n PluginOrgPermissionDeclaration & { pluginId: string }\n> {\n return orderedRegistrations().flatMap((entry) =>\n (entry.orgPermissions ?? []).map((permission) => ({\n ...permission,\n pluginId: entry.pluginId,\n })),\n )\n}\n\n/** Every declared seat add-on, in catalog order. */\nexport function listPluginSeatAddons(): PluginSeatAddonDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.seatAddons ?? [])])\n}\n\n/** One declared seat add-on by its `seatAddons` key. */\nexport function pluginSeatAddon(key: string): PluginSeatAddonDeclaration | undefined {\n return listPluginSeatAddons().find((entry) => entry.key === key)\n}\n\n/** Every declared feature, in catalog order. */\nexport function listPluginFeatures(): PluginFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.features ?? [])])\n}\n\n/** Every declared lockdown lever, in catalog order. */\nexport function listPluginLockdownFeatures(): PluginLockdownFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [\n ...(entry.lockdownFeatures ?? []),\n ])\n}\n\n/** One declared lockdown lever by key. */\nexport function pluginLockdownFeature(\n key: string,\n): PluginLockdownFeatureDeclaration | undefined {\n return listPluginLockdownFeatures().find((entry) => entry.key === key)\n}\n\n/** The plugin that declared a lockdown lever, for attribution. */\nexport function pluginIdForLockdownFeature(key: string): string | undefined {\n return pluginLockdownFeatureOwner(key)\n}\n\n/** Every registration, in catalog order. */\nexport function listPluginEntitlementRegistrations(): PluginEntitlementRegistration[] {\n return orderedRegistrations()\n}\n\n/**\n * Test seam: forget every registration. The per-tier permissions stay\n * registered; the catalog permissions go with their registrations.\n */\nexport function resetPluginEntitlementsForTests(): void {\n registrations.clear()\n notifyListeners()\n}\n"],"names":["FIRST_PARTY_PLUGINS","registerPluginPermissions","registrations","Map","listeners","Set","subscribePluginEntitlements","listener","add","delete","notifyListeners","CATALOG_ORDER","map","plugin","index","id","catalogRank","pluginId","get","Number","MAX_SAFE_INTEGER","orderedRegistrations","values","sort","a","b","localeCompare","refuseStolenKeys","kind","keys","ownerOf","key","owner","Error","registerPluginEntitlements","registration","trim","seatAddons","entry","pluginSeatAddonOwner","lockdownFeatures","pluginLockdownFeatureOwner","orgPermissions","pluginOrgPermissionOwner","set","permissions","length","permission","some","addon","undefined","feature","listPluginOrgPermissions","flatMap","listPluginSeatAddons","pluginSeatAddon","find","listPluginFeatures","features","listPluginLockdownFeatures","pluginLockdownFeature","pluginIdForLockdownFeature","listPluginEntitlementRegistrations","resetPluginEntitlementsForTests","clear"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-entitlements.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 * Plugin-declared billing and access keys (AGL-2940).\n *\n * A plugin that sells something, gates something, or can be switched off in\n * an incident declares the KEYS here — a seat add-on, a feature flag, a\n * lockdown lever, a permission — and the core catalogs that used to be\n * edited for each one read the declarations instead:\n *\n * - `plan-entitlements` folds every registered seat add-on into\n * `resolveOrgEntitlements` (a quota raised per unit, features switched on)\n * and answers a registered feature's plan defaults;\n * - `lockdown` lists a registered lever on the staff page, carries its label,\n * its staff-bypass rule and its visitor notice, and maps the API paths it\n * names onto it for the plugin dispatcher;\n * - `plugin-permissions` receives the permissions with the owner filled in;\n * - `org-permissions` folds every declared catalog key into the org\n * permission catalog — the role editor, custom roles, per-member overrides\n * and the rules stamp — and `host-permissions` decides the keys that\n * declare host-role defaults for a site collaborator, per site.\n *\n * The NUMBERS stay where they are checked. A seat add-on's price is a row in\n * `PLAN_PRICING`, read by the Stripe wiring and reconciled by\n * `check-pricing-drift`; a declaration names the quota it widens and by how\n * much per plan, and nothing here restates a price.\n *\n * Registration order is deterministic: the catalog order of\n * `FIRST_PARTY_PLUGINS`, then any other id alphabetically. Plugin modules\n * load in parallel, so the order they REGISTER in is whatever the network\n * gave, and a list that followed it would put the staff lockdown checklist\n * in a different order per session.\n */\n\nimport type { HostAccessRole, OrgPlan, OrgRole } from '../foundation'\nimport { FIRST_PARTY_PLUGINS } from './enabled-plugins'\nimport {\n registerPluginPermissions,\n type PluginPermission,\n} from './plugin-permissions'\n\nexport interface PluginSeatAddonDeclaration {\n /** The `org.seatAddons` key the purchase is stored under (`aiAddon`). */\n key: string\n label: string\n /**\n * How many purchased units count. `1` for an org-wide add-on that is\n * bought once, where a doubled webhook item or a hand edit above one is\n * still one purchase; absent to count the stored quantity.\n */\n maxUnits?: number\n /**\n * The quota each unit widens, per plan: the resolved entitlement named by\n * `key` gains `perUnitByPlan[plan] × units`. A plan the add-on is not sold\n * on declares 0.\n */\n quota?: {\n key: string\n perUnitByPlan: Readonly<Record<OrgPlan, number>>\n }\n /** Feature flags a purchased unit switches on. */\n features?: readonly string[]\n}\n\nexport interface PluginFeatureDeclaration {\n /** The `OrgFeatureFlags` key. */\n key: string\n label: string\n /**\n * The plan defaults, when the key is not one the plan tables already\n * carry. Absent means the plan tables decide, which is the case for every\n * key that predates this registry.\n */\n defaultByPlan?: Readonly<Partial<Record<OrgPlan, boolean>>>\n}\n\nexport interface PluginLockdownFeatureDeclaration {\n /** The lever's wire identity (`ai-generate`). */\n key: string\n /** The staff checklist's label. */\n label: string\n /**\n * What the lever is called in the customer's mail when staff pull it for\n * their workspace (`media uploads`, `AI generation`). A noun phrase that\n * reads right mid-sentence (\"our team paused media uploads\") and after a\n * colon, so lowercase unless it starts with a name or an acronym. The\n * staff `label` never reaches a customer.\n */\n customerName: string\n /**\n * Whether a verified staff claim passes the lock. Granted where a staff\n * action aids incident response — one real call proves a provider is back\n * — and withheld where the staff action would BE the incident.\n */\n staffBypass: boolean\n /**\n * What a visitor reads while the lever is pulled: what is paused AND what\n * still works. The expected-back window is appended by the notice builder.\n */\n notice: { title: string; body: string }\n /**\n * Plugin API paths the lever gates at the dispatcher: exact paths, and\n * prefixes matched on a segment boundary (`ai/generate` also gates\n * `ai/generate/section`, never `ai/generated-report`).\n */\n apiPaths?: { exact?: readonly string[]; prefixes?: readonly string[] }\n}\n\n/**\n * A key a plugin adds to the org permission CATALOG (AGL-2984): the granular\n * map custom roles and per-member overrides are stored in, that\n * `resolveOrgPermissions` layers and the rules stamp carries, and that the\n * role editor lists. A declared key is stored, layered and resolved exactly\n * like a core key; the declaration supplies what a core key's catalog entry\n * and role table supply.\n */\nexport interface PluginOrgPermissionDeclaration {\n /** The dotted key a custom role and a per-member override store (`reviews.reply`). */\n key: string\n /** The role editor's line for the key, and the word a refusal names. */\n label: string\n /** What the key opens, under the label in the role editor. */\n description: string\n /** Each org role's verdict when no custom role or per-member override sets the key. */\n roleDefaults: Readonly<Record<OrgRole, boolean>>\n /**\n * Each HOST role's verdict, for a key a site collaborator holds per site.\n *\n * A collaborator's membership carries no org standing to refine — it\n * exists only to carry access to named sites — so a key with this field is\n * decided for them on the site a request names: the host role they hold\n * there, refined by the per-site toggle on their member document. A key\n * without it is never held by a collaborator.\n */\n hostRoleDefaults?: Readonly<Record<HostAccessRole, boolean>>\n}\n\nexport interface PluginEntitlementRegistration {\n pluginId: string\n seatAddons?: readonly PluginSeatAddonDeclaration[]\n features?: readonly PluginFeatureDeclaration[]\n lockdownFeatures?: readonly PluginLockdownFeatureDeclaration[]\n /** Per-tier keys a plugin's own pages read (`registerPluginPermissions`). */\n permissions?: readonly Omit<PluginPermission, 'pluginId'>[]\n /** Keys added to the org permission catalog — see {@link PluginOrgPermissionDeclaration}. */\n orgPermissions?: readonly PluginOrgPermissionDeclaration[]\n}\n\nconst registrations = new Map<string, PluginEntitlementRegistration>()\n\n/**\n * Readers of the registrations that keep a derived view in step with them —\n * the org permission catalog rebuilds itself on every registration and\n * reset, because a plugin's declarations can register after a module that\n * imported the catalog was evaluated.\n */\nconst listeners = new Set<() => void>()\n\n/** Calls `listener` after every registration and reset; answers the unsubscribe. */\nexport function subscribePluginEntitlements(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\nfunction notifyListeners(): void {\n for (const listener of [...listeners]) listener()\n}\n\nconst CATALOG_ORDER = new Map(\n FIRST_PARTY_PLUGINS.map((plugin, index) => [plugin.id, index]),\n)\n\nfunction catalogRank(pluginId: string): number {\n return CATALOG_ORDER.get(pluginId) ?? Number.MAX_SAFE_INTEGER\n}\n\nfunction orderedRegistrations(): PluginEntitlementRegistration[] {\n return [...registrations.values()].sort(\n (a, b) =>\n catalogRank(a.pluginId) - catalogRank(b.pluginId) ||\n a.pluginId.localeCompare(b.pluginId),\n )\n}\n\nfunction refuseStolenKeys(\n pluginId: string,\n kind: 'seat add-on' | 'lockdown feature' | 'org permission',\n keys: readonly string[],\n ownerOf: (key: string) => string | undefined,\n): void {\n for (const key of keys) {\n const owner = ownerOf(key)\n if (owner && owner !== pluginId) {\n throw new Error(\n `${kind} \"${key}\" is already declared by \"${owner}\"; refused \"${pluginId}\"`,\n )\n }\n }\n}\n\n/**\n * Idempotent per plugin — re-registration replaces that plugin's\n * declarations. A seat add-on, lockdown or catalog permission key another\n * plugin already owns refuses the whole registration: one key, one owner,\n * or the two declarations disagree about what the key means.\n */\nexport function registerPluginEntitlements(\n registration: PluginEntitlementRegistration,\n): void {\n const pluginId = registration.pluginId.trim()\n if (!pluginId) throw new Error('plugin entitlements need a pluginId')\n refuseStolenKeys(\n pluginId,\n 'seat add-on',\n (registration.seatAddons ?? []).map((entry) => entry.key),\n (key) => pluginSeatAddonOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'lockdown feature',\n (registration.lockdownFeatures ?? []).map((entry) => entry.key),\n (key) => pluginLockdownFeatureOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'org permission',\n (registration.orgPermissions ?? []).map((entry) => entry.key),\n (key) => pluginOrgPermissionOwner(key),\n )\n registrations.set(pluginId, { ...registration, pluginId })\n if (registration.permissions?.length) {\n registerPluginPermissions(\n registration.permissions.map((permission) => ({ ...permission, pluginId })),\n )\n }\n notifyListeners()\n}\n\nfunction pluginSeatAddonOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.seatAddons?.some((addon) => addon.key === key)) return entry.pluginId\n }\n return undefined\n}\n\nfunction pluginLockdownFeatureOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.lockdownFeatures?.some((feature) => feature.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\nfunction pluginOrgPermissionOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.orgPermissions?.some((permission) => permission.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\n/** Every declared catalog permission, in catalog order, with the plugin that declared it. */\nexport function listPluginOrgPermissions(): Array<\n PluginOrgPermissionDeclaration & { pluginId: string }\n> {\n return orderedRegistrations().flatMap((entry) =>\n (entry.orgPermissions ?? []).map((permission) => ({\n ...permission,\n pluginId: entry.pluginId,\n })),\n )\n}\n\n/** Every declared seat add-on, in catalog order. */\nexport function listPluginSeatAddons(): PluginSeatAddonDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.seatAddons ?? [])])\n}\n\n/** One declared seat add-on by its `seatAddons` key. */\nexport function pluginSeatAddon(key: string): PluginSeatAddonDeclaration | undefined {\n return listPluginSeatAddons().find((entry) => entry.key === key)\n}\n\n/** Every declared feature, in catalog order. */\nexport function listPluginFeatures(): PluginFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.features ?? [])])\n}\n\n/** Every declared lockdown lever, in catalog order. */\nexport function listPluginLockdownFeatures(): PluginLockdownFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [\n ...(entry.lockdownFeatures ?? []),\n ])\n}\n\n/** One declared lockdown lever by key. */\nexport function pluginLockdownFeature(\n key: string,\n): PluginLockdownFeatureDeclaration | undefined {\n return listPluginLockdownFeatures().find((entry) => entry.key === key)\n}\n\n/** The plugin that declared a lockdown lever, for attribution. */\nexport function pluginIdForLockdownFeature(key: string): string | undefined {\n return pluginLockdownFeatureOwner(key)\n}\n\n/** Every registration, in catalog order. */\nexport function listPluginEntitlementRegistrations(): PluginEntitlementRegistration[] {\n return orderedRegistrations()\n}\n\n/**\n * Test seam: forget every registration. The per-tier permissions stay\n * registered; the catalog permissions go with their registrations.\n */\nexport function resetPluginEntitlementsForTests(): void {\n registrations.clear()\n notifyListeners()\n}\n"],"names":["FIRST_PARTY_PLUGINS","registerPluginPermissions","registrations","Map","listeners","Set","subscribePluginEntitlements","listener","add","delete","notifyListeners","CATALOG_ORDER","map","plugin","index","id","catalogRank","pluginId","get","Number","MAX_SAFE_INTEGER","orderedRegistrations","values","sort","a","b","localeCompare","refuseStolenKeys","kind","keys","ownerOf","key","owner","Error","registerPluginEntitlements","registration","trim","seatAddons","entry","pluginSeatAddonOwner","lockdownFeatures","pluginLockdownFeatureOwner","orgPermissions","pluginOrgPermissionOwner","set","permissions","length","permission","some","addon","undefined","feature","listPluginOrgPermissions","flatMap","listPluginSeatAddons","pluginSeatAddon","find","listPluginFeatures","features","listPluginLockdownFeatures","pluginLockdownFeature","pluginIdForLockdownFeature","listPluginEntitlementRegistrations","resetPluginEntitlementsForTests","clear"],"mappings":"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"}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import type { PluginHostResourceDeclaration } from './plugin-host-resources';
|
|
18
|
+
import type { PluginHostCollectionSiteExport } from './plugin-site-export';
|
|
17
19
|
/**
|
|
18
20
|
* The host subcollections a plugin owns, declared by that plugin (AGL-3124).
|
|
19
21
|
*
|
|
@@ -111,6 +113,21 @@ export interface PluginHostCollectionDeclaration {
|
|
|
111
113
|
* counters total beside its screens, layouts, components and templates.
|
|
112
114
|
*/
|
|
113
115
|
artifact?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* The kind a client CREATES a document here as, through the platform's
|
|
118
|
+
* generic create route — see `plugin-host-resources.ts`. Compiled only: a
|
|
119
|
+
* runtime registration carrying one is refused, because the route is the
|
|
120
|
+
* writable-field allow-list and must never depend on a registry a process
|
|
121
|
+
* may not have filled.
|
|
122
|
+
*/
|
|
123
|
+
resource?: PluginHostResourceDeclaration;
|
|
124
|
+
/**
|
|
125
|
+
* How the whole-site export carries this collection, and what a restore
|
|
126
|
+
* writes back — see `plugin-site-export.ts`. Compiled only, like
|
|
127
|
+
* `resource`: a backup that depended on a registry a process had not
|
|
128
|
+
* filled would be missing the collection with nothing to say so.
|
|
129
|
+
*/
|
|
130
|
+
siteExport?: PluginHostCollectionSiteExport;
|
|
114
131
|
}
|
|
115
132
|
/** A declaration with the plugin that made it. */
|
|
116
133
|
export type ResolvedPluginHostCollection = PluginHostCollectionDeclaration & {
|
|
@@ -36,6 +36,12 @@ export const PLUGIN_HOST_COLLECTIONS = definePluginServiceContract('core.host-co
|
|
|
36
36
|
var _declaration_name, _declaration_mediaScanReason;
|
|
37
37
|
const name = (_ref = (_declaration_name = declaration.name) == null ? void 0 : _declaration_name.trim()) != null ? _ref : '';
|
|
38
38
|
if (!name) throw new Error('a host collection needs a name');
|
|
39
|
+
if (declaration.resource) {
|
|
40
|
+
throw new Error(`host collection "${name}" declares a resource kind at runtime: the ` + 'create route reads only the compiled declarations in ' + 'plugins.config.json, so a kind registered here could never be created');
|
|
41
|
+
}
|
|
42
|
+
if (declaration.siteExport) {
|
|
43
|
+
throw new Error(`host collection "${name}" declares a site export at runtime: the ` + 'export and restore read only the compiled declarations in ' + 'plugins.config.json, so a backup would silently leave it out');
|
|
44
|
+
}
|
|
39
45
|
if (declaration.mediaScan === 'none' && !((_declaration_mediaScanReason = declaration.mediaScanReason) == null ? void 0 : _declaration_mediaScanReason.trim())) {
|
|
40
46
|
throw new Error(`host collection "${name}" is not scanned for media and says no ` + 'reason: mediaScanReason has to name what scanning would cost, or ' + 'what it would get wrong');
|
|
41
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-host-collections.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 { hostContentCollectionLabel } from '../foundation/definitions/host-content-collections'\nimport {\n PLUGIN_HOST_COLLECTIONS_DECLARED,\n PLUGIN_ORG_COLLECTIONS_DECLARED,\n} from './first-party-plugins.generated'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The host subcollections a plugin owns, declared by that plugin (AGL-3124).\n *\n * A first-party plugin writes ordinary documents under `hosts/{hostId}` with\n * the ordinary SDKs, and several core surfaces have to know which plugin owns\n * which of them: the media-usage scan decides what to read before an author\n * deletes an asset, a reference row deep-links to the console page that shows\n * one, and the artifact counters total what a site holds.\n *\n * Core answered all three by listing the collections and switching over plugin\n * ids — `services` and `resources` are bookings', `campaigns` and `experiments`\n * are marketing's, `functions`, `variables` and `actions` are logic's — which\n * is the platform holding a map of its plugins' storage. Every entry is also a\n * thing that can silently fall out of step: a plugin that ships a new\n * collection is invisible to all three until somebody remembers the list.\n *\n * So the plugin declares what it owns, once, and the three readers ask.\n *\n * ## What a declaration says, and what it deliberately does not\n *\n * A declaration names the collection, how the media scan should treat it, the\n * console slug a reference row links to, and whether a document in it is an\n * ARTIFACT the site's counters total beside screens and layouts. It does NOT\n * name fields: the scan flattens a document generically on purpose, and a\n * field list here would be the same staleness trap one level down.\n *\n * The media default is inherited from the scan's own rule and is the whole\n * reason that rule survives this move: **a collection is scanned unless its\n * declaration says why it is not.** A plugin that ships a media-bearing\n * collection is covered the day it lands rather than the day somebody\n * remembers it, and `mediaScan: 'none'` without a reason is refused — \"it\n * probably has no images in it\" is the guess the scan's design exists to\n * avoid making.\n *\n * ## One collection, one owner\n *\n * Two plugins writing one collection under one site would be two schemas in\n * one place, and the scan, the deep link and the counters would each pick a\n * winner by registration order. So a collection another plugin declared is\n * refused naming both, and the incumbent keeps it; the same plugin declaring\n * again replaces its own.\n *\n * ## Declared in the config, not registered at boot (AGL-3080)\n *\n * A first-party plugin declares its collections in `plugins.config.json` and\n * the generator compiles them, so every reader has the whole list the moment\n * it imports this module. That is not a preference: the media scan answers\n * \"what uses this asset\" in the moment before an author deletes it, every way\n * it can be wrong points at \"unused\", and it runs inside a console request\n * that loads no plugin code. A registry that request had not filled would\n * report every plugin-owned document as holding nothing, with nothing red —\n * the shape of AGL-3025, on the one answer here that somebody acts on.\n *\n * {@link registerPluginHostCollections} is still the door for a plugin the\n * compiler never sees, and the two compose: the compiled rows are a floor a\n * registration cannot lower, and a registration naming a compiled collection\n * is refused the same way a second plugin's would be.\n *\n * Reached by its own subpath rather than through `plugin-manager/index.ts`:\n * the readers are the media scan, the console counters and the reference\n * rows, none of which a published page renders.\n */\n\n/** How the media-usage scan reads a collection. */\nexport type PluginHostCollectionMediaScan =\n /** Flattened and searched generically, with no per-collection decoder. */\n | 'generic'\n /** Read by a pass that knows its shape, the way screens and layouts are. */\n | 'own'\n /** Not read; {@link PluginHostCollectionDeclaration.mediaScanReason} says why. */\n | 'none'\n\nexport interface PluginHostCollectionDeclaration {\n /** The subcollection under `hosts/{hostId}`, spelled as Firestore holds it. */\n name: string\n /**\n * What one of its documents is called in a reference row. Derived from the\n * collection name when absent, so a new collection reads correctly without\n * a second list to keep in step.\n */\n label?: string\n /** Default `generic` — see this module's note on why the default is to scan. */\n mediaScan?: PluginHostCollectionMediaScan\n /**\n * Why the scan does not read it. Required with `mediaScan: 'none'`, and it\n * has to answer what scanning would COST or what it would get WRONG.\n */\n mediaScanReason?: string\n /**\n * The `[pluginSlug]` segment of the plugin's console hub, for a reference\n * row's deep link. Optional on purpose: a row with no destination still\n * renders as text, which is better than the row not existing.\n */\n routeSlug?: string\n /**\n * Whether a document here is a live ARTIFACT — something the site's own\n * counters total beside its screens, layouts, components and templates.\n */\n artifact?: boolean\n}\n\n/** A declaration with the plugin that made it. */\nexport type ResolvedPluginHostCollection = PluginHostCollectionDeclaration & {\n pluginId: string\n}\n\nexport const PLUGIN_HOST_COLLECTIONS =\n definePluginServiceContract<PluginHostCollectionDeclaration>(\n 'core.host-collections',\n { multiple: true },\n )\n\n/**\n * Declares the host subcollections the registering plugin owns. The owner is\n * the loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws. A collection another plugin declared\n * throws naming both, and nothing in the same call is registered — a\n * declaration is one plugin's statement about its own storage, so half of it\n * landing would be worse than none.\n */\nexport function registerPluginHostCollections(\n collections: readonly PluginHostCollectionDeclaration[],\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const prepared = collections.map((declaration) => {\n const name = declaration.name?.trim() ?? ''\n if (!name) throw new Error('a host collection needs a name')\n if (declaration.mediaScan === 'none' && !declaration.mediaScanReason?.trim()) {\n throw new Error(\n `host collection \"${name}\" is not scanned for media and says no ` +\n 'reason: mediaScanReason has to name what scanning would cost, or ' +\n 'what it would get wrong',\n )\n }\n const incumbent = pluginHostCollection(name)\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `host collection \"${name}\" is already declared by ` +\n `\"${incumbent.pluginId}\"; refused \"${pluginId}\"`,\n )\n }\n return { ...declaration, name }\n })\n for (const declaration of prepared) {\n registerPluginService(PLUGIN_HOST_COLLECTIONS, declaration, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key: declaration.name,\n })\n }\n}\n\n/**\n * Every declared collection, with its owner: the compiled rows first, then\n * anything registered at runtime that they do not already name.\n */\nexport function listPluginHostCollections(): ResolvedPluginHostCollection[] {\n const compiled = PLUGIN_HOST_COLLECTIONS_DECLARED.map((row) => ({ ...row }))\n const named = new Set(compiled.map((row) => row.name))\n const registered = resolvePluginServices(PLUGIN_HOST_COLLECTIONS)\n .map((entry) => ({ ...entry.impl, pluginId: entry.pluginId }))\n .filter((row) => !named.has(row.name))\n return [...compiled, ...registered]\n}\n\n/** One declared collection by name, with its owner, or `null`. */\nexport function pluginHostCollection(\n name: string,\n): ResolvedPluginHostCollection | null {\n const key = name.trim()\n return listPluginHostCollections().find((one) => one.name === key) ?? null\n}\n\n/** The plugin that owns a collection, or `undefined` when none declares it. */\nexport function pluginIdForHostCollection(name: string): string | undefined {\n return pluginHostCollection(name)?.pluginId\n}\n\n/**\n * The console hub slug a reference row for this collection links to, or\n * `undefined` when the owner routes none. A row found by the org pass names an\n * org collection, so an org declaration answers when no host one does.\n */\nexport function pluginHostCollectionRouteSlug(\n name: string,\n): string | undefined {\n return pluginHostCollection(name)?.routeSlug ?? pluginOrgCollection(name)?.routeSlug\n}\n\n/**\n * The declared collections the media scan reads generically — the list the\n * scan walks, in place of one core keeps by hand.\n */\nexport function pluginHostCollectionsScannedGenerically(): string[] {\n return listPluginHostCollections()\n .filter((one) => (one.mediaScan ?? 'generic') === 'generic')\n .map((one) => one.name)\n}\n\n/** The declared collections the scan does not read, with the reason given. */\nexport function pluginHostCollectionsExcludedFromMediaScan(): Array<{\n name: string\n reason: string\n pluginId: string\n}> {\n return listPluginHostCollections()\n .filter((one) => one.mediaScan === 'none')\n .map((one) => ({\n name: one.name,\n reason: one.mediaScanReason ?? '',\n pluginId: one.pluginId,\n }))\n}\n\n/** The declared collections whose documents the site's artifact counters total. */\nexport function pluginHostArtifactCollections(): string[] {\n return listPluginHostCollections()\n .filter((one) => one.artifact === true)\n .map((one) => one.name)\n}\n\n/**\n * What to call one of a collection's documents in a reference row: the\n * declared label, else core's own derivation from the name.\n *\n * The derivation is `hostContentCollectionLabel` rather than a second copy of\n * it, because a reference row does not know or care whether the collection it\n * names belongs to a plugin — two spellings of \"Product category\" reachable\n * by two paths is the kind of difference nobody notices until a screenshot\n * disagrees with a test.\n */\nexport function pluginHostCollectionLabel(name: string): string {\n const declared = (\n pluginHostCollection(name)?.label ?? pluginOrgCollection(name)?.label\n )?.trim()\n return declared || hostContentCollectionLabel(name)\n}\n\n/**\n * The ORG collections a plugin owns, for the media-usage scan (AGL-3273).\n *\n * Everything above is about `hosts/{hostId}/…`, and the scan's completeness\n * guard sweeps the source for exactly that shape. Some plugin storage lives on\n * the organization instead — Marketing's email sends are\n * `orgs/{orgId}/campaigns`, each naming the site it was sent as — and a\n * collection the scan does not know about is read as holding nothing, which\n * is the answer \"Find where this is used\" must never give by omission.\n *\n * So a plugin declares those too, in the `orgCollections` block of\n * `plugins.config.json`, compiled like the host rows. The same inverted\n * default holds — scanned unless the declaration says why not — and\n * `siteField` names the field that says which site a document belongs to, so\n * a reference row can name that site and link into its hub.\n *\n * Compiled only. There is no runtime door: nothing outside the first-party\n * config writes org storage the scan must read, and a door nobody uses is a\n * registry the scan's request would have to be trusted to have filled.\n */\nexport interface PluginOrgCollectionDeclaration {\n /** The subcollection under `orgs/{orgId}`, spelled as Firestore holds it. */\n name: string\n /** What one of its documents is called in a reference row. */\n label?: string\n /** Default `generic`. An org collection has no dedicated pass to be `own`. */\n mediaScan?: Exclude<PluginHostCollectionMediaScan, 'own'>\n /** Why the scan does not read it. Required with `mediaScan: 'none'`. */\n mediaScanReason?: string\n /** The `[pluginSlug]` of the site hub a reference row links to. */\n routeSlug?: string\n /**\n * The field on each document naming the site it BELONGS to. Two readers\n * act on it: a reference row names that site and links into its hub, and\n * deleting the site deletes the documents that name it (`eraseHost`). A\n * collection whose documents are placed on sites rather than owned by one\n * declares none.\n */\n siteField?: string\n /**\n * The states in which a document the site owns is work still IN FLIGHT —\n * `{ field: 'status', values: ['scheduled', 'sending'] }` — and so holds\n * the site where it is. Moving the site to another organization is refused\n * while any document naming it matches (AGL-3381): the work would land\n * for an organization the site no longer belongs to. Needs `siteField`.\n */\n holdsTransferWhile?: { field: string; values: string[] }\n}\n\n/** An org declaration with the plugin that made it. */\nexport type ResolvedPluginOrgCollection = PluginOrgCollectionDeclaration & {\n pluginId: string\n}\n\n/** Every declared org collection, with its owner. */\nexport function listPluginOrgCollections(): ResolvedPluginOrgCollection[] {\n return PLUGIN_ORG_COLLECTIONS_DECLARED.map((row) => ({ ...row }))\n}\n\n/** One declared org collection by name, with its owner, or `null`. */\nexport function pluginOrgCollection(\n name: string,\n): ResolvedPluginOrgCollection | null {\n const key = name.trim()\n return listPluginOrgCollections().find((one) => one.name === key) ?? null\n}\n\n/** The declared org collections the media scan reads generically. */\nexport function pluginOrgCollectionsScannedGenerically(): ResolvedPluginOrgCollection[] {\n return listPluginOrgCollections().filter(\n (one) => (one.mediaScan ?? 'generic') === 'generic',\n )\n}\n"],"names":["getRegisteringPluginId","hostContentCollectionLabel","PLUGIN_HOST_COLLECTIONS_DECLARED","PLUGIN_ORG_COLLECTIONS_DECLARED","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_HOST_COLLECTIONS","multiple","registerPluginHostCollections","collections","options","pluginId","trim","prepared","map","declaration","name","Error","mediaScan","mediaScanReason","incumbent","pluginHostCollection","key","listPluginHostCollections","compiled","row","named","Set","registered","entry","impl","filter","has","find","one","pluginIdForHostCollection","pluginHostCollectionRouteSlug","pluginOrgCollection","routeSlug","pluginHostCollectionsScannedGenerically","pluginHostCollectionsExcludedFromMediaScan","reason","pluginHostArtifactCollections","artifact","pluginHostCollectionLabel","declared","label","listPluginOrgCollections","pluginOrgCollectionsScannedGenerically"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-host-collections.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 { hostContentCollectionLabel } from '../foundation/definitions/host-content-collections'\nimport type { PluginHostResourceDeclaration } from './plugin-host-resources'\nimport type { PluginHostCollectionSiteExport } from './plugin-site-export'\nimport {\n PLUGIN_HOST_COLLECTIONS_DECLARED,\n PLUGIN_ORG_COLLECTIONS_DECLARED,\n} from './first-party-plugins.generated'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The host subcollections a plugin owns, declared by that plugin (AGL-3124).\n *\n * A first-party plugin writes ordinary documents under `hosts/{hostId}` with\n * the ordinary SDKs, and several core surfaces have to know which plugin owns\n * which of them: the media-usage scan decides what to read before an author\n * deletes an asset, a reference row deep-links to the console page that shows\n * one, and the artifact counters total what a site holds.\n *\n * Core answered all three by listing the collections and switching over plugin\n * ids — `services` and `resources` are bookings', `campaigns` and `experiments`\n * are marketing's, `functions`, `variables` and `actions` are logic's — which\n * is the platform holding a map of its plugins' storage. Every entry is also a\n * thing that can silently fall out of step: a plugin that ships a new\n * collection is invisible to all three until somebody remembers the list.\n *\n * So the plugin declares what it owns, once, and the three readers ask.\n *\n * ## What a declaration says, and what it deliberately does not\n *\n * A declaration names the collection, how the media scan should treat it, the\n * console slug a reference row links to, and whether a document in it is an\n * ARTIFACT the site's counters total beside screens and layouts. It does NOT\n * name fields: the scan flattens a document generically on purpose, and a\n * field list here would be the same staleness trap one level down.\n *\n * The media default is inherited from the scan's own rule and is the whole\n * reason that rule survives this move: **a collection is scanned unless its\n * declaration says why it is not.** A plugin that ships a media-bearing\n * collection is covered the day it lands rather than the day somebody\n * remembers it, and `mediaScan: 'none'` without a reason is refused — \"it\n * probably has no images in it\" is the guess the scan's design exists to\n * avoid making.\n *\n * ## One collection, one owner\n *\n * Two plugins writing one collection under one site would be two schemas in\n * one place, and the scan, the deep link and the counters would each pick a\n * winner by registration order. So a collection another plugin declared is\n * refused naming both, and the incumbent keeps it; the same plugin declaring\n * again replaces its own.\n *\n * ## Declared in the config, not registered at boot (AGL-3080)\n *\n * A first-party plugin declares its collections in `plugins.config.json` and\n * the generator compiles them, so every reader has the whole list the moment\n * it imports this module. That is not a preference: the media scan answers\n * \"what uses this asset\" in the moment before an author deletes it, every way\n * it can be wrong points at \"unused\", and it runs inside a console request\n * that loads no plugin code. A registry that request had not filled would\n * report every plugin-owned document as holding nothing, with nothing red —\n * the shape of AGL-3025, on the one answer here that somebody acts on.\n *\n * {@link registerPluginHostCollections} is still the door for a plugin the\n * compiler never sees, and the two compose: the compiled rows are a floor a\n * registration cannot lower, and a registration naming a compiled collection\n * is refused the same way a second plugin's would be.\n *\n * Reached by its own subpath rather than through `plugin-manager/index.ts`:\n * the readers are the media scan, the console counters and the reference\n * rows, none of which a published page renders.\n */\n\n/** How the media-usage scan reads a collection. */\nexport type PluginHostCollectionMediaScan =\n /** Flattened and searched generically, with no per-collection decoder. */\n | 'generic'\n /** Read by a pass that knows its shape, the way screens and layouts are. */\n | 'own'\n /** Not read; {@link PluginHostCollectionDeclaration.mediaScanReason} says why. */\n | 'none'\n\nexport interface PluginHostCollectionDeclaration {\n /** The subcollection under `hosts/{hostId}`, spelled as Firestore holds it. */\n name: string\n /**\n * What one of its documents is called in a reference row. Derived from the\n * collection name when absent, so a new collection reads correctly without\n * a second list to keep in step.\n */\n label?: string\n /** Default `generic` — see this module's note on why the default is to scan. */\n mediaScan?: PluginHostCollectionMediaScan\n /**\n * Why the scan does not read it. Required with `mediaScan: 'none'`, and it\n * has to answer what scanning would COST or what it would get WRONG.\n */\n mediaScanReason?: string\n /**\n * The `[pluginSlug]` segment of the plugin's console hub, for a reference\n * row's deep link. Optional on purpose: a row with no destination still\n * renders as text, which is better than the row not existing.\n */\n routeSlug?: string\n /**\n * Whether a document here is a live ARTIFACT — something the site's own\n * counters total beside its screens, layouts, components and templates.\n */\n artifact?: boolean\n /**\n * The kind a client CREATES a document here as, through the platform's\n * generic create route — see `plugin-host-resources.ts`. Compiled only: a\n * runtime registration carrying one is refused, because the route is the\n * writable-field allow-list and must never depend on a registry a process\n * may not have filled.\n */\n resource?: PluginHostResourceDeclaration\n /**\n * How the whole-site export carries this collection, and what a restore\n * writes back — see `plugin-site-export.ts`. Compiled only, like\n * `resource`: a backup that depended on a registry a process had not\n * filled would be missing the collection with nothing to say so.\n */\n siteExport?: PluginHostCollectionSiteExport\n}\n\n/** A declaration with the plugin that made it. */\nexport type ResolvedPluginHostCollection = PluginHostCollectionDeclaration & {\n pluginId: string\n}\n\nexport const PLUGIN_HOST_COLLECTIONS =\n definePluginServiceContract<PluginHostCollectionDeclaration>(\n 'core.host-collections',\n { multiple: true },\n )\n\n/**\n * Declares the host subcollections the registering plugin owns. The owner is\n * the loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws. A collection another plugin declared\n * throws naming both, and nothing in the same call is registered — a\n * declaration is one plugin's statement about its own storage, so half of it\n * landing would be worse than none.\n */\nexport function registerPluginHostCollections(\n collections: readonly PluginHostCollectionDeclaration[],\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n const prepared = collections.map((declaration) => {\n const name = declaration.name?.trim() ?? ''\n if (!name) throw new Error('a host collection needs a name')\n if (declaration.resource) {\n throw new Error(\n `host collection \"${name}\" declares a resource kind at runtime: the ` +\n 'create route reads only the compiled declarations in ' +\n 'plugins.config.json, so a kind registered here could never be created',\n )\n }\n if (declaration.siteExport) {\n throw new Error(\n `host collection \"${name}\" declares a site export at runtime: the ` +\n 'export and restore read only the compiled declarations in ' +\n 'plugins.config.json, so a backup would silently leave it out',\n )\n }\n if (declaration.mediaScan === 'none' && !declaration.mediaScanReason?.trim()) {\n throw new Error(\n `host collection \"${name}\" is not scanned for media and says no ` +\n 'reason: mediaScanReason has to name what scanning would cost, or ' +\n 'what it would get wrong',\n )\n }\n const incumbent = pluginHostCollection(name)\n if (incumbent && pluginId && incumbent.pluginId !== pluginId) {\n throw new Error(\n `host collection \"${name}\" is already declared by ` +\n `\"${incumbent.pluginId}\"; refused \"${pluginId}\"`,\n )\n }\n return { ...declaration, name }\n })\n for (const declaration of prepared) {\n registerPluginService(PLUGIN_HOST_COLLECTIONS, declaration, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n key: declaration.name,\n })\n }\n}\n\n/**\n * Every declared collection, with its owner: the compiled rows first, then\n * anything registered at runtime that they do not already name.\n */\nexport function listPluginHostCollections(): ResolvedPluginHostCollection[] {\n const compiled = PLUGIN_HOST_COLLECTIONS_DECLARED.map((row) => ({ ...row }))\n const named = new Set(compiled.map((row) => row.name))\n const registered = resolvePluginServices(PLUGIN_HOST_COLLECTIONS)\n .map((entry) => ({ ...entry.impl, pluginId: entry.pluginId }))\n .filter((row) => !named.has(row.name))\n return [...compiled, ...registered]\n}\n\n/** One declared collection by name, with its owner, or `null`. */\nexport function pluginHostCollection(\n name: string,\n): ResolvedPluginHostCollection | null {\n const key = name.trim()\n return listPluginHostCollections().find((one) => one.name === key) ?? null\n}\n\n/** The plugin that owns a collection, or `undefined` when none declares it. */\nexport function pluginIdForHostCollection(name: string): string | undefined {\n return pluginHostCollection(name)?.pluginId\n}\n\n/**\n * The console hub slug a reference row for this collection links to, or\n * `undefined` when the owner routes none. A row found by the org pass names an\n * org collection, so an org declaration answers when no host one does.\n */\nexport function pluginHostCollectionRouteSlug(\n name: string,\n): string | undefined {\n return pluginHostCollection(name)?.routeSlug ?? pluginOrgCollection(name)?.routeSlug\n}\n\n/**\n * The declared collections the media scan reads generically — the list the\n * scan walks, in place of one core keeps by hand.\n */\nexport function pluginHostCollectionsScannedGenerically(): string[] {\n return listPluginHostCollections()\n .filter((one) => (one.mediaScan ?? 'generic') === 'generic')\n .map((one) => one.name)\n}\n\n/** The declared collections the scan does not read, with the reason given. */\nexport function pluginHostCollectionsExcludedFromMediaScan(): Array<{\n name: string\n reason: string\n pluginId: string\n}> {\n return listPluginHostCollections()\n .filter((one) => one.mediaScan === 'none')\n .map((one) => ({\n name: one.name,\n reason: one.mediaScanReason ?? '',\n pluginId: one.pluginId,\n }))\n}\n\n/** The declared collections whose documents the site's artifact counters total. */\nexport function pluginHostArtifactCollections(): string[] {\n return listPluginHostCollections()\n .filter((one) => one.artifact === true)\n .map((one) => one.name)\n}\n\n/**\n * What to call one of a collection's documents in a reference row: the\n * declared label, else core's own derivation from the name.\n *\n * The derivation is `hostContentCollectionLabel` rather than a second copy of\n * it, because a reference row does not know or care whether the collection it\n * names belongs to a plugin — two spellings of \"Product category\" reachable\n * by two paths is the kind of difference nobody notices until a screenshot\n * disagrees with a test.\n */\nexport function pluginHostCollectionLabel(name: string): string {\n const declared = (\n pluginHostCollection(name)?.label ?? pluginOrgCollection(name)?.label\n )?.trim()\n return declared || hostContentCollectionLabel(name)\n}\n\n/**\n * The ORG collections a plugin owns, for the media-usage scan (AGL-3273).\n *\n * Everything above is about `hosts/{hostId}/…`, and the scan's completeness\n * guard sweeps the source for exactly that shape. Some plugin storage lives on\n * the organization instead — Marketing's email sends are\n * `orgs/{orgId}/campaigns`, each naming the site it was sent as — and a\n * collection the scan does not know about is read as holding nothing, which\n * is the answer \"Find where this is used\" must never give by omission.\n *\n * So a plugin declares those too, in the `orgCollections` block of\n * `plugins.config.json`, compiled like the host rows. The same inverted\n * default holds — scanned unless the declaration says why not — and\n * `siteField` names the field that says which site a document belongs to, so\n * a reference row can name that site and link into its hub.\n *\n * Compiled only. There is no runtime door: nothing outside the first-party\n * config writes org storage the scan must read, and a door nobody uses is a\n * registry the scan's request would have to be trusted to have filled.\n */\nexport interface PluginOrgCollectionDeclaration {\n /** The subcollection under `orgs/{orgId}`, spelled as Firestore holds it. */\n name: string\n /** What one of its documents is called in a reference row. */\n label?: string\n /** Default `generic`. An org collection has no dedicated pass to be `own`. */\n mediaScan?: Exclude<PluginHostCollectionMediaScan, 'own'>\n /** Why the scan does not read it. Required with `mediaScan: 'none'`. */\n mediaScanReason?: string\n /** The `[pluginSlug]` of the site hub a reference row links to. */\n routeSlug?: string\n /**\n * The field on each document naming the site it BELONGS to. Two readers\n * act on it: a reference row names that site and links into its hub, and\n * deleting the site deletes the documents that name it (`eraseHost`). A\n * collection whose documents are placed on sites rather than owned by one\n * declares none.\n */\n siteField?: string\n /**\n * The states in which a document the site owns is work still IN FLIGHT —\n * `{ field: 'status', values: ['scheduled', 'sending'] }` — and so holds\n * the site where it is. Moving the site to another organization is refused\n * while any document naming it matches (AGL-3381): the work would land\n * for an organization the site no longer belongs to. Needs `siteField`.\n */\n holdsTransferWhile?: { field: string; values: string[] }\n}\n\n/** An org declaration with the plugin that made it. */\nexport type ResolvedPluginOrgCollection = PluginOrgCollectionDeclaration & {\n pluginId: string\n}\n\n/** Every declared org collection, with its owner. */\nexport function listPluginOrgCollections(): ResolvedPluginOrgCollection[] {\n return PLUGIN_ORG_COLLECTIONS_DECLARED.map((row) => ({ ...row }))\n}\n\n/** One declared org collection by name, with its owner, or `null`. */\nexport function pluginOrgCollection(\n name: string,\n): ResolvedPluginOrgCollection | null {\n const key = name.trim()\n return listPluginOrgCollections().find((one) => one.name === key) ?? null\n}\n\n/** The declared org collections the media scan reads generically. */\nexport function pluginOrgCollectionsScannedGenerically(): ResolvedPluginOrgCollection[] {\n return listPluginOrgCollections().filter(\n (one) => (one.mediaScan ?? 'generic') === 'generic',\n )\n}\n"],"names":["getRegisteringPluginId","hostContentCollectionLabel","PLUGIN_HOST_COLLECTIONS_DECLARED","PLUGIN_ORG_COLLECTIONS_DECLARED","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_HOST_COLLECTIONS","multiple","registerPluginHostCollections","collections","options","pluginId","trim","prepared","map","declaration","name","Error","resource","siteExport","mediaScan","mediaScanReason","incumbent","pluginHostCollection","key","listPluginHostCollections","compiled","row","named","Set","registered","entry","impl","filter","has","find","one","pluginIdForHostCollection","pluginHostCollectionRouteSlug","pluginOrgCollection","routeSlug","pluginHostCollectionsScannedGenerically","pluginHostCollectionsExcludedFromMediaScan","reason","pluginHostArtifactCollections","artifact","pluginHostCollectionLabel","declared","label","listPluginOrgCollections","pluginOrgCollectionsScannedGenerically"],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A plugin's host collection as a KIND the platform's generic create route
|
|
19
|
+
* writes (AGL-3080).
|
|
20
|
+
*
|
|
21
|
+
* `/api/hosts/resources` is the only door a client creates quota-governed
|
|
22
|
+
* site documents through: the rules deny client `create` on each of these
|
|
23
|
+
* collections, and the route is the allow-list of what a client may write
|
|
24
|
+
* (AGL-1377), the count it is met against (AGL-473, AGL-2231) and the
|
|
25
|
+
* provenance it stamps (AGL-118). `duplicateResource` is its twin for a copy.
|
|
26
|
+
* Both used to hold every plugin's row — which collection a workflow lives
|
|
27
|
+
* in, which entitlement a product needs, which fields a redirect may carry —
|
|
28
|
+
* so the platform was the keeper of its plugins' document models, and a
|
|
29
|
+
* plugin could not add a kind without editing core.
|
|
30
|
+
*
|
|
31
|
+
* So the plugin that owns a collection declares the kind it is created as,
|
|
32
|
+
* beside the collection itself in the `hostCollections` block of
|
|
33
|
+
* `plugins.config.json` (a host collection's `resource`),
|
|
34
|
+
* and both readers ask this module.
|
|
35
|
+
*
|
|
36
|
+
* ## Compiled, and refused when absent
|
|
37
|
+
*
|
|
38
|
+
* The route is a SECURITY path: its field list is what stands between a
|
|
39
|
+
* client and a document the UI later presents as trustworthy. A runtime
|
|
40
|
+
* registry that one process had not filled would either refuse a legitimate
|
|
41
|
+
* create or — worse, if a reader ever fell back to an open list — store
|
|
42
|
+
* whatever it was sent. So these rows are compiled by
|
|
43
|
+
* `tools/scripts/generate-plugin-manifests.mjs`, present the moment this
|
|
44
|
+
* module is imported, and a kind nobody declares is REFUSED as an unknown
|
|
45
|
+
* resource. There is no registrar: `registerPluginHostCollections` refuses a
|
|
46
|
+
* declaration that carries a `resource`.
|
|
47
|
+
*
|
|
48
|
+
* The generator checks what a reader would otherwise have to trust: one owner
|
|
49
|
+
* per kind and none of core's own; a count on every kind (a plan `quotaKey` or
|
|
50
|
+
* a `platformCap`); no server-stamped field, stamp or approver field on the
|
|
51
|
+
* client's list; `externalDestination` only with the publishing role; and a
|
|
52
|
+
* copy's fields a subset of the create's.
|
|
53
|
+
*
|
|
54
|
+
* ## What core keeps
|
|
55
|
+
*
|
|
56
|
+
* The NUMBERS. A declaration names its plan counter and feature flag as keys
|
|
57
|
+
* (`quotaKey`, `entitlement`) and core's plan table resolves them, exactly as
|
|
58
|
+
* it does for its own kinds. A flat platform cap is named the same way —
|
|
59
|
+
* `platformCap` is the name of a constant core holds, resolved by
|
|
60
|
+
* {@link hostResourcePlatformCap} — so no plugin carries a number that is the
|
|
61
|
+
* platform's abuse ceiling. Core also keeps everything a create does for
|
|
62
|
+
* EVERY kind: the role gate, the lockdown verdict, the transaction that counts
|
|
63
|
+
* and writes, the node codec, the timestamps and `createdBy`.
|
|
64
|
+
*
|
|
65
|
+
* Reached by its own subpath, never the barrel: its readers are server routes.
|
|
66
|
+
*/
|
|
67
|
+
/** A constant value a create writes: never the client's, never another stamp's. */
|
|
68
|
+
export type PluginHostResourceStampValue = string | number | boolean | null;
|
|
69
|
+
/** How a whole copy of the kind is made, when the kind can be duplicated. */
|
|
70
|
+
export interface PluginHostResourceDuplicate {
|
|
71
|
+
/** The field the name lives in; the copy's is made unique among the live siblings. */
|
|
72
|
+
nameField: string;
|
|
73
|
+
/** The source fields carried onto the copy — a subset of the create's. */
|
|
74
|
+
fields: readonly string[];
|
|
75
|
+
/** What the copy carries beyond the source's fields: a cleared trigger. */
|
|
76
|
+
stamps?: Readonly<Record<string, PluginHostResourceStampValue>>;
|
|
77
|
+
}
|
|
78
|
+
export interface PluginHostResourceDeclaration {
|
|
79
|
+
/** The `resource` a caller names, in camelCase: `product`, `workflow`. */
|
|
80
|
+
kind: string;
|
|
81
|
+
/** Plural, for quota copy: "this site can run 5 {label}". */
|
|
82
|
+
label: string;
|
|
83
|
+
/** Singular, for the site activity line: "Created {activityNoun}". */
|
|
84
|
+
activityNoun: string;
|
|
85
|
+
/** The activity target type a row filters and deep-links by; `content` when absent. */
|
|
86
|
+
activityType?: string;
|
|
87
|
+
/** The plan counter, an `OrgEntitlements` key core resolves. */
|
|
88
|
+
quotaKey?: string;
|
|
89
|
+
/** The plan feature the kind needs at all, an `OrgFeatureFlags` key. */
|
|
90
|
+
entitlement?: string;
|
|
91
|
+
/** A flat platform cap by the name of core's constant; see {@link hostResourcePlatformCap}. */
|
|
92
|
+
platformCap?: string;
|
|
93
|
+
/** Deleting stamps `deletedAt`, so the cap counts live documents only. */
|
|
94
|
+
softDeletes?: boolean;
|
|
95
|
+
/** Creating it needs the publishing role rather than the write role. */
|
|
96
|
+
requiresPublishRole?: boolean;
|
|
97
|
+
/** The keys a client may set. Anything else it sends is dropped. */
|
|
98
|
+
fields: readonly string[];
|
|
99
|
+
/** Constant values every create writes, e.g. `deletedAt: null` for a kind born live. */
|
|
100
|
+
stamps?: Readonly<Record<string, PluginHostResourceStampValue>>;
|
|
101
|
+
/**
|
|
102
|
+
* A destination field that may point off the platform. When the value is
|
|
103
|
+
* not a site path, the creating publisher's VERIFIED uid is stamped into
|
|
104
|
+
* `approvedByField` — the evidence a serve path trusts that somebody chose
|
|
105
|
+
* to send traffic away. Only with `requiresPublishRole`.
|
|
106
|
+
*/
|
|
107
|
+
externalDestination?: {
|
|
108
|
+
field: string;
|
|
109
|
+
approvedByField: string;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* The field holding the site path the document answers at the moment it
|
|
113
|
+
* exists. A create announces it to the site cache, because what used to
|
|
114
|
+
* answer there is already cached as the page.
|
|
115
|
+
*/
|
|
116
|
+
livePathField?: string;
|
|
117
|
+
duplicate?: PluginHostResourceDuplicate;
|
|
118
|
+
}
|
|
119
|
+
/** A declaration with the plugin that made it and the collection it writes. */
|
|
120
|
+
export type ResolvedPluginHostResource = PluginHostResourceDeclaration & {
|
|
121
|
+
pluginId: string;
|
|
122
|
+
/** The subcollection under `hosts/{hostId}` the kind is created in. */
|
|
123
|
+
collection: string;
|
|
124
|
+
};
|
|
125
|
+
/** Every declared kind, in config order. */
|
|
126
|
+
export declare function listPluginHostResources(): ResolvedPluginHostResource[];
|
|
127
|
+
/** One declared kind, or `null` — which every reader treats as unknown. */
|
|
128
|
+
export declare function pluginHostResource(kind: unknown): ResolvedPluginHostResource | null;
|
|
129
|
+
/**
|
|
130
|
+
* The number a `platformCap` names, or `null` for a name core does not hold —
|
|
131
|
+
* which the create route answers by refusing, never by leaving it uncapped.
|
|
132
|
+
*/
|
|
133
|
+
export declare function hostResourcePlatformCap(name: string): number | null;
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
*/ import { ACTIONS_MAX_PER_HOST, WEBHOOK_MAX_PER_HOST } from "../app-utils/actions.js";
|
|
18
|
+
import { PLUGIN_HOST_COLLECTIONS_DECLARED } from "./first-party-plugins.generated.js";
|
|
19
|
+
/** Every declared kind, in config order. */ export function listPluginHostResources() {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const row of PLUGIN_HOST_COLLECTIONS_DECLARED){
|
|
22
|
+
if (row.resource) {
|
|
23
|
+
out.push(_extends({}, row.resource, {
|
|
24
|
+
pluginId: row.pluginId,
|
|
25
|
+
collection: row.name
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/** One declared kind, or `null` — which every reader treats as unknown. */ export function pluginHostResource(kind) {
|
|
32
|
+
var _listPluginHostResources_find;
|
|
33
|
+
if (typeof kind !== 'string' || !kind) return null;
|
|
34
|
+
return (_listPluginHostResources_find = listPluginHostResources().find((one)=>one.kind === kind)) != null ? _listPluginHostResources_find : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The flat platform caps a declaration may name (AGL-1360, AGL-2266): a
|
|
38
|
+
* ceiling on documents mintable from a browser that no plan varies, so it has
|
|
39
|
+
* no `OrgEntitlements` key. Keyed by the constant's own name, so a search for
|
|
40
|
+
* the constant finds the declaration that uses it.
|
|
41
|
+
*/ const HOST_RESOURCE_PLATFORM_CAPS = {
|
|
42
|
+
ACTIONS_MAX_PER_HOST,
|
|
43
|
+
WEBHOOK_MAX_PER_HOST
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The number a `platformCap` names, or `null` for a name core does not hold —
|
|
47
|
+
* which the create route answers by refusing, never by leaving it uncapped.
|
|
48
|
+
*/ export function hostResourcePlatformCap(name) {
|
|
49
|
+
return Object.prototype.hasOwnProperty.call(HOST_RESOURCE_PLATFORM_CAPS, name) ? HOST_RESOURCE_PLATFORM_CAPS[name] : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=plugin-host-resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-host-resources.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 { ACTIONS_MAX_PER_HOST, WEBHOOK_MAX_PER_HOST } from '../app-utils/actions'\nimport { PLUGIN_HOST_COLLECTIONS_DECLARED } from './first-party-plugins.generated'\n\n/**\n * A plugin's host collection as a KIND the platform's generic create route\n * writes (AGL-3080).\n *\n * `/api/hosts/resources` is the only door a client creates quota-governed\n * site documents through: the rules deny client `create` on each of these\n * collections, and the route is the allow-list of what a client may write\n * (AGL-1377), the count it is met against (AGL-473, AGL-2231) and the\n * provenance it stamps (AGL-118). `duplicateResource` is its twin for a copy.\n * Both used to hold every plugin's row — which collection a workflow lives\n * in, which entitlement a product needs, which fields a redirect may carry —\n * so the platform was the keeper of its plugins' document models, and a\n * plugin could not add a kind without editing core.\n *\n * So the plugin that owns a collection declares the kind it is created as,\n * beside the collection itself in the `hostCollections` block of\n * `plugins.config.json` (a host collection's `resource`),\n * and both readers ask this module.\n *\n * ## Compiled, and refused when absent\n *\n * The route is a SECURITY path: its field list is what stands between a\n * client and a document the UI later presents as trustworthy. A runtime\n * registry that one process had not filled would either refuse a legitimate\n * create or — worse, if a reader ever fell back to an open list — store\n * whatever it was sent. So these rows are compiled by\n * `tools/scripts/generate-plugin-manifests.mjs`, present the moment this\n * module is imported, and a kind nobody declares is REFUSED as an unknown\n * resource. There is no registrar: `registerPluginHostCollections` refuses a\n * declaration that carries a `resource`.\n *\n * The generator checks what a reader would otherwise have to trust: one owner\n * per kind and none of core's own; a count on every kind (a plan `quotaKey` or\n * a `platformCap`); no server-stamped field, stamp or approver field on the\n * client's list; `externalDestination` only with the publishing role; and a\n * copy's fields a subset of the create's.\n *\n * ## What core keeps\n *\n * The NUMBERS. A declaration names its plan counter and feature flag as keys\n * (`quotaKey`, `entitlement`) and core's plan table resolves them, exactly as\n * it does for its own kinds. A flat platform cap is named the same way —\n * `platformCap` is the name of a constant core holds, resolved by\n * {@link hostResourcePlatformCap} — so no plugin carries a number that is the\n * platform's abuse ceiling. Core also keeps everything a create does for\n * EVERY kind: the role gate, the lockdown verdict, the transaction that counts\n * and writes, the node codec, the timestamps and `createdBy`.\n *\n * Reached by its own subpath, never the barrel: its readers are server routes.\n */\n\n/** A constant value a create writes: never the client's, never another stamp's. */\nexport type PluginHostResourceStampValue = string | number | boolean | null\n\n/** How a whole copy of the kind is made, when the kind can be duplicated. */\nexport interface PluginHostResourceDuplicate {\n /** The field the name lives in; the copy's is made unique among the live siblings. */\n nameField: string\n /** The source fields carried onto the copy — a subset of the create's. */\n fields: readonly string[]\n /** What the copy carries beyond the source's fields: a cleared trigger. */\n stamps?: Readonly<Record<string, PluginHostResourceStampValue>>\n}\n\nexport interface PluginHostResourceDeclaration {\n /** The `resource` a caller names, in camelCase: `product`, `workflow`. */\n kind: string\n /** Plural, for quota copy: \"this site can run 5 {label}\". */\n label: string\n /** Singular, for the site activity line: \"Created {activityNoun}\". */\n activityNoun: string\n /** The activity target type a row filters and deep-links by; `content` when absent. */\n activityType?: string\n /** The plan counter, an `OrgEntitlements` key core resolves. */\n quotaKey?: string\n /** The plan feature the kind needs at all, an `OrgFeatureFlags` key. */\n entitlement?: string\n /** A flat platform cap by the name of core's constant; see {@link hostResourcePlatformCap}. */\n platformCap?: string\n /** Deleting stamps `deletedAt`, so the cap counts live documents only. */\n softDeletes?: boolean\n /** Creating it needs the publishing role rather than the write role. */\n requiresPublishRole?: boolean\n /** The keys a client may set. Anything else it sends is dropped. */\n fields: readonly string[]\n /** Constant values every create writes, e.g. `deletedAt: null` for a kind born live. */\n stamps?: Readonly<Record<string, PluginHostResourceStampValue>>\n /**\n * A destination field that may point off the platform. When the value is\n * not a site path, the creating publisher's VERIFIED uid is stamped into\n * `approvedByField` — the evidence a serve path trusts that somebody chose\n * to send traffic away. Only with `requiresPublishRole`.\n */\n externalDestination?: { field: string; approvedByField: string }\n /**\n * The field holding the site path the document answers at the moment it\n * exists. A create announces it to the site cache, because what used to\n * answer there is already cached as the page.\n */\n livePathField?: string\n duplicate?: PluginHostResourceDuplicate\n}\n\n/** A declaration with the plugin that made it and the collection it writes. */\nexport type ResolvedPluginHostResource = PluginHostResourceDeclaration & {\n pluginId: string\n /** The subcollection under `hosts/{hostId}` the kind is created in. */\n collection: string\n}\n\n/** Every declared kind, in config order. */\nexport function listPluginHostResources(): ResolvedPluginHostResource[] {\n const out: ResolvedPluginHostResource[] = []\n for (const row of PLUGIN_HOST_COLLECTIONS_DECLARED) {\n if (row.resource) {\n out.push({ ...row.resource, pluginId: row.pluginId, collection: row.name })\n }\n }\n return out\n}\n\n/** One declared kind, or `null` — which every reader treats as unknown. */\nexport function pluginHostResource(kind: unknown): ResolvedPluginHostResource | null {\n if (typeof kind !== 'string' || !kind) return null\n return listPluginHostResources().find((one) => one.kind === kind) ?? null\n}\n\n/**\n * The flat platform caps a declaration may name (AGL-1360, AGL-2266): a\n * ceiling on documents mintable from a browser that no plan varies, so it has\n * no `OrgEntitlements` key. Keyed by the constant's own name, so a search for\n * the constant finds the declaration that uses it.\n */\nconst HOST_RESOURCE_PLATFORM_CAPS: Readonly<Record<string, number>> = {\n ACTIONS_MAX_PER_HOST,\n WEBHOOK_MAX_PER_HOST,\n}\n\n/**\n * The number a `platformCap` names, or `null` for a name core does not hold —\n * which the create route answers by refusing, never by leaving it uncapped.\n */\nexport function hostResourcePlatformCap(name: string): number | null {\n return Object.prototype.hasOwnProperty.call(HOST_RESOURCE_PLATFORM_CAPS, name)\n ? HOST_RESOURCE_PLATFORM_CAPS[name]\n : null\n}\n"],"names":["ACTIONS_MAX_PER_HOST","WEBHOOK_MAX_PER_HOST","PLUGIN_HOST_COLLECTIONS_DECLARED","listPluginHostResources","out","row","resource","push","pluginId","collection","name","pluginHostResource","kind","find","one","HOST_RESOURCE_PLATFORM_CAPS","hostResourcePlatformCap","Object","prototype","hasOwnProperty","call"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,EAAEC,oBAAoB,QAAQ,0BAAsB;AACjF,SAASC,gCAAgC,QAAQ,qCAAiC;AAgHlF,0CAA0C,GAC1C,OAAO,SAASC;IACd,MAAMC,MAAoC,EAAE;IAC5C,KAAK,MAAMC,OAAOH,iCAAkC;QAClD,IAAIG,IAAIC,QAAQ,EAAE;YAChBF,IAAIG,IAAI,CAAC,aAAKF,IAAIC,QAAQ;gBAAEE,UAAUH,IAAIG,QAAQ;gBAAEC,YAAYJ,IAAIK,IAAI;;QAC1E;IACF;IACA,OAAON;AACT;AAEA,yEAAyE,GACzE,OAAO,SAASO,mBAAmBC,IAAa;QAEvCT;IADP,IAAI,OAAOS,SAAS,YAAY,CAACA,MAAM,OAAO;IAC9C,QAAOT,gCAAAA,0BAA0BU,IAAI,CAAC,CAACC,MAAQA,IAAIF,IAAI,KAAKA,iBAArDT,gCAA8D;AACvE;AAEA;;;;;CAKC,GACD,MAAMY,8BAAgE;IACpEf;IACAC;AACF;AAEA;;;CAGC,GACD,OAAO,SAASe,wBAAwBN,IAAY;IAClD,OAAOO,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACL,6BAA6BL,QACrEK,2BAA2B,CAACL,KAAK,GACjC;AACN"}
|
|
@@ -19,7 +19,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
19
19
|
*
|
|
20
20
|
* A container — a campaign is the first — is a document other records join
|
|
21
21
|
* by naming it in a membership array on their OWN documents
|
|
22
|
-
* (`app-utils/
|
|
22
|
+
* (`app-utils/container-membership.ts` says why the edge lives on the
|
|
23
23
|
* member). When the container is removed, its owner clears the id off every
|
|
24
24
|
* member it can name. A plugin that keeps members of its own where that
|
|
25
25
|
* walk does not reach — records under the org, in collections the owner
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-membership-detach.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 * A plugin's share of taking a CONTAINER away (AGL-3254).\n *\n * A container — a campaign is the first — is a document other records join\n * by naming it in a membership array on their OWN documents\n * (`app-utils/
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-membership-detach.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 * A plugin's share of taking a CONTAINER away (AGL-3254).\n *\n * A container — a campaign is the first — is a document other records join\n * by naming it in a membership array on their OWN documents\n * (`app-utils/container-membership.ts` says why the edge lives on the\n * member). When the container is removed, its owner clears the id off every\n * member it can name. A plugin that keeps members of its own where that\n * walk does not reach — records under the org, in collections the owner\n * does not know — registers a detacher here, and the removal runs every\n * detacher with the container's id, the membership field it is held in,\n * the site and the org, so the plugin can clear its own records.\n *\n * Detachers run BEFORE the container is deleted, for the reason the owner's\n * own passes do: a run that stops between them leaves records already out\n * of the container, which every surface draws correctly, while deleting\n * first would leave a plugin's record naming an id nothing can resolve. A\n * detacher that reports `remaining` holds the deletion, exactly as the\n * owner's own pass does when the container is on more records than one\n * request can clear.\n *\n * Detachers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one.\n * The owner reads a `null` as \"this plugin's records may still name the\n * container\" and refuses to remove it, which is the direction a failure\n * here has to fall.\n *\n * Registered from a plugin's declarations — `consoleServerDeclarations`,\n * since a container is removed from the console — so the detacher is in\n * place in a process that never loaded the plugin's API surface. The core\n * never imports a plugin; this is how a removal one plugin performs reaches\n * another plugin's records.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** The container being removed, and where its members may be. */\nexport interface PluginMembershipDetachRequest {\n /** The site the container belongs to. */\n hostId: string\n /** The site's organization, or `''` for a site with no org behind it. */\n orgId: string\n /** The membership field a member holds the container's id in. */\n field: string\n /** The container's document id — what the field names. */\n id: string\n}\n\n/**\n * What a detacher reports: how many of its records it took the container\n * off, and whether any were left for a second run. Counts, never content.\n */\nexport interface PluginMembershipDetachReport {\n detached: number\n remaining: boolean\n}\n\nexport type PluginMembershipDetacher = (\n request: PluginMembershipDetachRequest,\n) => Promise<PluginMembershipDetachReport>\n\ninterface Registration {\n pluginId: string\n detacher: PluginMembershipDetacher\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's membership detacher. Owner = the loader's marker\n * inside a register fn, else `options.pluginId`; a detacher with neither\n * throws. One detacher per plugin: registering again replaces the plugin's\n * earlier detacher in place, so a module evaluated twice does not run twice.\n */\nexport function registerPluginMembershipDetacher(\n detacher: PluginMembershipDetacher,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin membership detacher was registered with no owner: pass ' +\n '{ pluginId } when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, detacher }\n else registrations.push({ pluginId, detacher })\n}\n\n/**\n * Runs every detacher, in registration order, and answers each plugin's\n * report by plugin id: the detacher's own report, or `null` for one that\n * threw. Never throws.\n */\nexport async function runPluginMembershipDetachers(\n request: PluginMembershipDetachRequest,\n): Promise<Record<string, PluginMembershipDetachReport | null>> {\n const reports: Record<string, PluginMembershipDetachReport | null> = {}\n for (const { pluginId, detacher } of [...registrations]) {\n try {\n const report = await detacher({ ...request })\n reports[pluginId] = {\n detached: Math.max(0, Math.floor(Number(report?.detached ?? 0)) || 0),\n remaining: report?.remaining === true,\n }\n } catch (error) {\n reports[pluginId] = null\n console.error(\n `[plugins] ${pluginId} failed to detach ${request.field} ${request.id} on host ${request.hostId}`,\n error,\n )\n }\n }\n return reports\n}\n\n/** The plugins with a detacher in place, in registration order. Only for specs and the guards. */\nexport function listPluginMembershipDetachers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every detacher. */\nexport function resetPluginMembershipDetachersForTests(): void {\n registrations.length = 0\n}\n"],"names":["getRegisteringPluginId","registrations","registerPluginMembershipDetacher","detacher","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginMembershipDetachers","request","reports","report","detached","Math","max","floor","Number","remaining","error","console","field","id","hostId","listPluginMembershipDetachers","map","resetPluginMembershipDetachersForTests","length"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAgCxE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,iCACdC,QAAkC,EAClCC,OAA+B;QAEbJ,MAAAA;IAAlB,MAAMK,WAAW,EAACL,QAAAA,0BAAAA,oCAAAA,0BAA4BI,2BAAAA,QAASC,QAAQ,YAA7CL,OAAiD,IAAIM,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,qEACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAS;SACvDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAS;AAC/C;AAEA;;;;CAIC,GACD,OAAO,eAAeS,6BACpBC,OAAsC;IAEtC,MAAMC,UAA+D,CAAC;IACtE,KAAK,MAAM,EAAET,QAAQ,EAAEF,QAAQ,EAAE,IAAI;WAAIF;KAAc,CAAE;QACvD,IAAI;;YACF,MAAMc,SAAS,MAAMZ,SAAS,aAAKU;YACnCC,OAAO,CAACT,SAAS,GAAG;gBAClBW,UAAUC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,eAAOL,0BAAAA,OAAQC,QAAQ,mBAAI,OAAO;gBACnEK,WAAWN,CAAAA,0BAAAA,OAAQM,SAAS,MAAK;YACnC;QACF,EAAE,OAAOC,OAAO;YACdR,OAAO,CAACT,SAAS,GAAG;YACpBkB,QAAQD,KAAK,CACX,CAAC,UAAU,EAAEjB,SAAS,kBAAkB,EAAEQ,QAAQW,KAAK,CAAC,CAAC,EAAEX,QAAQY,EAAE,CAAC,SAAS,EAAEZ,QAAQa,MAAM,EAAE,EACjGJ;QAEJ;IACF;IACA,OAAOR;AACT;AAEA,gGAAgG,GAChG,OAAO,SAASa;IACd,OAAO1B,cAAc2B,GAAG,CAAC,CAAClB,QAAUA,MAAML,QAAQ;AACpD;AAEA,sCAAsC,GACtC,OAAO,SAASwB;IACd5B,cAAc6B,MAAM,GAAG;AACzB"}
|
|
@@ -14,6 +14,20 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS } from './first-party-plugins.generated';
|
|
18
|
+
export { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS };
|
|
19
|
+
/**
|
|
20
|
+
* A plugin's top-level collection whose documents name an organization in a
|
|
21
|
+
* field, declared by that plugin. See the module note.
|
|
22
|
+
*/
|
|
23
|
+
export interface PluginOrgKeyedCollection {
|
|
24
|
+
/** The plugin that declared it. */
|
|
25
|
+
pluginId: string;
|
|
26
|
+
/** The top-level collection. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** The field naming the organization a document belongs to. */
|
|
29
|
+
orgField: string;
|
|
30
|
+
}
|
|
17
31
|
/** What an eraser is asked to erase. */
|
|
18
32
|
export interface PluginOrgErasureRequest {
|
|
19
33
|
/** The workspace being erased. */
|
|
@@ -43,10 +57,22 @@ export declare function registerPluginOrgEraser(eraser: PluginOrgEraser, options
|
|
|
43
57
|
/**
|
|
44
58
|
* Runs every eraser, in registration order, and answers each plugin's
|
|
45
59
|
* report by plugin id: the eraser's own report, or `null` for one that
|
|
46
|
-
* threw.
|
|
60
|
+
* threw.
|
|
61
|
+
*
|
|
62
|
+
* Throws in exactly two cases, both about a REQUIRED eraser (`required`,
|
|
63
|
+
* the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its
|
|
64
|
+
* own): before running anything, when one is not registered; and after
|
|
65
|
+
* running every eraser, when one threw.
|
|
47
66
|
*/
|
|
48
|
-
export declare function runPluginOrgErasers(request: PluginOrgErasureRequest): Promise<Record<string, PluginOrgErasureReport | null>>;
|
|
67
|
+
export declare function runPluginOrgErasers(request: PluginOrgErasureRequest, required?: readonly string[]): Promise<Record<string, PluginOrgErasureReport | null>>;
|
|
49
68
|
/** The plugins with a workspace eraser, in the order they run. */
|
|
50
69
|
export declare function listPluginOrgErasers(): string[];
|
|
51
70
|
/** Test seam: forget every eraser. */
|
|
52
71
|
export declare function resetPluginOrgErasersForTests(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Test seam: a no-op eraser for every REQUIRED plugin that has none, so a
|
|
74
|
+
* spec about the erasure's own sweeps can run it without loading a plugin
|
|
75
|
+
* (core and the data layer may not import one). A spec about a required
|
|
76
|
+
* plugin's share registers the real one through the app's manifest instead.
|
|
77
|
+
*/
|
|
78
|
+
export declare function standInRequiredOrgErasersForTests(): void;
|