@aglyn/aglyn 1.0.0-beta.217 → 1.0.0-beta.219
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -14
- package/src/lib/app-utils/actions.d.ts +25 -374
- package/src/lib/app-utils/actions.js +101 -561
- package/src/lib/app-utils/actions.js.map +1 -1
- package/src/lib/app-utils/activity-presenter.d.ts +3 -3
- package/src/lib/app-utils/activity-presenter.js +36 -52
- package/src/lib/app-utils/activity-presenter.js.map +1 -1
- package/src/lib/app-utils/admin-audit-index.js +2 -2
- package/src/lib/app-utils/admin-audit-index.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +11 -2
- package/src/lib/app-utils/analytics-events.js +11 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/analytics-provider.d.ts +182 -0
- package/src/lib/app-utils/analytics-provider.js +133 -0
- package/src/lib/app-utils/analytics-provider.js.map +1 -0
- package/src/lib/app-utils/api-plugins.d.ts +46 -0
- package/src/lib/app-utils/api-plugins.js +19 -0
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
- package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
- package/src/lib/app-utils/artifact-overrides.js.map +1 -0
- package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
- package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
- package/src/lib/app-utils/artifact-provenance.js.map +1 -0
- package/src/lib/app-utils/assist-credits.js +2 -1
- package/src/lib/app-utils/assist-credits.js.map +1 -1
- package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
- package/src/lib/app-utils/binding-token-catalog.js +17 -41
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
- package/src/lib/app-utils/binding-tokens.js +1 -1
- package/src/lib/app-utils/binding-tokens.js.map +1 -1
- package/src/lib/app-utils/brand-support-line.d.ts +36 -0
- package/src/lib/app-utils/brand-support-line.js +38 -0
- package/src/lib/app-utils/brand-support-line.js.map +1 -0
- package/src/lib/app-utils/campaign-touch.d.ts +5 -5
- package/src/lib/app-utils/campaign-touch.js +8 -8
- package/src/lib/app-utils/campaign-touch.js.map +1 -1
- package/src/lib/app-utils/collection-entries.d.ts +2 -2
- package/src/lib/app-utils/collection-entries.js.map +1 -1
- package/src/lib/app-utils/consent-group-change.d.ts +4 -0
- package/src/lib/app-utils/consent-group-change.js.map +1 -1
- package/src/lib/app-utils/console-record-links.d.ts +10 -89
- package/src/lib/app-utils/console-record-links.js +3 -107
- package/src/lib/app-utils/console-record-links.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +16 -7
- package/src/lib/app-utils/console-routes.js +9 -3
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +23 -1
- package/src/lib/app-utils/contacts.js +21 -5
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
- package/src/lib/app-utils/cross-pool-desync.js +47 -0
- package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
- package/src/lib/app-utils/csv-upload.d.ts +29 -0
- package/src/lib/app-utils/csv-upload.js +29 -0
- package/src/lib/app-utils/csv-upload.js.map +1 -0
- package/src/lib/app-utils/dataset-models.d.ts +5 -5
- package/src/lib/app-utils/dataset-models.js +19 -2
- package/src/lib/app-utils/dataset-models.js.map +1 -1
- package/src/lib/app-utils/datasets.d.ts +65 -11
- package/src/lib/app-utils/datasets.js +74 -11
- package/src/lib/app-utils/datasets.js.map +1 -1
- package/src/lib/app-utils/docs-deflection.d.ts +6 -6
- package/src/lib/app-utils/docs-deflection.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
- package/src/lib/app-utils/docs-help.generated.js +21 -6
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-index.generated.js +514 -318
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
- package/src/lib/app-utils/docs-retrieval.js +14 -10
- package/src/lib/app-utils/docs-retrieval.js.map +1 -1
- package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
- package/src/lib/app-utils/duplicate-resource.js +7 -4
- package/src/lib/app-utils/duplicate-resource.js.map +1 -1
- package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
- package/src/lib/app-utils/element-ui.d.ts +1 -1
- package/src/lib/app-utils/element-ui.js +1 -1
- package/src/lib/app-utils/element-ui.js.map +1 -1
- package/src/lib/app-utils/email-starter-policy.js +3 -3
- package/src/lib/app-utils/email-starter-policy.js.map +1 -1
- package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +75 -6
- package/src/lib/app-utils/error-beacon.js +160 -21
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
- package/src/lib/app-utils/expand-repeatables.js +10 -10
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/host-events.d.ts +82 -0
- package/src/lib/app-utils/host-events.js +62 -0
- package/src/lib/app-utils/host-events.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +33 -1
- package/src/lib/app-utils/lockdown.js +45 -4
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/margin-utilization.d.ts +4 -69
- package/src/lib/app-utils/margin-utilization.js +82 -69
- package/src/lib/app-utils/margin-utilization.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
- package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/node-interactions.d.ts +7 -7
- package/src/lib/app-utils/node-interactions.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +78 -47
- package/src/lib/app-utils/notifications.js +63 -44
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/payments-configured.d.ts +8 -21
- package/src/lib/app-utils/payments-configured.js +8 -23
- package/src/lib/app-utils/payments-configured.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
- package/src/lib/app-utils/plan-entitlements.js +145 -304
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/platform-brand.d.ts +10 -0
- package/src/lib/app-utils/platform-brand.js +14 -3
- package/src/lib/app-utils/platform-brand.js.map +1 -1
- package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
- package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
- package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +14 -5
- package/src/lib/app-utils/release-flags.js +21 -125
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/repeat-sources.d.ts +9 -0
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/retained-refusals.d.ts +73 -0
- package/src/lib/app-utils/retained-refusals.js +186 -0
- package/src/lib/app-utils/retained-refusals.js.map +1 -0
- package/src/lib/app-utils/scope-tokens.d.ts +27 -0
- package/src/lib/app-utils/scope-tokens.js +61 -0
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +3 -3
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +15 -0
- package/src/lib/app-utils/screen-route.js +21 -1
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +132 -0
- package/src/lib/app-utils/seo-audit.js +246 -0
- package/src/lib/app-utils/seo-audit.js.map +1 -0
- package/src/lib/app-utils/seo-keywords.d.ts +48 -0
- package/src/lib/app-utils/seo-keywords.js +60 -0
- package/src/lib/app-utils/seo-keywords.js.map +1 -0
- package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
- package/src/lib/app-utils/seo-page-facts.js +155 -0
- package/src/lib/app-utils/seo-page-facts.js.map +1 -0
- package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
- package/src/lib/app-utils/seo-site-scan.js +162 -0
- package/src/lib/app-utils/seo-site-scan.js.map +1 -0
- package/src/lib/app-utils/seo-title-variables.js +1 -1
- package/src/lib/app-utils/seo-title-variables.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +6 -9
- package/src/lib/app-utils/server.js +17 -27
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-interactions.d.ts +501 -0
- package/src/lib/app-utils/site-interactions.js +554 -0
- package/src/lib/app-utils/site-interactions.js.map +1 -0
- package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
- package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +5 -2
- package/src/lib/app-utils/site-theme.js.map +1 -0
- package/src/lib/app-utils/sitemap.d.ts +4 -3
- package/src/lib/app-utils/sitemap.js +9 -7
- package/src/lib/app-utils/sitemap.js.map +1 -1
- package/src/lib/app-utils/stale-build-error.d.ts +51 -0
- package/src/lib/app-utils/stale-build-error.js +100 -1
- package/src/lib/app-utils/stale-build-error.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +20 -0
- package/src/lib/app-utils/starter-templates.js +49 -0
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
- package/src/lib/app-utils/theme-library.d.ts +10 -3
- package/src/lib/app-utils/theme-library.js +13 -4
- package/src/lib/app-utils/theme-library.js.map +1 -1
- package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
- package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
- package/src/lib/app-utils/utm-attribution.js.map +1 -0
- package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
- package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
- package/src/lib/app-utils/utm-forwarding.js.map +1 -0
- package/src/lib/app-utils/video-object.js +6 -5
- package/src/lib/app-utils/video-object.js.map +1 -1
- package/src/lib/app-utils/visitor-consent.d.ts +41 -164
- package/src/lib/app-utils/visitor-consent.js +66 -277
- package/src/lib/app-utils/visitor-consent.js.map +1 -1
- package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
- package/src/lib/app-utils/web-vitals-rum.js +31 -63
- package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
- package/src/lib/app-utils/where-used.d.ts +11 -6
- package/src/lib/app-utils/where-used.js +3 -8
- package/src/lib/app-utils/where-used.js.map +1 -1
- package/src/lib/canvas-manager/canvas-manager.js +3 -3
- package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
- package/src/lib/foundation/definitions/components.types.d.ts +1 -1
- package/src/lib/foundation/definitions/components.types.js +1 -1
- package/src/lib/foundation/definitions/components.types.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +18 -135
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +27 -5
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
- package/src/lib/plugin-manager/besigner-documents.js +61 -0
- package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
- package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
- package/src/lib/plugin-manager/computed-variables.js +74 -0
- package/src/lib/plugin-manager/computed-variables.js.map +1 -0
- package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
- package/src/lib/plugin-manager/custom-fields.js +47 -1
- package/src/lib/plugin-manager/custom-fields.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
- package/src/lib/plugin-manager/feature-plugins.js +105 -17
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
- package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-dependents.js +75 -0
- package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
- package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-distribution.js +45 -0
- package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
- package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
- package/src/lib/plugin-manager/plugin-record-index.js +51 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-record-written.js +42 -0
- package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-site-export.js +40 -0
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
- package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
- package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
- package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
- package/src/lib/plugin-manager/record-captured-contact.js +10 -32
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
- package/src/lib/plugin-manager/repeat-rows.js +108 -0
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
- package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
- package/src/lib/plugin-manager/submission-record-target.js +125 -0
- package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
- package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
- package/src/lib/plugin-manager/video-embed-provider.js +165 -0
- package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
- package/src/lib/types/nodes.d.ts +2 -2
- package/src/lib/types/nodes.js.map +1 -1
- package/src/lib/app-utils/campaign-attribution.js.map +0 -1
- package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
- package/src/lib/app-utils/contact-holder.d.ts +0 -68
- package/src/lib/app-utils/contact-holder.js +0 -129
- package/src/lib/app-utils/contact-holder.js.map +0 -1
- package/src/lib/app-utils/contact-merge.d.ts +0 -136
- package/src/lib/app-utils/contact-merge.js +0 -412
- package/src/lib/app-utils/contact-merge.js.map +0 -1
- package/src/lib/app-utils/crm-csv.d.ts +0 -239
- package/src/lib/app-utils/crm-csv.js +0 -467
- package/src/lib/app-utils/crm-csv.js.map +0 -1
- package/src/lib/app-utils/crm-digest.d.ts +0 -185
- package/src/lib/app-utils/crm-digest.js +0 -272
- package/src/lib/app-utils/crm-digest.js.map +0 -1
- package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
- package/src/lib/app-utils/form-dataset-binding.js +0 -74
- package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
- package/src/lib/app-utils/list-import.d.ts +0 -158
- package/src/lib/app-utils/list-import.js +0 -332
- package/src/lib/app-utils/list-import.js.map +0 -1
- package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
- package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
- package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
- package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
- package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
- package/src/lib/app-utils/marketplace-theme.js.map +0 -1
- package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
- package/src/lib/app-utils/marketplace-update-state.js +0 -132
- package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
- package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
- package/src/lib/app-utils/publisher-agreement.js +0 -321
- package/src/lib/app-utils/publisher-agreement.js.map +0 -1
- package/src/lib/app-utils/wistia-embed.d.ts +0 -104
- package/src/lib/app-utils/wistia-embed.js +0 -138
- package/src/lib/app-utils/wistia-embed.js.map +0 -1
- package/src/lib/app-utils/workflows.d.ts +0 -136
- package/src/lib/app-utils/workflows.js +0 -346
- package/src/lib/app-utils/workflows.js.map +0 -1
- package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
- package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
- package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
// serves it — so it may import neither entry barrel, for the reason
|
|
18
18
|
// `usage-metering.ts` states at its own head. The specific modules underneath
|
|
19
19
|
// are safe in both.
|
|
20
|
-
import { assistCreditsFromUsd } from "./assist-credits.js";
|
|
21
20
|
import { hasAiAddon, INFRA_COGS_PER_SITE_USD, MARGIN_SCOPE_NOTE, netMarginRating, PLAN_PRICING, orgListPriceMonthlyUsd, orgMonthlyCogsUsd, orgMonthlyRevenueUsd, orgNetMonthlyRevenueUsd, orgSiteCount, pageViewsFromBandwidthGb, resolveOrgEntitlements, resolveEffectivePlan } from "./plan-entitlements.js";
|
|
21
|
+
import { planQuotaOf } from "../plugin-manager/plugin-plan-entitlements.js";
|
|
22
|
+
import { declaredMeterReading, pluginUsageBands } from "../plugin-manager/plugin-usage-axes.js";
|
|
22
23
|
export { MARGIN_SCOPE_NOTE };
|
|
23
24
|
/**
|
|
24
25
|
* WHAT SHARE OF THE BANDS THEY BOUGHT DO CUSTOMERS ACTUALLY SPEND?
|
|
@@ -71,47 +72,68 @@ export { MARGIN_SCOPE_NOTE };
|
|
|
71
72
|
* `tier-margin-floor.spec.ts` costs it unexpanded. Following either of those
|
|
72
73
|
* and not the name is what keeps this table agreeing with both.
|
|
73
74
|
*/ /**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
75
|
+
* The platform's own bands, at the positions the plugins' declared bands
|
|
76
|
+
* interleave with (`order`, a multiple of 10 here) — every meter
|
|
77
|
+
* `orgMonthlyCogsUsd` prices that is the platform's, plus `hosts`.
|
|
78
|
+
*/ const CORE_BANDS = [
|
|
79
|
+
{
|
|
80
|
+
id: 'hosts',
|
|
81
|
+
label: 'Sites',
|
|
82
|
+
order: 10
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'storageGb',
|
|
86
|
+
label: 'Media storage',
|
|
87
|
+
order: 20
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'pageViews',
|
|
91
|
+
label: 'Bandwidth (page views)',
|
|
92
|
+
order: 30
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'dataStorageMb',
|
|
96
|
+
label: 'Dataset storage',
|
|
97
|
+
order: 50
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'apiRequests',
|
|
101
|
+
label: 'API requests',
|
|
102
|
+
order: 60
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'emailSends',
|
|
106
|
+
label: 'Email sends',
|
|
107
|
+
order: 80
|
|
108
|
+
}
|
|
99
109
|
];
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Every band the table reads, in column order: the platform's, and each one a
|
|
112
|
+
* plugin declares beside the meter it contributes (`plugin-usage-axes.ts`) —
|
|
113
|
+
* the AI plugin's credits, the CRM's records, the forms plugin's
|
|
114
|
+
* submissions, the workflows plugin's runs.
|
|
115
|
+
*
|
|
116
|
+
* A plugin band sold in a unit OF cost is measured in that unit rather than
|
|
117
|
+
* in the dollars the rollup stores: the AI plugin's credits are the dollars
|
|
118
|
+
* over its declared unit cost, rounded up, which is the one conversion the
|
|
119
|
+
* customer's own meter uses. It is also the band most worth watching —
|
|
120
|
+
* provider spend is the only line item on the platform whose unit cost is
|
|
121
|
+
* real money paid to a third party, and the only one that can clear the
|
|
122
|
+
* $2/site floor on its own.
|
|
123
|
+
*/ const BANDS = [
|
|
124
|
+
...CORE_BANDS,
|
|
125
|
+
...pluginUsageBands().map((declared)=>({
|
|
126
|
+
id: declared.id,
|
|
127
|
+
label: declared.label,
|
|
128
|
+
order: declared.order,
|
|
129
|
+
declared
|
|
130
|
+
}))
|
|
131
|
+
].sort((a, b)=>a.order - b.order);
|
|
132
|
+
/** Every band, keyed by what it measures, in column order. */ export const UTILIZATION_BANDS = BANDS.map((band)=>band.id);
|
|
133
|
+
/** Column headings, so the page and the aggregate name a band identically. */ export const UTILIZATION_BAND_LABELS = Object.fromEntries(BANDS.map((band)=>[
|
|
134
|
+
band.id,
|
|
135
|
+
band.label
|
|
136
|
+
]));
|
|
115
137
|
const finite = (value)=>{
|
|
116
138
|
const parsed = Number(value != null ? value : 0);
|
|
117
139
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
@@ -128,12 +150,11 @@ const finite = (value)=>{
|
|
|
128
150
|
*/ export function orgIncludedBands(org) {
|
|
129
151
|
const entitlements = resolveOrgEntitlements(org);
|
|
130
152
|
const hostLimit = Math.max(1, entitlements.hostLimit);
|
|
131
|
-
|
|
153
|
+
const included = {
|
|
132
154
|
hosts: entitlements.hostLimit,
|
|
133
155
|
// Per host, expanded by the host limit — `meteredIncludedAllowance`
|
|
134
|
-
// expands
|
|
156
|
+
// expands the per-host bands and nothing else.
|
|
135
157
|
storageGb: hostLimit * entitlements.storagePerHostMb / 1024,
|
|
136
|
-
formSubmissions: hostLimit * entitlements.formSubmissionsPerMonth,
|
|
137
158
|
// Bandwidth IS the page-view band, expressed in the unit customers buy.
|
|
138
159
|
pageViews: pageViewsFromBandwidthGb(entitlements.bandwidthGb),
|
|
139
160
|
// Org-wide, in the unit the rollup stores. `dataStorageMbPerOrg` is
|
|
@@ -141,18 +162,15 @@ const finite = (value)=>{
|
|
|
141
162
|
// `orgMonthlyCogsUsd` and must not happen twice.
|
|
142
163
|
dataStorageMb: entitlements.dataStorageMbPerOrg,
|
|
143
164
|
apiRequests: entitlements.apiRequestsPerMonth,
|
|
144
|
-
|
|
145
|
-
contactsCount: entitlements.contactsPerHost,
|
|
146
|
-
emailSends: entitlements.emailSendsPerMonth,
|
|
147
|
-
// Never `UNLIMITED` on any plan, deliberately — Enterprise carries a
|
|
148
|
-
// finite default. The band is a third-party liability rather than capacity
|
|
149
|
-
// the platform already owns, so an uncapped one would be an uncapped bill.
|
|
150
|
-
assistCredits: entitlements.assistCreditsPerMonth,
|
|
151
|
-
// Two bands, one rate: the cost model prices both counters at `perRun`,
|
|
152
|
-
// and the utilization is read against the band each builder sells.
|
|
153
|
-
workflowRuns: entitlements.workflowRunsPerMonth,
|
|
154
|
-
actionRuns: entitlements.actionRunsPerMonth
|
|
165
|
+
emailSends: entitlements.emailSendsPerMonth
|
|
155
166
|
};
|
|
167
|
+
// A plugin's band reads the entitlement its declaration names — absent is
|
|
168
|
+
// nothing included — expanded by the host limit where it is per site.
|
|
169
|
+
for (const band of pluginUsageBands()){
|
|
170
|
+
const figure = planQuotaOf(entitlements, band.entitlement);
|
|
171
|
+
included[band.id] = band.perHost ? hostLimit * figure : figure;
|
|
172
|
+
}
|
|
173
|
+
return included;
|
|
156
174
|
}
|
|
157
175
|
/** One band's reading. The whole of the `UNLIMITED` and zero-band rule. */ export function bandUtilization(band, used, included) {
|
|
158
176
|
const usedSafe = Number.isFinite(used) && used > 0 ? used : 0;
|
|
@@ -190,26 +208,21 @@ const finite = (value)=>{
|
|
|
190
208
|
* site added since the cron last ran is a site the org has. The rollup's own
|
|
191
209
|
* `hostCount` is a snapshot of the same thing at cron time.
|
|
192
210
|
*/ function bandUsage(org, rollup) {
|
|
193
|
-
|
|
194
|
-
return {
|
|
211
|
+
const used = {
|
|
195
212
|
hosts: orgSiteCount(org),
|
|
196
213
|
storageGb: finite(rollup == null ? void 0 : rollup.storageGb),
|
|
197
214
|
pageViews: finite(rollup == null ? void 0 : rollup.pageViews),
|
|
198
|
-
formSubmissions: finite(rollup == null ? void 0 : rollup.formSubmissions),
|
|
199
215
|
dataStorageMb: finite(rollup == null ? void 0 : rollup.dataStorageMb),
|
|
200
216
|
apiRequests: finite(rollup == null ? void 0 : rollup.apiRequests),
|
|
201
|
-
|
|
202
|
-
// only on a row written before the band was widened — the same rule
|
|
203
|
-
// `orgMonthlyCogsUsd` applies, so utilization and cost read one number.
|
|
204
|
-
contactsCount: finite((_ref = rollup == null ? void 0 : rollup.crmRecordsCount) != null ? _ref : rollup == null ? void 0 : rollup.contactsCount),
|
|
205
|
-
emailSends: finite(rollup == null ? void 0 : rollup.emailSends),
|
|
206
|
-
// Dollars on the rollup, credits on the band. `assistCreditsFromUsd` is
|
|
207
|
-
// the ONE conversion between them, so this figure and the one the customer
|
|
208
|
-
// sees on their own billing page cannot disagree about the same month.
|
|
209
|
-
assistCredits: assistCreditsFromUsd(finite(rollup == null ? void 0 : rollup.assistCostUsd)),
|
|
210
|
-
workflowRuns: finite(rollup == null ? void 0 : rollup.workflowRuns),
|
|
211
|
-
actionRuns: finite(rollup == null ? void 0 : rollup.actionRuns)
|
|
217
|
+
emailSends: finite(rollup == null ? void 0 : rollup.emailSends)
|
|
212
218
|
};
|
|
219
|
+
for (const band of pluginUsageBands()){
|
|
220
|
+
const reading = declaredMeterReading(rollup, band);
|
|
221
|
+
// Dollars on the rollup, a unit of cost on the band: rounded UP, so spend
|
|
222
|
+
// already made never reads as less of the band.
|
|
223
|
+
used[band.id] = band.unitCostUsd && reading > 0 ? Math.ceil(reading / band.unitCostUsd) : reading;
|
|
224
|
+
}
|
|
225
|
+
return used;
|
|
213
226
|
}
|
|
214
227
|
/**
|
|
215
228
|
* One org's realised utilization, cost and margin.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/margin-utilization.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// Imported from BOTH graphs — the staff page (client) and the App Route that\n// serves it — so it may import neither entry barrel, for the reason\n// `usage-metering.ts` states at its own head. The specific modules underneath\n// are safe in both.\nimport type { AglynOrgBilling, OrgPlan } from '../foundation'\nimport { assistCreditsFromUsd } from './assist-credits'\nimport {\n hasAiAddon,\n INFRA_COGS_PER_SITE_USD,\n MARGIN_SCOPE_NOTE,\n netMarginRating,\n PLAN_PRICING,\n orgListPriceMonthlyUsd,\n orgMonthlyCogsUsd,\n orgMonthlyRevenueUsd,\n orgNetMonthlyRevenueUsd,\n orgSiteCount,\n pageViewsFromBandwidthGb,\n resolveOrgEntitlements,\n resolveEffectivePlan,\n type DiscountMarginRating,\n type OrgCogsResult,\n type OrgUsageRollupInput,\n} from './plan-entitlements'\n\nexport { MARGIN_SCOPE_NOTE }\n\n/**\n * WHAT SHARE OF THE BANDS THEY BOUGHT DO CUSTOMERS ACTUALLY SPEND?\n *\n * `tier-margin-floor.spec.ts` proves what every tier costs at 100% of every\n * band, and pins the margin at 3 / 25 / 50 / 100%. Which of those four columns\n * the business is actually operating in has never been measured — and the\n * spread between them is the whole question, because on Pro the same price\n * list yields 89.3% at the 3% column and 7.1% at the 100% one.\n *\n * Everything needed to answer it was already recorded and already priced. The\n * monthly rollup carries the meters, `ORG_COGS_UNIT_RATES_USD` carries their\n * unit costs, and `orgMonthlyCogsUsd` already turns one into the other. What\n * did not exist was the DIVISION: usage over the band the plan sold, per org\n * and across the fleet.\n *\n * This module is that division, and nothing else. It runs no query, holds no\n * rate of its own and re-derives no cost — every dollar here comes back out of\n * `orgMonthlyCogsUsd`, so a surface built on it cannot come to disagree with\n * the discount guardrail about what an org costs.\n *\n * ## The three answers a band can give, and why two of them are not numbers\n *\n * A percentage needs a denominator, and two of the bands in `PLAN_ENTITLEMENTS`\n * do not supply one:\n *\n * * `UNLIMITED` — `Number.POSITIVE_INFINITY`. Enterprise carries it on\n * contacts, API requests and dataset storage. `used / Infinity` is 0, which\n * would render the platform's largest customers as its lightest users; and\n * a model that clamps to 100% instead would render them as its heaviest.\n * Both are inventions. The band is UNCAPPED, the fraction is `null`, and no\n * aggregate may average it in either direction.\n * * `0` — Free carries it on email sends, API requests and dataset storage.\n * `0 / 0` is `NaN` and `n / 0` is `Infinity`. Neither is a utilization. The\n * band admits nothing, so there is no allowance to be a fraction of, and an\n * org that spent the meter anyway did so with no included band behind it —\n * which is a cost signal in its own right, reported as\n * `usageWithNoAllowance` rather than folded into a percentile.\n *\n * The two are kept DISTINCT from each other and from `0%`. An uncapped band\n * and an exhausted one are opposite facts, and the recurring defect in this\n * repo's cost work is exactly that an absent figure reads as a zero one.\n *\n * ## What the bands are\n *\n * `meteredIncludedAllowance` sizes three of them and is the shape followed\n * here: the two PER-HOST bands are expanded by `hostLimit`, the rest are\n * org-wide as stored. `contactsPerHost` is org-wide DESPITE ITS NAME —\n * `checkContactQuota` compares it against an org-wide headcount, and\n * `tier-margin-floor.spec.ts` costs it unexpanded. Following either of those\n * and not the name is what keeps this table agreeing with both.\n */\n\n/**\n * A meter with an included band, keyed by the ROLLUP field it is measured by.\n *\n * Every meter `orgMonthlyCogsUsd` prices, plus `hosts` and `assistCredits`.\n *\n * Assist is measured in CREDITS rather than in the dollars the rollup stores.\n * `assistCostUsd` is our provider bill and `assistCreditsPerMonth` is the band\n * the plan sells, so the two are only comparable through\n * `assistCreditsFromUsd` — the one conversion between the units, and the same\n * one the customer-facing meter uses.\n *\n * It is also the band most worth watching: Assist is the only line item on the\n * platform whose unit cost is real money paid to a third party, and the only\n * one that can clear the $2/site floor on its own.\n */\nexport const UTILIZATION_BANDS = [\n 'hosts',\n 'storageGb',\n 'pageViews',\n 'formSubmissions',\n 'dataStorageMb',\n 'apiRequests',\n 'contactsCount',\n 'emailSends',\n 'assistCredits',\n 'workflowRuns',\n 'actionRuns',\n] as const\n\nexport type UtilizationBand = (typeof UTILIZATION_BANDS)[number]\n\n/** Column headings, so the page and the aggregate name a band identically. */\nexport const UTILIZATION_BAND_LABELS: Record<UtilizationBand, string> = {\n hosts: 'Sites',\n storageGb: 'Media storage',\n pageViews: 'Bandwidth (page views)',\n formSubmissions: 'Form submissions',\n dataStorageMb: 'Dataset storage',\n apiRequests: 'API requests',\n // The band under the rollup's `contactsCount` key widened to contacts,\n // companies and deals (AGL-2611); the column says what it measures.\n contactsCount: 'CRM records',\n emailSends: 'Email sends',\n assistCredits: 'Assist credits',\n workflowRuns: 'Workflow runs',\n actionRuns: 'Action runs',\n}\n\n/**\n * Why a band has no percentage, or that it has one.\n *\n * `uncapped` and `noAllowance` are the two non-numbers described at the head\n * of this module. They are separate values rather than one `null` because the\n * remedies are opposite: an uncapped band is a pricing exposure, an exhausted\n * one is an upgrade conversation.\n */\nexport type BandState = 'measured' | 'uncapped' | 'noAllowance'\n\nexport interface BandUtilization {\n band: UtilizationBand\n /** The org-wide included allowance. `Infinity` when uncapped, 0 when none. */\n included: number\n /** What the rollup recorded. Always a finite, non-negative number. */\n used: number\n state: BandState\n /**\n * Share of the band consumed, as a fraction of 1.\n *\n * `null` for every state but `measured`, and callers must render it as\n * neither 0 nor 1. It is NOT clamped: an org past its included band reads\n * above 1, which is the number worth seeing.\n */\n fraction: number | null\n}\n\n/** One org's realised position: what it consumed, what it cost, what it paid. */\nexport interface OrgMarginRow {\n orgId: string\n name: string | null\n plan: OrgPlan\n /** The rollup month these figures describe, or null when none has run. */\n month: string | null\n bands: Record<UtilizationBand, BandUtilization>\n /**\n * The shared cost model's answer — the same function and the same fields\n * `/api/admin/org-discount` underwrites against.\n */\n cogs: OrgCogsResult\n /** Pre-discount sticker price. */\n listPriceUsd: number\n /** List price net of any per-org discount. */\n mrrUsd: number\n /** MRR net of Stripe's processing fee — the base the margin is taken on. */\n netRevenueUsd: number\n /**\n * The AI add-on's share of `listPriceUsd` (AGL-2930) — already INSIDE\n * `listPriceUsd`, `mrrUsd` and `netRevenueUsd`, because\n * `orgListPriceMonthlyUsd` folds every purchased add-on. Named here so the\n * surface can show that the assist band's cost has revenue against it\n * rather than reading as pure margin drag; adding it again would count it\n * twice. 0 on a custom-priced deal, whose quote already folds the add-on\n * into one negotiated figure.\n */\n aiAddonRevenueUsd: number\n /**\n * `(net revenue − COGS) / net revenue`, the same arithmetic\n * `checkDiscountMargin` rates a discount on. See `MARGIN_SCOPE_NOTE`: it is\n * a contribution margin, not a profit.\n *\n * `null` when the org bills nothing. A free or comped org has no revenue to\n * take a fraction of, and reporting it as 0% or −100% would put a customer\n * who was never meant to be profitable at the top of a list whose whole\n * purpose is finding the one who was.\n */\n marginPct: number | null\n rating: DiscountMarginRating | null\n}\n\n/**\n * A rollup as this surface reads it: every field `orgMonthlyCogsUsd` prices.\n *\n * The two run counters used to be the surface's own extension of that shape\n * — recorded and banded but unpriced. Since 2026-09-07 the cost model prices\n * them at `perRun`, so the shape is the model's and nothing here is extra.\n */\nexport type UtilizationRollup = OrgUsageRollupInput\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\n/**\n * The org-wide included band for every metered axis, on this org's plan and\n * with this org's overrides and add-ons applied.\n *\n * `resolveOrgEntitlements` rather than `PLAN_ENTITLEMENTS[plan]`: a staff\n * entitlement override and a purchased host add-on both change what the\n * customer actually bought, and measuring consumption against the catalogue\n * row instead of the resolved one is the same class of error as measuring it\n * against a constant.\n */\nexport function orgIncludedBands(\n org: Partial<AglynOrgBilling> | null | undefined,\n): Record<UtilizationBand, number> {\n const entitlements = resolveOrgEntitlements(org)\n const hostLimit = Math.max(1, entitlements.hostLimit)\n return {\n hosts: entitlements.hostLimit,\n // Per host, expanded by the host limit — `meteredIncludedAllowance`\n // expands these two and nothing else.\n storageGb: (hostLimit * entitlements.storagePerHostMb) / 1024,\n formSubmissions: hostLimit * entitlements.formSubmissionsPerMonth,\n // Bandwidth IS the page-view band, expressed in the unit customers buy.\n pageViews: pageViewsFromBandwidthGb(entitlements.bandwidthGb),\n // Org-wide, in the unit the rollup stores. `dataStorageMbPerOrg` is\n // megabytes and so is `dataStorageMb`; the GB conversion happens inside\n // `orgMonthlyCogsUsd` and must not happen twice.\n dataStorageMb: entitlements.dataStorageMbPerOrg,\n apiRequests: entitlements.apiRequestsPerMonth,\n // Org-wide despite the name — see the module note.\n contactsCount: entitlements.contactsPerHost,\n emailSends: entitlements.emailSendsPerMonth,\n // Never `UNLIMITED` on any plan, deliberately — Enterprise carries a\n // finite default. The band is a third-party liability rather than capacity\n // the platform already owns, so an uncapped one would be an uncapped bill.\n assistCredits: entitlements.assistCreditsPerMonth,\n // Two bands, one rate: the cost model prices both counters at `perRun`,\n // and the utilization is read against the band each builder sells.\n workflowRuns: entitlements.workflowRunsPerMonth,\n actionRuns: entitlements.actionRunsPerMonth,\n }\n}\n\n/** One band's reading. The whole of the `UNLIMITED` and zero-band rule. */\nexport function bandUtilization(\n band: UtilizationBand,\n used: number,\n included: number,\n): BandUtilization {\n const usedSafe = Number.isFinite(used) && used > 0 ? used : 0\n if (!Number.isFinite(included)) {\n return { band, included: Number.POSITIVE_INFINITY, used: usedSafe, state: 'uncapped', fraction: null }\n }\n if (!(included > 0)) {\n return { band, included: 0, used: usedSafe, state: 'noAllowance', fraction: null }\n }\n return { band, included, used: usedSafe, state: 'measured', fraction: usedSafe / included }\n}\n\n/**\n * What the rollup recorded, per band.\n *\n * `hosts` comes from the ORG rather than the rollup: `orgSiteCount` is what\n * the cost floor is charged on and what `checkDiscountMargin` counts, and a\n * site added since the cron last ran is a site the org has. The rollup's own\n * `hostCount` is a snapshot of the same thing at cron time.\n */\nfunction bandUsage(\n org: Partial<AglynOrgBilling> | null | undefined,\n rollup: UtilizationRollup | null | undefined,\n): Record<UtilizationBand, number> {\n return {\n hosts: orgSiteCount(org),\n storageGb: finite(rollup?.storageGb),\n pageViews: finite(rollup?.pageViews),\n formSubmissions: finite(rollup?.formSubmissions),\n dataStorageMb: finite(rollup?.dataStorageMb),\n apiRequests: finite(rollup?.apiRequests),\n // The records band's figure (AGL-2611), falling back to contacts alone\n // only on a row written before the band was widened — the same rule\n // `orgMonthlyCogsUsd` applies, so utilization and cost read one number.\n contactsCount: finite(rollup?.crmRecordsCount ?? rollup?.contactsCount),\n emailSends: finite(rollup?.emailSends),\n // Dollars on the rollup, credits on the band. `assistCreditsFromUsd` is\n // the ONE conversion between them, so this figure and the one the customer\n // sees on their own billing page cannot disagree about the same month.\n assistCredits: assistCreditsFromUsd(finite(rollup?.assistCostUsd)),\n workflowRuns: finite(rollup?.workflowRuns),\n actionRuns: finite(rollup?.actionRuns),\n }\n}\n\nexport interface OrgMarginInput {\n orgId: string\n name?: string | null\n /** The merged `{...orgDoc, ...billingDoc}` every revenue helper reads. */\n org: Partial<AglynOrgBilling> | null | undefined\n rollup: UtilizationRollup | null | undefined\n month?: string | null\n}\n\n/**\n * One org's realised utilization, cost and margin.\n *\n * The cost is `orgMonthlyCogsUsd`'s, unmodified, floor included — the same\n * `max(measured, $2 × sites)` the guardrail applies. On production today that\n * floor is what every org's figure is, which is worth reading off the `basis`\n * field rather than assuming the meters are driving anything yet.\n */\nexport function orgMarginRow(input: OrgMarginInput): OrgMarginRow {\n const { org, rollup } = input\n const included = orgIncludedBands(org)\n const used = bandUsage(org, rollup)\n const bands = Object.fromEntries(\n UTILIZATION_BANDS.map((band) => [band, bandUtilization(band, used[band], included[band])]),\n ) as Record<UtilizationBand, BandUtilization>\n\n const cogs = orgMonthlyCogsUsd(rollup, orgSiteCount(org))\n const listPriceUsd = orgListPriceMonthlyUsd(org)\n const mrrUsd = orgMonthlyRevenueUsd(org)\n const netRevenueUsd = orgNetMonthlyRevenueUsd(org)\n // Only when the org bills something at all — `listPriceUsd` is 0 for a\n // comped or lapsed org whatever its `seatAddons` say, and the add-on's\n // share of nothing is nothing. `hasAiAddon` already drops a dead\n // subscription's add-on; the list-price check catches the comped one.\n const plan = resolveEffectivePlan(org)\n const customPriced =\n typeof org?.subscription?.customMonthlyUsd === 'number' &&\n org.subscription.customMonthlyUsd > 0\n const aiAddonRevenueUsd =\n listPriceUsd > 0 && !customPriced && hasAiAddon(org)\n ? finite(PLAN_PRICING[plan]?.aiAddonMonthlyUsd)\n : 0\n // A margin needs revenue to be a fraction OF. Free and comped orgs have\n // none, and inventing one for them fills the top of a worst-first list with\n // customers who were never billed.\n const marginPct =\n netRevenueUsd > 0\n ? Math.round(((netRevenueUsd - cogs.cogsUsd) / netRevenueUsd) * 10000) / 10000\n : null\n\n return {\n orgId: input.orgId,\n name: input.name ?? null,\n plan,\n month: input.month ?? null,\n bands,\n cogs,\n listPriceUsd,\n mrrUsd,\n netRevenueUsd,\n aiAddonRevenueUsd,\n marginPct,\n rating: marginPct === null ? null : netMarginRating(marginPct),\n }\n}\n\n/**\n * Nearest-rank percentile over a sample sorted ascending.\n *\n * `null` for an empty sample. A median of nothing is not zero, and the fleet\n * view exists to answer a question that has no answer yet for most bands.\n */\nexport function percentile(sorted: readonly number[], p: number): number | null {\n if (!sorted.length) return null\n const rank = Math.ceil(p * sorted.length)\n return sorted[Math.min(sorted.length - 1, Math.max(0, rank - 1))]\n}\n\nexport interface BandDistribution {\n band: UtilizationBand\n /**\n * Orgs a percentage EXISTS for — a finite, positive band. Every percentile\n * below is over this sample and no other.\n */\n counted: number\n /** Excluded because the band is `UNLIMITED`. Not 0%, not 100%: no answer. */\n excludedUncapped: number\n /** Excluded because the plan includes none of this meter. */\n excludedNoAllowance: number\n /**\n * Orgs with no included allowance that spent the meter anyway — cost with no\n * band behind it. Excluded from the percentiles, reported on its own.\n */\n usageWithNoAllowance: number\n p50: number | null\n p25: number | null\n p75: number | null\n p90: number | null\n max: number | null\n /** Orgs at or past 100% of the band, out of `counted`. */\n overBand: number\n}\n\nexport interface FleetUtilization {\n /** Orgs folded in — every row handed to it, whatever its plan. */\n orgs: number\n /** Of those, how many had a usage rollup at all. */\n withRollup: number\n distributions: BandDistribution[]\n /** Median contribution margin across orgs that bill something. */\n medianMarginPct: number | null\n /** Orgs billing something whose margin sits under `NET_MARGIN_FLOOR_PCT`. */\n orgsUnderFloor: number\n /** Orgs billing something whose COGS exceeds their net revenue. */\n orgsUnderwater: number\n /** Total measured COGS and net revenue across the fold, in USD. */\n totalCogsUsd: number\n totalNetRevenueUsd: number\n /** Of `totalNetRevenueUsd`'s gross, how much the AI add-on brought in. */\n totalAiAddonRevenueUsd: number\n}\n\n/**\n * The fleet answer: the median and spread of utilization per band.\n *\n * This is the figure every margin projection has been assuming. It is folded\n * over whatever rows the caller read — see the route's truncation disclosure,\n * because a distribution over a partial fleet is a distribution over a partial\n * fleet and must say so.\n *\n * An org WITHOUT a rollup still contributes its `hosts` band (the org doc\n * knows its own sites) and is excluded from the other seven by having no\n * measurement, not by being scored at zero. `withRollup` is what says how much\n * of the fold is real.\n */\nexport function fleetUtilization(rows: readonly OrgMarginRow[]): FleetUtilization {\n const distributions = UTILIZATION_BANDS.map<BandDistribution>((band) => {\n const sample: number[] = []\n let excludedUncapped = 0\n let excludedNoAllowance = 0\n let usageWithNoAllowance = 0\n for (const row of rows) {\n const reading = row.bands[band]\n if (!reading) continue\n // A band the cron has never measured contributes nothing rather than a\n // zero: `hosts` is known without a rollup, the seven meters are not.\n if (band !== 'hosts' && row.month === null) continue\n if (reading.state === 'uncapped') {\n excludedUncapped += 1\n continue\n }\n if (reading.state === 'noAllowance') {\n excludedNoAllowance += 1\n if (reading.used > 0) usageWithNoAllowance += 1\n continue\n }\n sample.push(reading.fraction as number)\n }\n sample.sort((a, b) => a - b)\n return {\n band,\n counted: sample.length,\n excludedUncapped,\n excludedNoAllowance,\n usageWithNoAllowance,\n p50: percentile(sample, 0.5),\n p25: percentile(sample, 0.25),\n p75: percentile(sample, 0.75),\n p90: percentile(sample, 0.9),\n max: sample.length ? sample[sample.length - 1] : null,\n overBand: sample.filter((fraction) => fraction >= 1).length,\n }\n })\n\n const margins = rows\n .map((row) => row.marginPct)\n .filter((margin): margin is number => margin !== null)\n .sort((a, b) => a - b)\n\n return {\n orgs: rows.length,\n withRollup: rows.filter((row) => row.month !== null).length,\n distributions,\n medianMarginPct: percentile(margins, 0.5),\n orgsUnderFloor: margins.filter((margin) => netMarginRating(margin) !== 'ok').length,\n orgsUnderwater: margins.filter((margin) => margin < 0).length,\n totalCogsUsd: Math.round(rows.reduce((sum, row) => sum + row.cogs.cogsUsd, 0) * 100) / 100,\n totalNetRevenueUsd:\n Math.round(rows.reduce((sum, row) => sum + row.netRevenueUsd, 0) * 100) / 100,\n totalAiAddonRevenueUsd:\n Math.round(\n rows.reduce((sum, row) => sum + (row.aiAddonRevenueUsd ?? 0), 0) * 100,\n ) / 100,\n }\n}\n\n/**\n * Worst margin first — the ordering the surface exists for.\n *\n * An org with no revenue has no margin and sorts to the END regardless. It\n * cannot be the unprofitable customer this list is looking for, and putting\n * the free tier at the top would bury the one org that is.\n */\nexport function byWorstMargin(a: OrgMarginRow, b: OrgMarginRow): number {\n if (a.marginPct === null && b.marginPct === null) return b.cogs.cogsUsd - a.cogs.cogsUsd\n if (a.marginPct === null) return 1\n if (b.marginPct === null) return -1\n return a.marginPct - b.marginPct || b.cogs.cogsUsd - a.cogs.cogsUsd\n}\n\n/** Re-exported so the page states the floor it is colouring against. */\nexport { INFRA_COGS_PER_SITE_USD }\n"],"names":["assistCreditsFromUsd","hasAiAddon","INFRA_COGS_PER_SITE_USD","MARGIN_SCOPE_NOTE","netMarginRating","PLAN_PRICING","orgListPriceMonthlyUsd","orgMonthlyCogsUsd","orgMonthlyRevenueUsd","orgNetMonthlyRevenueUsd","orgSiteCount","pageViewsFromBandwidthGb","resolveOrgEntitlements","resolveEffectivePlan","UTILIZATION_BANDS","UTILIZATION_BAND_LABELS","hosts","storageGb","pageViews","formSubmissions","dataStorageMb","apiRequests","contactsCount","emailSends","assistCredits","workflowRuns","actionRuns","finite","value","parsed","Number","isFinite","orgIncludedBands","org","entitlements","hostLimit","Math","max","storagePerHostMb","formSubmissionsPerMonth","bandwidthGb","dataStorageMbPerOrg","apiRequestsPerMonth","contactsPerHost","emailSendsPerMonth","assistCreditsPerMonth","workflowRunsPerMonth","actionRunsPerMonth","bandUtilization","band","used","included","usedSafe","POSITIVE_INFINITY","state","fraction","bandUsage","rollup","crmRecordsCount","assistCostUsd","orgMarginRow","input","bands","Object","fromEntries","map","cogs","listPriceUsd","mrrUsd","netRevenueUsd","plan","customPriced","subscription","customMonthlyUsd","aiAddonRevenueUsd","aiAddonMonthlyUsd","marginPct","round","cogsUsd","orgId","name","month","rating","percentile","sorted","p","length","rank","ceil","min","fleetUtilization","rows","distributions","sample","excludedUncapped","excludedNoAllowance","usageWithNoAllowance","row","reading","push","sort","a","b","counted","p50","p25","p75","p90","overBand","filter","margins","margin","orgs","withRollup","medianMarginPct","orgsUnderFloor","orgsUnderwater","totalCogsUsd","reduce","sum","totalNetRevenueUsd","totalAiAddonRevenueUsd","byWorstMargin"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/margin-utilization.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// Imported from BOTH graphs — the staff page (client) and the App Route that\n// serves it — so it may import neither entry barrel, for the reason\n// `usage-metering.ts` states at its own head. The specific modules underneath\n// are safe in both.\nimport type { AglynOrgBilling, OrgPlan } from '../foundation'\nimport {\n hasAiAddon,\n INFRA_COGS_PER_SITE_USD,\n MARGIN_SCOPE_NOTE,\n netMarginRating,\n PLAN_PRICING,\n orgListPriceMonthlyUsd,\n orgMonthlyCogsUsd,\n orgMonthlyRevenueUsd,\n orgNetMonthlyRevenueUsd,\n orgSiteCount,\n pageViewsFromBandwidthGb,\n resolveOrgEntitlements,\n resolveEffectivePlan,\n type DiscountMarginRating,\n type OrgCogsResult,\n type OrgUsageRollupInput,\n} from './plan-entitlements'\nimport { planQuotaOf } from '../plugin-manager/plugin-plan-entitlements'\nimport {\n declaredMeterReading,\n pluginUsageBands,\n type ResolvedPluginUsageBand,\n} from '../plugin-manager/plugin-usage-axes'\n\nexport { MARGIN_SCOPE_NOTE }\n\n/**\n * WHAT SHARE OF THE BANDS THEY BOUGHT DO CUSTOMERS ACTUALLY SPEND?\n *\n * `tier-margin-floor.spec.ts` proves what every tier costs at 100% of every\n * band, and pins the margin at 3 / 25 / 50 / 100%. Which of those four columns\n * the business is actually operating in has never been measured — and the\n * spread between them is the whole question, because on Pro the same price\n * list yields 89.3% at the 3% column and 7.1% at the 100% one.\n *\n * Everything needed to answer it was already recorded and already priced. The\n * monthly rollup carries the meters, `ORG_COGS_UNIT_RATES_USD` carries their\n * unit costs, and `orgMonthlyCogsUsd` already turns one into the other. What\n * did not exist was the DIVISION: usage over the band the plan sold, per org\n * and across the fleet.\n *\n * This module is that division, and nothing else. It runs no query, holds no\n * rate of its own and re-derives no cost — every dollar here comes back out of\n * `orgMonthlyCogsUsd`, so a surface built on it cannot come to disagree with\n * the discount guardrail about what an org costs.\n *\n * ## The three answers a band can give, and why two of them are not numbers\n *\n * A percentage needs a denominator, and two of the bands in `PLAN_ENTITLEMENTS`\n * do not supply one:\n *\n * * `UNLIMITED` — `Number.POSITIVE_INFINITY`. Enterprise carries it on\n * contacts, API requests and dataset storage. `used / Infinity` is 0, which\n * would render the platform's largest customers as its lightest users; and\n * a model that clamps to 100% instead would render them as its heaviest.\n * Both are inventions. The band is UNCAPPED, the fraction is `null`, and no\n * aggregate may average it in either direction.\n * * `0` — Free carries it on email sends, API requests and dataset storage.\n * `0 / 0` is `NaN` and `n / 0` is `Infinity`. Neither is a utilization. The\n * band admits nothing, so there is no allowance to be a fraction of, and an\n * org that spent the meter anyway did so with no included band behind it —\n * which is a cost signal in its own right, reported as\n * `usageWithNoAllowance` rather than folded into a percentile.\n *\n * The two are kept DISTINCT from each other and from `0%`. An uncapped band\n * and an exhausted one are opposite facts, and the recurring defect in this\n * repo's cost work is exactly that an absent figure reads as a zero one.\n *\n * ## What the bands are\n *\n * `meteredIncludedAllowance` sizes three of them and is the shape followed\n * here: the two PER-HOST bands are expanded by `hostLimit`, the rest are\n * org-wide as stored. `contactsPerHost` is org-wide DESPITE ITS NAME —\n * `checkContactQuota` compares it against an org-wide headcount, and\n * `tier-margin-floor.spec.ts` costs it unexpanded. Following either of those\n * and not the name is what keeps this table agreeing with both.\n */\n\n/**\n * The platform's own bands, at the positions the plugins' declared bands\n * interleave with (`order`, a multiple of 10 here) — every meter\n * `orgMonthlyCogsUsd` prices that is the platform's, plus `hosts`.\n */\nconst CORE_BANDS: ReadonlyArray<{ id: string; label: string; order: number }> = [\n { id: 'hosts', label: 'Sites', order: 10 },\n { id: 'storageGb', label: 'Media storage', order: 20 },\n { id: 'pageViews', label: 'Bandwidth (page views)', order: 30 },\n { id: 'dataStorageMb', label: 'Dataset storage', order: 50 },\n { id: 'apiRequests', label: 'API requests', order: 60 },\n { id: 'emailSends', label: 'Email sends', order: 80 },\n]\n\n/**\n * Every band the table reads, in column order: the platform's, and each one a\n * plugin declares beside the meter it contributes (`plugin-usage-axes.ts`) —\n * the AI plugin's credits, the CRM's records, the forms plugin's\n * submissions, the workflows plugin's runs.\n *\n * A plugin band sold in a unit OF cost is measured in that unit rather than\n * in the dollars the rollup stores: the AI plugin's credits are the dollars\n * over its declared unit cost, rounded up, which is the one conversion the\n * customer's own meter uses. It is also the band most worth watching —\n * provider spend is the only line item on the platform whose unit cost is\n * real money paid to a third party, and the only one that can clear the\n * $2/site floor on its own.\n */\nconst BANDS: ReadonlyArray<{\n id: string\n label: string\n order: number\n declared?: ResolvedPluginUsageBand\n}> = [\n ...CORE_BANDS,\n ...pluginUsageBands().map((declared) => ({\n id: declared.id,\n label: declared.label,\n order: declared.order,\n declared,\n })),\n].sort((a, b) => a.order - b.order)\n\n/** Every band, keyed by what it measures, in column order. */\nexport const UTILIZATION_BANDS: readonly string[] = BANDS.map((band) => band.id)\n\nexport type UtilizationBand = string\n\n/** Column headings, so the page and the aggregate name a band identically. */\nexport const UTILIZATION_BAND_LABELS: Readonly<Record<UtilizationBand, string>> =\n Object.fromEntries(BANDS.map((band) => [band.id, band.label]))\n\n/**\n * Why a band has no percentage, or that it has one.\n *\n * `uncapped` and `noAllowance` are the two non-numbers described at the head\n * of this module. They are separate values rather than one `null` because the\n * remedies are opposite: an uncapped band is a pricing exposure, an exhausted\n * one is an upgrade conversation.\n */\nexport type BandState = 'measured' | 'uncapped' | 'noAllowance'\n\nexport interface BandUtilization {\n band: UtilizationBand\n /** The org-wide included allowance. `Infinity` when uncapped, 0 when none. */\n included: number\n /** What the rollup recorded. Always a finite, non-negative number. */\n used: number\n state: BandState\n /**\n * Share of the band consumed, as a fraction of 1.\n *\n * `null` for every state but `measured`, and callers must render it as\n * neither 0 nor 1. It is NOT clamped: an org past its included band reads\n * above 1, which is the number worth seeing.\n */\n fraction: number | null\n}\n\n/** One org's realised position: what it consumed, what it cost, what it paid. */\nexport interface OrgMarginRow {\n orgId: string\n name: string | null\n plan: OrgPlan\n /** The rollup month these figures describe, or null when none has run. */\n month: string | null\n bands: Record<UtilizationBand, BandUtilization>\n /**\n * The shared cost model's answer — the same function and the same fields\n * `/api/admin/org-discount` underwrites against.\n */\n cogs: OrgCogsResult\n /** Pre-discount sticker price. */\n listPriceUsd: number\n /** List price net of any per-org discount. */\n mrrUsd: number\n /** MRR net of Stripe's processing fee — the base the margin is taken on. */\n netRevenueUsd: number\n /**\n * The AI add-on's share of `listPriceUsd` (AGL-2930) — already INSIDE\n * `listPriceUsd`, `mrrUsd` and `netRevenueUsd`, because\n * `orgListPriceMonthlyUsd` folds every purchased add-on. Named here so the\n * surface can show that the assist band's cost has revenue against it\n * rather than reading as pure margin drag; adding it again would count it\n * twice. 0 on a custom-priced deal, whose quote already folds the add-on\n * into one negotiated figure.\n */\n aiAddonRevenueUsd: number\n /**\n * `(net revenue − COGS) / net revenue`, the same arithmetic\n * `checkDiscountMargin` rates a discount on. See `MARGIN_SCOPE_NOTE`: it is\n * a contribution margin, not a profit.\n *\n * `null` when the org bills nothing. A free or comped org has no revenue to\n * take a fraction of, and reporting it as 0% or −100% would put a customer\n * who was never meant to be profitable at the top of a list whose whole\n * purpose is finding the one who was.\n */\n marginPct: number | null\n rating: DiscountMarginRating | null\n}\n\n/**\n * A rollup as this surface reads it: every field `orgMonthlyCogsUsd` prices.\n *\n * The two run counters used to be the surface's own extension of that shape\n * — recorded and banded but unpriced. Since 2026-09-07 the cost model prices\n * them at `perRun`, so the shape is the model's and nothing here is extra.\n */\nexport type UtilizationRollup = OrgUsageRollupInput\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\n/**\n * The org-wide included band for every metered axis, on this org's plan and\n * with this org's overrides and add-ons applied.\n *\n * `resolveOrgEntitlements` rather than `PLAN_ENTITLEMENTS[plan]`: a staff\n * entitlement override and a purchased host add-on both change what the\n * customer actually bought, and measuring consumption against the catalogue\n * row instead of the resolved one is the same class of error as measuring it\n * against a constant.\n */\nexport function orgIncludedBands(\n org: Partial<AglynOrgBilling> | null | undefined,\n): Record<UtilizationBand, number> {\n const entitlements = resolveOrgEntitlements(org)\n const hostLimit = Math.max(1, entitlements.hostLimit)\n const included: Record<UtilizationBand, number> = {\n hosts: entitlements.hostLimit,\n // Per host, expanded by the host limit — `meteredIncludedAllowance`\n // expands the per-host bands and nothing else.\n storageGb: (hostLimit * entitlements.storagePerHostMb) / 1024,\n // Bandwidth IS the page-view band, expressed in the unit customers buy.\n pageViews: pageViewsFromBandwidthGb(entitlements.bandwidthGb),\n // Org-wide, in the unit the rollup stores. `dataStorageMbPerOrg` is\n // megabytes and so is `dataStorageMb`; the GB conversion happens inside\n // `orgMonthlyCogsUsd` and must not happen twice.\n dataStorageMb: entitlements.dataStorageMbPerOrg,\n apiRequests: entitlements.apiRequestsPerMonth,\n emailSends: entitlements.emailSendsPerMonth,\n }\n // A plugin's band reads the entitlement its declaration names — absent is\n // nothing included — expanded by the host limit where it is per site.\n for (const band of pluginUsageBands()) {\n const figure = planQuotaOf(entitlements, band.entitlement)\n included[band.id] = band.perHost ? hostLimit * figure : figure\n }\n return included\n}\n\n/** One band's reading. The whole of the `UNLIMITED` and zero-band rule. */\nexport function bandUtilization(\n band: UtilizationBand,\n used: number,\n included: number,\n): BandUtilization {\n const usedSafe = Number.isFinite(used) && used > 0 ? used : 0\n if (!Number.isFinite(included)) {\n return { band, included: Number.POSITIVE_INFINITY, used: usedSafe, state: 'uncapped', fraction: null }\n }\n if (!(included > 0)) {\n return { band, included: 0, used: usedSafe, state: 'noAllowance', fraction: null }\n }\n return { band, included, used: usedSafe, state: 'measured', fraction: usedSafe / included }\n}\n\n/**\n * What the rollup recorded, per band.\n *\n * `hosts` comes from the ORG rather than the rollup: `orgSiteCount` is what\n * the cost floor is charged on and what `checkDiscountMargin` counts, and a\n * site added since the cron last ran is a site the org has. The rollup's own\n * `hostCount` is a snapshot of the same thing at cron time.\n */\nfunction bandUsage(\n org: Partial<AglynOrgBilling> | null | undefined,\n rollup: UtilizationRollup | null | undefined,\n): Record<UtilizationBand, number> {\n const used: Record<UtilizationBand, number> = {\n hosts: orgSiteCount(org),\n storageGb: finite(rollup?.storageGb),\n pageViews: finite(rollup?.pageViews),\n dataStorageMb: finite(rollup?.dataStorageMb),\n apiRequests: finite(rollup?.apiRequests),\n emailSends: finite(rollup?.emailSends),\n }\n for (const band of pluginUsageBands()) {\n const reading = declaredMeterReading(rollup, band)\n // Dollars on the rollup, a unit of cost on the band: rounded UP, so spend\n // already made never reads as less of the band.\n used[band.id] =\n band.unitCostUsd && reading > 0\n ? Math.ceil(reading / band.unitCostUsd)\n : reading\n }\n return used\n}\n\nexport interface OrgMarginInput {\n orgId: string\n name?: string | null\n /** The merged `{...orgDoc, ...billingDoc}` every revenue helper reads. */\n org: Partial<AglynOrgBilling> | null | undefined\n rollup: UtilizationRollup | null | undefined\n month?: string | null\n}\n\n/**\n * One org's realised utilization, cost and margin.\n *\n * The cost is `orgMonthlyCogsUsd`'s, unmodified, floor included — the same\n * `max(measured, $2 × sites)` the guardrail applies. On production today that\n * floor is what every org's figure is, which is worth reading off the `basis`\n * field rather than assuming the meters are driving anything yet.\n */\nexport function orgMarginRow(input: OrgMarginInput): OrgMarginRow {\n const { org, rollup } = input\n const included = orgIncludedBands(org)\n const used = bandUsage(org, rollup)\n const bands = Object.fromEntries(\n UTILIZATION_BANDS.map((band) => [band, bandUtilization(band, used[band], included[band])]),\n ) as Record<UtilizationBand, BandUtilization>\n\n const cogs = orgMonthlyCogsUsd(rollup, orgSiteCount(org))\n const listPriceUsd = orgListPriceMonthlyUsd(org)\n const mrrUsd = orgMonthlyRevenueUsd(org)\n const netRevenueUsd = orgNetMonthlyRevenueUsd(org)\n // Only when the org bills something at all — `listPriceUsd` is 0 for a\n // comped or lapsed org whatever its `seatAddons` say, and the add-on's\n // share of nothing is nothing. `hasAiAddon` already drops a dead\n // subscription's add-on; the list-price check catches the comped one.\n const plan = resolveEffectivePlan(org)\n const customPriced =\n typeof org?.subscription?.customMonthlyUsd === 'number' &&\n org.subscription.customMonthlyUsd > 0\n const aiAddonRevenueUsd =\n listPriceUsd > 0 && !customPriced && hasAiAddon(org)\n ? finite(PLAN_PRICING[plan]?.aiAddonMonthlyUsd)\n : 0\n // A margin needs revenue to be a fraction OF. Free and comped orgs have\n // none, and inventing one for them fills the top of a worst-first list with\n // customers who were never billed.\n const marginPct =\n netRevenueUsd > 0\n ? Math.round(((netRevenueUsd - cogs.cogsUsd) / netRevenueUsd) * 10000) / 10000\n : null\n\n return {\n orgId: input.orgId,\n name: input.name ?? null,\n plan,\n month: input.month ?? null,\n bands,\n cogs,\n listPriceUsd,\n mrrUsd,\n netRevenueUsd,\n aiAddonRevenueUsd,\n marginPct,\n rating: marginPct === null ? null : netMarginRating(marginPct),\n }\n}\n\n/**\n * Nearest-rank percentile over a sample sorted ascending.\n *\n * `null` for an empty sample. A median of nothing is not zero, and the fleet\n * view exists to answer a question that has no answer yet for most bands.\n */\nexport function percentile(sorted: readonly number[], p: number): number | null {\n if (!sorted.length) return null\n const rank = Math.ceil(p * sorted.length)\n return sorted[Math.min(sorted.length - 1, Math.max(0, rank - 1))]\n}\n\nexport interface BandDistribution {\n band: UtilizationBand\n /**\n * Orgs a percentage EXISTS for — a finite, positive band. Every percentile\n * below is over this sample and no other.\n */\n counted: number\n /** Excluded because the band is `UNLIMITED`. Not 0%, not 100%: no answer. */\n excludedUncapped: number\n /** Excluded because the plan includes none of this meter. */\n excludedNoAllowance: number\n /**\n * Orgs with no included allowance that spent the meter anyway — cost with no\n * band behind it. Excluded from the percentiles, reported on its own.\n */\n usageWithNoAllowance: number\n p50: number | null\n p25: number | null\n p75: number | null\n p90: number | null\n max: number | null\n /** Orgs at or past 100% of the band, out of `counted`. */\n overBand: number\n}\n\nexport interface FleetUtilization {\n /** Orgs folded in — every row handed to it, whatever its plan. */\n orgs: number\n /** Of those, how many had a usage rollup at all. */\n withRollup: number\n distributions: BandDistribution[]\n /** Median contribution margin across orgs that bill something. */\n medianMarginPct: number | null\n /** Orgs billing something whose margin sits under `NET_MARGIN_FLOOR_PCT`. */\n orgsUnderFloor: number\n /** Orgs billing something whose COGS exceeds their net revenue. */\n orgsUnderwater: number\n /** Total measured COGS and net revenue across the fold, in USD. */\n totalCogsUsd: number\n totalNetRevenueUsd: number\n /** Of `totalNetRevenueUsd`'s gross, how much the AI add-on brought in. */\n totalAiAddonRevenueUsd: number\n}\n\n/**\n * The fleet answer: the median and spread of utilization per band.\n *\n * This is the figure every margin projection has been assuming. It is folded\n * over whatever rows the caller read — see the route's truncation disclosure,\n * because a distribution over a partial fleet is a distribution over a partial\n * fleet and must say so.\n *\n * An org WITHOUT a rollup still contributes its `hosts` band (the org doc\n * knows its own sites) and is excluded from the other seven by having no\n * measurement, not by being scored at zero. `withRollup` is what says how much\n * of the fold is real.\n */\nexport function fleetUtilization(rows: readonly OrgMarginRow[]): FleetUtilization {\n const distributions = UTILIZATION_BANDS.map<BandDistribution>((band) => {\n const sample: number[] = []\n let excludedUncapped = 0\n let excludedNoAllowance = 0\n let usageWithNoAllowance = 0\n for (const row of rows) {\n const reading = row.bands[band]\n if (!reading) continue\n // A band the cron has never measured contributes nothing rather than a\n // zero: `hosts` is known without a rollup, the seven meters are not.\n if (band !== 'hosts' && row.month === null) continue\n if (reading.state === 'uncapped') {\n excludedUncapped += 1\n continue\n }\n if (reading.state === 'noAllowance') {\n excludedNoAllowance += 1\n if (reading.used > 0) usageWithNoAllowance += 1\n continue\n }\n sample.push(reading.fraction as number)\n }\n sample.sort((a, b) => a - b)\n return {\n band,\n counted: sample.length,\n excludedUncapped,\n excludedNoAllowance,\n usageWithNoAllowance,\n p50: percentile(sample, 0.5),\n p25: percentile(sample, 0.25),\n p75: percentile(sample, 0.75),\n p90: percentile(sample, 0.9),\n max: sample.length ? sample[sample.length - 1] : null,\n overBand: sample.filter((fraction) => fraction >= 1).length,\n }\n })\n\n const margins = rows\n .map((row) => row.marginPct)\n .filter((margin): margin is number => margin !== null)\n .sort((a, b) => a - b)\n\n return {\n orgs: rows.length,\n withRollup: rows.filter((row) => row.month !== null).length,\n distributions,\n medianMarginPct: percentile(margins, 0.5),\n orgsUnderFloor: margins.filter((margin) => netMarginRating(margin) !== 'ok').length,\n orgsUnderwater: margins.filter((margin) => margin < 0).length,\n totalCogsUsd: Math.round(rows.reduce((sum, row) => sum + row.cogs.cogsUsd, 0) * 100) / 100,\n totalNetRevenueUsd:\n Math.round(rows.reduce((sum, row) => sum + row.netRevenueUsd, 0) * 100) / 100,\n totalAiAddonRevenueUsd:\n Math.round(\n rows.reduce((sum, row) => sum + (row.aiAddonRevenueUsd ?? 0), 0) * 100,\n ) / 100,\n }\n}\n\n/**\n * Worst margin first — the ordering the surface exists for.\n *\n * An org with no revenue has no margin and sorts to the END regardless. It\n * cannot be the unprofitable customer this list is looking for, and putting\n * the free tier at the top would bury the one org that is.\n */\nexport function byWorstMargin(a: OrgMarginRow, b: OrgMarginRow): number {\n if (a.marginPct === null && b.marginPct === null) return b.cogs.cogsUsd - a.cogs.cogsUsd\n if (a.marginPct === null) return 1\n if (b.marginPct === null) return -1\n return a.marginPct - b.marginPct || b.cogs.cogsUsd - a.cogs.cogsUsd\n}\n\n/** Re-exported so the page states the floor it is colouring against. */\nexport { INFRA_COGS_PER_SITE_USD }\n"],"names":["hasAiAddon","INFRA_COGS_PER_SITE_USD","MARGIN_SCOPE_NOTE","netMarginRating","PLAN_PRICING","orgListPriceMonthlyUsd","orgMonthlyCogsUsd","orgMonthlyRevenueUsd","orgNetMonthlyRevenueUsd","orgSiteCount","pageViewsFromBandwidthGb","resolveOrgEntitlements","resolveEffectivePlan","planQuotaOf","declaredMeterReading","pluginUsageBands","CORE_BANDS","id","label","order","BANDS","map","declared","sort","a","b","UTILIZATION_BANDS","band","UTILIZATION_BAND_LABELS","Object","fromEntries","finite","value","parsed","Number","isFinite","orgIncludedBands","org","entitlements","hostLimit","Math","max","included","hosts","storageGb","storagePerHostMb","pageViews","bandwidthGb","dataStorageMb","dataStorageMbPerOrg","apiRequests","apiRequestsPerMonth","emailSends","emailSendsPerMonth","figure","entitlement","perHost","bandUtilization","used","usedSafe","POSITIVE_INFINITY","state","fraction","bandUsage","rollup","reading","unitCostUsd","ceil","orgMarginRow","input","bands","cogs","listPriceUsd","mrrUsd","netRevenueUsd","plan","customPriced","subscription","customMonthlyUsd","aiAddonRevenueUsd","aiAddonMonthlyUsd","marginPct","round","cogsUsd","orgId","name","month","rating","percentile","sorted","p","length","rank","min","fleetUtilization","rows","distributions","sample","excludedUncapped","excludedNoAllowance","usageWithNoAllowance","row","push","counted","p50","p25","p75","p90","overBand","filter","margins","margin","orgs","withRollup","medianMarginPct","orgsUnderFloor","orgsUnderwater","totalCogsUsd","reduce","sum","totalNetRevenueUsd","totalAiAddonRevenueUsd","byWorstMargin"],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { SAFE_SRC_PATTERN } from './node-url-policy';
|
|
18
18
|
import { SAFE_HREF_PATTERN } from './screen-link-value';
|
|
19
19
|
/**
|
|
20
|
-
* Component ids
|
|
20
|
+
* Component ids a portable tree may hold. Mirrors the persisted ids in
|
|
21
21
|
* plugins-mui (plugin.spec.ts) minus `reusableInstance` — nested
|
|
22
22
|
* instances would smuggle references to another tenant's private
|
|
23
23
|
* definitions — and minus `layoutSlot`, which is layout chrome. Keep sorted.
|
|
@@ -35,7 +35,7 @@ import { SAFE_HREF_PATTERN } from './screen-link-value';
|
|
|
35
35
|
* so the whole category was unpublishable. `blocks-publishable.spec.ts` walks
|
|
36
36
|
* the presets and fails if this list falls behind them again.
|
|
37
37
|
*/
|
|
38
|
-
export declare const
|
|
38
|
+
export declare const PORTABLE_COMPONENT_ID_ALLOWLIST: readonly string[];
|
|
39
39
|
/**
|
|
40
40
|
* Email block ids publishable as an `emailTemplate` (AGL-657).
|
|
41
41
|
*
|
|
@@ -50,7 +50,7 @@ export declare const MARKETPLACE_COMPONENT_ID_ALLOWLIST: readonly string[];
|
|
|
50
50
|
* else's domain is a phishing and tracking-pixel vector that no amount of
|
|
51
51
|
* render-time sanitization makes reviewable. Keep sorted.
|
|
52
52
|
*/
|
|
53
|
-
export declare const
|
|
53
|
+
export declare const PORTABLE_EMAIL_COMPONENT_ID_ALLOWLIST: readonly string[];
|
|
54
54
|
/**
|
|
55
55
|
* Email block ids publishable as an `emailStarter` — the transactional list
|
|
56
56
|
* minus `emailRichtext`.
|
|
@@ -74,20 +74,20 @@ export declare const MARKETPLACE_EMAIL_COMPONENT_ID_ALLOWLIST: readonly string[]
|
|
|
74
74
|
* paragraph, a link, a button — the remaining blocks already express as a
|
|
75
75
|
* structured tree we render ourselves.
|
|
76
76
|
*/
|
|
77
|
-
export declare const
|
|
77
|
+
export declare const PORTABLE_EMAIL_STARTER_COMPONENT_ID_ALLOWLIST: readonly string[];
|
|
78
78
|
/** Serialized definition size cap (Firestore doc limit is 1 MiB). */
|
|
79
|
-
export declare const
|
|
79
|
+
export declare const PORTABLE_DEFINITION_MAX_BYTES: number;
|
|
80
80
|
/**
|
|
81
81
|
* The address rules a published node's `href` and `src` meet — the linking
|
|
82
82
|
* elements' navigable protocols, and https, inline or site-relative images
|
|
83
83
|
* (see `node-url-policy`).
|
|
84
84
|
*
|
|
85
|
-
* Exported under these names for the marketplace's
|
|
86
|
-
* holds a
|
|
87
|
-
* node's `href`, and an Image default to the `src` rule.
|
|
85
|
+
* Exported under these names for a property sanitizer (the marketplace's is
|
|
86
|
+
* one), which holds a portable property's Link default to the same rule as a
|
|
87
|
+
* portable node's `href`, and an Image default to the `src` rule.
|
|
88
88
|
*/
|
|
89
|
-
export { SAFE_HREF_PATTERN as
|
|
90
|
-
export type
|
|
89
|
+
export { SAFE_HREF_PATTERN as PORTABLE_SAFE_HREF, SAFE_SRC_PATTERN as PORTABLE_SAFE_SRC, };
|
|
90
|
+
export type PortableDefinitionNodes = Record<string, {
|
|
91
91
|
$id: string;
|
|
92
92
|
componentId: string;
|
|
93
93
|
pluginId?: string;
|
|
@@ -110,7 +110,7 @@ export type MarketplaceDefinitionNodes = Record<string, {
|
|
|
110
110
|
* `src`) are hardened HERE as well, because publishing hands them to a
|
|
111
111
|
* different org's render tree.
|
|
112
112
|
*/
|
|
113
|
-
export declare function
|
|
113
|
+
export declare function sanitizePortableDefinition(definition: {
|
|
114
114
|
rootId: string;
|
|
115
115
|
nodes: Record<string, any>;
|
|
116
116
|
}, options?: {
|
|
@@ -143,22 +143,22 @@ export declare function sanitizeMarketplaceDefinition(definition: {
|
|
|
143
143
|
}): {
|
|
144
144
|
ok: true;
|
|
145
145
|
rootId: string;
|
|
146
|
-
nodes:
|
|
146
|
+
nodes: PortableDefinitionNodes;
|
|
147
147
|
} | {
|
|
148
148
|
ok: false;
|
|
149
149
|
error: string;
|
|
150
150
|
};
|
|
151
151
|
/**
|
|
152
|
-
* Converts a sanitized
|
|
152
|
+
* Converts a sanitized portable definition (normalized map) into the
|
|
153
153
|
* nested node shape `canvas.addNodeFromPreset` grafts — ids regenerate on
|
|
154
154
|
* insert, so collisions with existing canvas nodes are impossible
|
|
155
155
|
* (AGL-169). A seen-set guards malformed self-referencing trees.
|
|
156
156
|
*/
|
|
157
|
-
export declare function
|
|
157
|
+
export declare function portableDefinitionToNested(rootId: string, nodes: PortableDefinitionNodes): Record<string, unknown> | null;
|
|
158
158
|
/**
|
|
159
|
-
* Field types a
|
|
159
|
+
* Field types a portable dataset schema may declare (AGL-657). Mirrors
|
|
160
160
|
* `DATASET_FIELD_TYPES` in core; duplicated rather than imported to keep this
|
|
161
161
|
* module dependency-free (it is imported by API routes and client components
|
|
162
162
|
* alike), and asserted against the source of truth in the spec.
|
|
163
163
|
*/
|
|
164
|
-
export declare const
|
|
164
|
+
export declare const PORTABLE_DATASET_FIELD_TYPES: readonly string[];
|
|
@@ -15,17 +15,21 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ /**
|
|
17
17
|
* The node-definition sanitizer and the component allowlists (AGL-2939):
|
|
18
|
-
* what a node tree must satisfy
|
|
19
|
-
* author it
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* what a PORTABLE node tree must satisfy — one that lands on a canvas that did
|
|
19
|
+
* not author it. Today that is a tree installed from another workspace and a
|
|
20
|
+
* tree a model generated, and the rule is the same for both: whatever reaches
|
|
21
|
+
* the canvas renders in a site whose author never saw it.
|
|
22
|
+
*
|
|
23
|
+
* The platform's rule, so named for what it guards rather than for who asks
|
|
24
|
+
* (AGL-3080): any plugin that brings a tree in from elsewhere passes it
|
|
25
|
+
* through here, and a plugin may not import another plugin's model. The
|
|
22
26
|
* marketplace re-exports it under the names its publishers use.
|
|
23
27
|
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
24
28
|
import { matchComponentPropToken } from "./compose-reusable-components.js";
|
|
25
29
|
import { isSafeNodeUrl, SAFE_SRC_PATTERN } from "./node-url-policy.js";
|
|
26
30
|
import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
|
|
27
31
|
/**
|
|
28
|
-
* Component ids
|
|
32
|
+
* Component ids a portable tree may hold. Mirrors the persisted ids in
|
|
29
33
|
* plugins-mui (plugin.spec.ts) minus `reusableInstance` — nested
|
|
30
34
|
* instances would smuggle references to another tenant's private
|
|
31
35
|
* definitions — and minus `layoutSlot`, which is layout chrome. Keep sorted.
|
|
@@ -42,7 +46,7 @@ import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
|
|
|
42
46
|
* came to be missing (AGL-1033): every Sections & Blocks preset composes it,
|
|
43
47
|
* so the whole category was unpublishable. `blocks-publishable.spec.ts` walks
|
|
44
48
|
* the presets and fails if this list falls behind them again.
|
|
45
|
-
*/ export const
|
|
49
|
+
*/ export const PORTABLE_COMPONENT_ID_ALLOWLIST = [
|
|
46
50
|
'form',
|
|
47
51
|
'formField',
|
|
48
52
|
'image',
|
|
@@ -76,7 +80,7 @@ import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
|
|
|
76
80
|
* scripts, so this isn't XSS — it's that arbitrary markup sent from someone
|
|
77
81
|
* else's domain is a phishing and tracking-pixel vector that no amount of
|
|
78
82
|
* render-time sanitization makes reviewable. Keep sorted.
|
|
79
|
-
*/ export const
|
|
83
|
+
*/ export const PORTABLE_EMAIL_COMPONENT_ID_ALLOWLIST = [
|
|
80
84
|
'emailButton',
|
|
81
85
|
'emailDivider',
|
|
82
86
|
'emailImage',
|
|
@@ -108,8 +112,8 @@ import { SAFE_HREF_PATTERN } from "./screen-link-value.js";
|
|
|
108
112
|
* re-making, and everything rich text can express in an email — a heading, a
|
|
109
113
|
* paragraph, a link, a button — the remaining blocks already express as a
|
|
110
114
|
* structured tree we render ourselves.
|
|
111
|
-
*/ export const
|
|
112
|
-
/** Serialized definition size cap (Firestore doc limit is 1 MiB). */ export const
|
|
115
|
+
*/ export const PORTABLE_EMAIL_STARTER_COMPONENT_ID_ALLOWLIST = PORTABLE_EMAIL_COMPONENT_ID_ALLOWLIST.filter((componentId)=>componentId !== 'emailRichtext');
|
|
116
|
+
/** Serialized definition size cap (Firestore doc limit is 1 MiB). */ export const PORTABLE_DEFINITION_MAX_BYTES = 200 * 1024;
|
|
113
117
|
const KEPT_NODE_KEYS = [
|
|
114
118
|
'$id',
|
|
115
119
|
'componentId',
|
|
@@ -123,10 +127,10 @@ const KEPT_NODE_KEYS = [
|
|
|
123
127
|
* elements' navigable protocols, and https, inline or site-relative images
|
|
124
128
|
* (see `node-url-policy`).
|
|
125
129
|
*
|
|
126
|
-
* Exported under these names for the marketplace's
|
|
127
|
-
* holds a
|
|
128
|
-
* node's `href`, and an Image default to the `src` rule.
|
|
129
|
-
*/ export { SAFE_HREF_PATTERN as
|
|
130
|
+
* Exported under these names for a property sanitizer (the marketplace's is
|
|
131
|
+
* one), which holds a portable property's Link default to the same rule as a
|
|
132
|
+
* portable node's `href`, and an Image default to the `src` rule.
|
|
133
|
+
*/ export { SAFE_HREF_PATTERN as PORTABLE_SAFE_HREF, SAFE_SRC_PATTERN as PORTABLE_SAFE_SRC, };
|
|
130
134
|
/**
|
|
131
135
|
* The property kind whose value may fill each address prop through a binding
|
|
132
136
|
* (AGL-2933): a Link feeds an `href`, an Image a `src`. Each kind's default is
|
|
@@ -238,11 +242,11 @@ const KEPT_NODE_KEYS = [
|
|
|
238
242
|
* that are spread onto the DOM (`dangerouslySetInnerHTML`, `on*`, `href`,
|
|
239
243
|
* `src`) are hardened HERE as well, because publishing hands them to a
|
|
240
244
|
* different org's render tree.
|
|
241
|
-
*/ export function
|
|
245
|
+
*/ export function sanitizePortableDefinition(definition, options) {
|
|
242
246
|
var _ref;
|
|
243
247
|
var _options_extraComponentIds;
|
|
244
248
|
const { rootId, nodes } = definition;
|
|
245
|
-
const base = (_ref = options == null ? void 0 : options.componentIds) != null ? _ref :
|
|
249
|
+
const base = (_ref = options == null ? void 0 : options.componentIds) != null ? _ref : PORTABLE_COMPONENT_ID_ALLOWLIST;
|
|
246
250
|
const allowed = (options == null ? void 0 : (_options_extraComponentIds = options.extraComponentIds) == null ? void 0 : _options_extraComponentIds.length) ? [
|
|
247
251
|
...base,
|
|
248
252
|
...options.extraComponentIds
|
|
@@ -328,7 +332,7 @@ const KEPT_NODE_KEYS = [
|
|
|
328
332
|
error: 'Definition is not serializable'
|
|
329
333
|
};
|
|
330
334
|
}
|
|
331
|
-
if (serialized.length >
|
|
335
|
+
if (serialized.length > PORTABLE_DEFINITION_MAX_BYTES) {
|
|
332
336
|
return {
|
|
333
337
|
ok: false,
|
|
334
338
|
error: 'Definition is too large to publish'
|
|
@@ -356,11 +360,11 @@ const KEPT_NODE_KEYS = [
|
|
|
356
360
|
};
|
|
357
361
|
}
|
|
358
362
|
/**
|
|
359
|
-
* Converts a sanitized
|
|
363
|
+
* Converts a sanitized portable definition (normalized map) into the
|
|
360
364
|
* nested node shape `canvas.addNodeFromPreset` grafts — ids regenerate on
|
|
361
365
|
* insert, so collisions with existing canvas nodes are impossible
|
|
362
366
|
* (AGL-169). A seen-set guards malformed self-referencing trees.
|
|
363
|
-
*/ export function
|
|
367
|
+
*/ export function portableDefinitionToNested(rootId, nodes) {
|
|
364
368
|
const seen = new Set();
|
|
365
369
|
const build = (id)=>{
|
|
366
370
|
var _node_nodes;
|
|
@@ -380,11 +384,11 @@ const KEPT_NODE_KEYS = [
|
|
|
380
384
|
return build(rootId);
|
|
381
385
|
}
|
|
382
386
|
/**
|
|
383
|
-
* Field types a
|
|
387
|
+
* Field types a portable dataset schema may declare (AGL-657). Mirrors
|
|
384
388
|
* `DATASET_FIELD_TYPES` in core; duplicated rather than imported to keep this
|
|
385
389
|
* module dependency-free (it is imported by API routes and client components
|
|
386
390
|
* alike), and asserted against the source of truth in the spec.
|
|
387
|
-
*/ export const
|
|
391
|
+
*/ export const PORTABLE_DATASET_FIELD_TYPES = [
|
|
388
392
|
'bool',
|
|
389
393
|
'bytes',
|
|
390
394
|
'coordinates',
|