@aglyn/aglyn 1.0.0-beta.221 → 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 +22 -22
- package/src/lib/app-utils/docs-help.generated.js +44 -10
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +738 -150
- 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 +11 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- 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/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-route.d.ts +154 -36
- package/src/lib/app-utils/screen-route.js +185 -40
- 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 +50 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
- 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-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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
var _globalScope_ERASERS_KEY;
|
|
1
2
|
/**
|
|
2
3
|
* @license
|
|
3
4
|
* Copyright 2026 Aglyn LLC
|
|
@@ -71,9 +72,17 @@
|
|
|
71
72
|
* the plugin loaded — a deletion that must not depend on which bundles an
|
|
72
73
|
* erasing process happened to load, so it is data rather than an eraser.
|
|
73
74
|
*/ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
75
|
+
import { runPluginDeclarationsRepair } from "./plugin-declarations-repair.js";
|
|
74
76
|
import { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS } from "./first-party-plugins.generated.js";
|
|
75
77
|
export { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS };
|
|
76
|
-
|
|
78
|
+
/**
|
|
79
|
+
* One list per process, on `globalThis` (AGL-3464): the app registers its
|
|
80
|
+
* plugins' erasers from `instrumentation.ts`, which Next compiles apart from
|
|
81
|
+
* the route that runs the erasure, and a module-scoped list is filled in one
|
|
82
|
+
* copy and read empty in the other — the AGL-3412 shape.
|
|
83
|
+
*/ const ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-org-erasers');
|
|
84
|
+
const globalScope = globalThis;
|
|
85
|
+
const registrations = (_globalScope_ERASERS_KEY = globalScope[ERASERS_KEY]) != null ? _globalScope_ERASERS_KEY : globalScope[ERASERS_KEY] = [];
|
|
77
86
|
/**
|
|
78
87
|
* Registers a plugin's workspace eraser. Owner = the loader's marker inside a
|
|
79
88
|
* register fn, else `options.pluginId`; an eraser with neither throws. One
|
|
@@ -102,11 +111,11 @@ const registrations = [];
|
|
|
102
111
|
*
|
|
103
112
|
* Throws in exactly two cases, both about a REQUIRED eraser (`required`,
|
|
104
113
|
* the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its
|
|
105
|
-
* own): before running anything, when one is not registered
|
|
106
|
-
* running every eraser, when
|
|
114
|
+
* own): before running anything, when one is not registered even after the
|
|
115
|
+
* app's boot step has run once more; and after running every eraser, when
|
|
116
|
+
* one threw.
|
|
107
117
|
*/ export async function runPluginOrgErasers(request, required = PLUGIN_REQUIRED_ORG_ERASERS) {
|
|
108
|
-
const
|
|
109
|
-
const missing = required.filter((pluginId)=>!registered.has(pluginId));
|
|
118
|
+
const missing = await missingAfterRepair(required);
|
|
110
119
|
if (missing.length) {
|
|
111
120
|
throw new Error(`[plugins] erasing org ${request.orgId} refused: ${missing.join(', ')} ` + 'declared a required org eraser and none is registered in this process');
|
|
112
121
|
}
|
|
@@ -131,6 +140,22 @@ const registrations = [];
|
|
|
131
140
|
}
|
|
132
141
|
return reports;
|
|
133
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The required erasers not registered in this process, after running the
|
|
145
|
+
* app's boot step once when any is missing (AGL-3464): a boot whose
|
|
146
|
+
* declarations failed looks the same from here as one that never declared
|
|
147
|
+
* the eraser, and only the second is a reason to refuse.
|
|
148
|
+
*/ async function missingAfterRepair(required) {
|
|
149
|
+
const missing = ()=>{
|
|
150
|
+
const registered = new Set(registrations.map((entry)=>entry.pluginId));
|
|
151
|
+
return required.filter((pluginId)=>!registered.has(pluginId));
|
|
152
|
+
};
|
|
153
|
+
if (!missing().length) return [];
|
|
154
|
+
await runPluginDeclarationsRepair().catch((error)=>{
|
|
155
|
+
console.error('[plugins] the declarations repair failed before an org erasure', error);
|
|
156
|
+
});
|
|
157
|
+
return missing();
|
|
158
|
+
}
|
|
134
159
|
/** The plugins with a workspace eraser, in the order they run. */ export function listPluginOrgErasers() {
|
|
135
160
|
return registrations.map((entry)=>entry.pluginId);
|
|
136
161
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-org-erasure.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A plugin's share of a workspace erasure (AGL-2978).\n *\n * The workspace erasure destroys what the platform holds for an\n * organization: its document tree, its sites, and the top-level records\n * keyed to it by a field. A plugin that holds something those deletes cannot\n * finish on their own registers an eraser here — a grant at a provider that\n * only the plugin can revoke, or a record outside every path and field the\n * erasure sweeps — and the erasure runs every eraser with the organization's\n * id.\n *\n * Erasers run BEFORE the erasure deletes anything of its own. The\n * organization, its roster and every record the erasure is about to sweep\n * still exist when an eraser runs, so an eraser that has to act through a\n * record — open a stored grant to revoke it — finds the record there. What\n * the erasure deletes on its own stays its to delete: an eraser adds to the\n * erasure, and the erasure does not wait on one to finish its own sweeps.\n *\n * Erasers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one or\n * the erasure. An erasure runs against a legal clock, and one plugin's\n * outage must not keep every other plugin's data, or the workspace, past it.\n * The `null` is how the erasure's audit record says that plugin's share may\n * not be done, which a report of zero would hide.\n *\n * A DRY RUN is handed to every eraser, which must then touch no provider and\n * write nothing: it counts what it would do. A plan that reached a vendor\n * would be an erasure that happened without anybody deciding it should.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for an eraser that opens a credential only the\n * console holds — so the eraser is in place in a server process that never\n * loaded the plugin's API surface.\n *\n * ## A REQUIRED eraser (AGL-3080)\n *\n * Isolation is right for a share whose failure leaves the workspace's data\n * gone and only a provider-side courtesy undone. It is wrong for a share the\n * erasure PROMISES: a public record naming the erased organization, a payout\n * identifier on a world-readable document. A plugin holding such a record\n * declares `\"requiredOrgEraser\": true` in `plugins.config.json`, compiled into\n * {@link PLUGIN_REQUIRED_ORG_ERASERS} so no registration can opt out of it, and:\n *\n * - an erasure REFUSES to start while a required eraser is not registered —\n * a boot that failed to declare it must not produce a \"complete\" erasure\n * with the record still standing;\n * - a required eraser that throws still lets every other eraser run, and then\n * fails the erasure's `plugins` step, which the cron retries.\n *\n * ## Plugin collections keyed by the organization (AGL-3080)\n *\n * A plugin's TOP-LEVEL collection whose documents carry the organization's id\n * in a field is invisible to the erasure's path deletes. The plugin declares\n * it (`orgKeyedCollections`, compiled into {@link PLUGIN_ORG_KEYED_COLLECTIONS})\n * and the erasure sweeps it by that field in every process, with or without\n * the plugin loaded — a deletion that must not depend on which bundles an\n * erasing process happened to load, so it is data rather than an eraser.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport {\n PLUGIN_ORG_KEYED_COLLECTIONS,\n PLUGIN_REQUIRED_ORG_ERASERS,\n} from './first-party-plugins.generated'\n\nexport { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS }\n\n/**\n * A plugin's top-level collection whose documents name an organization in a\n * field, declared by that plugin. See the module note.\n */\nexport interface PluginOrgKeyedCollection {\n /** The plugin that declared it. */\n pluginId: string\n /** The top-level collection. */\n name: string\n /** The field naming the organization a document belongs to. */\n orgField: string\n}\n\n/** What an eraser is asked to erase. */\nexport interface PluginOrgErasureRequest {\n /** The workspace being erased. */\n orgId: string\n /**\n * A plan, not an erasure: count, touch no provider, write nothing. The\n * erasure's own sweeps honor the same flag.\n */\n dryRun: boolean\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content, because the record outlives the data by design.\n * A `null` field is a figure the eraser could not measure, which is not zero.\n */\nexport type PluginOrgErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginOrgEraser = (\n request: PluginOrgErasureRequest,\n) => Promise<PluginOrgErasureReport>\n\ninterface Registration {\n pluginId: string\n eraser: PluginOrgEraser\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's workspace eraser. Owner = the loader's marker inside a\n * register fn, else `options.pluginId`; an eraser with neither throws. One\n * eraser per plugin: registering again replaces the plugin's earlier eraser\n * in place, so a module evaluated twice does not erase twice.\n */\nexport function registerPluginOrgEraser(\n eraser: PluginOrgEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin org eraser was registered with no owner: pass { pluginId } ' +\n 'when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, eraser }\n else registrations.push({ pluginId, eraser })\n}\n\n/**\n * Runs every eraser, in registration order, and answers each plugin's\n * report by plugin id: the eraser's own report, or `null` for one that\n * threw.\n *\n * Throws in exactly two cases, both about a REQUIRED eraser (`required`,\n * the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its\n * own): before running anything, when one is not registered; and after\n * running every eraser, when one threw.\n */\nexport async function runPluginOrgErasers(\n request: PluginOrgErasureRequest,\n required: readonly string[] = PLUGIN_REQUIRED_ORG_ERASERS,\n): Promise<Record<string, PluginOrgErasureReport | null>> {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n const missing = required.filter((pluginId) => !registered.has(pluginId))\n if (missing.length) {\n throw new Error(\n `[plugins] erasing org ${request.orgId} refused: ${missing.join(', ')} ` +\n 'declared a required org eraser and none is registered in this process',\n )\n }\n const reports: Record<string, PluginOrgErasureReport | null> = {}\n const failedRequired: string[] = []\n for (const { pluginId, eraser } of [...registrations]) {\n try {\n reports[pluginId] = await eraser({ orgId: request.orgId, dryRun: request.dryRun })\n } catch (error) {\n reports[pluginId] = null\n console.error(`[plugins] ${pluginId} failed to erase org ${request.orgId}`, error)\n if (required.includes(pluginId)) failedRequired.push(pluginId)\n }\n }\n if (failedRequired.length) {\n throw new Error(\n `[plugins] erasing org ${request.orgId} incomplete: the required eraser of ` +\n `${failedRequired.join(', ')} failed`,\n )\n }\n return reports\n}\n\n/** The plugins with a workspace eraser, in the order they run. */\nexport function listPluginOrgErasers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginOrgErasersForTests(): void {\n registrations.length = 0\n}\n\n/**\n * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a\n * spec about the erasure's own sweeps can run it without loading a plugin\n * (core and the data layer may not import one). A spec about a required\n * plugin's share registers the real one through the app's manifest instead.\n */\nexport function standInRequiredOrgErasersForTests(): void {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n for (const pluginId of PLUGIN_REQUIRED_ORG_ERASERS) {\n if (!registered.has(pluginId)) {\n registrations.push({ pluginId, eraser: async () => ({ standIn: true }) })\n }\n }\n}\n"],"names":["getRegisteringPluginId","PLUGIN_ORG_KEYED_COLLECTIONS","PLUGIN_REQUIRED_ORG_ERASERS","registrations","registerPluginOrgEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginOrgErasers","request","required","registered","Set","map","missing","filter","has","length","orgId","join","reports","failedRequired","dryRun","error","console","includes","listPluginOrgErasers","resetPluginOrgErasersForTests","standInRequiredOrgErasersForTests","standIn"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AACxE,SACEC,4BAA4B,EAC5BC,2BAA2B,QACtB,qCAAiC;AAExC,SAASD,4BAA4B,EAAEC,2BAA2B,GAAE;AA0CpE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,wBACdC,MAAuB,EACvBC,OAA+B;QAEbN,MAAAA;IAAlB,MAAMO,WAAW,EAACP,QAAAA,0BAAAA,oCAAAA,0BAA4BM,2BAAAA,QAASC,QAAQ,YAA7CP,OAAiD,IAAIQ,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,yEACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAO;SACrDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAO;AAC7C;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAeS,oBACpBC,OAAgC,EAChCC,WAA8Bd,2BAA2B;IAEzD,MAAMe,aAAa,IAAIC,IAAIf,cAAcgB,GAAG,CAAC,CAACP,QAAUA,MAAML,QAAQ;IACtE,MAAMa,UAAUJ,SAASK,MAAM,CAAC,CAACd,WAAa,CAACU,WAAWK,GAAG,CAACf;IAC9D,IAAIa,QAAQG,MAAM,EAAE;QAClB,MAAM,IAAId,MACR,CAAC,sBAAsB,EAAEM,QAAQS,KAAK,CAAC,UAAU,EAAEJ,QAAQK,IAAI,CAAC,MAAM,CAAC,CAAC,GACtE;IAEN;IACA,MAAMC,UAAyD,CAAC;IAChE,MAAMC,iBAA2B,EAAE;IACnC,KAAK,MAAM,EAAEpB,QAAQ,EAAEF,MAAM,EAAE,IAAI;WAAIF;KAAc,CAAE;QACrD,IAAI;YACFuB,OAAO,CAACnB,SAAS,GAAG,MAAMF,OAAO;gBAAEmB,OAAOT,QAAQS,KAAK;gBAAEI,QAAQb,QAAQa,MAAM;YAAC;QAClF,EAAE,OAAOC,OAAO;YACdH,OAAO,CAACnB,SAAS,GAAG;YACpBuB,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEtB,SAAS,qBAAqB,EAAEQ,QAAQS,KAAK,EAAE,EAAEK;YAC5E,IAAIb,SAASe,QAAQ,CAACxB,WAAWoB,eAAed,IAAI,CAACN;QACvD;IACF;IACA,IAAIoB,eAAeJ,MAAM,EAAE;QACzB,MAAM,IAAId,MACR,CAAC,sBAAsB,EAAEM,QAAQS,KAAK,CAAC,oCAAoC,CAAC,GAC1E,GAAGG,eAAeF,IAAI,CAAC,MAAM,OAAO,CAAC;IAE3C;IACA,OAAOC;AACT;AAEA,gEAAgE,GAChE,OAAO,SAASM;IACd,OAAO7B,cAAcgB,GAAG,CAAC,CAACP,QAAUA,MAAML,QAAQ;AACpD;AAEA,oCAAoC,GACpC,OAAO,SAAS0B;IACd9B,cAAcoB,MAAM,GAAG;AACzB;AAEA;;;;;CAKC,GACD,OAAO,SAASW;IACd,MAAMjB,aAAa,IAAIC,IAAIf,cAAcgB,GAAG,CAAC,CAACP,QAAUA,MAAML,QAAQ;IACtE,KAAK,MAAMA,YAAYL,4BAA6B;QAClD,IAAI,CAACe,WAAWK,GAAG,CAACf,WAAW;YAC7BJ,cAAcU,IAAI,CAAC;gBAAEN;gBAAUF,QAAQ,UAAa,CAAA;wBAAE8B,SAAS;oBAAK,CAAA;YAAG;QACzE;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-org-erasure.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A plugin's share of a workspace erasure (AGL-2978).\n *\n * The workspace erasure destroys what the platform holds for an\n * organization: its document tree, its sites, and the top-level records\n * keyed to it by a field. A plugin that holds something those deletes cannot\n * finish on their own registers an eraser here — a grant at a provider that\n * only the plugin can revoke, or a record outside every path and field the\n * erasure sweeps — and the erasure runs every eraser with the organization's\n * id.\n *\n * Erasers run BEFORE the erasure deletes anything of its own. The\n * organization, its roster and every record the erasure is about to sweep\n * still exist when an eraser runs, so an eraser that has to act through a\n * record — open a stored grant to revoke it — finds the record there. What\n * the erasure deletes on its own stays its to delete: an eraser adds to the\n * erasure, and the erasure does not wait on one to finish its own sweeps.\n *\n * Erasers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one or\n * the erasure. An erasure runs against a legal clock, and one plugin's\n * outage must not keep every other plugin's data, or the workspace, past it.\n * The `null` is how the erasure's audit record says that plugin's share may\n * not be done, which a report of zero would hide.\n *\n * A DRY RUN is handed to every eraser, which must then touch no provider and\n * write nothing: it counts what it would do. A plan that reached a vendor\n * would be an erasure that happened without anybody deciding it should.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for an eraser that opens a credential only the\n * console holds — so the eraser is in place in a server process that never\n * loaded the plugin's API surface.\n *\n * ## A REQUIRED eraser (AGL-3080)\n *\n * Isolation is right for a share whose failure leaves the workspace's data\n * gone and only a provider-side courtesy undone. It is wrong for a share the\n * erasure PROMISES: a public record naming the erased organization, a payout\n * identifier on a world-readable document. A plugin holding such a record\n * declares `\"requiredOrgEraser\": true` in `plugins.config.json`, compiled into\n * {@link PLUGIN_REQUIRED_ORG_ERASERS} so no registration can opt out of it, and:\n *\n * - an erasure REFUSES to start while a required eraser is not registered —\n * a boot that failed to declare it must not produce a \"complete\" erasure\n * with the record still standing;\n * - a required eraser that throws still lets every other eraser run, and then\n * fails the erasure's `plugins` step, which the cron retries.\n *\n * ## Plugin collections keyed by the organization (AGL-3080)\n *\n * A plugin's TOP-LEVEL collection whose documents carry the organization's id\n * in a field is invisible to the erasure's path deletes. The plugin declares\n * it (`orgKeyedCollections`, compiled into {@link PLUGIN_ORG_KEYED_COLLECTIONS})\n * and the erasure sweeps it by that field in every process, with or without\n * the plugin loaded — a deletion that must not depend on which bundles an\n * erasing process happened to load, so it is data rather than an eraser.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport { runPluginDeclarationsRepair } from './plugin-declarations-repair'\nimport {\n PLUGIN_ORG_KEYED_COLLECTIONS,\n PLUGIN_REQUIRED_ORG_ERASERS,\n} from './first-party-plugins.generated'\n\nexport { PLUGIN_ORG_KEYED_COLLECTIONS, PLUGIN_REQUIRED_ORG_ERASERS }\n\n/**\n * A plugin's top-level collection whose documents name an organization in a\n * field, declared by that plugin. See the module note.\n */\nexport interface PluginOrgKeyedCollection {\n /** The plugin that declared it. */\n pluginId: string\n /** The top-level collection. */\n name: string\n /** The field naming the organization a document belongs to. */\n orgField: string\n}\n\n/** What an eraser is asked to erase. */\nexport interface PluginOrgErasureRequest {\n /** The workspace being erased. */\n orgId: string\n /**\n * A plan, not an erasure: count, touch no provider, write nothing. The\n * erasure's own sweeps honor the same flag.\n */\n dryRun: boolean\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content, because the record outlives the data by design.\n * A `null` field is a figure the eraser could not measure, which is not zero.\n */\nexport type PluginOrgErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginOrgEraser = (\n request: PluginOrgErasureRequest,\n) => Promise<PluginOrgErasureReport>\n\ninterface Registration {\n pluginId: string\n eraser: PluginOrgEraser\n}\n\n/**\n * One list per process, on `globalThis` (AGL-3464): the app registers its\n * plugins' erasers from `instrumentation.ts`, which Next compiles apart from\n * the route that runs the erasure, and a module-scoped list is filled in one\n * copy and read empty in the other — the AGL-3412 shape.\n */\nconst ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-org-erasers')\n\nconst globalScope = globalThis as typeof globalThis & {\n [ERASERS_KEY]?: Registration[]\n}\n\nconst registrations: Registration[] =\n globalScope[ERASERS_KEY] ?? (globalScope[ERASERS_KEY] = [])\n\n/**\n * Registers a plugin's workspace eraser. Owner = the loader's marker inside a\n * register fn, else `options.pluginId`; an eraser with neither throws. One\n * eraser per plugin: registering again replaces the plugin's earlier eraser\n * in place, so a module evaluated twice does not erase twice.\n */\nexport function registerPluginOrgEraser(\n eraser: PluginOrgEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin org eraser was registered with no owner: pass { pluginId } ' +\n 'when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, eraser }\n else registrations.push({ pluginId, eraser })\n}\n\n/**\n * Runs every eraser, in registration order, and answers each plugin's\n * report by plugin id: the eraser's own report, or `null` for one that\n * threw.\n *\n * Throws in exactly two cases, both about a REQUIRED eraser (`required`,\n * the compiled {@link PLUGIN_REQUIRED_ORG_ERASERS} unless a spec passes its\n * own): before running anything, when one is not registered even after the\n * app's boot step has run once more; and after running every eraser, when\n * one threw.\n */\nexport async function runPluginOrgErasers(\n request: PluginOrgErasureRequest,\n required: readonly string[] = PLUGIN_REQUIRED_ORG_ERASERS,\n): Promise<Record<string, PluginOrgErasureReport | null>> {\n const missing = await missingAfterRepair(required)\n if (missing.length) {\n throw new Error(\n `[plugins] erasing org ${request.orgId} refused: ${missing.join(', ')} ` +\n 'declared a required org eraser and none is registered in this process',\n )\n }\n const reports: Record<string, PluginOrgErasureReport | null> = {}\n const failedRequired: string[] = []\n for (const { pluginId, eraser } of [...registrations]) {\n try {\n reports[pluginId] = await eraser({ orgId: request.orgId, dryRun: request.dryRun })\n } catch (error) {\n reports[pluginId] = null\n console.error(`[plugins] ${pluginId} failed to erase org ${request.orgId}`, error)\n if (required.includes(pluginId)) failedRequired.push(pluginId)\n }\n }\n if (failedRequired.length) {\n throw new Error(\n `[plugins] erasing org ${request.orgId} incomplete: the required eraser of ` +\n `${failedRequired.join(', ')} failed`,\n )\n }\n return reports\n}\n\n/**\n * The required erasers not registered in this process, after running the\n * app's boot step once when any is missing (AGL-3464): a boot whose\n * declarations failed looks the same from here as one that never declared\n * the eraser, and only the second is a reason to refuse.\n */\nasync function missingAfterRepair(required: readonly string[]): Promise<string[]> {\n const missing = () => {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n return required.filter((pluginId) => !registered.has(pluginId))\n }\n if (!missing().length) return []\n await runPluginDeclarationsRepair().catch((error: unknown) => {\n console.error('[plugins] the declarations repair failed before an org erasure', error)\n })\n return missing()\n}\n\n/** The plugins with a workspace eraser, in the order they run. */\nexport function listPluginOrgErasers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginOrgErasersForTests(): void {\n registrations.length = 0\n}\n\n/**\n * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a\n * spec about the erasure's own sweeps can run it without loading a plugin\n * (core and the data layer may not import one). A spec about a required\n * plugin's share registers the real one through the app's manifest instead.\n */\nexport function standInRequiredOrgErasersForTests(): void {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n for (const pluginId of PLUGIN_REQUIRED_ORG_ERASERS) {\n if (!registered.has(pluginId)) {\n registrations.push({ pluginId, eraser: async () => ({ standIn: true }) })\n }\n }\n}\n"],"names":["globalScope","getRegisteringPluginId","runPluginDeclarationsRepair","PLUGIN_ORG_KEYED_COLLECTIONS","PLUGIN_REQUIRED_ORG_ERASERS","ERASERS_KEY","Symbol","for","globalThis","registrations","registerPluginOrgEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginOrgErasers","request","required","missing","missingAfterRepair","length","orgId","join","reports","failedRequired","dryRun","error","console","includes","registered","Set","map","filter","has","catch","listPluginOrgErasers","resetPluginOrgErasersForTests","standInRequiredOrgErasersForTests","standIn"],"mappings":"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"}
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
export
|
|
17
|
+
import { PLUGIN_REQUIRED_PERSON_ERASERS } from './first-party-plugins.generated';
|
|
18
|
+
export { PLUGIN_REQUIRED_PERSON_ERASERS };
|
|
19
|
+
/** Who is being erased, and from which workspace: what is known before anyone erases. */
|
|
20
|
+
export interface PluginPersonErasureTarget {
|
|
19
21
|
/** The workspace the person is erased from. */
|
|
20
22
|
orgId: string;
|
|
21
23
|
/** The person's address, normalized. */
|
|
@@ -25,10 +27,18 @@ export interface PluginPersonErasureRequest {
|
|
|
25
27
|
* keeps in the same way, names the person without their address.
|
|
26
28
|
*/
|
|
27
29
|
key: string;
|
|
28
|
-
/** The contact documents the erasure is about to delete, by id. */
|
|
29
|
-
contactIds: readonly string[];
|
|
30
30
|
/** A plan, not an erasure: count, write nothing. */
|
|
31
31
|
dryRun: boolean;
|
|
32
|
+
/** When the erasure runs, epoch ms: the one time every stamp it leaves carries. */
|
|
33
|
+
atMs: number;
|
|
34
|
+
}
|
|
35
|
+
/** What every eraser is handed. */
|
|
36
|
+
export interface PluginPersonErasureRequest extends PluginPersonErasureTarget {
|
|
37
|
+
/**
|
|
38
|
+
* The person's records, by the record system's ids, as its `locate`
|
|
39
|
+
* answered before anything was erased — empty when no plugin keeps people.
|
|
40
|
+
*/
|
|
41
|
+
contactIds: readonly string[];
|
|
32
42
|
}
|
|
33
43
|
/**
|
|
34
44
|
* What an eraser reports for the erasure's audit record: counts and flags,
|
|
@@ -37,6 +47,13 @@ export interface PluginPersonErasureRequest {
|
|
|
37
47
|
*/
|
|
38
48
|
export type PluginPersonErasureReport = Readonly<Record<string, number | boolean | null>>;
|
|
39
49
|
export type PluginPersonEraser = (request: PluginPersonErasureRequest) => Promise<PluginPersonErasureReport>;
|
|
50
|
+
/** The plugin that keeps the people: names the person's records first, erases them last. */
|
|
51
|
+
export interface PluginPersonRecordsEraser {
|
|
52
|
+
/** The ids of the person's records in this workspace. Writes nothing. */
|
|
53
|
+
locate(target: PluginPersonErasureTarget): Promise<readonly string[]>;
|
|
54
|
+
/** Its own share, run after every other eraser. */
|
|
55
|
+
erase: PluginPersonEraser;
|
|
56
|
+
}
|
|
40
57
|
/**
|
|
41
58
|
* Registers a plugin's person eraser. Owner = the loader's marker inside a
|
|
42
59
|
* register fn, else `options.pluginId`; an eraser with neither throws. One
|
|
@@ -47,12 +64,54 @@ export declare function registerPluginPersonEraser(eraser: PluginPersonEraser, o
|
|
|
47
64
|
pluginId?: string;
|
|
48
65
|
}): void;
|
|
49
66
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
67
|
+
* Registers the records eraser of the plugin that keeps people. One per
|
|
68
|
+
* process: the same plugin registering again replaces its own, and a second
|
|
69
|
+
* plugin is refused naming both — a workspace keeps one set of people.
|
|
70
|
+
*/
|
|
71
|
+
export declare function registerPluginPersonRecordsEraser(eraser: PluginPersonRecordsEraser, options?: {
|
|
72
|
+
pluginId?: string;
|
|
73
|
+
}): void;
|
|
74
|
+
/**
|
|
75
|
+
* The required erasers not registered in this process, in `required` order —
|
|
76
|
+
* empty when the erasure may run. The erasure asks before it writes anything.
|
|
77
|
+
*/
|
|
78
|
+
export declare function missingRequiredPersonErasers(required?: readonly string[]): string[];
|
|
79
|
+
/**
|
|
80
|
+
* {@link missingRequiredPersonErasers}, after running the app's boot step
|
|
81
|
+
* once when any is missing (AGL-3464): a boot whose declarations failed looks
|
|
82
|
+
* the same from here as one that never declared the eraser, and only the
|
|
83
|
+
* second is a reason to refuse. What the erasure asks before it writes
|
|
84
|
+
* anything.
|
|
53
85
|
*/
|
|
54
|
-
export declare function
|
|
55
|
-
/**
|
|
86
|
+
export declare function missingRequiredPersonErasersAfterRepair(required?: readonly string[]): Promise<string[]>;
|
|
87
|
+
/** What one erasure's plugins did. */
|
|
88
|
+
export interface PluginPersonErasureOutcome {
|
|
89
|
+
/** The records `locate` named, handed to every eraser. */
|
|
90
|
+
contactIds: string[];
|
|
91
|
+
/** Each plugin's report by plugin id, or `null` for an eraser that threw. */
|
|
92
|
+
reports: Record<string, PluginPersonErasureReport | null>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Runs the plugins' shares of one person erasure: the records eraser's
|
|
96
|
+
* `locate`, every other eraser in registration order, then the records
|
|
97
|
+
* eraser's `erase`. See the module note.
|
|
98
|
+
*
|
|
99
|
+
* Throws in three cases, each about a share the erasure promises (`required`,
|
|
100
|
+
* the compiled {@link PLUGIN_REQUIRED_PERSON_ERASERS} unless a spec passes its
|
|
101
|
+
* own): before running anything, when a required eraser is not registered
|
|
102
|
+
* even after the app's boot step has run once more, or the records eraser
|
|
103
|
+
* cannot `locate`; and after running every eraser, when a required one
|
|
104
|
+
* threw. Otherwise never.
|
|
105
|
+
*/
|
|
106
|
+
export declare function runPluginPersonErasure(target: PluginPersonErasureTarget, required?: readonly string[]): Promise<PluginPersonErasureOutcome>;
|
|
107
|
+
/** The plugins with a person eraser, in the order they run: the records eraser last. */
|
|
56
108
|
export declare function listPluginPersonErasers(): string[];
|
|
57
109
|
/** Test seam: forget every eraser. */
|
|
58
110
|
export declare function resetPluginPersonErasersForTests(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Test seam: a no-op eraser for every REQUIRED plugin that has none, so a spec
|
|
113
|
+
* about the erasure's own sweeps can run it without loading a plugin (core
|
|
114
|
+
* and the data layer may not import one). A spec about a plugin's share
|
|
115
|
+
* registers the real one through the app's manifest instead.
|
|
116
|
+
*/
|
|
117
|
+
export declare function standInRequiredPersonErasersForTests(): void;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
var _globalScope_ERASERS_KEY;
|
|
1
3
|
/**
|
|
2
4
|
* @license
|
|
3
5
|
* Copyright 2026 Aglyn LLC
|
|
@@ -14,54 +16,103 @@
|
|
|
14
16
|
* See the License for the specific language governing permissions and
|
|
15
17
|
* limitations under the License.
|
|
16
18
|
*/ /**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* The person erasure removes one person from one workspace — a
|
|
20
|
-
* workspace kept, not an account
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
19
|
+
* The plugins' shares of a PERSON erasure (AGL-2981, AGL-3080).
|
|
20
|
+
*
|
|
21
|
+
* The person erasure removes one person from one workspace — a person the
|
|
22
|
+
* workspace kept, not an account. The platform closes every site's door to
|
|
23
|
+
* the address (a suppression row per site), sweeps the delivery log filed
|
|
24
|
+
* under it, and keeps the audit; everything else the workspace keeps about the
|
|
25
|
+
* person is a plugin's, and each plugin that keeps some registers an eraser
|
|
26
|
+
* here: the record system's people and what it files beside them, a shop's
|
|
27
|
+
* orders, a calendar's bookings, an audience's members, a sequence's
|
|
28
|
+
* enrollments.
|
|
29
|
+
*
|
|
30
|
+
* ## The plugin that keeps the people goes first, and last
|
|
31
|
+
*
|
|
32
|
+
* Other plugins key what they keep about a person on the record system's ids
|
|
33
|
+
* — an enrollment names the contact it enrolled — so the erasure has to know
|
|
34
|
+
* those ids before anybody erases, and the records themselves have to outlive
|
|
35
|
+
* every other eraser. The plugin that keeps people registers a RECORDS eraser
|
|
36
|
+
* (`registerPluginPersonRecordsEraser`, one per workspace) with two halves:
|
|
37
|
+
*
|
|
38
|
+
* 1. `locate` names the person's records, read before anything is erased; the
|
|
39
|
+
* ids are handed to every eraser as `contactIds`;
|
|
40
|
+
* 2. every other eraser runs, in registration order;
|
|
41
|
+
* 3. the records eraser's own `erase` runs LAST, while nothing else is left
|
|
42
|
+
* that needs the records to find what it keeps.
|
|
43
|
+
*
|
|
44
|
+
* Every eraser runs AFTER the erasure has closed the door — every site's
|
|
45
|
+
* suppression row is already written, so nothing a plugin sends can reach the
|
|
46
|
+
* person while it runs.
|
|
32
47
|
*
|
|
33
48
|
* The two halves of the rule that holds everywhere else in the erasure hold
|
|
34
49
|
* here too. A record ABOUT the person goes. A record that the person asked
|
|
35
|
-
* not to be contacted — keyed by `personKey`, the hash every suppression
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
50
|
+
* not to be contacted — keyed by `personKey`, the hash every suppression list
|
|
51
|
+
* is keyed by, and holding no address — is KEPT, because the promise it
|
|
52
|
+
* records has to outlive the person's data; an eraser strips from it anything
|
|
53
|
+
* that could identify the person again.
|
|
54
|
+
*
|
|
55
|
+
* ## Isolated, unless REQUIRED
|
|
39
56
|
*
|
|
40
|
-
* Erasers
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
57
|
+
* Erasers are ISOLATED: a throw is logged against its plugin, recorded as
|
|
58
|
+
* `null`, and does not stop the next one. The `null` is how the erasure's
|
|
59
|
+
* audit record says that plugin's share may remain, which a report of zero
|
|
60
|
+
* would hide.
|
|
61
|
+
*
|
|
62
|
+
* Isolation is right for a courtesy and wrong for a share the erasure
|
|
63
|
+
* PROMISES the person — their contact record, their leads, their name on an
|
|
64
|
+
* order. A plugin keeping such a share declares `"requiredPersonEraser": true`
|
|
65
|
+
* in `plugins.config.json`, compiled into {@link PLUGIN_REQUIRED_PERSON_ERASERS}
|
|
66
|
+
* so no registration can opt out of it, and:
|
|
67
|
+
*
|
|
68
|
+
* - the erasure REFUSES to start while a required eraser is not registered —
|
|
69
|
+
* a boot that failed to declare one must not produce an "erased" request
|
|
70
|
+
* with the person still on file;
|
|
71
|
+
* - a records eraser whose `locate` throws stops the erasure before anything
|
|
72
|
+
* is erased: nobody can be handed the ids;
|
|
73
|
+
* - a required eraser that throws still lets every other eraser run, and then
|
|
74
|
+
* fails the erasure, which the job retries.
|
|
44
75
|
*
|
|
45
76
|
* A DRY RUN is handed to every eraser, which must then write nothing: it
|
|
46
77
|
* counts what it would do.
|
|
47
78
|
*
|
|
48
79
|
* Registered from a plugin's declarations — `serverDeclarations`, or
|
|
49
|
-
* `consoleServerDeclarations` for a plugin whose server code runs only in
|
|
50
|
-
*
|
|
51
|
-
*
|
|
80
|
+
* `consoleServerDeclarations` for a plugin whose server code runs only in the
|
|
81
|
+
* console — so the eraser is in place in a process that never loaded the
|
|
82
|
+
* plugin's API surface.
|
|
52
83
|
*/ import { getRegisteringPluginId } from "../app-utils/registering-plugin.js";
|
|
53
|
-
|
|
84
|
+
import { PLUGIN_REQUIRED_PERSON_ERASERS } from "./first-party-plugins.generated.js";
|
|
85
|
+
import { runPluginDeclarationsRepair } from "./plugin-declarations-repair.js";
|
|
86
|
+
export { PLUGIN_REQUIRED_PERSON_ERASERS };
|
|
87
|
+
/**
|
|
88
|
+
* One set of erasers per process, on `globalThis` (AGL-3464): the app registers its
|
|
89
|
+
* plugins' erasers from `instrumentation.ts`, which Next compiles apart from
|
|
90
|
+
* the route that runs the erasure, and a module-scoped list is filled in one
|
|
91
|
+
* copy and read empty in the other — the AGL-3412 shape.
|
|
92
|
+
* Here a required share registered in the other copy reads as missing, and
|
|
93
|
+
* every erasure refuses.
|
|
94
|
+
*/ const ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-person-erasers');
|
|
95
|
+
const globalScope = globalThis;
|
|
96
|
+
const erasers = (_globalScope_ERASERS_KEY = globalScope[ERASERS_KEY]) != null ? _globalScope_ERASERS_KEY : globalScope[ERASERS_KEY] = {
|
|
97
|
+
registrations: [],
|
|
98
|
+
records: null
|
|
99
|
+
};
|
|
100
|
+
const registrations = erasers.registrations;
|
|
101
|
+
function ownerOf(options, what) {
|
|
102
|
+
var _ref, _getRegisteringPluginId;
|
|
103
|
+
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
104
|
+
if (!pluginId) {
|
|
105
|
+
throw new Error(`a plugin ${what} was registered with no owner: pass { pluginId } ` + 'when registering outside a plugin register fn');
|
|
106
|
+
}
|
|
107
|
+
return pluginId;
|
|
108
|
+
}
|
|
54
109
|
/**
|
|
55
110
|
* Registers a plugin's person eraser. Owner = the loader's marker inside a
|
|
56
111
|
* register fn, else `options.pluginId`; an eraser with neither throws. One
|
|
57
112
|
* eraser per plugin: registering again replaces the plugin's earlier eraser
|
|
58
113
|
* in place, so a module evaluated twice does not erase twice.
|
|
59
114
|
*/ export function registerPluginPersonEraser(eraser, options) {
|
|
60
|
-
|
|
61
|
-
const pluginId = ((_ref = (_getRegisteringPluginId = getRegisteringPluginId()) != null ? _getRegisteringPluginId : options == null ? void 0 : options.pluginId) != null ? _ref : '').trim();
|
|
62
|
-
if (!pluginId) {
|
|
63
|
-
throw new Error('a plugin person eraser was registered with no owner: pass { pluginId } ' + 'when registering outside a plugin register fn');
|
|
64
|
-
}
|
|
115
|
+
const pluginId = ownerOf(options, 'person eraser');
|
|
65
116
|
const index = registrations.findIndex((entry)=>entry.pluginId === pluginId);
|
|
66
117
|
if (index >= 0) registrations[index] = {
|
|
67
118
|
pluginId,
|
|
@@ -73,37 +124,131 @@ const registrations = [];
|
|
|
73
124
|
});
|
|
74
125
|
}
|
|
75
126
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*/ export
|
|
127
|
+
* Registers the records eraser of the plugin that keeps people. One per
|
|
128
|
+
* process: the same plugin registering again replaces its own, and a second
|
|
129
|
+
* plugin is refused naming both — a workspace keeps one set of people.
|
|
130
|
+
*/ export function registerPluginPersonRecordsEraser(eraser, options) {
|
|
131
|
+
const pluginId = ownerOf(options, 'person records eraser');
|
|
132
|
+
if (erasers.records && erasers.records.pluginId !== pluginId) {
|
|
133
|
+
throw new Error(`the person records eraser is "${erasers.records.pluginId}"'s; refused one from "${pluginId}"`);
|
|
134
|
+
}
|
|
135
|
+
erasers.records = {
|
|
136
|
+
pluginId,
|
|
137
|
+
eraser
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The required erasers not registered in this process, in `required` order —
|
|
142
|
+
* empty when the erasure may run. The erasure asks before it writes anything.
|
|
143
|
+
*/ export function missingRequiredPersonErasers(required = PLUGIN_REQUIRED_PERSON_ERASERS) {
|
|
144
|
+
const registered = new Set(registrations.map((entry)=>entry.pluginId));
|
|
145
|
+
if (erasers.records) registered.add(erasers.records.pluginId);
|
|
146
|
+
return required.filter((pluginId)=>!registered.has(pluginId));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* {@link missingRequiredPersonErasers}, after running the app's boot step
|
|
150
|
+
* once when any is missing (AGL-3464): a boot whose declarations failed looks
|
|
151
|
+
* the same from here as one that never declared the eraser, and only the
|
|
152
|
+
* second is a reason to refuse. What the erasure asks before it writes
|
|
153
|
+
* anything.
|
|
154
|
+
*/ export async function missingRequiredPersonErasersAfterRepair(required = PLUGIN_REQUIRED_PERSON_ERASERS) {
|
|
155
|
+
if (!missingRequiredPersonErasers(required).length) return [];
|
|
156
|
+
await runPluginDeclarationsRepair().catch((error)=>{
|
|
157
|
+
console.error('[plugins] the declarations repair failed before a person erasure', error);
|
|
158
|
+
});
|
|
159
|
+
return missingRequiredPersonErasers(required);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Runs the plugins' shares of one person erasure: the records eraser's
|
|
163
|
+
* `locate`, every other eraser in registration order, then the records
|
|
164
|
+
* eraser's `erase`. See the module note.
|
|
165
|
+
*
|
|
166
|
+
* Throws in three cases, each about a share the erasure promises (`required`,
|
|
167
|
+
* the compiled {@link PLUGIN_REQUIRED_PERSON_ERASERS} unless a spec passes its
|
|
168
|
+
* own): before running anything, when a required eraser is not registered
|
|
169
|
+
* even after the app's boot step has run once more, or the records eraser
|
|
170
|
+
* cannot `locate`; and after running every eraser, when a required one
|
|
171
|
+
* threw. Otherwise never.
|
|
172
|
+
*/ export async function runPluginPersonErasure(target, required = PLUGIN_REQUIRED_PERSON_ERASERS) {
|
|
173
|
+
const missing = await missingRequiredPersonErasersAfterRepair(required);
|
|
174
|
+
if (missing.length) {
|
|
175
|
+
throw new Error(`[plugins] erasing a person in org ${target.orgId} refused: ${missing.join(', ')} ` + 'declared a required person eraser and none is registered in this process');
|
|
176
|
+
}
|
|
177
|
+
const base = {
|
|
178
|
+
orgId: target.orgId,
|
|
179
|
+
email: target.email,
|
|
180
|
+
key: target.key,
|
|
181
|
+
dryRun: target.dryRun,
|
|
182
|
+
atMs: target.atMs
|
|
183
|
+
};
|
|
184
|
+
const records = erasers.records;
|
|
185
|
+
const contactIds = records ? [
|
|
186
|
+
...await records.eraser.locate(_extends({}, base))
|
|
187
|
+
] : [];
|
|
80
188
|
const reports = {};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
]){
|
|
189
|
+
const failedRequired = [];
|
|
190
|
+
const run = async (pluginId, eraser)=>{
|
|
84
191
|
try {
|
|
85
|
-
reports[pluginId] = await eraser({
|
|
86
|
-
orgId: request.orgId,
|
|
87
|
-
email: request.email,
|
|
88
|
-
key: request.key,
|
|
192
|
+
reports[pluginId] = await eraser(_extends({}, base, {
|
|
89
193
|
contactIds: [
|
|
90
|
-
...
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
});
|
|
194
|
+
...contactIds
|
|
195
|
+
]
|
|
196
|
+
}));
|
|
94
197
|
} catch (error) {
|
|
95
198
|
reports[pluginId] = null;
|
|
96
199
|
// The org, never the address: this line outlives the erasure.
|
|
97
|
-
console.error(`[plugins] ${pluginId} failed to erase a person in org ${
|
|
200
|
+
console.error(`[plugins] ${pluginId} failed to erase a person in org ${target.orgId}`, error);
|
|
201
|
+
if (required.includes(pluginId)) failedRequired.push(pluginId);
|
|
98
202
|
}
|
|
203
|
+
};
|
|
204
|
+
const owner = records;
|
|
205
|
+
for (const { pluginId, eraser } of [
|
|
206
|
+
...registrations
|
|
207
|
+
]){
|
|
208
|
+
if (pluginId === (owner == null ? void 0 : owner.pluginId)) continue;
|
|
209
|
+
await run(pluginId, eraser);
|
|
210
|
+
}
|
|
211
|
+
if (owner) await run(owner.pluginId, owner.eraser.erase);
|
|
212
|
+
if (failedRequired.length) {
|
|
213
|
+
throw new Error(`[plugins] erasing a person in org ${target.orgId} incomplete: the required eraser of ` + `${failedRequired.join(', ')} failed`);
|
|
99
214
|
}
|
|
100
|
-
return
|
|
215
|
+
return {
|
|
216
|
+
contactIds,
|
|
217
|
+
reports
|
|
218
|
+
};
|
|
101
219
|
}
|
|
102
|
-
/** The plugins with a person eraser, in the order they run. */ export function listPluginPersonErasers() {
|
|
103
|
-
|
|
220
|
+
/** The plugins with a person eraser, in the order they run: the records eraser last. */ export function listPluginPersonErasers() {
|
|
221
|
+
var _erasers_records;
|
|
222
|
+
const owner = (_erasers_records = erasers.records) == null ? void 0 : _erasers_records.pluginId;
|
|
223
|
+
return [
|
|
224
|
+
...registrations.map((entry)=>entry.pluginId).filter((pluginId)=>pluginId !== owner),
|
|
225
|
+
...owner ? [
|
|
226
|
+
owner
|
|
227
|
+
] : []
|
|
228
|
+
];
|
|
104
229
|
}
|
|
105
230
|
/** Test seam: forget every eraser. */ export function resetPluginPersonErasersForTests() {
|
|
106
231
|
registrations.length = 0;
|
|
232
|
+
erasers.records = null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Test seam: a no-op eraser for every REQUIRED plugin that has none, so a spec
|
|
236
|
+
* about the erasure's own sweeps can run it without loading a plugin (core
|
|
237
|
+
* and the data layer may not import one). A spec about a plugin's share
|
|
238
|
+
* registers the real one through the app's manifest instead.
|
|
239
|
+
*/ export function standInRequiredPersonErasersForTests() {
|
|
240
|
+
const registered = new Set(registrations.map((entry)=>entry.pluginId));
|
|
241
|
+
if (erasers.records) registered.add(erasers.records.pluginId);
|
|
242
|
+
for (const pluginId of PLUGIN_REQUIRED_PERSON_ERASERS){
|
|
243
|
+
if (!registered.has(pluginId)) {
|
|
244
|
+
registrations.push({
|
|
245
|
+
pluginId,
|
|
246
|
+
eraser: async ()=>({
|
|
247
|
+
standIn: true
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
107
252
|
}
|
|
108
253
|
|
|
109
254
|
//# sourceMappingURL=plugin-person-erasure.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-person-erasure.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A plugin's share of a PERSON erasure (AGL-2981).\n *\n * The person erasure removes one person from one workspace — a contact the\n * workspace kept, not an account: the contact document, what the CRM files\n * beside it, the leads, list memberships and delivery log under the\n * address, and the person's identity on orders and bookings. A plugin that\n * keeps records about such a person where none of those sweeps reach — a\n * collection under the organization keyed by the contact or by the address\n * — registers an eraser here, and the erasure runs every eraser with the\n * person's address, its key and the contacts it is about to delete.\n *\n * Erasers run AFTER the erasure has closed the door and BEFORE it deletes\n * the contacts: every site's suppression row is already written, so nothing\n * a plugin sends can reach the person while its eraser runs, and the contact\n * documents still exist for an eraser that has to read one.\n *\n * The two halves of the rule that holds everywhere else in the erasure hold\n * here too. A record ABOUT the person goes. A record that the person asked\n * not to be contacted — keyed by `personKey`, the hash every suppression\n * list is keyed by, and holding no address — is KEPT, because the promise\n * it records has to outlive the person's data; an eraser strips from it\n * anything that could identify the person again.\n *\n * Erasers run in registration order and are ISOLATED: a throw is logged\n * against its plugin, recorded as `null`, and does not stop the next one or\n * the erasure. The `null` is how the erasure's audit record says that\n * plugin's share may remain, which a report of zero would hide.\n *\n * A DRY RUN is handed to every eraser, which must then write nothing: it\n * counts what it would do.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for a plugin whose server code runs only in\n * the console — so the eraser is in place in a process that never loaded\n * the plugin's API surface.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\n\n/** Who is being erased, and from which workspace. */\nexport interface PluginPersonErasureRequest {\n /** The workspace the person is erased from. */\n orgId: string\n /** The person's address, normalized. */\n email: string\n /**\n * `personKey(email)`: how every suppression list, and any list a plugin\n * keeps in the same way, names the person without their address.\n */\n key: string\n /** The contact documents the erasure is about to delete, by id. */\n contactIds: readonly string[]\n /** A plan, not an erasure: count, write nothing. */\n dryRun: boolean\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content. A `null` field is a figure the eraser could not\n * measure, which is not zero.\n */\nexport type PluginPersonErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginPersonEraser = (\n request: PluginPersonErasureRequest,\n) => Promise<PluginPersonErasureReport>\n\ninterface Registration {\n pluginId: string\n eraser: PluginPersonEraser\n}\n\nconst registrations: Registration[] = []\n\n/**\n * Registers a plugin's person eraser. Owner = the loader's marker inside a\n * register fn, else `options.pluginId`; an eraser with neither throws. One\n * eraser per plugin: registering again replaces the plugin's earlier eraser\n * in place, so a module evaluated twice does not erase twice.\n */\nexport function registerPluginPersonEraser(\n eraser: PluginPersonEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n 'a plugin person eraser was registered with no owner: pass { pluginId } ' +\n 'when registering outside a plugin register fn',\n )\n }\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, eraser }\n else registrations.push({ pluginId, eraser })\n}\n\n/**\n * Runs every eraser, in registration order, and answers each plugin's\n * report by plugin id: the eraser's own report, or `null` for one that\n * threw. Never throws.\n */\nexport async function runPluginPersonErasers(\n request: PluginPersonErasureRequest,\n): Promise<Record<string, PluginPersonErasureReport | null>> {\n const reports: Record<string, PluginPersonErasureReport | null> = {}\n for (const { pluginId, eraser } of [...registrations]) {\n try {\n reports[pluginId] = await eraser({\n orgId: request.orgId,\n email: request.email,\n key: request.key,\n contactIds: [...request.contactIds],\n dryRun: request.dryRun,\n })\n } catch (error) {\n reports[pluginId] = null\n // The org, never the address: this line outlives the erasure.\n console.error(`[plugins] ${pluginId} failed to erase a person in org ${request.orgId}`, error)\n }\n }\n return reports\n}\n\n/** The plugins with a person eraser, in the order they run. */\nexport function listPluginPersonErasers(): string[] {\n return registrations.map((entry) => entry.pluginId)\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginPersonErasersForTests(): void {\n registrations.length = 0\n}\n"],"names":["getRegisteringPluginId","registrations","registerPluginPersonEraser","eraser","options","pluginId","trim","Error","index","findIndex","entry","push","runPluginPersonErasers","request","reports","orgId","email","key","contactIds","dryRun","error","console","listPluginPersonErasers","map","resetPluginPersonErasersForTests","length"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCC,GAED,SAASA,sBAAsB,QAAQ,qCAAiC;AAmCxE,MAAMC,gBAAgC,EAAE;AAExC;;;;;CAKC,GACD,OAAO,SAASC,2BACdC,MAA0B,EAC1BC,OAA+B;QAEbJ,MAAAA;IAAlB,MAAMK,WAAW,EAACL,QAAAA,0BAAAA,oCAAAA,0BAA4BI,2BAAAA,QAASC,QAAQ,YAA7CL,OAAiD,IAAIM,IAAI;IAC3E,IAAI,CAACD,UAAU;QACb,MAAM,IAAIE,MACR,4EACE;IAEN;IACA,MAAMC,QAAQP,cAAcQ,SAAS,CAAC,CAACC,QAAUA,MAAML,QAAQ,KAAKA;IACpE,IAAIG,SAAS,GAAGP,aAAa,CAACO,MAAM,GAAG;QAAEH;QAAUF;IAAO;SACrDF,cAAcU,IAAI,CAAC;QAAEN;QAAUF;IAAO;AAC7C;AAEA;;;;CAIC,GACD,OAAO,eAAeS,uBACpBC,OAAmC;IAEnC,MAAMC,UAA4D,CAAC;IACnE,KAAK,MAAM,EAAET,QAAQ,EAAEF,MAAM,EAAE,IAAI;WAAIF;KAAc,CAAE;QACrD,IAAI;YACFa,OAAO,CAACT,SAAS,GAAG,MAAMF,OAAO;gBAC/BY,OAAOF,QAAQE,KAAK;gBACpBC,OAAOH,QAAQG,KAAK;gBACpBC,KAAKJ,QAAQI,GAAG;gBAChBC,YAAY;uBAAIL,QAAQK,UAAU;iBAAC;gBACnCC,QAAQN,QAAQM,MAAM;YACxB;QACF,EAAE,OAAOC,OAAO;YACdN,OAAO,CAACT,SAAS,GAAG;YACpB,8DAA8D;YAC9DgB,QAAQD,KAAK,CAAC,CAAC,UAAU,EAAEf,SAAS,iCAAiC,EAAEQ,QAAQE,KAAK,EAAE,EAAEK;QAC1F;IACF;IACA,OAAON;AACT;AAEA,6DAA6D,GAC7D,OAAO,SAASQ;IACd,OAAOrB,cAAcsB,GAAG,CAAC,CAACb,QAAUA,MAAML,QAAQ;AACpD;AAEA,oCAAoC,GACpC,OAAO,SAASmB;IACdvB,cAAcwB,MAAM,GAAG;AACzB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-person-erasure.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 plugins' shares of a PERSON erasure (AGL-2981, AGL-3080).\n *\n * The person erasure removes one person from one workspace — a person the\n * workspace kept, not an account. The platform closes every site's door to\n * the address (a suppression row per site), sweeps the delivery log filed\n * under it, and keeps the audit; everything else the workspace keeps about the\n * person is a plugin's, and each plugin that keeps some registers an eraser\n * here: the record system's people and what it files beside them, a shop's\n * orders, a calendar's bookings, an audience's members, a sequence's\n * enrollments.\n *\n * ## The plugin that keeps the people goes first, and last\n *\n * Other plugins key what they keep about a person on the record system's ids\n * — an enrollment names the contact it enrolled — so the erasure has to know\n * those ids before anybody erases, and the records themselves have to outlive\n * every other eraser. The plugin that keeps people registers a RECORDS eraser\n * (`registerPluginPersonRecordsEraser`, one per workspace) with two halves:\n *\n * 1. `locate` names the person's records, read before anything is erased; the\n * ids are handed to every eraser as `contactIds`;\n * 2. every other eraser runs, in registration order;\n * 3. the records eraser's own `erase` runs LAST, while nothing else is left\n * that needs the records to find what it keeps.\n *\n * Every eraser runs AFTER the erasure has closed the door — every site's\n * suppression row is already written, so nothing a plugin sends can reach the\n * person while it runs.\n *\n * The two halves of the rule that holds everywhere else in the erasure hold\n * here too. A record ABOUT the person goes. A record that the person asked\n * not to be contacted — keyed by `personKey`, the hash every suppression list\n * is keyed by, and holding no address — is KEPT, because the promise it\n * records has to outlive the person's data; an eraser strips from it anything\n * that could identify the person again.\n *\n * ## Isolated, unless REQUIRED\n *\n * Erasers are ISOLATED: a throw is logged against its plugin, recorded as\n * `null`, and does not stop the next one. The `null` is how the erasure's\n * audit record says that plugin's share may remain, which a report of zero\n * would hide.\n *\n * Isolation is right for a courtesy and wrong for a share the erasure\n * PROMISES the person — their contact record, their leads, their name on an\n * order. A plugin keeping such a share declares `\"requiredPersonEraser\": true`\n * in `plugins.config.json`, compiled into {@link PLUGIN_REQUIRED_PERSON_ERASERS}\n * so no registration can opt out of it, and:\n *\n * - the erasure REFUSES to start while a required eraser is not registered —\n * a boot that failed to declare one must not produce an \"erased\" request\n * with the person still on file;\n * - a records eraser whose `locate` throws stops the erasure before anything\n * is erased: nobody can be handed the ids;\n * - a required eraser that throws still lets every other eraser run, and then\n * fails the erasure, which the job retries.\n *\n * A DRY RUN is handed to every eraser, which must then write nothing: it\n * counts what it would do.\n *\n * Registered from a plugin's declarations — `serverDeclarations`, or\n * `consoleServerDeclarations` for a plugin whose server code runs only in the\n * console — so the eraser is in place in a process that never loaded the\n * plugin's API surface.\n */\n\nimport { getRegisteringPluginId } from '../app-utils/registering-plugin'\nimport { PLUGIN_REQUIRED_PERSON_ERASERS } from './first-party-plugins.generated'\nimport { runPluginDeclarationsRepair } from './plugin-declarations-repair'\n\nexport { PLUGIN_REQUIRED_PERSON_ERASERS }\n\n/** Who is being erased, and from which workspace: what is known before anyone erases. */\nexport interface PluginPersonErasureTarget {\n /** The workspace the person is erased from. */\n orgId: string\n /** The person's address, normalized. */\n email: string\n /**\n * `personKey(email)`: how every suppression list, and any list a plugin\n * keeps in the same way, names the person without their address.\n */\n key: string\n /** A plan, not an erasure: count, write nothing. */\n dryRun: boolean\n /** When the erasure runs, epoch ms: the one time every stamp it leaves carries. */\n atMs: number\n}\n\n/** What every eraser is handed. */\nexport interface PluginPersonErasureRequest extends PluginPersonErasureTarget {\n /**\n * The person's records, by the record system's ids, as its `locate`\n * answered before anything was erased — empty when no plugin keeps people.\n */\n contactIds: readonly string[]\n}\n\n/**\n * What an eraser reports for the erasure's audit record: counts and flags,\n * never the erased content. A `null` field is a figure the eraser could not\n * measure, which is not zero.\n */\nexport type PluginPersonErasureReport = Readonly<Record<string, number | boolean | null>>\n\nexport type PluginPersonEraser = (\n request: PluginPersonErasureRequest,\n) => Promise<PluginPersonErasureReport>\n\n/** The plugin that keeps the people: names the person's records first, erases them last. */\nexport interface PluginPersonRecordsEraser {\n /** The ids of the person's records in this workspace. Writes nothing. */\n locate(target: PluginPersonErasureTarget): Promise<readonly string[]>\n /** Its own share, run after every other eraser. */\n erase: PluginPersonEraser\n}\n\ninterface Registration {\n pluginId: string\n eraser: PluginPersonEraser\n}\n\ninterface PersonErasers {\n registrations: Registration[]\n records: { pluginId: string; eraser: PluginPersonRecordsEraser } | null\n}\n\n/**\n * One set of erasers per process, on `globalThis` (AGL-3464): the app registers its\n * plugins' erasers from `instrumentation.ts`, which Next compiles apart from\n * the route that runs the erasure, and a module-scoped list is filled in one\n * copy and read empty in the other — the AGL-3412 shape.\n * Here a required share registered in the other copy reads as missing, and\n * every erasure refuses.\n */\nconst ERASERS_KEY = Symbol.for('@aglyn/aglyn:plugin-person-erasers')\n\nconst globalScope = globalThis as typeof globalThis & {\n [ERASERS_KEY]?: PersonErasers\n}\n\nconst erasers: PersonErasers =\n globalScope[ERASERS_KEY] ?? (globalScope[ERASERS_KEY] = { registrations: [], records: null })\nconst registrations = erasers.registrations\n\nfunction ownerOf(options: { pluginId?: string } | undefined, what: string): string {\n const pluginId = (getRegisteringPluginId() ?? options?.pluginId ?? '').trim()\n if (!pluginId) {\n throw new Error(\n `a plugin ${what} was registered with no owner: pass { pluginId } ` +\n 'when registering outside a plugin register fn',\n )\n }\n return pluginId\n}\n\n/**\n * Registers a plugin's person eraser. Owner = the loader's marker inside a\n * register fn, else `options.pluginId`; an eraser with neither throws. One\n * eraser per plugin: registering again replaces the plugin's earlier eraser\n * in place, so a module evaluated twice does not erase twice.\n */\nexport function registerPluginPersonEraser(\n eraser: PluginPersonEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = ownerOf(options, 'person eraser')\n const index = registrations.findIndex((entry) => entry.pluginId === pluginId)\n if (index >= 0) registrations[index] = { pluginId, eraser }\n else registrations.push({ pluginId, eraser })\n}\n\n/**\n * Registers the records eraser of the plugin that keeps people. One per\n * process: the same plugin registering again replaces its own, and a second\n * plugin is refused naming both — a workspace keeps one set of people.\n */\nexport function registerPluginPersonRecordsEraser(\n eraser: PluginPersonRecordsEraser,\n options?: { pluginId?: string },\n): void {\n const pluginId = ownerOf(options, 'person records eraser')\n if (erasers.records && erasers.records.pluginId !== pluginId) {\n throw new Error(\n `the person records eraser is \"${erasers.records.pluginId}\"'s; refused one from \"${pluginId}\"`,\n )\n }\n erasers.records = { pluginId, eraser }\n}\n\n/**\n * The required erasers not registered in this process, in `required` order —\n * empty when the erasure may run. The erasure asks before it writes anything.\n */\nexport function missingRequiredPersonErasers(\n required: readonly string[] = PLUGIN_REQUIRED_PERSON_ERASERS,\n): string[] {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n if (erasers.records) registered.add(erasers.records.pluginId)\n return required.filter((pluginId) => !registered.has(pluginId))\n}\n\n/**\n * {@link missingRequiredPersonErasers}, after running the app's boot step\n * once when any is missing (AGL-3464): a boot whose declarations failed looks\n * the same from here as one that never declared the eraser, and only the\n * second is a reason to refuse. What the erasure asks before it writes\n * anything.\n */\nexport async function missingRequiredPersonErasersAfterRepair(\n required: readonly string[] = PLUGIN_REQUIRED_PERSON_ERASERS,\n): Promise<string[]> {\n if (!missingRequiredPersonErasers(required).length) return []\n await runPluginDeclarationsRepair().catch((error: unknown) => {\n console.error('[plugins] the declarations repair failed before a person erasure', error)\n })\n return missingRequiredPersonErasers(required)\n}\n\n/** What one erasure's plugins did. */\nexport interface PluginPersonErasureOutcome {\n /** The records `locate` named, handed to every eraser. */\n contactIds: string[]\n /** Each plugin's report by plugin id, or `null` for an eraser that threw. */\n reports: Record<string, PluginPersonErasureReport | null>\n}\n\n/**\n * Runs the plugins' shares of one person erasure: the records eraser's\n * `locate`, every other eraser in registration order, then the records\n * eraser's `erase`. See the module note.\n *\n * Throws in three cases, each about a share the erasure promises (`required`,\n * the compiled {@link PLUGIN_REQUIRED_PERSON_ERASERS} unless a spec passes its\n * own): before running anything, when a required eraser is not registered\n * even after the app's boot step has run once more, or the records eraser\n * cannot `locate`; and after running every eraser, when a required one\n * threw. Otherwise never.\n */\nexport async function runPluginPersonErasure(\n target: PluginPersonErasureTarget,\n required: readonly string[] = PLUGIN_REQUIRED_PERSON_ERASERS,\n): Promise<PluginPersonErasureOutcome> {\n const missing = await missingRequiredPersonErasersAfterRepair(required)\n if (missing.length) {\n throw new Error(\n `[plugins] erasing a person in org ${target.orgId} refused: ${missing.join(', ')} ` +\n 'declared a required person eraser and none is registered in this process',\n )\n }\n const base: PluginPersonErasureTarget = {\n orgId: target.orgId,\n email: target.email,\n key: target.key,\n dryRun: target.dryRun,\n atMs: target.atMs,\n }\n const records = erasers.records\n const contactIds = records ? [...(await records.eraser.locate({ ...base }))] : []\n const reports: Record<string, PluginPersonErasureReport | null> = {}\n const failedRequired: string[] = []\n const run = async (pluginId: string, eraser: PluginPersonEraser) => {\n try {\n reports[pluginId] = await eraser({ ...base, contactIds: [...contactIds] })\n } catch (error) {\n reports[pluginId] = null\n // The org, never the address: this line outlives the erasure.\n console.error(`[plugins] ${pluginId} failed to erase a person in org ${target.orgId}`, error)\n if (required.includes(pluginId)) failedRequired.push(pluginId)\n }\n }\n const owner = records\n for (const { pluginId, eraser } of [...registrations]) {\n if (pluginId === owner?.pluginId) continue\n await run(pluginId, eraser)\n }\n if (owner) await run(owner.pluginId, owner.eraser.erase)\n if (failedRequired.length) {\n throw new Error(\n `[plugins] erasing a person in org ${target.orgId} incomplete: the required eraser of ` +\n `${failedRequired.join(', ')} failed`,\n )\n }\n return { contactIds, reports }\n}\n\n/** The plugins with a person eraser, in the order they run: the records eraser last. */\nexport function listPluginPersonErasers(): string[] {\n const owner = erasers.records?.pluginId\n return [\n ...registrations.map((entry) => entry.pluginId).filter((pluginId) => pluginId !== owner),\n ...(owner ? [owner] : []),\n ]\n}\n\n/** Test seam: forget every eraser. */\nexport function resetPluginPersonErasersForTests(): void {\n registrations.length = 0\n erasers.records = null\n}\n\n/**\n * Test seam: a no-op eraser for every REQUIRED plugin that has none, so a spec\n * about the erasure's own sweeps can run it without loading a plugin (core\n * and the data layer may not import one). A spec about a plugin's share\n * registers the real one through the app's manifest instead.\n */\nexport function standInRequiredPersonErasersForTests(): void {\n const registered = new Set(registrations.map((entry) => entry.pluginId))\n if (erasers.records) registered.add(erasers.records.pluginId)\n for (const pluginId of PLUGIN_REQUIRED_PERSON_ERASERS) {\n if (!registered.has(pluginId)) {\n registrations.push({ pluginId, eraser: async () => ({ standIn: true }) })\n }\n }\n}\n"],"names":["globalScope","getRegisteringPluginId","PLUGIN_REQUIRED_PERSON_ERASERS","runPluginDeclarationsRepair","ERASERS_KEY","Symbol","for","globalThis","erasers","registrations","records","ownerOf","options","what","pluginId","trim","Error","registerPluginPersonEraser","eraser","index","findIndex","entry","push","registerPluginPersonRecordsEraser","missingRequiredPersonErasers","required","registered","Set","map","add","filter","has","missingRequiredPersonErasersAfterRepair","length","catch","error","console","runPluginPersonErasure","target","missing","orgId","join","base","email","key","dryRun","atMs","contactIds","locate","reports","failedRequired","run","includes","owner","erase","listPluginPersonErasers","resetPluginPersonErasersForTests","standInRequiredPersonErasersForTests","standIn"],"mappings":"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"}
|