@aglyn/aglyn 1.0.0-beta.220 → 1.0.0-beta.222
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 +15 -11
- package/src/lib/app-utils/agent-openapi.js +1 -1
- package/src/lib/app-utils/agent-openapi.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +3 -2
- package/src/lib/app-utils/analytics-events.js +3 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/api-plugins.d.ts +30 -0
- package/src/lib/app-utils/api-plugins.js +24 -1
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/author-html.d.ts +1 -1
- package/src/lib/app-utils/author-html.js.map +1 -1
- package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
- package/src/lib/app-utils/bandwidth-cap.js +17 -1
- package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
- package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
- package/src/lib/app-utils/compose-reusable-components.js +116 -26
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +4 -0
- package/src/lib/app-utils/console-routes.js +3 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +71 -0
- package/src/lib/app-utils/contacts.js +55 -0
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/contexts.d.ts +1 -0
- package/src/lib/app-utils/contexts.js +1 -0
- package/src/lib/app-utils/contexts.js.map +1 -1
- package/src/lib/app-utils/crm-email-templates.js +21 -4
- package/src/lib/app-utils/crm-email-templates.js.map +1 -1
- package/src/lib/app-utils/crm-kinds.d.ts +2 -0
- package/src/lib/app-utils/crm-kinds.js +1 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +1199 -67
- package/src/lib/app-utils/crm.js +1951 -310
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/csv-import.d.ts +10 -38
- package/src/lib/app-utils/csv-import.js +17 -42
- package/src/lib/app-utils/csv-import.js.map +1 -1
- package/src/lib/app-utils/csv.d.ts +32 -11
- package/src/lib/app-utils/csv.js +43 -5
- package/src/lib/app-utils/csv.js.map +1 -1
- package/src/lib/app-utils/default-site.d.ts +116 -0
- package/src/lib/app-utils/default-site.js +1024 -0
- package/src/lib/app-utils/default-site.js.map +1 -0
- package/src/lib/app-utils/docs-help.generated.d.ts +23 -23
- package/src/lib/app-utils/docs-help.generated.js +47 -10
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +746 -151
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
- package/src/lib/app-utils/draft-placeholders.js +115 -0
- package/src/lib/app-utils/draft-placeholders.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
- package/src/lib/app-utils/entity-picker-context.js +4 -4
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
- package/src/lib/app-utils/expand-repeatables.js +88 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +33 -22
- package/src/lib/app-utils/forms.js +47 -28
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/full-use-cost.d.ts +277 -0
- package/src/lib/app-utils/full-use-cost.js +421 -0
- package/src/lib/app-utils/full-use-cost.js.map +1 -0
- package/src/lib/app-utils/health-report.js +33 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/leaving-notice.d.ts +96 -0
- package/src/lib/app-utils/leaving-notice.js +157 -0
- package/src/lib/app-utils/leaving-notice.js.map +1 -0
- package/src/lib/app-utils/llms-txt.d.ts +20 -0
- package/src/lib/app-utils/llms-txt.js +7 -1
- package/src/lib/app-utils/llms-txt.js.map +1 -1
- package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
- package/src/lib/app-utils/media-asset-facts.js +40 -13
- package/src/lib/app-utils/media-asset-facts.js.map +1 -1
- package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
- package/src/lib/app-utils/media-bandwidth.js +94 -0
- package/src/lib/app-utils/media-bandwidth.js.map +1 -0
- package/src/lib/app-utils/media-ref.d.ts +241 -33
- package/src/lib/app-utils/media-ref.js +324 -43
- package/src/lib/app-utils/media-ref.js.map +1 -1
- package/src/lib/app-utils/media-src.d.ts +8 -8
- package/src/lib/app-utils/media-src.js +27 -11
- package/src/lib/app-utils/media-src.js.map +1 -1
- package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
- package/src/lib/app-utils/media-takedown-reach.js +6 -3
- package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +1 -1
- package/src/lib/app-utils/notifications.js +2 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.js +17 -0
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/organizations.d.ts +51 -2
- package/src/lib/app-utils/organizations.js +56 -3
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/page-record-sources.d.ts +87 -0
- package/src/lib/app-utils/page-record-sources.js +53 -0
- package/src/lib/app-utils/page-record-sources.js.map +1 -0
- package/src/lib/app-utils/paid-media-source.d.ts +3 -7
- package/src/lib/app-utils/paid-media-source.js +5 -19
- package/src/lib/app-utils/paid-media-source.js.map +1 -1
- package/src/lib/app-utils/person-erasure.d.ts +5 -2
- package/src/lib/app-utils/person-erasure.js.map +1 -1
- package/src/lib/app-utils/picklists.d.ts +187 -0
- package/src/lib/app-utils/picklists.js +320 -0
- package/src/lib/app-utils/picklists.js.map +1 -0
- package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
- package/src/lib/app-utils/plan-entitlements.js +309 -115
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- 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 +1 -1
- package/src/lib/app-utils/release-flags.js +11 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/rendered-widths.d.ts +121 -0
- package/src/lib/app-utils/rendered-widths.js +211 -0
- package/src/lib/app-utils/rendered-widths.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +2 -1
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/screen-link-context-value.d.ts +7 -0
- package/src/lib/app-utils/screen-link-context-value.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +14 -2
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +157 -36
- package/src/lib/app-utils/screen-route.js +191 -41
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +11 -1
- package/src/lib/app-utils/seo-audit.js +33 -13
- package/src/lib/app-utils/seo-audit.js.map +1 -1
- package/src/lib/app-utils/seo-keywords.d.ts +11 -0
- package/src/lib/app-utils/seo-keywords.js +19 -5
- package/src/lib/app-utils/seo-keywords.js.map +1 -1
- package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
- package/src/lib/app-utils/seo-page-facts.js +136 -31
- package/src/lib/app-utils/seo-page-facts.js.map +1 -1
- package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
- package/src/lib/app-utils/seo-site-scan.js +113 -12
- package/src/lib/app-utils/seo-site-scan.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +5 -4
- package/src/lib/app-utils/server.js +14 -5
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-icon-set.d.ts +195 -0
- package/src/lib/app-utils/site-icon-set.js +351 -0
- package/src/lib/app-utils/site-icon-set.js.map +1 -0
- package/src/lib/app-utils/site-interactions.d.ts +101 -4
- package/src/lib/app-utils/site-interactions.js +120 -4
- package/src/lib/app-utils/site-interactions.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
- package/src/lib/app-utils/starter-template-nodes.js +3 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +5 -19
- package/src/lib/app-utils/starter-templates.js +5 -49
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
- package/src/lib/app-utils/transfer-launcher-context.js +23 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
- package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
- package/src/lib/app-utils/transfer-resume-import.js +128 -0
- package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
- package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
- package/src/lib/app-utils/upgrade-proposal.js +95 -0
- package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
- package/src/lib/app-utils/utc-month.d.ts +33 -0
- package/src/lib/app-utils/utc-month.js +35 -0
- package/src/lib/app-utils/utc-month.js.map +1 -0
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/wire-nodes.d.ts +39 -19
- package/src/lib/app-utils/wire-nodes.js +120 -34
- package/src/lib/app-utils/wire-nodes.js.map +1 -1
- package/src/lib/data-transfer/access.d.ts +74 -0
- package/src/lib/data-transfer/access.js +93 -0
- package/src/lib/data-transfer/access.js.map +1 -0
- package/src/lib/data-transfer/derive.d.ts +246 -0
- package/src/lib/data-transfer/derive.js +1007 -0
- package/src/lib/data-transfer/derive.js.map +1 -0
- package/src/lib/data-transfer/export-file.d.ts +46 -0
- package/src/lib/data-transfer/export-file.js +146 -0
- package/src/lib/data-transfer/export-file.js.map +1 -0
- package/src/lib/data-transfer/field-catalog.d.ts +134 -0
- package/src/lib/data-transfer/field-catalog.js +278 -0
- package/src/lib/data-transfer/field-catalog.js.map +1 -0
- package/src/lib/data-transfer/header-match.d.ts +129 -0
- package/src/lib/data-transfer/header-match.js +367 -0
- package/src/lib/data-transfer/header-match.js.map +1 -0
- package/src/lib/data-transfer/index.d.ts +34 -0
- package/src/lib/data-transfer/index.js +40 -0
- package/src/lib/data-transfer/index.js.map +1 -0
- package/src/lib/data-transfer/job.d.ts +190 -0
- package/src/lib/data-transfer/job.js +229 -0
- package/src/lib/data-transfer/job.js.map +1 -0
- package/src/lib/data-transfer/lookup.d.ts +45 -0
- package/src/lib/data-transfer/lookup.js +71 -0
- package/src/lib/data-transfer/lookup.js.map +1 -0
- package/src/lib/data-transfer/match.d.ts +99 -0
- package/src/lib/data-transfer/match.js +244 -0
- package/src/lib/data-transfer/match.js.map +1 -0
- package/src/lib/data-transfer/package-plan.d.ts +205 -0
- package/src/lib/data-transfer/package-plan.js +475 -0
- package/src/lib/data-transfer/package-plan.js.map +1 -0
- package/src/lib/data-transfer/package.d.ts +181 -0
- package/src/lib/data-transfer/package.js +426 -0
- package/src/lib/data-transfer/package.js.map +1 -0
- package/src/lib/data-transfer/picklist-map.d.ts +139 -0
- package/src/lib/data-transfer/picklist-map.js +271 -0
- package/src/lib/data-transfer/picklist-map.js.map +1 -0
- package/src/lib/data-transfer/plan.d.ts +176 -0
- package/src/lib/data-transfer/plan.js +451 -0
- package/src/lib/data-transfer/plan.js.map +1 -0
- package/src/lib/data-transfer/policy.d.ts +170 -0
- package/src/lib/data-transfer/policy.js +378 -0
- package/src/lib/data-transfer/policy.js.map +1 -0
- package/src/lib/data-transfer/resource.d.ts +211 -0
- package/src/lib/data-transfer/resource.js +241 -0
- package/src/lib/data-transfer/resource.js.map +1 -0
- package/src/lib/data-transfer/review.d.ts +70 -0
- package/src/lib/data-transfer/review.js +233 -0
- package/src/lib/data-transfer/review.js.map +1 -0
- package/src/lib/data-transfer/similarity.d.ts +40 -0
- package/src/lib/data-transfer/similarity.js +106 -0
- package/src/lib/data-transfer/similarity.js.map +1 -0
- package/src/lib/data-transfer/site-package.d.ts +365 -0
- package/src/lib/data-transfer/site-package.js +1077 -0
- package/src/lib/data-transfer/site-package.js.map +1 -0
- package/src/lib/data-transfer/source.d.ts +74 -0
- package/src/lib/data-transfer/source.js +293 -0
- package/src/lib/data-transfer/source.js.map +1 -0
- package/src/lib/data-transfer/transfer-api.d.ts +898 -0
- package/src/lib/data-transfer/transfer-api.js +107 -0
- package/src/lib/data-transfer/transfer-api.js.map +1 -0
- package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
- package/src/lib/foundation/definitions/platform.types.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/feature-plugins.d.ts +91 -2
- package/src/lib/plugin-manager/feature-plugins.js +48 -3
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +56 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +1132 -27
- 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/interaction-recipes.d.ts +142 -0
- package/src/lib/plugin-manager/interaction-recipes.js +104 -0
- package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
- package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
- package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
- package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
- package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
- package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-jobs.js +7 -7
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
- package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
- package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
- package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
- package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
- package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
- package/src/lib/plugin-manager/plugin-person-records.js +107 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
- package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
- package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
- package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
- package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
- package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
- package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
- package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
- package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
- package/src/lib/plugin-manager/plugin-revocations.js +47 -0
- package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
- package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
- package/src/lib/plugin-manager/plugin-server-steps.d.ts +170 -0
- package/src/lib/plugin-manager/plugin-server-steps.js +116 -0
- package/src/lib/plugin-manager/plugin-server-steps.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
- package/src/lib/plugin-manager/plugin-site-export.js +23 -10
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
- package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
- package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
- 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/repeat-rows.d.ts +5 -6
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
- package/src/lib/app-utils/actions.d.ts +0 -310
- package/src/lib/app-utils/actions.js +0 -506
- package/src/lib/app-utils/actions.js.map +0 -1
- package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
- package/src/lib/app-utils/automation-placeholders.js +0 -207
- package/src/lib/app-utils/automation-placeholders.js.map +0 -1
- package/src/lib/app-utils/dataset-models.d.ts +0 -339
- package/src/lib/app-utils/dataset-models.js +0 -722
- package/src/lib/app-utils/dataset-models.js.map +0 -1
- package/src/lib/app-utils/datasets.d.ts +0 -171
- package/src/lib/app-utils/datasets.js +0 -227
- package/src/lib/app-utils/datasets.js.map +0 -1
- package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
- package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
- package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
- package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
- package/src/lib/plugin-manager/order-fulfilment.js +0 -104
- package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* THE KINDS OF ENTITY A BESIGNER PICKER LISTS, AS THEIR PLUGINS DECLARE THEM
|
|
18
|
+
* (AGL-3080).
|
|
19
|
+
*
|
|
20
|
+
* An element's attribute can name another record by id — the product a
|
|
21
|
+
* product grid shows, the form a Form element files into, the dataset a
|
|
22
|
+
* repeat walks — and the besigner offers that attribute as a picker of the
|
|
23
|
+
* site's records of that kind (AGL-343/344). Core used to know every kind
|
|
24
|
+
* there was: a closed union over commerce's products, collections and
|
|
25
|
+
* categories, the forms plugin's forms and the data plugin's datasets, with
|
|
26
|
+
* each one's collection, name field, scope, server filter and words spelled
|
|
27
|
+
* out in the console's provider and the besigner's tables. A plugin that
|
|
28
|
+
* kept a record an element could name had no way to be picked.
|
|
29
|
+
*
|
|
30
|
+
* So a plugin DECLARES each kind it supplies, in `plugins.config.json`
|
|
31
|
+
* (`entityPickers`), and the console's provider and the besigner read the
|
|
32
|
+
* declarations: which attribute type lists the kind, where its documents
|
|
33
|
+
* hang (a site's own, or the organization's data shared per site), the field
|
|
34
|
+
* a document is named by, the equality clauses that narrow it on the server,
|
|
35
|
+
* whether it carries the name-search keys, the words a picker says about it,
|
|
36
|
+
* and — for a kind whose documents carry a field model — the attribute type
|
|
37
|
+
* that offers a chosen entity's fields and the record kind whose list source
|
|
38
|
+
* shares them (`plugin-record-lists`).
|
|
39
|
+
*
|
|
40
|
+
* ## What core keeps
|
|
41
|
+
*
|
|
42
|
+
* The machinery every kind shares and none may vary: the bounded browse with
|
|
43
|
+
* its probe, the search past it, the keyed resolution of a stored value, the
|
|
44
|
+
* demand gate, and the platform's visibility rule for the organization's
|
|
45
|
+
* data — an `orgData` kind lists only what the site may use (`visibleTo`
|
|
46
|
+
* against the site's scope tokens, AGL-1044) and resolves anything else as
|
|
47
|
+
* unavailable. A declaration names fields and words; it cannot widen what a
|
|
48
|
+
* site may see.
|
|
49
|
+
*
|
|
50
|
+
* ## Compiled, and deliberately without a runtime registrar
|
|
51
|
+
*
|
|
52
|
+
* The besigner decides whether an attribute IS a picker from these, on the
|
|
53
|
+
* first render of the panel, and a picker that was not yet registered would
|
|
54
|
+
* render as an ordinary field — a bound element shown as an unbound one,
|
|
55
|
+
* which an author repairs by choosing again. So these are compiled from
|
|
56
|
+
* `plugins.config.json` like the org capacities and the switchboard catalog:
|
|
57
|
+
* the declarations are in the manifest before anything runs.
|
|
58
|
+
*/ import { PLUGIN_ENTITY_PICKERS_DECLARED } from "./first-party-plugins.generated.js";
|
|
59
|
+
/**
|
|
60
|
+
* The load point a plugin whose console list source shares an entity's
|
|
61
|
+
* fields declares in its `console.slots` (`fieldsFrom`), so the console's
|
|
62
|
+
* picker provider loads it: the declaration is compiled, the fields are read
|
|
63
|
+
* through the source at run time.
|
|
64
|
+
*/ export const ENTITY_PICKERS_LOAD_POINT = 'entityPickers';
|
|
65
|
+
/** Every declared picker kind, in the order the plugins are configured. */ export function pluginEntityPickers() {
|
|
66
|
+
return PLUGIN_ENTITY_PICKERS_DECLARED;
|
|
67
|
+
}
|
|
68
|
+
/** The declared kind with this key, or `null`. */ export function pluginEntityPicker(kind) {
|
|
69
|
+
var _PLUGIN_ENTITY_PICKERS_DECLARED_find;
|
|
70
|
+
return (_PLUGIN_ENTITY_PICKERS_DECLARED_find = PLUGIN_ENTITY_PICKERS_DECLARED.find((one)=>one.kind === kind)) != null ? _PLUGIN_ENTITY_PICKERS_DECLARED_find : null;
|
|
71
|
+
}
|
|
72
|
+
/** The kind an attribute type's picker lists, or `null` when it lists none. */ export function entityPickerForAttribute(attribute) {
|
|
73
|
+
var _PLUGIN_ENTITY_PICKERS_DECLARED_find;
|
|
74
|
+
if (!attribute) return null;
|
|
75
|
+
return (_PLUGIN_ENTITY_PICKERS_DECLARED_find = PLUGIN_ENTITY_PICKERS_DECLARED.find((one)=>one.attribute === attribute)) != null ? _PLUGIN_ENTITY_PICKERS_DECLARED_find : null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The kind whose entity FIELDS an attribute type offers, or `null` — the
|
|
79
|
+
* picker needs that kind's list to resolve the entity its fields are read
|
|
80
|
+
* from, without being a picker of the kind itself.
|
|
81
|
+
*/ export function entityFieldsPickerForAttribute(attribute) {
|
|
82
|
+
var _PLUGIN_ENTITY_PICKERS_DECLARED_find;
|
|
83
|
+
if (!attribute) return null;
|
|
84
|
+
return (_PLUGIN_ENTITY_PICKERS_DECLARED_find = PLUGIN_ENTITY_PICKERS_DECLARED.find((one)=>one.fieldsAttribute === attribute)) != null ? _PLUGIN_ENTITY_PICKERS_DECLARED_find : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=plugin-entity-pickers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-entity-pickers.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 * THE KINDS OF ENTITY A BESIGNER PICKER LISTS, AS THEIR PLUGINS DECLARE THEM\n * (AGL-3080).\n *\n * An element's attribute can name another record by id — the product a\n * product grid shows, the form a Form element files into, the dataset a\n * repeat walks — and the besigner offers that attribute as a picker of the\n * site's records of that kind (AGL-343/344). Core used to know every kind\n * there was: a closed union over commerce's products, collections and\n * categories, the forms plugin's forms and the data plugin's datasets, with\n * each one's collection, name field, scope, server filter and words spelled\n * out in the console's provider and the besigner's tables. A plugin that\n * kept a record an element could name had no way to be picked.\n *\n * So a plugin DECLARES each kind it supplies, in `plugins.config.json`\n * (`entityPickers`), and the console's provider and the besigner read the\n * declarations: which attribute type lists the kind, where its documents\n * hang (a site's own, or the organization's data shared per site), the field\n * a document is named by, the equality clauses that narrow it on the server,\n * whether it carries the name-search keys, the words a picker says about it,\n * and — for a kind whose documents carry a field model — the attribute type\n * that offers a chosen entity's fields and the record kind whose list source\n * shares them (`plugin-record-lists`).\n *\n * ## What core keeps\n *\n * The machinery every kind shares and none may vary: the bounded browse with\n * its probe, the search past it, the keyed resolution of a stored value, the\n * demand gate, and the platform's visibility rule for the organization's\n * data — an `orgData` kind lists only what the site may use (`visibleTo`\n * against the site's scope tokens, AGL-1044) and resolves anything else as\n * unavailable. A declaration names fields and words; it cannot widen what a\n * site may see.\n *\n * ## Compiled, and deliberately without a runtime registrar\n *\n * The besigner decides whether an attribute IS a picker from these, on the\n * first render of the panel, and a picker that was not yet registered would\n * render as an ordinary field — a bound element shown as an unbound one,\n * which an author repairs by choosing again. So these are compiled from\n * `plugins.config.json` like the org capacities and the switchboard catalog:\n * the declarations are in the manifest before anything runs.\n */\n\nimport { PLUGIN_ENTITY_PICKERS_DECLARED } from './first-party-plugins.generated'\n\n/** One equality clause a kind's documents are narrowed by, on the server. */\nexport interface PluginEntityPickerClause {\n field: string\n equals: string | number | boolean\n}\n\nexport interface PluginEntityPickerDeclaration {\n /**\n * The kind's key in the picker context — `options[kind]`, `status[kind]`,\n * `resolved[kind]` — and in a repeat source's `entityKind`. Unique.\n */\n kind: string\n /** The attribute type (`FieldComponentType`) whose picker lists this kind. */\n attribute: string\n /**\n * Where the kind's documents hang: `host` — the site's own\n * `hosts/<hostId>/<collection>`; `orgData` — the organization's data, read\n * where the site's data scope resolves and narrowed to what the site may\n * use.\n */\n scope: 'host' | 'orgData'\n /** The collection's name under the scope's root. */\n collection: string\n /** The field holding a document's name; `name` is read when it is empty. */\n nameField: string\n /** Equality clauses applied to the browse AND the search, on the server. */\n where?: readonly PluginEntityPickerClause[]\n /**\n * The documents carry the name-search keys (`nameTokens`, `nameLower`),\n * so a typed query reaches past the browse window.\n */\n searchable?: boolean\n /**\n * An attribute type whose options are the FIELDS of a chosen entity of\n * this kind — a form field's \"Maps to schema field\" — with `fieldsFrom`,\n * the record kind whose console list source shares each entity's `fields`\n * as `{ id, name }` (`plugin-record-lists`).\n */\n fieldsAttribute?: string\n fieldsFrom?: string\n /** The words a picker says: \"No (plural) yet — add one on (page)\". */\n singular: string\n plural: string\n page: string\n}\n\n/** A declaration with the plugin that made it. */\nexport type ResolvedPluginEntityPicker = PluginEntityPickerDeclaration & {\n pluginId: string\n}\n\n/**\n * The load point a plugin whose console list source shares an entity's\n * fields declares in its `console.slots` (`fieldsFrom`), so the console's\n * picker provider loads it: the declaration is compiled, the fields are read\n * through the source at run time.\n */\nexport const ENTITY_PICKERS_LOAD_POINT = 'entityPickers'\n\n/** Every declared picker kind, in the order the plugins are configured. */\nexport function pluginEntityPickers(): readonly ResolvedPluginEntityPicker[] {\n return PLUGIN_ENTITY_PICKERS_DECLARED\n}\n\n/** The declared kind with this key, or `null`. */\nexport function pluginEntityPicker(kind: string): ResolvedPluginEntityPicker | null {\n return PLUGIN_ENTITY_PICKERS_DECLARED.find((one) => one.kind === kind) ?? null\n}\n\n/** The kind an attribute type's picker lists, or `null` when it lists none. */\nexport function entityPickerForAttribute(\n attribute: string | undefined,\n): ResolvedPluginEntityPicker | null {\n if (!attribute) return null\n return PLUGIN_ENTITY_PICKERS_DECLARED.find((one) => one.attribute === attribute) ?? null\n}\n\n/**\n * The kind whose entity FIELDS an attribute type offers, or `null` — the\n * picker needs that kind's list to resolve the entity its fields are read\n * from, without being a picker of the kind itself.\n */\nexport function entityFieldsPickerForAttribute(\n attribute: string | undefined,\n): ResolvedPluginEntityPicker | null {\n if (!attribute) return null\n return PLUGIN_ENTITY_PICKERS_DECLARED.find((one) => one.fieldsAttribute === attribute) ?? null\n}\n"],"names":["PLUGIN_ENTITY_PICKERS_DECLARED","ENTITY_PICKERS_LOAD_POINT","pluginEntityPickers","pluginEntityPicker","kind","find","one","entityPickerForAttribute","attribute","entityFieldsPickerForAttribute","fieldsAttribute"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CC,GAED,SAASA,8BAA8B,QAAQ,qCAAiC;AAqDhF;;;;;CAKC,GACD,OAAO,MAAMC,4BAA4B,gBAAe;AAExD,yEAAyE,GACzE,OAAO,SAASC;IACd,OAAOF;AACT;AAEA,gDAAgD,GAChD,OAAO,SAASG,mBAAmBC,IAAY;QACtCJ;IAAP,QAAOA,uCAAAA,+BAA+BK,IAAI,CAAC,CAACC,MAAQA,IAAIF,IAAI,KAAKA,iBAA1DJ,uCAAmE;AAC5E;AAEA,6EAA6E,GAC7E,OAAO,SAASO,yBACdC,SAA6B;QAGtBR;IADP,IAAI,CAACQ,WAAW,OAAO;IACvB,QAAOR,uCAAAA,+BAA+BK,IAAI,CAAC,CAACC,MAAQA,IAAIE,SAAS,KAAKA,sBAA/DR,uCAA6E;AACtF;AAEA;;;;CAIC,GACD,OAAO,SAASS,+BACdD,SAA6B;QAGtBR;IADP,IAAI,CAACQ,WAAW,OAAO;IACvB,QAAOR,uCAAAA,+BAA+BK,IAAI,CAAC,CAACC,MAAQA,IAAII,eAAe,KAAKF,sBAArER,uCAAmF;AAC5F"}
|
|
@@ -126,6 +126,12 @@ export type ResolvedPluginHostResource = PluginHostResourceDeclaration & {
|
|
|
126
126
|
export declare function listPluginHostResources(): ResolvedPluginHostResource[];
|
|
127
127
|
/** One declared kind, or `null` — which every reader treats as unknown. */
|
|
128
128
|
export declare function pluginHostResource(kind: unknown): ResolvedPluginHostResource | null;
|
|
129
|
+
/**
|
|
130
|
+
* The most webhooks one site keeps (AGL-1360). Counted by the site create
|
|
131
|
+
* route from a server read: the rules refuse a client-direct webhook create,
|
|
132
|
+
* so no browser count can be stale.
|
|
133
|
+
*/
|
|
134
|
+
export declare const WEBHOOK_MAX_PER_HOST = 5;
|
|
129
135
|
/**
|
|
130
136
|
* The number a `platformCap` names, or `null` for a name core does not hold —
|
|
131
137
|
* which the create route answers by refusing, never by leaving it uncapped.
|
|
@@ -14,7 +14,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import { ACTIONS_MAX_PER_HOST
|
|
17
|
+
*/ import { ACTIONS_MAX_PER_HOST } from "../app-utils/site-interactions.js";
|
|
18
18
|
import { PLUGIN_HOST_COLLECTIONS_DECLARED } from "./first-party-plugins.generated.js";
|
|
19
19
|
/** Every declared kind, in config order. */ export function listPluginHostResources() {
|
|
20
20
|
const out = [];
|
|
@@ -33,6 +33,11 @@ import { PLUGIN_HOST_COLLECTIONS_DECLARED } from "./first-party-plugins.generate
|
|
|
33
33
|
if (typeof kind !== 'string' || !kind) return null;
|
|
34
34
|
return (_listPluginHostResources_find = listPluginHostResources().find((one)=>one.kind === kind)) != null ? _listPluginHostResources_find : null;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The most webhooks one site keeps (AGL-1360). Counted by the site create
|
|
38
|
+
* route from a server read: the rules refuse a client-direct webhook create,
|
|
39
|
+
* so no browser count can be stale.
|
|
40
|
+
*/ export const WEBHOOK_MAX_PER_HOST = 5;
|
|
36
41
|
/**
|
|
37
42
|
* The flat platform caps a declaration may name (AGL-1360, AGL-2266): a
|
|
38
43
|
* ceiling on documents mintable from a browser that no plan varies, so it has
|
|
@@ -1 +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
|
|
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 } from '../app-utils/site-interactions'\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 most webhooks one site keeps (AGL-1360). Counted by the site create\n * route from a server read: the rules refuse a client-direct webhook create,\n * so no browser count can be stale.\n */\nexport const WEBHOOK_MAX_PER_HOST = 5\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","PLUGIN_HOST_COLLECTIONS_DECLARED","listPluginHostResources","out","row","resource","push","pluginId","collection","name","pluginHostResource","kind","find","one","WEBHOOK_MAX_PER_HOST","HOST_RESOURCE_PLATFORM_CAPS","hostResourcePlatformCap","Object","prototype","hasOwnProperty","call"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,QAAQ,oCAAgC;AACrE,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;;;;CAIC,GACD,OAAO,MAAMY,uBAAuB,EAAC;AAErC;;;;;CAKC,GACD,MAAMC,8BAAgE;IACpEf;IACAc;AACF;AAEA;;;CAGC,GACD,OAAO,SAASE,wBAAwBP,IAAY;IAClD,OAAOQ,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACL,6BAA6BN,QACrEM,2BAA2B,CAACN,KAAK,GACjC;AACN"}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
* WOULD A VISITOR'S NEXT WRITE THROUGH A PLUGIN'S DOOR BE ACCEPTED (AGL-3080)?
|
|
19
|
+
*
|
|
20
|
+
* A plugin that keeps a public door — the forms plugin's `/api/forms/submit`
|
|
21
|
+
* — runs gates of its own in front of the write: whether the site has the
|
|
22
|
+
* plugin on, what the plan allows this month, the ceiling a flood trips. A
|
|
23
|
+
* monitor that must not WRITE to find out (a synthetic submission would file
|
|
24
|
+
* fake leads, bill the customer and notify the site's managers) has to ask
|
|
25
|
+
* the same predicates about the same documents, and those are the door's
|
|
26
|
+
* owner's to evaluate.
|
|
27
|
+
*
|
|
28
|
+
* So the owner registers an INTAKE GATE under the door's name (`form`), and
|
|
29
|
+
* the monitor asks it. The gate answers what the door would answer for a
|
|
30
|
+
* visitor arriving now, BEFORE its first write, and writes nothing:
|
|
31
|
+
*
|
|
32
|
+
* - `open` — accepted;
|
|
33
|
+
* - `switched-off` — the site has the door's plugin off;
|
|
34
|
+
* - `plan-exhausted` — the plan's monthly allowance is spent and the plan
|
|
35
|
+
* walls rather than meters;
|
|
36
|
+
* - `flood-ceiling` — the site tripped the ceiling that contains a flood.
|
|
37
|
+
*
|
|
38
|
+
* The platform's own gates are not the gate's to answer: whether the site is
|
|
39
|
+
* paused (lockdown), and who would be told, are asked by the monitor itself.
|
|
40
|
+
* The caller hands over the site and organization documents it has already
|
|
41
|
+
* read, so a gate reads only what is its own.
|
|
42
|
+
*
|
|
43
|
+
* ## Not registered is an answer
|
|
44
|
+
*
|
|
45
|
+
* No gate means no plugin keeps the door in this process; a caller runs the
|
|
46
|
+
* app's declarations step once and asks again before it concludes the door
|
|
47
|
+
* is shut. One gate per door: a second plugin registering one another plugin
|
|
48
|
+
* already answers is refused, naming both.
|
|
49
|
+
*/
|
|
50
|
+
/** What a door would answer a visitor arriving now. */
|
|
51
|
+
export type PluginIntakeVerdict = 'open' | 'switched-off' | 'plan-exhausted' | 'flood-ceiling';
|
|
52
|
+
/** The site and organization the caller has read already. */
|
|
53
|
+
export interface PluginIntakeRequest {
|
|
54
|
+
hostId: string;
|
|
55
|
+
/** The site's document, as read. */
|
|
56
|
+
host: Readonly<Record<string, unknown>> | undefined;
|
|
57
|
+
/** The owning organization's document, as read; absent for a site with none. */
|
|
58
|
+
org: Readonly<Record<string, unknown>> | undefined;
|
|
59
|
+
}
|
|
60
|
+
/** Evaluates the door's own gates without writing. Throws what its reads throw. */
|
|
61
|
+
export type PluginIntakeGate = (request: PluginIntakeRequest) => Promise<PluginIntakeVerdict>;
|
|
62
|
+
export declare const PLUGIN_INTAKE_GATES: import("./plugin-services").PluginServiceContract<PluginIntakeGate>;
|
|
63
|
+
/**
|
|
64
|
+
* Registers the gate for one door. The owner is the loader's marker when a
|
|
65
|
+
* register fn is running, else `options.pluginId`; with neither it throws. A
|
|
66
|
+
* door another plugin already answers throws naming both.
|
|
67
|
+
*/
|
|
68
|
+
export declare function registerPluginIntakeGate(door: string, gate: PluginIntakeGate, options?: {
|
|
69
|
+
pluginId?: string;
|
|
70
|
+
}): void;
|
|
71
|
+
/** The gate for a door, with the plugin that keeps it, or `null` when none does here. */
|
|
72
|
+
export declare function pluginIntakeGate(door: string): {
|
|
73
|
+
pluginId: string;
|
|
74
|
+
gate: PluginIntakeGate;
|
|
75
|
+
} | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
*/ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
17
|
+
import { definePluginServiceContract, registerPluginService, resolvePluginServices } from "./plugin-services.js";
|
|
18
|
+
export const PLUGIN_INTAKE_GATES = definePluginServiceContract('core.intake-gates', {
|
|
19
|
+
multiple: true
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Registers the gate for one door. The owner is the loader's marker when a
|
|
23
|
+
* register fn is running, else `options.pluginId`; with neither it throws. A
|
|
24
|
+
* door another plugin already answers throws naming both.
|
|
25
|
+
*/ export function registerPluginIntakeGate(door, gate, options) {
|
|
26
|
+
var _ref, _getRegisteringPluginId;
|
|
27
|
+
const key = door.trim();
|
|
28
|
+
if (!key) throw new Error('an intake gate needs the door it answers for');
|
|
29
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
30
|
+
if (!pluginId) throw new Error(`the intake gate for "${key}" was registered with no owner`);
|
|
31
|
+
const incumbent = resolvePluginServices(PLUGIN_INTAKE_GATES).find((entry)=>entry.key === key);
|
|
32
|
+
if (incumbent && incumbent.pluginId !== pluginId) {
|
|
33
|
+
throw new Error(`door "${key}" is already gated by "${incumbent.pluginId}"; refused "${pluginId}"`);
|
|
34
|
+
}
|
|
35
|
+
registerPluginService(PLUGIN_INTAKE_GATES, gate, {
|
|
36
|
+
pluginId,
|
|
37
|
+
key
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** The gate for a door, with the plugin that keeps it, or `null` when none does here. */ export function pluginIntakeGate(door) {
|
|
41
|
+
const key = door.trim();
|
|
42
|
+
const entry = resolvePluginServices(PLUGIN_INTAKE_GATES).find((one)=>one.key === key);
|
|
43
|
+
return entry ? {
|
|
44
|
+
pluginId: entry.pluginId,
|
|
45
|
+
gate: entry.impl
|
|
46
|
+
} : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=plugin-intake-gates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-intake-gates.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 {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * WOULD A VISITOR'S NEXT WRITE THROUGH A PLUGIN'S DOOR BE ACCEPTED (AGL-3080)?\n *\n * A plugin that keeps a public door — the forms plugin's `/api/forms/submit`\n * — runs gates of its own in front of the write: whether the site has the\n * plugin on, what the plan allows this month, the ceiling a flood trips. A\n * monitor that must not WRITE to find out (a synthetic submission would file\n * fake leads, bill the customer and notify the site's managers) has to ask\n * the same predicates about the same documents, and those are the door's\n * owner's to evaluate.\n *\n * So the owner registers an INTAKE GATE under the door's name (`form`), and\n * the monitor asks it. The gate answers what the door would answer for a\n * visitor arriving now, BEFORE its first write, and writes nothing:\n *\n * - `open` — accepted;\n * - `switched-off` — the site has the door's plugin off;\n * - `plan-exhausted` — the plan's monthly allowance is spent and the plan\n * walls rather than meters;\n * - `flood-ceiling` — the site tripped the ceiling that contains a flood.\n *\n * The platform's own gates are not the gate's to answer: whether the site is\n * paused (lockdown), and who would be told, are asked by the monitor itself.\n * The caller hands over the site and organization documents it has already\n * read, so a gate reads only what is its own.\n *\n * ## Not registered is an answer\n *\n * No gate means no plugin keeps the door in this process; a caller runs the\n * app's declarations step once and asks again before it concludes the door\n * is shut. One gate per door: a second plugin registering one another plugin\n * already answers is refused, naming both.\n */\n\n/** What a door would answer a visitor arriving now. */\nexport type PluginIntakeVerdict = 'open' | 'switched-off' | 'plan-exhausted' | 'flood-ceiling'\n\n/** The site and organization the caller has read already. */\nexport interface PluginIntakeRequest {\n hostId: string\n /** The site's document, as read. */\n host: Readonly<Record<string, unknown>> | undefined\n /** The owning organization's document, as read; absent for a site with none. */\n org: Readonly<Record<string, unknown>> | undefined\n}\n\n/** Evaluates the door's own gates without writing. Throws what its reads throw. */\nexport type PluginIntakeGate = (request: PluginIntakeRequest) => Promise<PluginIntakeVerdict>\n\nexport const PLUGIN_INTAKE_GATES = definePluginServiceContract<PluginIntakeGate>(\n 'core.intake-gates',\n { multiple: true },\n)\n\n/**\n * Registers the gate for one door. The owner is the loader's marker when a\n * register fn is running, else `options.pluginId`; with neither it throws. A\n * door another plugin already answers throws naming both.\n */\nexport function registerPluginIntakeGate(\n door: string,\n gate: PluginIntakeGate,\n options?: { pluginId?: string },\n): void {\n const key = door.trim()\n if (!key) throw new Error('an intake gate needs the door it answers for')\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) throw new Error(`the intake gate for \"${key}\" was registered with no owner`)\n const incumbent = resolvePluginServices(PLUGIN_INTAKE_GATES).find((entry) => entry.key === key)\n if (incumbent && incumbent.pluginId !== pluginId) {\n throw new Error(\n `door \"${key}\" is already gated by \"${incumbent.pluginId}\"; refused \"${pluginId}\"`,\n )\n }\n registerPluginService(PLUGIN_INTAKE_GATES, gate, { pluginId, key })\n}\n\n/** The gate for a door, with the plugin that keeps it, or `null` when none does here. */\nexport function pluginIntakeGate(\n door: string,\n): { pluginId: string; gate: PluginIntakeGate } | null {\n const key = door.trim()\n const entry = resolvePluginServices(PLUGIN_INTAKE_GATES).find((one) => one.key === key)\n return entry ? { pluginId: entry.pluginId, gate: entry.impl } : null\n}\n"],"names":["getRegisteringPluginId","definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_INTAKE_GATES","multiple","registerPluginIntakeGate","door","gate","options","key","trim","Error","pluginId","incumbent","find","entry","pluginIntakeGate","one","impl"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AAmD1B,OAAO,MAAMC,sBAAsBH,4BACjC,qBACA;IAAEI,UAAU;AAAK,GAClB;AAED;;;;CAIC,GACD,OAAO,SAASC,yBACdC,IAAY,EACZC,IAAsB,EACtBC,OAA+B;QAIbT,MAAAA;IAFlB,MAAMU,MAAMH,KAAKI,IAAI;IACrB,IAAI,CAACD,KAAK,MAAM,IAAIE,MAAM;IAC1B,MAAMC,WAAW,EAACb,QAAAA,0BAAAA,oCAAAA,0BAA4BS,2BAAAA,QAASI,QAAQ,YAA7Cb,OAAiD,IAAIW,IAAI;IAC3E,IAAI,CAACE,UAAU,MAAM,IAAID,MAAM,CAAC,qBAAqB,EAAEF,IAAI,8BAA8B,CAAC;IAC1F,MAAMI,YAAYX,sBAAsBC,qBAAqBW,IAAI,CAAC,CAACC,QAAUA,MAAMN,GAAG,KAAKA;IAC3F,IAAII,aAAaA,UAAUD,QAAQ,KAAKA,UAAU;QAChD,MAAM,IAAID,MACR,CAAC,MAAM,EAAEF,IAAI,uBAAuB,EAAEI,UAAUD,QAAQ,CAAC,YAAY,EAAEA,SAAS,CAAC,CAAC;IAEtF;IACAX,sBAAsBE,qBAAqBI,MAAM;QAAEK;QAAUH;IAAI;AACnE;AAEA,uFAAuF,GACvF,OAAO,SAASO,iBACdV,IAAY;IAEZ,MAAMG,MAAMH,KAAKI,IAAI;IACrB,MAAMK,QAAQb,sBAAsBC,qBAAqBW,IAAI,CAAC,CAACG,MAAQA,IAAIR,GAAG,KAAKA;IACnF,OAAOM,QAAQ;QAAEH,UAAUG,MAAMH,QAAQ;QAAEL,MAAMQ,MAAMG,IAAI;IAAC,IAAI;AAClE"}
|
|
@@ -33,11 +33,11 @@ export type PluginJobHostLockedResolver = (hostId: string) => Promise<boolean> |
|
|
|
33
33
|
* `apps/tenant/utils/plugin-job-lockdown.ts`, loaded for its side effect by
|
|
34
34
|
* the runner route beside the core job registrations.
|
|
35
35
|
*
|
|
36
|
-
* Idempotent and last-registration-wins, unlike
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
36
|
+
* Idempotent and last-registration-wins, unlike a registry that holds a
|
|
37
|
+
* capability with a plugin OWNER, where a second claimant is a
|
|
38
|
+
* misconfiguration. This is infrastructure the deployment supplies exactly
|
|
39
|
+
* once, and a process that loads the module twice must not throw on the
|
|
40
|
+
* second pass.
|
|
41
41
|
*/
|
|
42
42
|
export declare function registerPluginJobHostLockdown(resolver: PluginJobHostLockedResolver): void;
|
|
43
43
|
/** Has the deployment wired a verdict? Reported by the runner route. */
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
*
|
|
49
49
|
* `@aglyn/tenant-data-admin` imports `@aglyn/aglyn/server`, so a core import
|
|
50
50
|
* of it is a project CYCLE. Hence the same registry shape the platform uses
|
|
51
|
-
* for every other app↔core edge (`
|
|
52
|
-
* `
|
|
51
|
+
* for every other app↔core edge (`registerBillingWebhookHandler`,
|
|
52
|
+
* `registerSitePageResolver`): the host app
|
|
53
53
|
* registers the resolver from a place that may import the admin lib, and core
|
|
54
54
|
* looks it up. Nothing is imported statically in either direction and a
|
|
55
55
|
* self-host build with a different carrier registers its own.
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
* `apps/tenant/utils/plugin-job-lockdown.ts`, loaded for its side effect by
|
|
71
71
|
* the runner route beside the core job registrations.
|
|
72
72
|
*
|
|
73
|
-
* Idempotent and last-registration-wins, unlike
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
73
|
+
* Idempotent and last-registration-wins, unlike a registry that holds a
|
|
74
|
+
* capability with a plugin OWNER, where a second claimant is a
|
|
75
|
+
* misconfiguration. This is infrastructure the deployment supplies exactly
|
|
76
|
+
* once, and a process that loads the module twice must not throw on the
|
|
77
|
+
* second pass.
|
|
78
78
|
*/ export function registerPluginJobHostLockdown(resolver) {
|
|
79
79
|
hostLockedResolver = resolver;
|
|
80
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-jobs.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 scheduled jobs (AGL-435, Strapi cron parity): `/server` entries\n * register named jobs with an interval; a guarded platform runner route\n * (`/api/plugins/run-jobs`, secret header, invoked by the deployment's\n * scheduler) executes the due ones. The REGISTRY is pure — due-ness and\n * last-run persistence belong to the runner, so core stays storage-free.\n * Handlers must be idempotent and bounded (they share the API process).\n */\n\n/*==========================================\n * THE LOCKDOWN GATE FOR BACKGROUND WORK (AGL-2495, from the AGL-1621 drill).\n *\n * A job runs on PLATFORM credentials, from a secret-gated route, with no\n * visitor, no session and no org — so none of the gates that cover the\n * visitor-facing write paths are anywhere near it. That is exactly the shape\n * the drill found in `publish-schedule-job.ts`: a scheduled publish firing on\n * a locked host. The commerce and bookings beats are the sharper version of\n * the same thing, because they touch orders, stock and money for a host that\n * may be suspended for abuse, non-payment or a legal takedown.\n *\n * ## Why the gate lives HERE and not at the six call sites\n *\n * Six call-site edits close six holes and guarantee the seventh is forgotten.\n * Gating in the job contract means the seventh job is covered the day it is\n * written: `lockdown` below is REQUIRED, so a new `registerPluginJob({…})`\n * does not compile until its author has answered \"what does this touch\".\n *\n * (The nx boundary is NOT why. `libs/plugins/*` CAN import\n * `@aglyn/tenant-data-admin` — it carries `scope:data` and `scope:aglyn`,\n * both on the `aglyn:addons` allowlist, and 196 files in `libs/plugins`\n * already do. The AGL-2495 sweep recorded the opposite as its reason for\n * deferring; it was wrong about the constraint and right about the design.)\n *\n * ## Why core cannot just call `getSiteLockdown` itself\n *\n * `@aglyn/tenant-data-admin` imports `@aglyn/aglyn/server`, so a core import\n * of it is a project CYCLE. Hence the same registry shape the platform uses\n * for every other app↔core edge (`registerOrderFulfilmentService`,\n * `registerBillingWebhookHandler`, `registerSitePageResolver`): the host app\n * registers the resolver from a place that may import the admin lib, and core\n * looks it up. Nothing is imported statically in either direction and a\n * self-host build with a different carrier registers its own.\n *\n * ## Fail-open is INHERITED, not re-decided\n *\n * The gate asks whatever the app registered — on the tenant that is\n * `getSiteLockdown`, which owns the fail-open/fail-closed decision (and the\n * takedown-class ratchet on top of it). This module deliberately holds no\n * parallel notion of what a lock is: it has one question and no opinion.\n *=========================================*/\n\n/**\n * The one question a beat asks before touching a host: is a lock active that\n * forbids this write? Resolved by the host app, never by core.\n */\nexport type PluginJobHostLockedResolver = (\n hostId: string,\n) => Promise<boolean> | boolean\n\nlet hostLockedResolver: PluginJobHostLockedResolver | null = null\n\n/**\n * Registers the platform's per-host lockdown verdict for background work,\n * from a place that may import the admin data lib — on this platform,\n * `apps/tenant/utils/plugin-job-lockdown.ts`, loaded for its side effect by\n * the runner route beside the core job registrations.\n *\n * Idempotent and last-registration-wins, unlike `registerOrderFulfilmentService`\n * next door: that one holds a capability with a plugin OWNER, where a second\n * claimant is a misconfiguration. This is infrastructure the deployment\n * supplies exactly once, and a process that loads the module twice must not\n * throw on the second pass.\n */\nexport function registerPluginJobHostLockdown(\n resolver: PluginJobHostLockedResolver,\n): void {\n hostLockedResolver = resolver\n}\n\n/** Has the deployment wired a verdict? Reported by the runner route. */\nexport function hasPluginJobHostLockdown(): boolean {\n return hostLockedResolver !== null\n}\n\n/** Test seam: forget the registration. */\nexport function resetPluginJobHostLockdownForTests(): void {\n hostLockedResolver = null\n}\n\n/**\n * The gate handed to a `per-host` job. One method, so there is exactly one\n * thing to call and one thing for the coverage guard to look for:\n *\n * if (await gate.isLocked(hostId)) continue\n *\n * SKIPPED, NOT DROPPED is the caller's obligation and the reason this\n * answers a question rather than performing the skip: `continue` leaves the\n * row exactly as it was, so the work lands on the next beat after the lift.\n * A gate that swallowed the row — stamping it done, deleting it, advancing a\n * cursor past it — would turn a pause into a cancellation, which is what\n * `publish-schedule-job.ts` argued at length and what every job here copies.\n */\nexport interface PluginJobHostGate {\n isLocked(hostId: string): Promise<boolean>\n}\n\n/**\n * The gate as the runner builds it.\n *\n * The MANUAL doors — `commerce/process-abandoned`, `commerce/process-restock`,\n * `bookings/reminders` — do NOT use it (AGL-3356). They are served by the\n * console, which registers no resolver, so this answered \"not locked\" for\n * every site there and a forced pass mailed a suspended site's customers.\n * They take `siteLockdownJobGate()` from the admin lib instead: the same\n * function the tenant registers here, needing no registration at all.\n *\n * NO resolver registered answers \"not locked\", loudly and once. That is the\n * fail-open direction on purpose: a deployment that has not wired a carrier\n * (a self-host, a test process) must not have every background job welded\n * shut by infrastructure it never asked for. The loss is real and is why\n * `hasPluginJobHostLockdown` is reported by the runner route rather than\n * left to be inferred from nothing happening.\n */\nexport function pluginJobHostGate(): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n if (!hostId) return false\n if (!hostLockedResolver) {\n warnMissingResolver()\n return false\n }\n return (await hostLockedResolver(hostId)) === true\n },\n }\n}\n\nlet warnedMissingResolver = false\nfunction warnMissingResolver(): void {\n if (warnedMissingResolver) return\n warnedMissingResolver = true\n console.error(\n '[plugin-jobs] no host lockdown resolver is registered — background ' +\n 'jobs are running UNGATED. Register one with ' +\n 'registerPluginJobHostLockdown() from the app that owns the runner.',\n )\n}\n\n/** Test seam: let the \"warned once\" latch be re-armed. */\nexport function resetPluginJobLockdownWarningForTests(): void {\n warnedMissingResolver = false\n}\n\n/**\n * What a job touches, and therefore what the runner owes it. REQUIRED on\n * every registration — the point of the field is that a new job cannot\n * compile without its author answering.\n */\nexport type PluginJobLockdown =\n /**\n * The job acts FOR HOSTS. The runner injects a {@link PluginJobHostGate}\n * and the handler must ask it for every host it is about to touch.\n */\n | { scope: 'per-host' }\n /**\n * The job touches nothing a lock could be about. `reason` is the argument,\n * and `lockdown-tenant-api-coverage.spec.ts` holds the set by name so a new\n * one has to be argued rather than declared.\n */\n | { scope: 'platform'; reason: string }\n\nexport interface PluginJob {\n pluginId: string\n /** Stable job name, unique within the plugin ('expire-stale-holds'). */\n name: string\n intervalMinutes: number\n description?: string\n /** See {@link PluginJobLockdown}. Required, deliberately. */\n lockdown: PluginJobLockdown\n handler: (gate: PluginJobHostGate) => Promise<void> | void\n}\n\nconst jobs = new Map<string, PluginJob>()\n\nexport const pluginJobKey = (job: Pick<PluginJob, 'pluginId' | 'name'>) =>\n `${job.pluginId}:${job.name}`\n\n/** Idempotent per pluginId:name. */\nexport function registerPluginJob(job: PluginJob): void {\n jobs.set(pluginJobKey(job), job)\n}\n\nexport function listPluginJobs(): PluginJob[] {\n return [...jobs.values()]\n}\n\nexport interface PluginJobResult {\n key: string\n ok: boolean\n error?: string\n}\n\n/**\n * The gate a `platform`-scoped job gets: one that REFUSES to answer.\n *\n * A declaration of `platform` scope is a claim that there is no host to ask\n * about. If such a job asks anyway, the claim is false and the registration\n * is the thing that needs fixing — so the runner surfaces it as that job's\n * own failure (isolated, like every other job error) instead of quietly\n * answering \"not locked\" and letting a mislabelled job mutate a locked site.\n *\n * This is the one place the contract is enforced at RUNTIME rather than by\n * the type or by the guard, and it is enforceable precisely because asking\n * is the only way to get an answer.\n */\nfunction refusingGate(key: string): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n throw new Error(\n `plugin job ${key} declared lockdown scope 'platform' but asked ` +\n `about host \"${hostId}\". Declare { scope: 'per-host' } instead.`,\n )\n },\n }\n}\n\n/**\n * Runs the jobs `due` selects (default: all), sequentially with error\n * isolation — one broken job never blocks the rest.\n *\n * Every handler is handed a {@link PluginJobHostGate} (AGL-2495). It is a\n * parameter rather than something the handler imports so that the runner\n * stays the single place the verdict comes from: swap what\n * `registerPluginJobHostLockdown` holds and every job on the beat changes\n * behaviour together, including one written next month.\n */\nexport async function runPluginJobs(\n due?: (job: PluginJob) => boolean,\n): Promise<PluginJobResult[]> {\n const results: PluginJobResult[] = []\n for (const job of jobs.values()) {\n if (due && !due(job)) continue\n const key = pluginJobKey(job)\n try {\n await job.handler(\n job.lockdown?.scope === 'platform'\n ? refusingGate(key)\n : pluginJobHostGate(),\n )\n results.push({ key, ok: true })\n } catch (error) {\n console.error(`plugin job ${key} failed:`, error)\n results.push({ key, ok: false, error: String(error) })\n }\n }\n return results\n}\n"],"names":["hostLockedResolver","registerPluginJobHostLockdown","resolver","hasPluginJobHostLockdown","resetPluginJobHostLockdownForTests","pluginJobHostGate","isLocked","hostId","warnMissingResolver","warnedMissingResolver","console","error","resetPluginJobLockdownWarningForTests","jobs","Map","pluginJobKey","job","pluginId","name","registerPluginJob","set","listPluginJobs","values","refusingGate","key","Error","runPluginJobs","due","results","handler","lockdown","scope","push","ok","String"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAwC2C,GAE3C;;;CAGC,GAKD,IAAIA,qBAAyD;AAE7D;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,8BACdC,QAAqC;IAErCF,qBAAqBE;AACvB;AAEA,sEAAsE,GACtE,OAAO,SAASC;IACd,OAAOH,uBAAuB;AAChC;AAEA,wCAAwC,GACxC,OAAO,SAASI;IACdJ,qBAAqB;AACvB;AAmBA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASK;IACd,OAAO;QACLC,UAAU,OAAOC;YACf,IAAI,CAACA,QAAQ,OAAO;YACpB,IAAI,CAACP,oBAAoB;gBACvBQ;gBACA,OAAO;YACT;YACA,OAAO,AAAC,MAAMR,mBAAmBO,YAAa;QAChD;IACF;AACF;AAEA,IAAIE,wBAAwB;AAC5B,SAASD;IACP,IAAIC,uBAAuB;IAC3BA,wBAAwB;IACxBC,QAAQC,KAAK,CACX,wEACE,iDACA;AAEN;AAEA,wDAAwD,GACxD,OAAO,SAASC;IACdH,wBAAwB;AAC1B;AA+BA,MAAMI,OAAO,IAAIC;AAEjB,OAAO,MAAMC,eAAe,CAACC,MAC3B,GAAGA,IAAIC,QAAQ,CAAC,CAAC,EAAED,IAAIE,IAAI,EAAE,CAAA;AAE/B,kCAAkC,GAClC,OAAO,SAASC,kBAAkBH,GAAc;IAC9CH,KAAKO,GAAG,CAACL,aAAaC,MAAMA;AAC9B;AAEA,OAAO,SAASK;IACd,OAAO;WAAIR,KAAKS,MAAM;KAAG;AAC3B;AAQA;;;;;;;;;;;;CAYC,GACD,SAASC,aAAaC,GAAW;IAC/B,OAAO;QACLlB,UAAU,OAAOC;YACf,MAAM,IAAIkB,MACR,CAAC,WAAW,EAAED,IAAI,8CAA8C,CAAC,GAC/D,CAAC,YAAY,EAAEjB,OAAO,yCAAyC,CAAC;QAEtE;IACF;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAemB,cACpBC,GAAiC;IAEjC,MAAMC,UAA6B,EAAE;IACrC,KAAK,MAAMZ,OAAOH,KAAKS,MAAM,GAAI;QAC/B,IAAIK,OAAO,CAACA,IAAIX,MAAM;QACtB,MAAMQ,MAAMT,aAAaC;QACzB,IAAI;gBAEAA;YADF,MAAMA,IAAIa,OAAO,CACfb,EAAAA,gBAAAA,IAAIc,QAAQ,qBAAZd,cAAce,KAAK,MAAK,aACpBR,aAAaC,OACbnB;YAENuB,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;YAAK;QAC/B,EAAE,OAAOtB,OAAO;YACdD,QAAQC,KAAK,CAAC,CAAC,WAAW,EAAEa,IAAI,QAAQ,CAAC,EAAEb;YAC3CiB,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;gBAAOtB,OAAOuB,OAAOvB;YAAO;QACtD;IACF;IACA,OAAOiB;AACT"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-jobs.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 scheduled jobs (AGL-435, Strapi cron parity): `/server` entries\n * register named jobs with an interval; a guarded platform runner route\n * (`/api/plugins/run-jobs`, secret header, invoked by the deployment's\n * scheduler) executes the due ones. The REGISTRY is pure — due-ness and\n * last-run persistence belong to the runner, so core stays storage-free.\n * Handlers must be idempotent and bounded (they share the API process).\n */\n\n/*==========================================\n * THE LOCKDOWN GATE FOR BACKGROUND WORK (AGL-2495, from the AGL-1621 drill).\n *\n * A job runs on PLATFORM credentials, from a secret-gated route, with no\n * visitor, no session and no org — so none of the gates that cover the\n * visitor-facing write paths are anywhere near it. That is exactly the shape\n * the drill found in `publish-schedule-job.ts`: a scheduled publish firing on\n * a locked host. The commerce and bookings beats are the sharper version of\n * the same thing, because they touch orders, stock and money for a host that\n * may be suspended for abuse, non-payment or a legal takedown.\n *\n * ## Why the gate lives HERE and not at the six call sites\n *\n * Six call-site edits close six holes and guarantee the seventh is forgotten.\n * Gating in the job contract means the seventh job is covered the day it is\n * written: `lockdown` below is REQUIRED, so a new `registerPluginJob({…})`\n * does not compile until its author has answered \"what does this touch\".\n *\n * (The nx boundary is NOT why. `libs/plugins/*` CAN import\n * `@aglyn/tenant-data-admin` — it carries `scope:data` and `scope:aglyn`,\n * both on the `aglyn:addons` allowlist, and 196 files in `libs/plugins`\n * already do. The AGL-2495 sweep recorded the opposite as its reason for\n * deferring; it was wrong about the constraint and right about the design.)\n *\n * ## Why core cannot just call `getSiteLockdown` itself\n *\n * `@aglyn/tenant-data-admin` imports `@aglyn/aglyn/server`, so a core import\n * of it is a project CYCLE. Hence the same registry shape the platform uses\n * for every other app↔core edge (`registerBillingWebhookHandler`,\n * `registerSitePageResolver`): the host app\n * registers the resolver from a place that may import the admin lib, and core\n * looks it up. Nothing is imported statically in either direction and a\n * self-host build with a different carrier registers its own.\n *\n * ## Fail-open is INHERITED, not re-decided\n *\n * The gate asks whatever the app registered — on the tenant that is\n * `getSiteLockdown`, which owns the fail-open/fail-closed decision (and the\n * takedown-class ratchet on top of it). This module deliberately holds no\n * parallel notion of what a lock is: it has one question and no opinion.\n *=========================================*/\n\n/**\n * The one question a beat asks before touching a host: is a lock active that\n * forbids this write? Resolved by the host app, never by core.\n */\nexport type PluginJobHostLockedResolver = (\n hostId: string,\n) => Promise<boolean> | boolean\n\nlet hostLockedResolver: PluginJobHostLockedResolver | null = null\n\n/**\n * Registers the platform's per-host lockdown verdict for background work,\n * from a place that may import the admin data lib — on this platform,\n * `apps/tenant/utils/plugin-job-lockdown.ts`, loaded for its side effect by\n * the runner route beside the core job registrations.\n *\n * Idempotent and last-registration-wins, unlike a registry that holds a\n * capability with a plugin OWNER, where a second claimant is a\n * misconfiguration. This is infrastructure the deployment supplies exactly\n * once, and a process that loads the module twice must not throw on the\n * second pass.\n */\nexport function registerPluginJobHostLockdown(\n resolver: PluginJobHostLockedResolver,\n): void {\n hostLockedResolver = resolver\n}\n\n/** Has the deployment wired a verdict? Reported by the runner route. */\nexport function hasPluginJobHostLockdown(): boolean {\n return hostLockedResolver !== null\n}\n\n/** Test seam: forget the registration. */\nexport function resetPluginJobHostLockdownForTests(): void {\n hostLockedResolver = null\n}\n\n/**\n * The gate handed to a `per-host` job. One method, so there is exactly one\n * thing to call and one thing for the coverage guard to look for:\n *\n * if (await gate.isLocked(hostId)) continue\n *\n * SKIPPED, NOT DROPPED is the caller's obligation and the reason this\n * answers a question rather than performing the skip: `continue` leaves the\n * row exactly as it was, so the work lands on the next beat after the lift.\n * A gate that swallowed the row — stamping it done, deleting it, advancing a\n * cursor past it — would turn a pause into a cancellation, which is what\n * `publish-schedule-job.ts` argued at length and what every job here copies.\n */\nexport interface PluginJobHostGate {\n isLocked(hostId: string): Promise<boolean>\n}\n\n/**\n * The gate as the runner builds it.\n *\n * The MANUAL doors — `commerce/process-abandoned`, `commerce/process-restock`,\n * `bookings/reminders` — do NOT use it (AGL-3356). They are served by the\n * console, which registers no resolver, so this answered \"not locked\" for\n * every site there and a forced pass mailed a suspended site's customers.\n * They take `siteLockdownJobGate()` from the admin lib instead: the same\n * function the tenant registers here, needing no registration at all.\n *\n * NO resolver registered answers \"not locked\", loudly and once. That is the\n * fail-open direction on purpose: a deployment that has not wired a carrier\n * (a self-host, a test process) must not have every background job welded\n * shut by infrastructure it never asked for. The loss is real and is why\n * `hasPluginJobHostLockdown` is reported by the runner route rather than\n * left to be inferred from nothing happening.\n */\nexport function pluginJobHostGate(): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n if (!hostId) return false\n if (!hostLockedResolver) {\n warnMissingResolver()\n return false\n }\n return (await hostLockedResolver(hostId)) === true\n },\n }\n}\n\nlet warnedMissingResolver = false\nfunction warnMissingResolver(): void {\n if (warnedMissingResolver) return\n warnedMissingResolver = true\n console.error(\n '[plugin-jobs] no host lockdown resolver is registered — background ' +\n 'jobs are running UNGATED. Register one with ' +\n 'registerPluginJobHostLockdown() from the app that owns the runner.',\n )\n}\n\n/** Test seam: let the \"warned once\" latch be re-armed. */\nexport function resetPluginJobLockdownWarningForTests(): void {\n warnedMissingResolver = false\n}\n\n/**\n * What a job touches, and therefore what the runner owes it. REQUIRED on\n * every registration — the point of the field is that a new job cannot\n * compile without its author answering.\n */\nexport type PluginJobLockdown =\n /**\n * The job acts FOR HOSTS. The runner injects a {@link PluginJobHostGate}\n * and the handler must ask it for every host it is about to touch.\n */\n | { scope: 'per-host' }\n /**\n * The job touches nothing a lock could be about. `reason` is the argument,\n * and `lockdown-tenant-api-coverage.spec.ts` holds the set by name so a new\n * one has to be argued rather than declared.\n */\n | { scope: 'platform'; reason: string }\n\nexport interface PluginJob {\n pluginId: string\n /** Stable job name, unique within the plugin ('expire-stale-holds'). */\n name: string\n intervalMinutes: number\n description?: string\n /** See {@link PluginJobLockdown}. Required, deliberately. */\n lockdown: PluginJobLockdown\n handler: (gate: PluginJobHostGate) => Promise<void> | void\n}\n\nconst jobs = new Map<string, PluginJob>()\n\nexport const pluginJobKey = (job: Pick<PluginJob, 'pluginId' | 'name'>) =>\n `${job.pluginId}:${job.name}`\n\n/** Idempotent per pluginId:name. */\nexport function registerPluginJob(job: PluginJob): void {\n jobs.set(pluginJobKey(job), job)\n}\n\nexport function listPluginJobs(): PluginJob[] {\n return [...jobs.values()]\n}\n\nexport interface PluginJobResult {\n key: string\n ok: boolean\n error?: string\n}\n\n/**\n * The gate a `platform`-scoped job gets: one that REFUSES to answer.\n *\n * A declaration of `platform` scope is a claim that there is no host to ask\n * about. If such a job asks anyway, the claim is false and the registration\n * is the thing that needs fixing — so the runner surfaces it as that job's\n * own failure (isolated, like every other job error) instead of quietly\n * answering \"not locked\" and letting a mislabelled job mutate a locked site.\n *\n * This is the one place the contract is enforced at RUNTIME rather than by\n * the type or by the guard, and it is enforceable precisely because asking\n * is the only way to get an answer.\n */\nfunction refusingGate(key: string): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n throw new Error(\n `plugin job ${key} declared lockdown scope 'platform' but asked ` +\n `about host \"${hostId}\". Declare { scope: 'per-host' } instead.`,\n )\n },\n }\n}\n\n/**\n * Runs the jobs `due` selects (default: all), sequentially with error\n * isolation — one broken job never blocks the rest.\n *\n * Every handler is handed a {@link PluginJobHostGate} (AGL-2495). It is a\n * parameter rather than something the handler imports so that the runner\n * stays the single place the verdict comes from: swap what\n * `registerPluginJobHostLockdown` holds and every job on the beat changes\n * behaviour together, including one written next month.\n */\nexport async function runPluginJobs(\n due?: (job: PluginJob) => boolean,\n): Promise<PluginJobResult[]> {\n const results: PluginJobResult[] = []\n for (const job of jobs.values()) {\n if (due && !due(job)) continue\n const key = pluginJobKey(job)\n try {\n await job.handler(\n job.lockdown?.scope === 'platform'\n ? refusingGate(key)\n : pluginJobHostGate(),\n )\n results.push({ key, ok: true })\n } catch (error) {\n console.error(`plugin job ${key} failed:`, error)\n results.push({ key, ok: false, error: String(error) })\n }\n }\n return results\n}\n"],"names":["hostLockedResolver","registerPluginJobHostLockdown","resolver","hasPluginJobHostLockdown","resetPluginJobHostLockdownForTests","pluginJobHostGate","isLocked","hostId","warnMissingResolver","warnedMissingResolver","console","error","resetPluginJobLockdownWarningForTests","jobs","Map","pluginJobKey","job","pluginId","name","registerPluginJob","set","listPluginJobs","values","refusingGate","key","Error","runPluginJobs","due","results","handler","lockdown","scope","push","ok","String"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAwC2C,GAE3C;;;CAGC,GAKD,IAAIA,qBAAyD;AAE7D;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,8BACdC,QAAqC;IAErCF,qBAAqBE;AACvB;AAEA,sEAAsE,GACtE,OAAO,SAASC;IACd,OAAOH,uBAAuB;AAChC;AAEA,wCAAwC,GACxC,OAAO,SAASI;IACdJ,qBAAqB;AACvB;AAmBA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASK;IACd,OAAO;QACLC,UAAU,OAAOC;YACf,IAAI,CAACA,QAAQ,OAAO;YACpB,IAAI,CAACP,oBAAoB;gBACvBQ;gBACA,OAAO;YACT;YACA,OAAO,AAAC,MAAMR,mBAAmBO,YAAa;QAChD;IACF;AACF;AAEA,IAAIE,wBAAwB;AAC5B,SAASD;IACP,IAAIC,uBAAuB;IAC3BA,wBAAwB;IACxBC,QAAQC,KAAK,CACX,wEACE,iDACA;AAEN;AAEA,wDAAwD,GACxD,OAAO,SAASC;IACdH,wBAAwB;AAC1B;AA+BA,MAAMI,OAAO,IAAIC;AAEjB,OAAO,MAAMC,eAAe,CAACC,MAC3B,GAAGA,IAAIC,QAAQ,CAAC,CAAC,EAAED,IAAIE,IAAI,EAAE,CAAA;AAE/B,kCAAkC,GAClC,OAAO,SAASC,kBAAkBH,GAAc;IAC9CH,KAAKO,GAAG,CAACL,aAAaC,MAAMA;AAC9B;AAEA,OAAO,SAASK;IACd,OAAO;WAAIR,KAAKS,MAAM;KAAG;AAC3B;AAQA;;;;;;;;;;;;CAYC,GACD,SAASC,aAAaC,GAAW;IAC/B,OAAO;QACLlB,UAAU,OAAOC;YACf,MAAM,IAAIkB,MACR,CAAC,WAAW,EAAED,IAAI,8CAA8C,CAAC,GAC/D,CAAC,YAAY,EAAEjB,OAAO,yCAAyC,CAAC;QAEtE;IACF;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAemB,cACpBC,GAAiC;IAEjC,MAAMC,UAA6B,EAAE;IACrC,KAAK,MAAMZ,OAAOH,KAAKS,MAAM,GAAI;QAC/B,IAAIK,OAAO,CAACA,IAAIX,MAAM;QACtB,MAAMQ,MAAMT,aAAaC;QACzB,IAAI;gBAEAA;YADF,MAAMA,IAAIa,OAAO,CACfb,EAAAA,gBAAAA,IAAIc,QAAQ,qBAAZd,cAAce,KAAK,MAAK,aACpBR,aAAaC,OACbnB;YAENuB,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;YAAK;QAC/B,EAAE,OAAOtB,OAAO;YACdD,QAAQC,KAAK,CAAC,CAAC,WAAW,EAAEa,IAAI,QAAQ,CAAC,EAAEb;YAC3CiB,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;gBAAOtB,OAAOuB,OAAOvB;YAAO;QACtD;IACF;IACA,OAAOiB;AACT"}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
* The addresses a plugin serves FROM a site's screens, for the cache drops a
|
|
19
|
+
* publish makes (AGL-3475).
|
|
20
|
+
*
|
|
21
|
+
* A publish drops the cached pages it changed, and it finds them through the
|
|
22
|
+
* site's routing map: screen id → address. A page a plugin serves through a
|
|
23
|
+
* site page resolver is not in that map. A record template draws
|
|
24
|
+
* `/services/roofing`, `/services/siding` and every other record page, yet its
|
|
25
|
+
* own entry in the map is an address that serves nothing — so publishing the
|
|
26
|
+
* template dropped none of the pages it draws, and each kept its old design
|
|
27
|
+
* until the cache window lapsed.
|
|
28
|
+
*
|
|
29
|
+
* So the plugin answers: given screens being published, which addresses does
|
|
30
|
+
* it serve from them; given none, which addresses does it serve on the site at
|
|
31
|
+
* all (a whole-site publish). The publish routes add the answers to the paths
|
|
32
|
+
* they drop. The tenant's own route caps the paths it accepts, so an answer is
|
|
33
|
+
* bounded by the plugin, and a plugin that cannot answer costs the publish
|
|
34
|
+
* nothing: the host's data tag, which every publish busts, still refreshes the
|
|
35
|
+
* pages behind it on their next visit.
|
|
36
|
+
*
|
|
37
|
+
* Best effort, by design. A reader that throws is logged and skipped, because
|
|
38
|
+
* the publish it rides has already succeeded.
|
|
39
|
+
*
|
|
40
|
+
* Reached by its own subpath, never the barrel: only server routes ask.
|
|
41
|
+
*/
|
|
42
|
+
/** What a publish asks about. */
|
|
43
|
+
export interface PluginLivePathsRequest {
|
|
44
|
+
hostId: string;
|
|
45
|
+
/**
|
|
46
|
+
* The screens being published. Absent asks for every address the plugin
|
|
47
|
+
* serves on the site.
|
|
48
|
+
*/
|
|
49
|
+
screenIds?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/** Answers with site-absolute paths: `/services/roofing`. */
|
|
52
|
+
export type PluginLivePathsReader = (request: PluginLivePathsRequest) => Promise<string[]>;
|
|
53
|
+
/** The most paths one reader's answer contributes to one publish. */
|
|
54
|
+
export declare const PLUGIN_LIVE_PATHS_MAX = 200;
|
|
55
|
+
/**
|
|
56
|
+
* Registers a plugin's reader. The owner is the plugin whose register fn is
|
|
57
|
+
* running, or `options.pluginId`; with neither it throws. A plugin registering
|
|
58
|
+
* again replaces its own reader. Returns the unregister.
|
|
59
|
+
*/
|
|
60
|
+
export declare function registerPluginLivePaths(reader: PluginLivePathsReader, options?: {
|
|
61
|
+
pluginId?: string;
|
|
62
|
+
}): () => void;
|
|
63
|
+
/** Only for specs: forgets every registered reader. */
|
|
64
|
+
export declare function resetPluginLivePathsForTests(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Every address the registered plugins serve from the request's screens —
|
|
67
|
+
* site-absolute, without duplicates, at most {@link PLUGIN_LIVE_PATHS_MAX}
|
|
68
|
+
* from each plugin. Never throws.
|
|
69
|
+
*/
|
|
70
|
+
export declare function pluginLivePaths(request: PluginLivePathsRequest): Promise<string[]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var _globalScope_READERS_KEY;
|
|
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 { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
18
|
+
/** The most paths one reader's answer contributes to one publish. */ export const PLUGIN_LIVE_PATHS_MAX = 200;
|
|
19
|
+
/**
|
|
20
|
+
* One table per process, on `globalThis` (AGL-3412): readers register from a
|
|
21
|
+
* plugin's server declarations, which run in `instrumentation.ts`, compiled
|
|
22
|
+
* apart from the routes that read.
|
|
23
|
+
*/ const READERS_KEY = Symbol.for('@aglyn/aglyn:plugin-live-paths');
|
|
24
|
+
const globalScope = globalThis;
|
|
25
|
+
const readers = (_globalScope_READERS_KEY = globalScope[READERS_KEY]) != null ? _globalScope_READERS_KEY : globalScope[READERS_KEY] = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* Registers a plugin's reader. The owner is the plugin whose register fn is
|
|
28
|
+
* running, or `options.pluginId`; with neither it throws. A plugin registering
|
|
29
|
+
* again replaces its own reader. Returns the unregister.
|
|
30
|
+
*/ export function registerPluginLivePaths(reader, options) {
|
|
31
|
+
var _ref, _getRegisteringPluginId;
|
|
32
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
33
|
+
if (!pluginId) {
|
|
34
|
+
throw new Error('a live paths reader registered with no owner: pass { pluginId } when ' + 'registering outside a plugin register fn');
|
|
35
|
+
}
|
|
36
|
+
readers.set(pluginId, reader);
|
|
37
|
+
return ()=>{
|
|
38
|
+
if (readers.get(pluginId) === reader) readers.delete(pluginId);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Only for specs: forgets every registered reader. */ export function resetPluginLivePathsForTests() {
|
|
42
|
+
readers.clear();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every address the registered plugins serve from the request's screens —
|
|
46
|
+
* site-absolute, without duplicates, at most {@link PLUGIN_LIVE_PATHS_MAX}
|
|
47
|
+
* from each plugin. Never throws.
|
|
48
|
+
*/ export async function pluginLivePaths(request) {
|
|
49
|
+
var _request_screenIds;
|
|
50
|
+
if (!request.hostId || ((_request_screenIds = request.screenIds) == null ? void 0 : _request_screenIds.length) === 0) return [];
|
|
51
|
+
const answers = await Promise.all([
|
|
52
|
+
...readers.entries()
|
|
53
|
+
].map(async ([pluginId, reader])=>{
|
|
54
|
+
try {
|
|
55
|
+
return (await reader(request)).filter((path)=>typeof path === 'string' && path.startsWith('/')).slice(0, PLUGIN_LIVE_PATHS_MAX);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('[plugin-live-paths] a reader failed', pluginId, error);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
return [
|
|
62
|
+
...new Set(answers.flat())
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=plugin-live-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-live-paths.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'\n\n/**\n * The addresses a plugin serves FROM a site's screens, for the cache drops a\n * publish makes (AGL-3475).\n *\n * A publish drops the cached pages it changed, and it finds them through the\n * site's routing map: screen id → address. A page a plugin serves through a\n * site page resolver is not in that map. A record template draws\n * `/services/roofing`, `/services/siding` and every other record page, yet its\n * own entry in the map is an address that serves nothing — so publishing the\n * template dropped none of the pages it draws, and each kept its old design\n * until the cache window lapsed.\n *\n * So the plugin answers: given screens being published, which addresses does\n * it serve from them; given none, which addresses does it serve on the site at\n * all (a whole-site publish). The publish routes add the answers to the paths\n * they drop. The tenant's own route caps the paths it accepts, so an answer is\n * bounded by the plugin, and a plugin that cannot answer costs the publish\n * nothing: the host's data tag, which every publish busts, still refreshes the\n * pages behind it on their next visit.\n *\n * Best effort, by design. A reader that throws is logged and skipped, because\n * the publish it rides has already succeeded.\n *\n * Reached by its own subpath, never the barrel: only server routes ask.\n */\n\n/** What a publish asks about. */\nexport interface PluginLivePathsRequest {\n hostId: string\n /**\n * The screens being published. Absent asks for every address the plugin\n * serves on the site.\n */\n screenIds?: readonly string[]\n}\n\n/** Answers with site-absolute paths: `/services/roofing`. */\nexport type PluginLivePathsReader = (request: PluginLivePathsRequest) => Promise<string[]>\n\n/** The most paths one reader's answer contributes to one publish. */\nexport const PLUGIN_LIVE_PATHS_MAX = 200\n\n/**\n * One table per process, on `globalThis` (AGL-3412): readers register from a\n * plugin's server declarations, which run in `instrumentation.ts`, compiled\n * apart from the routes that read.\n */\nconst READERS_KEY = Symbol.for('@aglyn/aglyn:plugin-live-paths')\n\nconst globalScope = globalThis as typeof globalThis & {\n [READERS_KEY]?: Map<string, PluginLivePathsReader>\n}\n\nconst readers: Map<string, PluginLivePathsReader> =\n globalScope[READERS_KEY] ?? (globalScope[READERS_KEY] = new Map())\n\n/**\n * Registers a plugin's reader. The owner is the plugin whose register fn is\n * running, or `options.pluginId`; with neither it throws. A plugin registering\n * again replaces its own reader. Returns the unregister.\n */\nexport function registerPluginLivePaths(\n reader: PluginLivePathsReader,\n options?: { pluginId?: string },\n): () => void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a live paths reader registered with no owner: pass { pluginId } when ' +\n 'registering outside a plugin register fn',\n )\n }\n readers.set(pluginId, reader)\n return () => {\n if (readers.get(pluginId) === reader) readers.delete(pluginId)\n }\n}\n\n/** Only for specs: forgets every registered reader. */\nexport function resetPluginLivePathsForTests(): void {\n readers.clear()\n}\n\n/**\n * Every address the registered plugins serve from the request's screens —\n * site-absolute, without duplicates, at most {@link PLUGIN_LIVE_PATHS_MAX}\n * from each plugin. Never throws.\n */\nexport async function pluginLivePaths(request: PluginLivePathsRequest): Promise<string[]> {\n if (!request.hostId || request.screenIds?.length === 0) return []\n const answers = await Promise.all(\n [...readers.entries()].map(async ([pluginId, reader]) => {\n try {\n return (await reader(request))\n .filter((path) => typeof path === 'string' && path.startsWith('/'))\n .slice(0, PLUGIN_LIVE_PATHS_MAX)\n } catch (error) {\n console.error('[plugin-live-paths] a reader failed', pluginId, error)\n return []\n }\n }),\n )\n return [...new Set(answers.flat())]\n}\n"],"names":["globalScope","getRegisteringPluginId","PLUGIN_LIVE_PATHS_MAX","READERS_KEY","Symbol","for","globalThis","readers","Map","registerPluginLivePaths","reader","options","pluginId","trim","Error","set","get","delete","resetPluginLivePathsForTests","clear","pluginLivePaths","request","hostId","screenIds","length","answers","Promise","all","entries","map","filter","path","startsWith","slice","error","console","Set","flat"],"mappings":"IAyEEA;AAzEF;;;;;;;;;;;;;;;CAeC,GAED,SAASC,sBAAsB,QAAQ,qCAAiC;AAyCxE,mEAAmE,GACnE,OAAO,MAAMC,wBAAwB,IAAG;AAExC;;;;CAIC,GACD,MAAMC,cAAcC,OAAOC,GAAG,CAAC;AAE/B,MAAML,cAAcM;AAIpB,MAAMC,WACJP,2BAAAA,WAAW,CAACG,YAAY,YAAxBH,2BAA6BA,WAAW,CAACG,YAAY,GAAG,IAAIK;AAE9D;;;;CAIC,GACD,OAAO,SAASC,wBACdC,MAA6B,EAC7BC,OAA+B;QAEbV,MAAAA;IAAlB,MAAMW,WAAW,EAACX,QAAAA,0BAAAA,oCAAAA,0BAA4BU,2BAAAA,QAASC,QAAQ,YAA7CX,OAAiD,IAAIY,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,0EACE;IAEN;IACAP,QAAQQ,GAAG,CAACH,UAAUF;IACtB,OAAO;QACL,IAAIH,QAAQS,GAAG,CAACJ,cAAcF,QAAQH,QAAQU,MAAM,CAACL;IACvD;AACF;AAEA,qDAAqD,GACrD,OAAO,SAASM;IACdX,QAAQY,KAAK;AACf;AAEA;;;;CAIC,GACD,OAAO,eAAeC,gBAAgBC,OAA+B;QAC5CA;IAAvB,IAAI,CAACA,QAAQC,MAAM,IAAID,EAAAA,qBAAAA,QAAQE,SAAS,qBAAjBF,mBAAmBG,MAAM,MAAK,GAAG,OAAO,EAAE;IACjE,MAAMC,UAAU,MAAMC,QAAQC,GAAG,CAC/B;WAAIpB,QAAQqB,OAAO;KAAG,CAACC,GAAG,CAAC,OAAO,CAACjB,UAAUF,OAAO;QAClD,IAAI;YACF,OAAO,AAAC,CAAA,MAAMA,OAAOW,QAAO,EACzBS,MAAM,CAAC,CAACC,OAAS,OAAOA,SAAS,YAAYA,KAAKC,UAAU,CAAC,MAC7DC,KAAK,CAAC,GAAG/B;QACd,EAAE,OAAOgC,OAAO;YACdC,QAAQD,KAAK,CAAC,uCAAuCtB,UAAUsB;YAC/D,OAAO,EAAE;QACX;IACF;IAEF,OAAO;WAAI,IAAIE,IAAIX,QAAQY,IAAI;KAAI;AACrC"}
|
|
@@ -61,8 +61,9 @@ export declare function registerPluginOrgEraser(eraser: PluginOrgEraser, options
|
|
|
61
61
|
*
|
|
62
62
|
* Throws in exactly two cases, both about a REQUIRED eraser (`required`,
|
|
63
63
|
* the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its
|
|
64
|
-
* own): before running anything, when one is not registered
|
|
65
|
-
* running every eraser, when
|
|
64
|
+
* own): before running anything, when one is not registered even after the
|
|
65
|
+
* app's boot step has run once more; and after running every eraser, when
|
|
66
|
+
* one threw.
|
|
66
67
|
*/
|
|
67
68
|
export declare function runPluginOrgErasers(request: PluginOrgErasureRequest, required?: readonly string[]): Promise<Record<string, PluginOrgErasureReport | null>>;
|
|
68
69
|
/** The plugins with a workspace eraser, in the order they run. */
|