@aglyn/tenant-data-admin 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/jest.integration.config.d.ts +39 -0
- package/jest.integration.setup.d.ts +17 -0
- package/package.json +51 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/lib/render-cache.d.ts +184 -0
- package/src/lib/render-cache.js +198 -0
- package/src/lib/render-cache.js.map +1 -0
- package/src/lib/server/account-addresses.d.ts +160 -0
- package/src/lib/server/account-addresses.js +311 -0
- package/src/lib/server/account-addresses.js.map +1 -0
- package/src/lib/server/account-emails.d.ts +219 -0
- package/src/lib/server/account-emails.js +640 -0
- package/src/lib/server/account-emails.js.map +1 -0
- package/src/lib/server/admin-audit.d.ts +180 -0
- package/src/lib/server/admin-audit.js +233 -0
- package/src/lib/server/admin-audit.js.map +1 -0
- package/src/lib/server/analytics-retention.d.ts +63 -0
- package/src/lib/server/analytics-retention.js +66 -0
- package/src/lib/server/analytics-retention.js.map +1 -0
- package/src/lib/server/api-http.d.ts +159 -0
- package/src/lib/server/api-http.js +230 -0
- package/src/lib/server/api-http.js.map +1 -0
- package/src/lib/server/api-keys.d.ts +117 -0
- package/src/lib/server/api-keys.js +298 -0
- package/src/lib/server/api-keys.js.map +1 -0
- package/src/lib/server/auth-handoff.d.ts +165 -0
- package/src/lib/server/auth-handoff.js +482 -0
- package/src/lib/server/auth-handoff.js.map +1 -0
- package/src/lib/server/auth-pools.d.ts +246 -0
- package/src/lib/server/auth-pools.js +551 -0
- package/src/lib/server/auth-pools.js.map +1 -0
- package/src/lib/server/billing-addons.d.ts +171 -0
- package/src/lib/server/billing-addons.js +277 -0
- package/src/lib/server/billing-addons.js.map +1 -0
- package/src/lib/server/campaign-attribution-store.d.ts +55 -0
- package/src/lib/server/campaign-attribution-store.js +95 -0
- package/src/lib/server/campaign-attribution-store.js.map +1 -0
- package/src/lib/server/campaign-conversion-attribution.d.ts +205 -0
- package/src/lib/server/campaign-conversion-attribution.js +279 -0
- package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
- package/src/lib/server/client-error-report.d.ts +301 -0
- package/src/lib/server/client-error-report.js +755 -0
- package/src/lib/server/client-error-report.js.map +1 -0
- package/src/lib/server/connect-account-status.d.ts +74 -0
- package/src/lib/server/connect-account-status.js +94 -0
- package/src/lib/server/connect-account-status.js.map +1 -0
- package/src/lib/server/connect-payout-failure.d.ts +103 -0
- package/src/lib/server/connect-payout-failure.js +127 -0
- package/src/lib/server/connect-payout-failure.js.map +1 -0
- package/src/lib/server/console-api-rate-limit.d.ts +81 -0
- package/src/lib/server/console-api-rate-limit.js +111 -0
- package/src/lib/server/console-api-rate-limit.js.map +1 -0
- package/src/lib/server/console-domains.d.ts +326 -0
- package/src/lib/server/console-domains.js +779 -0
- package/src/lib/server/console-domains.js.map +1 -0
- package/src/lib/server/consume-once.d.ts +91 -0
- package/src/lib/server/consume-once.js +96 -0
- package/src/lib/server/consume-once.js.map +1 -0
- package/src/lib/server/contact-company-link.d.ts +85 -0
- package/src/lib/server/contact-company-link.js +105 -0
- package/src/lib/server/contact-company-link.js.map +1 -0
- package/src/lib/server/contact-email-engagement.d.ts +32 -0
- package/src/lib/server/contact-email-engagement.js +145 -0
- package/src/lib/server/contact-email-engagement.js.map +1 -0
- package/src/lib/server/contact-email-index.d.ts +56 -0
- package/src/lib/server/contact-email-index.js +158 -0
- package/src/lib/server/contact-email-index.js.map +1 -0
- package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
- package/src/lib/server/contact-lifecycle-floor.js +88 -0
- package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
- package/src/lib/server/contact-merge.d.ts +55 -0
- package/src/lib/server/contact-merge.js +279 -0
- package/src/lib/server/contact-merge.js.map +1 -0
- package/src/lib/server/contact-suppression.d.ts +153 -0
- package/src/lib/server/contact-suppression.js +259 -0
- package/src/lib/server/contact-suppression.js.map +1 -0
- package/src/lib/server/crm-booking-activity.d.ts +86 -0
- package/src/lib/server/crm-booking-activity.js +251 -0
- package/src/lib/server/crm-booking-activity.js.map +1 -0
- package/src/lib/server/crm-email-activity.d.ts +112 -0
- package/src/lib/server/crm-email-activity.js +143 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.d.ts +145 -0
- package/src/lib/server/crm-inbound-email.js +287 -0
- package/src/lib/server/crm-inbound-email.js.map +1 -0
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- package/src/lib/server/crm-records.d.ts +144 -0
- package/src/lib/server/crm-records.js +218 -0
- package/src/lib/server/crm-records.js.map +1 -0
- package/src/lib/server/csp-aggregate.d.ts +62 -0
- package/src/lib/server/csp-aggregate.js +276 -0
- package/src/lib/server/csp-aggregate.js.map +1 -0
- package/src/lib/server/data-storage-gate.d.ts +66 -0
- package/src/lib/server/data-storage-gate.js +48 -0
- package/src/lib/server/data-storage-gate.js.map +1 -0
- package/src/lib/server/dataset-live-pages.d.ts +179 -0
- package/src/lib/server/dataset-live-pages.js +421 -0
- package/src/lib/server/dataset-live-pages.js.map +1 -0
- package/src/lib/server/dns-probe.d.ts +53 -0
- package/src/lib/server/dns-probe.js +184 -0
- package/src/lib/server/dns-probe.js.map +1 -0
- package/src/lib/server/document-id.d.ts +29 -0
- package/src/lib/server/document-id.js +100 -0
- package/src/lib/server/document-id.js.map +1 -0
- package/src/lib/server/domain-provider-vercel.d.ts +33 -0
- package/src/lib/server/domain-provider-vercel.js +298 -0
- package/src/lib/server/domain-provider-vercel.js.map +1 -0
- package/src/lib/server/domain-provider-webhook.d.ts +73 -0
- package/src/lib/server/domain-provider-webhook.js +223 -0
- package/src/lib/server/domain-provider-webhook.js.map +1 -0
- package/src/lib/server/domain-provider-wildcard.d.ts +54 -0
- package/src/lib/server/domain-provider-wildcard.js +163 -0
- package/src/lib/server/domain-provider-wildcard.js.map +1 -0
- package/src/lib/server/domain-provider.d.ts +197 -0
- package/src/lib/server/domain-provider.js +221 -0
- package/src/lib/server/domain-provider.js.map +1 -0
- package/src/lib/server/duplicate-activity.d.ts +54 -0
- package/src/lib/server/duplicate-activity.js +67 -0
- package/src/lib/server/duplicate-activity.js.map +1 -0
- package/src/lib/server/duplicate-resource.d.ts +55 -0
- package/src/lib/server/duplicate-resource.js +474 -0
- package/src/lib/server/duplicate-resource.js.map +1 -0
- package/src/lib/server/dynamic-list-materialize.d.ts +153 -0
- package/src/lib/server/dynamic-list-materialize.js +581 -0
- package/src/lib/server/dynamic-list-materialize.js.map +1 -0
- package/src/lib/server/edit-access-authz.d.ts +56 -0
- package/src/lib/server/edit-access-authz.js +107 -0
- package/src/lib/server/edit-access-authz.js.map +1 -0
- package/src/lib/server/edit-access-token.d.ts +72 -0
- package/src/lib/server/edit-access-token.js +116 -0
- package/src/lib/server/edit-access-token.js.map +1 -0
- package/src/lib/server/edit-hint-token.d.ts +83 -0
- package/src/lib/server/edit-hint-token.js +119 -0
- package/src/lib/server/edit-hint-token.js.map +1 -0
- package/src/lib/server/email-delivery-log.d.ts +509 -0
- package/src/lib/server/email-delivery-log.js +1043 -0
- package/src/lib/server/email-delivery-log.js.map +1 -0
- package/src/lib/server/email-flow-gate.d.ts +80 -0
- package/src/lib/server/email-flow-gate.js +153 -0
- package/src/lib/server/email-flow-gate.js.map +1 -0
- package/src/lib/server/email-marketing-gate.d.ts +333 -0
- package/src/lib/server/email-marketing-gate.js +639 -0
- package/src/lib/server/email-marketing-gate.js.map +1 -0
- package/src/lib/server/email-metering.d.ts +293 -0
- package/src/lib/server/email-metering.js +359 -0
- package/src/lib/server/email-metering.js.map +1 -0
- package/src/lib/server/email-revenue-attribution.d.ts +185 -0
- package/src/lib/server/email-revenue-attribution.js +300 -0
- package/src/lib/server/email-revenue-attribution.js.map +1 -0
- package/src/lib/server/email-send-rate.d.ts +232 -0
- package/src/lib/server/email-send-rate.js +417 -0
- package/src/lib/server/email-send-rate.js.map +1 -0
- package/src/lib/server/email-sender-reputation.d.ts +317 -0
- package/src/lib/server/email-sender-reputation.js +464 -0
- package/src/lib/server/email-sender-reputation.js.map +1 -0
- package/src/lib/server/email-suppression.d.ts +490 -0
- package/src/lib/server/email-suppression.js +648 -0
- package/src/lib/server/email-suppression.js.map +1 -0
- package/src/lib/server/email-topic-confirmation.d.ts +150 -0
- package/src/lib/server/email-topic-confirmation.js +272 -0
- package/src/lib/server/email-topic-confirmation.js.map +1 -0
- package/src/lib/server/email-unsubscribe-link.d.ts +174 -0
- package/src/lib/server/email-unsubscribe-link.js +236 -0
- package/src/lib/server/email-unsubscribe-link.js.map +1 -0
- package/src/lib/server/erase-person.d.ts +116 -0
- package/src/lib/server/erase-person.js +272 -0
- package/src/lib/server/erase-person.js.map +1 -0
- package/src/lib/server/erase.d.ts +458 -0
- package/src/lib/server/erase.js +1506 -0
- package/src/lib/server/erase.js.map +1 -0
- package/src/lib/server/firebase-admin.d.ts +71 -0
- package/src/lib/server/firebase-admin.js +343 -0
- package/src/lib/server/firebase-admin.js.map +1 -0
- package/src/lib/server/form-dataset-binding-token.d.ts +35 -0
- package/src/lib/server/form-dataset-binding-token.js +107 -0
- package/src/lib/server/form-dataset-binding-token.js.map +1 -0
- package/src/lib/server/free-workspace-cap.d.ts +201 -0
- package/src/lib/server/free-workspace-cap.js +404 -0
- package/src/lib/server/free-workspace-cap.js.map +1 -0
- package/src/lib/server/ga4-measurement-protocol.d.ts +256 -0
- package/src/lib/server/ga4-measurement-protocol.js +558 -0
- package/src/lib/server/ga4-measurement-protocol.js.map +1 -0
- package/src/lib/server/host-email-tokens.d.ts +47 -0
- package/src/lib/server/host-email-tokens.js +72 -0
- package/src/lib/server/host-email-tokens.js.map +1 -0
- package/src/lib/server/host-memberships.d.ts +54 -0
- package/src/lib/server/host-memberships.js +194 -0
- package/src/lib/server/host-memberships.js.map +1 -0
- package/src/lib/server/host-sending-domain.d.ts +292 -0
- package/src/lib/server/host-sending-domain.js +451 -0
- package/src/lib/server/host-sending-domain.js.map +1 -0
- package/src/lib/server/host-visitor-records.d.ts +150 -0
- package/src/lib/server/host-visitor-records.js +262 -0
- package/src/lib/server/host-visitor-records.js.map +1 -0
- package/src/lib/server/id-token-refusal.d.ts +28 -0
- package/src/lib/server/id-token-refusal.js +111 -0
- package/src/lib/server/id-token-refusal.js.map +1 -0
- package/src/lib/server/legal-acceptance.d.ts +214 -0
- package/src/lib/server/legal-acceptance.js +305 -0
- package/src/lib/server/legal-acceptance.js.map +1 -0
- package/src/lib/server/list-members.d.ts +211 -0
- package/src/lib/server/list-members.js +176 -0
- package/src/lib/server/list-members.js.map +1 -0
- package/src/lib/server/live-page-usage.d.ts +202 -0
- package/src/lib/server/live-page-usage.js +300 -0
- package/src/lib/server/live-page-usage.js.map +1 -0
- package/src/lib/server/lockdown.d.ts +313 -0
- package/src/lib/server/lockdown.js +643 -0
- package/src/lib/server/lockdown.js.map +1 -0
- package/src/lib/server/media-cdn-rate-limit.d.ts +188 -0
- package/src/lib/server/media-cdn-rate-limit.js +204 -0
- package/src/lib/server/media-cdn-rate-limit.js.map +1 -0
- package/src/lib/server/media-delivery.d.ts +267 -0
- package/src/lib/server/media-delivery.js +485 -0
- package/src/lib/server/media-delivery.js.map +1 -0
- package/src/lib/server/media-download-tokens.d.ts +161 -0
- package/src/lib/server/media-download-tokens.js +386 -0
- package/src/lib/server/media-download-tokens.js.map +1 -0
- package/src/lib/server/media-quarantine.d.ts +127 -0
- package/src/lib/server/media-quarantine.js +170 -0
- package/src/lib/server/media-quarantine.js.map +1 -0
- package/src/lib/server/media-signing.d.ts +126 -0
- package/src/lib/server/media-signing.js +159 -0
- package/src/lib/server/media-signing.js.map +1 -0
- package/src/lib/server/media-storage-path.d.ts +96 -0
- package/src/lib/server/media-storage-path.js +111 -0
- package/src/lib/server/media-storage-path.js.map +1 -0
- package/src/lib/server/media-strong-digest.d.ts +85 -0
- package/src/lib/server/media-strong-digest.js +190 -0
- package/src/lib/server/media-strong-digest.js.map +1 -0
- package/src/lib/server/media-tombstone.d.ts +276 -0
- package/src/lib/server/media-tombstone.js +421 -0
- package/src/lib/server/media-tombstone.js.map +1 -0
- package/src/lib/server/media-variants.d.ts +309 -0
- package/src/lib/server/media-variants.js +428 -0
- package/src/lib/server/media-variants.js.map +1 -0
- package/src/lib/server/member-email-aliases.d.ts +180 -0
- package/src/lib/server/member-email-aliases.js +420 -0
- package/src/lib/server/member-email-aliases.js.map +1 -0
- package/src/lib/server/member-photo.d.ts +88 -0
- package/src/lib/server/member-photo.js +185 -0
- package/src/lib/server/member-photo.js.map +1 -0
- package/src/lib/server/membership-recover-throttle.d.ts +211 -0
- package/src/lib/server/membership-recover-throttle.js +231 -0
- package/src/lib/server/membership-recover-throttle.js.map +1 -0
- package/src/lib/server/notifications.d.ts +55 -0
- package/src/lib/server/notifications.js +134 -0
- package/src/lib/server/notifications.js.map +1 -0
- package/src/lib/server/org-billing.d.ts +97 -0
- package/src/lib/server/org-billing.js +236 -0
- package/src/lib/server/org-billing.js.map +1 -0
- package/src/lib/server/organizations.d.ts +837 -0
- package/src/lib/server/organizations.js +1814 -0
- package/src/lib/server/organizations.js.map +1 -0
- package/src/lib/server/password-reset-throttle.d.ts +84 -0
- package/src/lib/server/password-reset-throttle.js +102 -0
- package/src/lib/server/password-reset-throttle.js.map +1 -0
- package/src/lib/server/personal-data-export.d.ts +241 -0
- package/src/lib/server/personal-data-export.js +858 -0
- package/src/lib/server/personal-data-export.js.map +1 -0
- package/src/lib/server/platform-domain-names.d.ts +62 -0
- package/src/lib/server/platform-domain-names.js +197 -0
- package/src/lib/server/platform-domain-names.js.map +1 -0
- package/src/lib/server/platform-marketing-consent.d.ts +133 -0
- package/src/lib/server/platform-marketing-consent.js +209 -0
- package/src/lib/server/platform-marketing-consent.js.map +1 -0
- package/src/lib/server/rate-limit-store.d.ts +702 -0
- package/src/lib/server/rate-limit-store.js +1220 -0
- package/src/lib/server/rate-limit-store.js.map +1 -0
- package/src/lib/server/realm-plugins.d.ts +67 -0
- package/src/lib/server/realm-plugins.js +250 -0
- package/src/lib/server/realm-plugins.js.map +1 -0
- package/src/lib/server/recaptcha-allowlist.d.ts +90 -0
- package/src/lib/server/recaptcha-allowlist.js +409 -0
- package/src/lib/server/recaptcha-allowlist.js.map +1 -0
- package/src/lib/server/release-flags.d.ts +105 -0
- package/src/lib/server/release-flags.js +183 -0
- package/src/lib/server/release-flags.js.map +1 -0
- package/src/lib/server/replay-erasures.d.ts +126 -0
- package/src/lib/server/replay-erasures.js +217 -0
- package/src/lib/server/replay-erasures.js.map +1 -0
- package/src/lib/server/resolve-people.d.ts +66 -0
- package/src/lib/server/resolve-people.js +112 -0
- package/src/lib/server/resolve-people.js.map +1 -0
- package/src/lib/server/safe-equal.d.ts +45 -0
- package/src/lib/server/safe-equal.js +58 -0
- package/src/lib/server/safe-equal.js.map +1 -0
- package/src/lib/server/sending-domain-debt.d.ts +127 -0
- package/src/lib/server/sending-domain-debt.js +268 -0
- package/src/lib/server/sending-domain-debt.js.map +1 -0
- package/src/lib/server/sending-domain-recheck.d.ts +151 -0
- package/src/lib/server/sending-domain-recheck.js +253 -0
- package/src/lib/server/sending-domain-recheck.js.map +1 -0
- package/src/lib/server/sending-domains.d.ts +364 -0
- package/src/lib/server/sending-domains.js +683 -0
- package/src/lib/server/sending-domains.js.map +1 -0
- package/src/lib/server/serve-media-cdn.d.ts +433 -0
- package/src/lib/server/serve-media-cdn.js +1370 -0
- package/src/lib/server/serve-media-cdn.js.map +1 -0
- package/src/lib/server/serve-plugin-fetch.d.ts +61 -0
- package/src/lib/server/serve-plugin-fetch.js +294 -0
- package/src/lib/server/serve-plugin-fetch.js.map +1 -0
- package/src/lib/server/sms-keywords.d.ts +63 -0
- package/src/lib/server/sms-keywords.js +146 -0
- package/src/lib/server/sms-keywords.js.map +1 -0
- package/src/lib/server/sso-break-glass-owners.d.ts +91 -0
- package/src/lib/server/sso-break-glass-owners.js +227 -0
- package/src/lib/server/sso-break-glass-owners.js.map +1 -0
- package/src/lib/server/sso-domain-policy.d.ts +140 -0
- package/src/lib/server/sso-domain-policy.js +239 -0
- package/src/lib/server/sso-domain-policy.js.map +1 -0
- package/src/lib/server/sso-drift-logic.d.ts +106 -0
- package/src/lib/server/sso-drift-logic.js +95 -0
- package/src/lib/server/sso-drift-logic.js.map +1 -0
- package/src/lib/server/sso-enforcement.d.ts +204 -0
- package/src/lib/server/sso-enforcement.js +309 -0
- package/src/lib/server/sso-enforcement.js.map +1 -0
- package/src/lib/server/sso-provisioning.d.ts +320 -0
- package/src/lib/server/sso-provisioning.js +715 -0
- package/src/lib/server/sso-provisioning.js.map +1 -0
- package/src/lib/server/staff-alert-email.d.ts +41 -0
- package/src/lib/server/staff-alert-email.js +63 -0
- package/src/lib/server/staff-alert-email.js.map +1 -0
- package/src/lib/server/stripe-account-mode.d.ts +156 -0
- package/src/lib/server/stripe-account-mode.js +162 -0
- package/src/lib/server/stripe-account-mode.js.map +1 -0
- package/src/lib/server/tenant-write-lockdown.d.ts +131 -0
- package/src/lib/server/tenant-write-lockdown.js +176 -0
- package/src/lib/server/tenant-write-lockdown.js.map +1 -0
- package/src/lib/server/test-firestore.d.ts +45 -0
- package/src/lib/server/test-firestore.js +124 -0
- package/src/lib/server/test-firestore.js.map +1 -0
- package/src/lib/server/token-revocation.d.ts +140 -0
- package/src/lib/server/token-revocation.js +255 -0
- package/src/lib/server/token-revocation.js.map +1 -0
- package/src/lib/server/update-existing.d.ts +32 -0
- package/src/lib/server/update-existing.js +73 -0
- package/src/lib/server/update-existing.js.map +1 -0
- package/src/lib/server/upload-cors-reconcile.d.ts +118 -0
- package/src/lib/server/upload-cors-reconcile.js +234 -0
- package/src/lib/server/upload-cors-reconcile.js.map +1 -0
- package/src/lib/server/upsert-contact.d.ts +262 -0
- package/src/lib/server/upsert-contact.js +546 -0
- package/src/lib/server/upsert-contact.js.map +1 -0
- package/src/lib/server/usage-invoice.d.ts +203 -0
- package/src/lib/server/usage-invoice.js +397 -0
- package/src/lib/server/usage-invoice.js.map +1 -0
- package/src/lib/server/user-profiles.d.ts +151 -0
- package/src/lib/server/user-profiles.js +364 -0
- package/src/lib/server/user-profiles.js.map +1 -0
- package/src/lib/server/vercel-drain-signature.d.ts +30 -0
- package/src/lib/server/vercel-drain-signature.js +61 -0
- package/src/lib/server/vercel-drain-signature.js.map +1 -0
- package/src/lib/server/vercel-log-drain.d.ts +259 -0
- package/src/lib/server/vercel-log-drain.js +479 -0
- package/src/lib/server/vercel-log-drain.js.map +1 -0
- package/src/lib/server/verify-email-cooldown.d.ts +102 -0
- package/src/lib/server/verify-email-cooldown.js +114 -0
- package/src/lib/server/verify-email-cooldown.js.map +1 -0
- package/src/lib/server/visitor-write-rate-limit.d.ts +87 -0
- package/src/lib/server/visitor-write-rate-limit.js +117 -0
- package/src/lib/server/visitor-write-rate-limit.js.map +1 -0
- package/src/lib/server/workspace-domains.d.ts +156 -0
- package/src/lib/server/workspace-domains.js +271 -0
- package/src/lib/server/workspace-domains.js.map +1 -0
- package/src/tenancy.d.ts +115 -0
- package/src/tenancy.js +142 -0
- package/src/tenancy.js.map +1 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { MEDIA_POSTER_OBJECT_SUFFIX, mediaPosterObjectPath, readImageDimensions } from "@aglyn/aglyn/server";
|
|
18
|
+
import { MEDIA_CDN_VARIANT_WIDTHS } from "./serve-media-cdn.js";
|
|
19
|
+
/**
|
|
20
|
+
* Formats a WebP copy at the SOURCE'S OWN width beats.
|
|
21
|
+
*
|
|
22
|
+
* A renderer's srcSet offers every width in `MEDIA_CDN_VARIANT_WIDTHS`, and
|
|
23
|
+
* `serveMediaCdn` answers a width an asset has no variant for with the
|
|
24
|
+
* original. For a JPEG or PNG no wider than a candidate, that original is what
|
|
25
|
+
* every request at or above its width downloads — the `1280w` a phone asks
|
|
26
|
+
* for, the `1920w` a desktop asks for. Measured on aglyn.com's hero poster, a
|
|
27
|
+
* 1920x1080 JPEG: 166,756 B served for `?w=1920`, where a WebP of the same
|
|
28
|
+
* 1920 pixels at the generator's quality is 40,598 B.
|
|
29
|
+
*
|
|
30
|
+
* So these formats also get the widths AT and ABOVE their own, each encoded at
|
|
31
|
+
* the source width (`withoutEnlargement`): a re-encode, never an upscale, and
|
|
32
|
+
* the same pixels the original answer served. Not GIF, whose animation a WebP
|
|
33
|
+
* frame would drop; not WebP or AVIF, which a re-encode could make larger.
|
|
34
|
+
*/ const SOURCE_WIDTH_WEBP_TYPES = new Set([
|
|
35
|
+
'image/jpeg',
|
|
36
|
+
'image/jpg',
|
|
37
|
+
'image/png'
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* `image/*` types that produce no variants, for two different reasons.
|
|
41
|
+
*
|
|
42
|
+
* SVG is a vector: there is nothing to downscale, and rasterizing it would be
|
|
43
|
+
* a different asset rather than a variant of this one.
|
|
44
|
+
*
|
|
45
|
+
* ICO carries no `sharp` decoder at all, so attempting it raises an
|
|
46
|
+
* unsupported-format error from `toBuffer` — an ERROR on the media document,
|
|
47
|
+
* which is the signal reserved for generation that was attempted and broke. An
|
|
48
|
+
* icon having no variants is neither attempted nor broken; it is the ordinary
|
|
49
|
+
* outcome, and it has to read as one or the fault counter measures the favicon
|
|
50
|
+
* instead of a fault (AGL-3121).
|
|
51
|
+
*
|
|
52
|
+
* Exported because the backfill script has to skip exactly this set. It kept a
|
|
53
|
+
* second copy of the SVG half, which is how ICO came to be missing from one
|
|
54
|
+
* and not the other.
|
|
55
|
+
*/ export const MEDIA_TYPES_WITHOUT_VARIANTS = new Set([
|
|
56
|
+
'image/svg+xml',
|
|
57
|
+
'image/x-icon',
|
|
58
|
+
'image/vnd.microsoft.icon'
|
|
59
|
+
]);
|
|
60
|
+
/**
|
|
61
|
+
* Which widths this source should produce.
|
|
62
|
+
*
|
|
63
|
+
* Split out and exported because it is the predicate that decides whether an
|
|
64
|
+
* empty result is NORMAL, and both the routes and the health probe have to
|
|
65
|
+
* agree on it. Inlined, the skip rule was a `continue` buried in the loop that
|
|
66
|
+
* a reader had to reconstruct in order to know whether `[]` was a bug.
|
|
67
|
+
*
|
|
68
|
+
* Widths narrower than the source always; for {@link SOURCE_WIDTH_WEBP_TYPES}
|
|
69
|
+
* the rest as well, provided the source is wider than the narrowest width — a
|
|
70
|
+
* source smaller than every candidate is an icon, and it is served as it is.
|
|
71
|
+
*/ export function mediaVariantWidthsFor(options) {
|
|
72
|
+
var _options_sourceWidth;
|
|
73
|
+
if (!options.contentType.startsWith('image/')) return [];
|
|
74
|
+
if (MEDIA_TYPES_WITHOUT_VARIANTS.has(options.contentType)) return [];
|
|
75
|
+
const sourceWidth = (_options_sourceWidth = options.sourceWidth) != null ? _options_sourceWidth : 0;
|
|
76
|
+
// No known width means generate and let `withoutEnlargement` decide, which
|
|
77
|
+
// is the same call the loop made before: an unreadable header must not
|
|
78
|
+
// silently opt an asset out of the CDN.
|
|
79
|
+
if (!sourceWidth) return [
|
|
80
|
+
...MEDIA_CDN_VARIANT_WIDTHS
|
|
81
|
+
];
|
|
82
|
+
const narrower = MEDIA_CDN_VARIANT_WIDTHS.filter((width)=>width < sourceWidth);
|
|
83
|
+
if (!narrower.length || !SOURCE_WIDTH_WEBP_TYPES.has(options.contentType)) {
|
|
84
|
+
return narrower;
|
|
85
|
+
}
|
|
86
|
+
return [
|
|
87
|
+
...MEDIA_CDN_VARIANT_WIDTHS
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve `sharp`'s callable through either module shape.
|
|
92
|
+
*
|
|
93
|
+
* `(await import('sharp')).default` is correct under Node ESM and under a
|
|
94
|
+
* bundler that synthesises the CJS namespace. It is `undefined` under one that
|
|
95
|
+
* hands back `module.exports` directly — and `undefined(buffer)` is a
|
|
96
|
+
* `TypeError` raised INSIDE the same `try` that catches a genuine load
|
|
97
|
+
* failure, so from outside the two are the same event: no variants, no
|
|
98
|
+
* message, a 200. This normalises the shape and, when neither shape yields a
|
|
99
|
+
* function, throws something that NAMES the problem instead of a bare
|
|
100
|
+
* `is not a function` from three frames away.
|
|
101
|
+
*/ export async function loadSharp() {
|
|
102
|
+
const imported = await import("sharp");
|
|
103
|
+
const candidate = typeof imported === 'function' ? imported : imported == null ? void 0 : imported.default;
|
|
104
|
+
if (typeof candidate !== 'function') {
|
|
105
|
+
throw new Error(`sharp did not resolve to a function (module was ${typeof imported}, ` + `default was ${typeof candidate})`);
|
|
106
|
+
}
|
|
107
|
+
return candidate;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A short, PUBLISHABLE name for why the encoder is unavailable (AGL-1471).
|
|
111
|
+
*
|
|
112
|
+
* The first thing the probe said on production was `sharp-unavailable`, and
|
|
113
|
+
* that answered less than it looked like it did: it is the fallback for "the
|
|
114
|
+
* error carried no `code`", and BOTH interesting failures land there. `sharp`
|
|
115
|
+
* catches every `require` of its prebuilt binaries and rethrows one composed
|
|
116
|
+
* `Error` with no `code` at all; `loadSharp` throws a plain `Error` too when
|
|
117
|
+
* the module resolves to something that is not callable. One means the native
|
|
118
|
+
* library did not load, the other means the bundler handed back the wrong
|
|
119
|
+
* shape, and they have nothing in common except the remedy being different.
|
|
120
|
+
*
|
|
121
|
+
* Matching on `sharp`'s own help text is the only signal available — the
|
|
122
|
+
* distinction is not in a field, only in the prose. It is matched loosely and
|
|
123
|
+
* it degrades to the old fallback, so a reworded upstream message costs a
|
|
124
|
+
* detail rather than the answer.
|
|
125
|
+
*
|
|
126
|
+
* The MESSAGE still never leaves the process. A native loader failure names
|
|
127
|
+
* every path it tried, the health endpoint is public, and the point of a code
|
|
128
|
+
* is that it is a fixed vocabulary the caller can branch on.
|
|
129
|
+
*/ export function classifyLoadFailure(error) {
|
|
130
|
+
const code = error == null ? void 0 : error.code;
|
|
131
|
+
if (code) return String(code);
|
|
132
|
+
const message = error instanceof Error ? error.message : '';
|
|
133
|
+
if (/Could not load the "?sharp"? module/i.test(message)) {
|
|
134
|
+
return 'sharp-native-missing';
|
|
135
|
+
}
|
|
136
|
+
if (/did not resolve to a function/i.test(message)) {
|
|
137
|
+
return 'sharp-not-a-function';
|
|
138
|
+
}
|
|
139
|
+
return 'sharp-unavailable';
|
|
140
|
+
}
|
|
141
|
+
/** Error text kept short — it is stored on a document, not in a log. */ function describe(error) {
|
|
142
|
+
const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error != null ? error : 'unknown');
|
|
143
|
+
return text.slice(0, 300);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Generate the WebP variants for one asset and report the outcome.
|
|
147
|
+
*
|
|
148
|
+
* `saveVariant` is a callback rather than a `Bucket` so this stays free of a
|
|
149
|
+
* storage dependency and so a spec can assert on the BYTES it produces —
|
|
150
|
+
* which is the only assertion that distinguishes a working variant from the
|
|
151
|
+
* original served back under a `?w=` that selects nothing.
|
|
152
|
+
*/ export async function generateMediaVariants(options) {
|
|
153
|
+
const widths = mediaVariantWidthsFor(options);
|
|
154
|
+
if (!widths.length) return {
|
|
155
|
+
variants: []
|
|
156
|
+
};
|
|
157
|
+
const variants = [];
|
|
158
|
+
try {
|
|
159
|
+
const sharp = await loadSharp();
|
|
160
|
+
for (const width of widths){
|
|
161
|
+
const webp = await sharp(options.buffer).resize({
|
|
162
|
+
width,
|
|
163
|
+
withoutEnlargement: true
|
|
164
|
+
}).webp({
|
|
165
|
+
quality: 80
|
|
166
|
+
}).toBuffer();
|
|
167
|
+
await options.saveVariant(`${options.objectPath}__w${width}.webp`, webp);
|
|
168
|
+
variants.push(width);
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
// Still never fails the upload — but no longer only whispers.
|
|
172
|
+
console.error('media variant generation failed', options.objectPath, error);
|
|
173
|
+
return {
|
|
174
|
+
variants,
|
|
175
|
+
error: describe(error)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
variants
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The largest object this will pull back OUT of storage in order to generate.
|
|
184
|
+
*
|
|
185
|
+
* Deliberately equal to `IMAGE_MAX_BYTES`, the console's per-type ceiling for
|
|
186
|
+
* an image (`apps/console/utils/media-upload-limits.ts`), rather than derived
|
|
187
|
+
* from it: this library cannot import that file, and the two numbers answer
|
|
188
|
+
* different questions. That one asks "may this be uploaded at all"; this one
|
|
189
|
+
* asks "may a serverless function afford to fetch it back and decode it". They
|
|
190
|
+
* agree today, which is the point — every image the DAM accepts gets variants.
|
|
191
|
+
* If the upload ceiling is ever raised for a format where 15 MB is small (a
|
|
192
|
+
* camera RAW, a TIFF), the download stops there instead of silently following
|
|
193
|
+
* it into a timeout, and says so.
|
|
194
|
+
*/ export const MEDIA_VARIANT_SOURCE_MAX_BYTES = 15 * 1024 * 1024;
|
|
195
|
+
/**
|
|
196
|
+
* Generate variants for an object whose bytes are in STORAGE, not in hand
|
|
197
|
+
* (AGL-1476).
|
|
198
|
+
*
|
|
199
|
+
* `/api/media/upload` has the buffer — the client base64'd it into the request
|
|
200
|
+
* body. `/api/media/upload-url` never sees it: the browser PUTs straight to GCS
|
|
201
|
+
* with a signed URL, and finalize is handed a path. Since AGL-1465 gave images
|
|
202
|
+
* a signed-path ceiling, every image between 3 MB and 15 MB takes that route,
|
|
203
|
+
* so the assets that most need a 320px WebP became precisely the ones that got
|
|
204
|
+
* none. Measured on production 2026-08-13: a 6,606,921 B PNG landed with
|
|
205
|
+
* `variants` **absent**, and `?w=320` served back all 6,606,921 B of
|
|
206
|
+
* `image/png`; a 2,168,376 B PNG on the base64 route landed with
|
|
207
|
+
* `variants: [320, 640]` and answered `?w=320` in 31,566 B of `image/webp`.
|
|
208
|
+
*
|
|
209
|
+
* So the bytes have to come back. `readSource` is a callback, for the same
|
|
210
|
+
* reason `saveVariant` is one: this stays free of a storage dependency, and a
|
|
211
|
+
* spec can hand it a real PNG and count what comes out.
|
|
212
|
+
*
|
|
213
|
+
* **The read is the last thing that happens, not the first.** Widths are
|
|
214
|
+
* decided from the header dimensions the caller already has, so a source
|
|
215
|
+
* narrower than every target width — and every non-image — costs nothing at
|
|
216
|
+
* all. That ordering is what keeps this from pulling a 200 MB video back out
|
|
217
|
+
* of the bucket to discover it has no variants.
|
|
218
|
+
*
|
|
219
|
+
* The size refusal is reported as an ERROR rather than as an empty success,
|
|
220
|
+
* and the distinction is AGL-1468's: `variants: []` with no error means
|
|
221
|
+
* nothing was ELIGIBLE, and an image the platform accepted and then declined
|
|
222
|
+
* to optimize is not that. It is eligible work that did not happen, so it
|
|
223
|
+
* belongs on the document and in `variantFailures` where it can be counted.
|
|
224
|
+
*/ export async function generateStoredMediaVariants(options) {
|
|
225
|
+
var _options_maxSourceBytes;
|
|
226
|
+
const widths = mediaVariantWidthsFor(options);
|
|
227
|
+
if (!widths.length) return {
|
|
228
|
+
variants: []
|
|
229
|
+
};
|
|
230
|
+
const maxSourceBytes = (_options_maxSourceBytes = options.maxSourceBytes) != null ? _options_maxSourceBytes : MEDIA_VARIANT_SOURCE_MAX_BYTES;
|
|
231
|
+
if (options.sizeBytes > maxSourceBytes) {
|
|
232
|
+
return {
|
|
233
|
+
variants: [],
|
|
234
|
+
error: `source too large to fetch for variants (${options.sizeBytes} > ${maxSourceBytes} bytes)`
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
let buffer;
|
|
238
|
+
try {
|
|
239
|
+
buffer = await options.readSource();
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// A download failure is a genuine fault on an eligible asset, and it is
|
|
242
|
+
// NOT the same event as `sharp` being unavailable — so it is described
|
|
243
|
+
// here rather than left to be inferred from a message three frames away.
|
|
244
|
+
console.error('media variant source download failed', options.objectPath, error);
|
|
245
|
+
return {
|
|
246
|
+
variants: [],
|
|
247
|
+
error: `source download failed — ${describe(error)}`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return generateMediaVariants(_extends({}, options, {
|
|
251
|
+
buffer
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Can this deployment produce a variant at all?
|
|
256
|
+
*
|
|
257
|
+
* Synthesises a small image in memory, downscales it, and asserts the result
|
|
258
|
+
* is both a WebP and SMALLER than the source. No Storage, no Firestore, no
|
|
259
|
+
* upload — so it can run from a health endpoint on a schedule instead of
|
|
260
|
+
* waiting for a customer to notice their thumbnails are full-size originals.
|
|
261
|
+
*
|
|
262
|
+
* The size comparison is the point. A resize that silently returns its input
|
|
263
|
+
* is exactly the failure this issue is about: `?w=640` answering 200 with the
|
|
264
|
+
* original is indistinguishable from success unless something counts bytes.
|
|
265
|
+
*/ export async function probeMediaVariantSupport() {
|
|
266
|
+
try {
|
|
267
|
+
const sharp = await loadSharp();
|
|
268
|
+
// A smooth gradient, not a flat fill and not noise. A flat fill
|
|
269
|
+
// compresses to a handful of bytes at every width, so the size
|
|
270
|
+
// comparison below would pass on a resize that did nothing; noise
|
|
271
|
+
// re-expands under lossy WebP, so it would FAIL on a perfectly healthy
|
|
272
|
+
// encoder. Real images sit between the two, and so does this.
|
|
273
|
+
const width = 640;
|
|
274
|
+
const height = 128;
|
|
275
|
+
const pixels = Buffer.alloc(width * height * 3);
|
|
276
|
+
for(let y = 0; y < height; y += 1){
|
|
277
|
+
for(let x = 0; x < width; x += 1){
|
|
278
|
+
const index = (y * width + x) * 3;
|
|
279
|
+
pixels[index] = Math.round(x * 255 / width);
|
|
280
|
+
pixels[index + 1] = Math.round(y * 255 / height);
|
|
281
|
+
pixels[index + 2] = Math.round((x + y) % 300 * 255 / 300);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const source = await sharp(pixels, {
|
|
285
|
+
raw: {
|
|
286
|
+
width,
|
|
287
|
+
height,
|
|
288
|
+
channels: 3
|
|
289
|
+
}
|
|
290
|
+
}).png().toBuffer();
|
|
291
|
+
const resized = await sharp(source).resize({
|
|
292
|
+
width: 320,
|
|
293
|
+
withoutEnlargement: true
|
|
294
|
+
}).webp({
|
|
295
|
+
quality: 80
|
|
296
|
+
}).toBuffer();
|
|
297
|
+
// `RIFF....WEBP` — a WebP, not the PNG handed back.
|
|
298
|
+
const isWebp = resized.length > 12 && resized.toString('ascii', 0, 4) === 'RIFF' && resized.toString('ascii', 8, 12) === 'WEBP';
|
|
299
|
+
if (!isWebp) return {
|
|
300
|
+
ok: false,
|
|
301
|
+
code: 'not-webp'
|
|
302
|
+
};
|
|
303
|
+
if (resized.length >= source.length) return {
|
|
304
|
+
ok: false,
|
|
305
|
+
code: 'not-smaller'
|
|
306
|
+
};
|
|
307
|
+
return {
|
|
308
|
+
ok: true
|
|
309
|
+
};
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// A CODE, never the message: the health endpoint is public and an error
|
|
312
|
+
// from a native module can carry filesystem paths.
|
|
313
|
+
return {
|
|
314
|
+
ok: false,
|
|
315
|
+
code: classifyLoadFailure(error)
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The largest poster this will accept from a client (AGL-2742).
|
|
321
|
+
*
|
|
322
|
+
* The browser encodes the still it captured as WebP at quality ~0.72, and a
|
|
323
|
+
* 1920-wide frame of real video lands between 40 KB and 250 KB. 4 MB is
|
|
324
|
+
* therefore ~16x the worst realistic case and is not a quality knob: it is
|
|
325
|
+
* the point past which the bytes stopped being a poster. It has to sit well
|
|
326
|
+
* under the platform's 4.5 MB request-body wall, because the poster rides
|
|
327
|
+
* base64 in the finalize call and a ceiling that admits a body the platform
|
|
328
|
+
* will reject turns a refusal we can explain into a 413 we cannot.
|
|
329
|
+
*/ export const MEDIA_POSTER_MAX_BYTES = 4 * 1024 * 1024;
|
|
330
|
+
/**
|
|
331
|
+
* The widest poster stored. Above this a still is being kept at a resolution
|
|
332
|
+
* no `<video poster>` paints — the element scales it to the player box, and
|
|
333
|
+
* the largest realistic player box is the 1920 the variant widths already
|
|
334
|
+
* top out at.
|
|
335
|
+
*/ export const MEDIA_POSTER_MAX_WIDTH = 1920;
|
|
336
|
+
/** WebP quality for the poster. Higher than a variant's 80 would not show. */ const POSTER_QUALITY = 72;
|
|
337
|
+
/**
|
|
338
|
+
* Turn a client-supplied still into the asset's poster (AGL-2742).
|
|
339
|
+
*
|
|
340
|
+
* ## The re-encode is a security control, not a compression pass
|
|
341
|
+
*
|
|
342
|
+
* These bytes come from a browser, over the wire, and land in the shared
|
|
343
|
+
* media bucket under an org's own prefix, where `serveMediaCdn` will stream
|
|
344
|
+
* them `inline` from the console's origin and from every tenant site's. That
|
|
345
|
+
* is precisely the shape of AGL-1474, where an uploaded `image/svg+xml`
|
|
346
|
+
* turned out to be a scripted document served from `app.aglyn.com`.
|
|
347
|
+
*
|
|
348
|
+
* Nothing here trusts the declared type, the magic bytes, or the extension.
|
|
349
|
+
* The buffer is decoded by `sharp` and **re-encoded** to WebP, so what gets
|
|
350
|
+
* stored is bytes this process produced. A polyglot, an SVG, an HTML file
|
|
351
|
+
* with a PNG header — each either fails to decode (reported, no object
|
|
352
|
+
* written) or becomes a real raster WebP with whatever it was smuggling
|
|
353
|
+
* discarded. That is a stronger guarantee than any sniffing gate, and it is
|
|
354
|
+
* why the poster is allowed to be client-generated at all.
|
|
355
|
+
*
|
|
356
|
+
* ## Why it reuses `generateMediaVariants` rather than looping itself
|
|
357
|
+
*
|
|
358
|
+
* A poster IS an image, and the width set a poster should offer is the width
|
|
359
|
+
* set every other image offers — the srcSet the renderer already knows how
|
|
360
|
+
* to write. Handing the generator `{objectPath}__poster` as its object path
|
|
361
|
+
* makes it emit `{objectPath}__poster__w{n}.webp` with no knowledge that
|
|
362
|
+
* video exists, and inherits its skip rule, its partial-run reporting and
|
|
363
|
+
* its refusal to fail an upload. A private loop here would be a second set
|
|
364
|
+
* of widths to keep in step, which is the defect AGL-175's widths list was
|
|
365
|
+
* moved into `@aglyn/aglyn` to prevent.
|
|
366
|
+
*
|
|
367
|
+
* The dimensions returned are read back from the ENCODED bytes rather than
|
|
368
|
+
* taken from the caller. The browser's report is what sized the capture, and
|
|
369
|
+
* the resize below may have changed it; a document that records what was
|
|
370
|
+
* asked for instead of what was written is how an `aspect-ratio` drifts off
|
|
371
|
+
* by the cap.
|
|
372
|
+
*/ export async function generateMediaPoster(options) {
|
|
373
|
+
const posterPath = mediaPosterObjectPath(options.objectPath);
|
|
374
|
+
let webp;
|
|
375
|
+
try {
|
|
376
|
+
var _options_maxWidth;
|
|
377
|
+
const sharp = await loadSharp();
|
|
378
|
+
webp = await sharp(options.buffer).resize({
|
|
379
|
+
width: (_options_maxWidth = options.maxWidth) != null ? _options_maxWidth : MEDIA_POSTER_MAX_WIDTH,
|
|
380
|
+
withoutEnlargement: true
|
|
381
|
+
}).webp({
|
|
382
|
+
quality: POSTER_QUALITY
|
|
383
|
+
}).toBuffer();
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error('media poster encode failed', options.objectPath, error);
|
|
386
|
+
return {
|
|
387
|
+
error: `poster encode failed — ${describe(error)}`
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// Read the WRITTEN bytes. `readImageDimensions` parses the WebP chunk
|
|
391
|
+
// headers this encoder just emitted, so this is a measurement of the
|
|
392
|
+
// stored object and not a restatement of the request.
|
|
393
|
+
const dimensions = readImageDimensions(new Uint8Array(webp));
|
|
394
|
+
if (!dimensions) {
|
|
395
|
+
return {
|
|
396
|
+
error: 'poster encoded but its dimensions were unreadable'
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
await options.saveVariant(posterPath, webp);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('media poster save failed', posterPath, error);
|
|
403
|
+
return {
|
|
404
|
+
error: `poster save failed — ${describe(error)}`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
// Widths for the poster, generated from the poster's own bytes. A failure
|
|
408
|
+
// past this point still leaves a usable full-size poster on the document,
|
|
409
|
+
// which is the whole reason the object above is written first.
|
|
410
|
+
const outcome = await generateMediaVariants({
|
|
411
|
+
buffer: webp,
|
|
412
|
+
contentType: 'image/webp',
|
|
413
|
+
sourceWidth: dimensions.width,
|
|
414
|
+
objectPath: `${options.objectPath}${MEDIA_POSTER_OBJECT_SUFFIX}`,
|
|
415
|
+
saveVariant: options.saveVariant
|
|
416
|
+
});
|
|
417
|
+
return _extends({
|
|
418
|
+
poster: {
|
|
419
|
+
width: dimensions.width,
|
|
420
|
+
height: dimensions.height,
|
|
421
|
+
variants: outcome.variants
|
|
422
|
+
}
|
|
423
|
+
}, outcome.error ? {
|
|
424
|
+
error: outcome.error
|
|
425
|
+
} : {});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
//# sourceMappingURL=media-variants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/media-variants.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MEDIA_POSTER_OBJECT_SUFFIX,\n mediaPosterObjectPath,\n readImageDimensions,\n} from '@aglyn/aglyn/server'\n\nimport { MEDIA_CDN_VARIANT_WIDTHS } from './serve-media-cdn'\n\n/**\n * WebP variant generation for a media asset (AGL-175), and the record of\n * whether it worked (AGL-1468).\n *\n * ## Why this is a library function and not two copies of a `try`\n *\n * `/api/media/upload` and `/api/media/replace` each carried the same eleven\n * lines, wrapped in a `catch` whose comment read *\"Variants are an\n * optimization — never fail the upload for them.\"* That judgement is right and\n * it survives here. What did not survive is its consequence: the catch wrote\n * `console.error` and nothing else, so a **total** failure and a **healthy**\n * asset produced byte-identical documents — `variants: []` either way.\n *\n * Measured 2026-08-13 on production: **1 of 180 media documents has a\n * non-empty `variants` array.** The last successful generation was\n * 2026-07-19; every image uploaded since has an empty one. Nothing surfaced\n * that for three weeks, because the only trace was a serverless log line\n * whose retention is about an hour.\n *\n * So the contract of this function is not \"generate variants\". It is\n * **generate variants and say what happened**, and the two failure classes\n * are kept apart on purpose:\n *\n * - `variants: []` with **no** `error` — nothing was ELIGIBLE. A type in\n * {@link MEDIA_TYPES_WITHOUT_VARIANTS}, a non-image, or a source already\n * narrower than every target width. This is the correct, common,\n * uninteresting outcome and must never look like a fault or the fault signal\n * is worthless.\n * - `variants: []` with an `error` — generation was attempted and did not\n * complete. That string goes onto the media document and bumps a counter,\n * which is what makes \"how many assets failed?\" a query instead of an\n * archaeology project.\n *\n * A partial run reports both: the widths that landed AND the error, because\n * the widths that landed are real files that the CDN route can serve.\n */\n\n/** The shape of `sharp`'s default export, narrowed to what is used here. */\ntype SharpFactory = (input: Buffer) => {\n resize(options: { width: number; withoutEnlargement: boolean }): {\n webp(options: { quality: number }): { toBuffer(): Promise<Buffer> }\n }\n}\n\nexport interface MediaVariantOutcome {\n /** Widths actually written. Safe to store — every entry is a real object. */\n variants: number[]\n /**\n * Present ONLY when generation was attempted and did not complete.\n * `undefined` means nothing went wrong, which includes \"nothing to do\".\n */\n error?: string\n}\n\n/**\n * Formats a WebP copy at the SOURCE'S OWN width beats.\n *\n * A renderer's srcSet offers every width in `MEDIA_CDN_VARIANT_WIDTHS`, and\n * `serveMediaCdn` answers a width an asset has no variant for with the\n * original. For a JPEG or PNG no wider than a candidate, that original is what\n * every request at or above its width downloads — the `1280w` a phone asks\n * for, the `1920w` a desktop asks for. Measured on aglyn.com's hero poster, a\n * 1920x1080 JPEG: 166,756 B served for `?w=1920`, where a WebP of the same\n * 1920 pixels at the generator's quality is 40,598 B.\n *\n * So these formats also get the widths AT and ABOVE their own, each encoded at\n * the source width (`withoutEnlargement`): a re-encode, never an upscale, and\n * the same pixels the original answer served. Not GIF, whose animation a WebP\n * frame would drop; not WebP or AVIF, which a re-encode could make larger.\n */\nconst SOURCE_WIDTH_WEBP_TYPES: ReadonlySet<string> = new Set([\n 'image/jpeg',\n 'image/jpg',\n 'image/png',\n])\n\n/**\n * `image/*` types that produce no variants, for two different reasons.\n *\n * SVG is a vector: there is nothing to downscale, and rasterizing it would be\n * a different asset rather than a variant of this one.\n *\n * ICO carries no `sharp` decoder at all, so attempting it raises an\n * unsupported-format error from `toBuffer` — an ERROR on the media document,\n * which is the signal reserved for generation that was attempted and broke. An\n * icon having no variants is neither attempted nor broken; it is the ordinary\n * outcome, and it has to read as one or the fault counter measures the favicon\n * instead of a fault (AGL-3121).\n *\n * Exported because the backfill script has to skip exactly this set. It kept a\n * second copy of the SVG half, which is how ICO came to be missing from one\n * and not the other.\n */\nexport const MEDIA_TYPES_WITHOUT_VARIANTS: ReadonlySet<string> = new Set([\n 'image/svg+xml',\n 'image/x-icon',\n 'image/vnd.microsoft.icon',\n])\n\n/**\n * Which widths this source should produce.\n *\n * Split out and exported because it is the predicate that decides whether an\n * empty result is NORMAL, and both the routes and the health probe have to\n * agree on it. Inlined, the skip rule was a `continue` buried in the loop that\n * a reader had to reconstruct in order to know whether `[]` was a bug.\n *\n * Widths narrower than the source always; for {@link SOURCE_WIDTH_WEBP_TYPES}\n * the rest as well, provided the source is wider than the narrowest width — a\n * source smaller than every candidate is an icon, and it is served as it is.\n */\nexport function mediaVariantWidthsFor(options: {\n contentType: string\n /** Source pixel width, when it could be read from the header. */\n sourceWidth?: number | null\n}): number[] {\n if (!options.contentType.startsWith('image/')) return []\n if (MEDIA_TYPES_WITHOUT_VARIANTS.has(options.contentType)) return []\n const sourceWidth = options.sourceWidth ?? 0\n // No known width means generate and let `withoutEnlargement` decide, which\n // is the same call the loop made before: an unreadable header must not\n // silently opt an asset out of the CDN.\n if (!sourceWidth) return [...MEDIA_CDN_VARIANT_WIDTHS]\n const narrower = MEDIA_CDN_VARIANT_WIDTHS.filter((width) => width < sourceWidth)\n if (!narrower.length || !SOURCE_WIDTH_WEBP_TYPES.has(options.contentType)) {\n return narrower\n }\n return [...MEDIA_CDN_VARIANT_WIDTHS]\n}\n\n/**\n * Resolve `sharp`'s callable through either module shape.\n *\n * `(await import('sharp')).default` is correct under Node ESM and under a\n * bundler that synthesises the CJS namespace. It is `undefined` under one that\n * hands back `module.exports` directly — and `undefined(buffer)` is a\n * `TypeError` raised INSIDE the same `try` that catches a genuine load\n * failure, so from outside the two are the same event: no variants, no\n * message, a 200. This normalises the shape and, when neither shape yields a\n * function, throws something that NAMES the problem instead of a bare\n * `is not a function` from three frames away.\n */\nexport async function loadSharp(): Promise<SharpFactory> {\n const imported: unknown = await import('sharp')\n const candidate =\n typeof imported === 'function'\n ? imported\n : (imported as { default?: unknown } | null)?.default\n if (typeof candidate !== 'function') {\n throw new Error(\n `sharp did not resolve to a function (module was ${typeof imported}, ` +\n `default was ${typeof candidate})`,\n )\n }\n return candidate as SharpFactory\n}\n\n/**\n * A short, PUBLISHABLE name for why the encoder is unavailable (AGL-1471).\n *\n * The first thing the probe said on production was `sharp-unavailable`, and\n * that answered less than it looked like it did: it is the fallback for \"the\n * error carried no `code`\", and BOTH interesting failures land there. `sharp`\n * catches every `require` of its prebuilt binaries and rethrows one composed\n * `Error` with no `code` at all; `loadSharp` throws a plain `Error` too when\n * the module resolves to something that is not callable. One means the native\n * library did not load, the other means the bundler handed back the wrong\n * shape, and they have nothing in common except the remedy being different.\n *\n * Matching on `sharp`'s own help text is the only signal available — the\n * distinction is not in a field, only in the prose. It is matched loosely and\n * it degrades to the old fallback, so a reworded upstream message costs a\n * detail rather than the answer.\n *\n * The MESSAGE still never leaves the process. A native loader failure names\n * every path it tried, the health endpoint is public, and the point of a code\n * is that it is a fixed vocabulary the caller can branch on.\n */\nexport function classifyLoadFailure(error: unknown): string {\n const code = (error as { code?: string })?.code\n if (code) return String(code)\n const message = error instanceof Error ? error.message : ''\n if (/Could not load the \"?sharp\"? module/i.test(message)) {\n return 'sharp-native-missing'\n }\n if (/did not resolve to a function/i.test(message)) {\n return 'sharp-not-a-function'\n }\n return 'sharp-unavailable'\n}\n\n/** Error text kept short — it is stored on a document, not in a log. */\nfunction describe(error: unknown): string {\n const text =\n error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error ?? 'unknown')\n return text.slice(0, 300)\n}\n\n/**\n * Generate the WebP variants for one asset and report the outcome.\n *\n * `saveVariant` is a callback rather than a `Bucket` so this stays free of a\n * storage dependency and so a spec can assert on the BYTES it produces —\n * which is the only assertion that distinguishes a working variant from the\n * original served back under a `?w=` that selects nothing.\n */\nexport async function generateMediaVariants(options: {\n buffer: Buffer\n contentType: string\n sourceWidth?: number | null\n /** Object path of the ORIGINAL; variants are `${objectPath}__w{n}.webp`. */\n objectPath: string\n saveVariant: (path: string, webp: Buffer) => Promise<void>\n}): Promise<MediaVariantOutcome> {\n const widths = mediaVariantWidthsFor(options)\n if (!widths.length) return { variants: [] }\n\n const variants: number[] = []\n try {\n const sharp = await loadSharp()\n for (const width of widths) {\n const webp = await sharp(options.buffer)\n .resize({ width, withoutEnlargement: true })\n .webp({ quality: 80 })\n .toBuffer()\n await options.saveVariant(`${options.objectPath}__w${width}.webp`, webp)\n variants.push(width)\n }\n } catch (error) {\n // Still never fails the upload — but no longer only whispers.\n console.error('media variant generation failed', options.objectPath, error)\n return { variants, error: describe(error) }\n }\n return { variants }\n}\n\n/**\n * The largest object this will pull back OUT of storage in order to generate.\n *\n * Deliberately equal to `IMAGE_MAX_BYTES`, the console's per-type ceiling for\n * an image (`apps/console/utils/media-upload-limits.ts`), rather than derived\n * from it: this library cannot import that file, and the two numbers answer\n * different questions. That one asks \"may this be uploaded at all\"; this one\n * asks \"may a serverless function afford to fetch it back and decode it\". They\n * agree today, which is the point — every image the DAM accepts gets variants.\n * If the upload ceiling is ever raised for a format where 15 MB is small (a\n * camera RAW, a TIFF), the download stops there instead of silently following\n * it into a timeout, and says so.\n */\nexport const MEDIA_VARIANT_SOURCE_MAX_BYTES = 15 * 1024 * 1024\n\n/**\n * Generate variants for an object whose bytes are in STORAGE, not in hand\n * (AGL-1476).\n *\n * `/api/media/upload` has the buffer — the client base64'd it into the request\n * body. `/api/media/upload-url` never sees it: the browser PUTs straight to GCS\n * with a signed URL, and finalize is handed a path. Since AGL-1465 gave images\n * a signed-path ceiling, every image between 3 MB and 15 MB takes that route,\n * so the assets that most need a 320px WebP became precisely the ones that got\n * none. Measured on production 2026-08-13: a 6,606,921 B PNG landed with\n * `variants` **absent**, and `?w=320` served back all 6,606,921 B of\n * `image/png`; a 2,168,376 B PNG on the base64 route landed with\n * `variants: [320, 640]` and answered `?w=320` in 31,566 B of `image/webp`.\n *\n * So the bytes have to come back. `readSource` is a callback, for the same\n * reason `saveVariant` is one: this stays free of a storage dependency, and a\n * spec can hand it a real PNG and count what comes out.\n *\n * **The read is the last thing that happens, not the first.** Widths are\n * decided from the header dimensions the caller already has, so a source\n * narrower than every target width — and every non-image — costs nothing at\n * all. That ordering is what keeps this from pulling a 200 MB video back out\n * of the bucket to discover it has no variants.\n *\n * The size refusal is reported as an ERROR rather than as an empty success,\n * and the distinction is AGL-1468's: `variants: []` with no error means\n * nothing was ELIGIBLE, and an image the platform accepted and then declined\n * to optimize is not that. It is eligible work that did not happen, so it\n * belongs on the document and in `variantFailures` where it can be counted.\n */\nexport async function generateStoredMediaVariants(options: {\n contentType: string\n /** The object's REAL size, as storage reports it. */\n sizeBytes: number\n sourceWidth?: number | null\n objectPath: string\n /** Fetches the whole object. Only ever called when there is work to do. */\n readSource: () => Promise<Buffer>\n saveVariant: (path: string, webp: Buffer) => Promise<void>\n maxSourceBytes?: number\n}): Promise<MediaVariantOutcome> {\n const widths = mediaVariantWidthsFor(options)\n if (!widths.length) return { variants: [] }\n\n const maxSourceBytes = options.maxSourceBytes ?? MEDIA_VARIANT_SOURCE_MAX_BYTES\n if (options.sizeBytes > maxSourceBytes) {\n return {\n variants: [],\n error: `source too large to fetch for variants (${options.sizeBytes} > ${maxSourceBytes} bytes)`,\n }\n }\n\n let buffer: Buffer\n try {\n buffer = await options.readSource()\n } catch (error) {\n // A download failure is a genuine fault on an eligible asset, and it is\n // NOT the same event as `sharp` being unavailable — so it is described\n // here rather than left to be inferred from a message three frames away.\n console.error('media variant source download failed', options.objectPath, error)\n return { variants: [], error: `source download failed — ${describe(error)}` }\n }\n\n return generateMediaVariants({ ...options, buffer })\n}\n\n/**\n * Can this deployment produce a variant at all?\n *\n * Synthesises a small image in memory, downscales it, and asserts the result\n * is both a WebP and SMALLER than the source. No Storage, no Firestore, no\n * upload — so it can run from a health endpoint on a schedule instead of\n * waiting for a customer to notice their thumbnails are full-size originals.\n *\n * The size comparison is the point. A resize that silently returns its input\n * is exactly the failure this issue is about: `?w=640` answering 200 with the\n * original is indistinguishable from success unless something counts bytes.\n */\nexport async function probeMediaVariantSupport(): Promise<{\n ok: boolean\n code?: string\n}> {\n try {\n const sharp = await loadSharp()\n // A smooth gradient, not a flat fill and not noise. A flat fill\n // compresses to a handful of bytes at every width, so the size\n // comparison below would pass on a resize that did nothing; noise\n // re-expands under lossy WebP, so it would FAIL on a perfectly healthy\n // encoder. Real images sit between the two, and so does this.\n const width = 640\n const height = 128\n const pixels = Buffer.alloc(width * height * 3)\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n const index = (y * width + x) * 3\n pixels[index] = Math.round((x * 255) / width)\n pixels[index + 1] = Math.round((y * 255) / height)\n pixels[index + 2] = Math.round((((x + y) % 300) * 255) / 300)\n }\n }\n const source = await (\n sharp as unknown as (\n input: Buffer,\n options: unknown,\n ) => { png(): { toBuffer(): Promise<Buffer> } }\n )(pixels, { raw: { width, height, channels: 3 } })\n .png()\n .toBuffer()\n const resized = await sharp(source)\n .resize({ width: 320, withoutEnlargement: true })\n .webp({ quality: 80 })\n .toBuffer()\n // `RIFF....WEBP` — a WebP, not the PNG handed back.\n const isWebp =\n resized.length > 12 &&\n resized.toString('ascii', 0, 4) === 'RIFF' &&\n resized.toString('ascii', 8, 12) === 'WEBP'\n if (!isWebp) return { ok: false, code: 'not-webp' }\n if (resized.length >= source.length) return { ok: false, code: 'not-smaller' }\n return { ok: true }\n } catch (error) {\n // A CODE, never the message: the health endpoint is public and an error\n // from a native module can carry filesystem paths.\n return { ok: false, code: classifyLoadFailure(error) }\n }\n}\n\n/**\n * The largest poster this will accept from a client (AGL-2742).\n *\n * The browser encodes the still it captured as WebP at quality ~0.72, and a\n * 1920-wide frame of real video lands between 40 KB and 250 KB. 4 MB is\n * therefore ~16x the worst realistic case and is not a quality knob: it is\n * the point past which the bytes stopped being a poster. It has to sit well\n * under the platform's 4.5 MB request-body wall, because the poster rides\n * base64 in the finalize call and a ceiling that admits a body the platform\n * will reject turns a refusal we can explain into a 413 we cannot.\n */\nexport const MEDIA_POSTER_MAX_BYTES = 4 * 1024 * 1024\n\n/**\n * The widest poster stored. Above this a still is being kept at a resolution\n * no `<video poster>` paints — the element scales it to the player box, and\n * the largest realistic player box is the 1920 the variant widths already\n * top out at.\n */\nexport const MEDIA_POSTER_MAX_WIDTH = 1920\n\n/** WebP quality for the poster. Higher than a variant's 80 would not show. */\nconst POSTER_QUALITY = 72\n\nexport interface MediaPosterOutcome {\n /** Set only when a poster object was actually written. */\n poster?: { width: number; height: number; variants: number[] }\n /**\n * Present ONLY when a poster was offered and could not be produced —\n * the same two-failure-class contract {@link MediaVariantOutcome} keeps.\n * An upload with no poster offered returns an empty object, not an error.\n */\n error?: string\n}\n\n/**\n * Turn a client-supplied still into the asset's poster (AGL-2742).\n *\n * ## The re-encode is a security control, not a compression pass\n *\n * These bytes come from a browser, over the wire, and land in the shared\n * media bucket under an org's own prefix, where `serveMediaCdn` will stream\n * them `inline` from the console's origin and from every tenant site's. That\n * is precisely the shape of AGL-1474, where an uploaded `image/svg+xml`\n * turned out to be a scripted document served from `app.aglyn.com`.\n *\n * Nothing here trusts the declared type, the magic bytes, or the extension.\n * The buffer is decoded by `sharp` and **re-encoded** to WebP, so what gets\n * stored is bytes this process produced. A polyglot, an SVG, an HTML file\n * with a PNG header — each either fails to decode (reported, no object\n * written) or becomes a real raster WebP with whatever it was smuggling\n * discarded. That is a stronger guarantee than any sniffing gate, and it is\n * why the poster is allowed to be client-generated at all.\n *\n * ## Why it reuses `generateMediaVariants` rather than looping itself\n *\n * A poster IS an image, and the width set a poster should offer is the width\n * set every other image offers — the srcSet the renderer already knows how\n * to write. Handing the generator `{objectPath}__poster` as its object path\n * makes it emit `{objectPath}__poster__w{n}.webp` with no knowledge that\n * video exists, and inherits its skip rule, its partial-run reporting and\n * its refusal to fail an upload. A private loop here would be a second set\n * of widths to keep in step, which is the defect AGL-175's widths list was\n * moved into `@aglyn/aglyn` to prevent.\n *\n * The dimensions returned are read back from the ENCODED bytes rather than\n * taken from the caller. The browser's report is what sized the capture, and\n * the resize below may have changed it; a document that records what was\n * asked for instead of what was written is how an `aspect-ratio` drifts off\n * by the cap.\n */\nexport async function generateMediaPoster(options: {\n /** Raw bytes as received. Never assumed to be an image of any kind. */\n buffer: Buffer\n /** Object path of the ORIGINAL video, without the poster suffix. */\n objectPath: string\n saveVariant: (path: string, bytes: Buffer) => Promise<void>\n maxWidth?: number\n}): Promise<MediaPosterOutcome> {\n const posterPath = mediaPosterObjectPath(options.objectPath)\n let webp: Buffer\n try {\n const sharp = await loadSharp()\n webp = await sharp(options.buffer)\n .resize({\n width: options.maxWidth ?? MEDIA_POSTER_MAX_WIDTH,\n withoutEnlargement: true,\n })\n .webp({ quality: POSTER_QUALITY })\n .toBuffer()\n } catch (error) {\n console.error('media poster encode failed', options.objectPath, error)\n return { error: `poster encode failed — ${describe(error)}` }\n }\n\n // Read the WRITTEN bytes. `readImageDimensions` parses the WebP chunk\n // headers this encoder just emitted, so this is a measurement of the\n // stored object and not a restatement of the request.\n const dimensions = readImageDimensions(new Uint8Array(webp))\n if (!dimensions) {\n return { error: 'poster encoded but its dimensions were unreadable' }\n }\n\n try {\n await options.saveVariant(posterPath, webp)\n } catch (error) {\n console.error('media poster save failed', posterPath, error)\n return { error: `poster save failed — ${describe(error)}` }\n }\n\n // Widths for the poster, generated from the poster's own bytes. A failure\n // past this point still leaves a usable full-size poster on the document,\n // which is the whole reason the object above is written first.\n const outcome = await generateMediaVariants({\n buffer: webp,\n contentType: 'image/webp',\n sourceWidth: dimensions.width,\n objectPath: `${options.objectPath}${MEDIA_POSTER_OBJECT_SUFFIX}`,\n saveVariant: options.saveVariant,\n })\n\n return {\n poster: {\n width: dimensions.width,\n height: dimensions.height,\n variants: outcome.variants,\n },\n ...(outcome.error ? { error: outcome.error } : {}),\n }\n}\n"],"names":["MEDIA_POSTER_OBJECT_SUFFIX","mediaPosterObjectPath","readImageDimensions","MEDIA_CDN_VARIANT_WIDTHS","SOURCE_WIDTH_WEBP_TYPES","Set","MEDIA_TYPES_WITHOUT_VARIANTS","mediaVariantWidthsFor","options","contentType","startsWith","has","sourceWidth","narrower","filter","width","length","loadSharp","imported","candidate","default","Error","classifyLoadFailure","error","code","String","message","test","describe","text","name","slice","generateMediaVariants","widths","variants","sharp","webp","buffer","resize","withoutEnlargement","quality","toBuffer","saveVariant","objectPath","push","console","MEDIA_VARIANT_SOURCE_MAX_BYTES","generateStoredMediaVariants","maxSourceBytes","sizeBytes","readSource","probeMediaVariantSupport","height","pixels","Buffer","alloc","y","x","index","Math","round","source","raw","channels","png","resized","isWebp","toString","ok","MEDIA_POSTER_MAX_BYTES","MEDIA_POSTER_MAX_WIDTH","POSTER_QUALITY","generateMediaPoster","posterPath","maxWidth","dimensions","Uint8Array","outcome","poster"],"mappings":"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"}
|