emdash 1.0.1 → 1.1.0
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/dist/action-token-AgOs1UWD.mjs +26 -0
- package/dist/action-token-AgOs1UWD.mjs.map +1 -0
- package/dist/activation-xomsCMJg.mjs +387 -0
- package/dist/activation-xomsCMJg.mjs.map +1 -0
- package/dist/adapters-DM1oyHIN.d.mts +173 -0
- package/dist/adapters-DM1oyHIN.d.mts.map +1 -0
- package/dist/allowed-origins-CFtudhyw.mjs +116 -0
- package/dist/allowed-origins-CFtudhyw.mjs.map +1 -0
- package/dist/api/route-utils.d.mts +4 -4
- package/dist/api/route-utils.mjs +48 -47
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +10 -10
- package/dist/api-CltsXhiX.mjs +4980 -0
- package/dist/api-CltsXhiX.mjs.map +1 -0
- package/dist/api-tokens-P3mAaQgr.mjs +166 -0
- package/dist/api-tokens-P3mAaQgr.mjs.map +1 -0
- package/dist/apply-Q7uvzBWW.mjs +1680 -0
- package/dist/apply-Q7uvzBWW.mjs.map +1 -0
- package/dist/approvals-DycwI6lZ.mjs +460 -0
- package/dist/approvals-DycwI6lZ.mjs.map +1 -0
- package/dist/artifact-fetch-CWxyELVI.mjs +659 -0
- package/dist/artifact-fetch-CWxyELVI.mjs.map +1 -0
- package/dist/artifacts-UzWbysb8.mjs +307 -0
- package/dist/artifacts-UzWbysb8.mjs.map +1 -0
- package/dist/astro/index.d.mts +14 -14
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +38 -12
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +12 -12
- package/dist/astro/middleware/auth.mjs +12 -11
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/media-usage-write-fence.mjs +6 -6
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +9 -22
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +33 -21
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +12 -12
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +203 -120
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +15 -15
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +15 -15
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +7 -7
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +7 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +17 -17
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +17 -17
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +17 -17
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +22 -22
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +22 -23
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +23 -23
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +19 -19
- package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/bulk.mjs +18 -18
- package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/index.mjs +18 -18
- package/dist/astro/routes/api/admin/core-update.mjs +8 -8
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +7 -7
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +6 -6
- package/dist/astro/routes/api/admin/media-usage/activation.mjs +21 -21
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +22 -22
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +22 -22
- package/dist/astro/routes/api/admin/media-usage/progress.mjs +23 -23
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +29 -29
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +22 -22
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +22 -22
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +7 -7
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +7 -7
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +9 -9
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +9 -9
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +72 -71
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +8 -8
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +70 -69
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +71 -70
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +81 -80
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +71 -70
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +8 -8
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +70 -69
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +80 -79
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +70 -69
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +71 -70
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +82 -81
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +10 -10
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +73 -72
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +8 -8
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +71 -70
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +70 -69
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +70 -69
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +8 -8
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +70 -69
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/_...path_.mjs +5 -5
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +39 -39
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +39 -39
- package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/capabilities.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/exports/index.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +41 -41
- package/dist/astro/routes/api/admin/transfer/imports/index.mjs +43 -43
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +15 -15
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +7 -7
- package/dist/astro/routes/api/admin/users/index.mjs +15 -15
- package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/accept.mjs +5 -5
- package/dist/astro/routes/api/auth/invite/complete.mjs +20 -20
- package/dist/astro/routes/api/auth/invite/index.mjs +17 -17
- package/dist/astro/routes/api/auth/invite/register-options.mjs +18 -18
- package/dist/astro/routes/api/auth/logout.mjs +6 -6
- package/dist/astro/routes/api/auth/magic-link/send.mjs +19 -19
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +16 -16
- package/dist/astro/routes/api/auth/me.mjs +16 -16
- package/dist/astro/routes/api/auth/mode.mjs +6 -6
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +36 -13
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +20 -5
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +15 -15
- package/dist/astro/routes/api/auth/passkey/index.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/options.mjs +20 -20
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +18 -18
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +19 -19
- package/dist/astro/routes/api/auth/passkey/verify.mjs +20 -20
- package/dist/astro/routes/api/auth/signup/complete.mjs +20 -20
- package/dist/astro/routes/api/auth/signup/request.mjs +18 -18
- package/dist/astro/routes/api/auth/signup/verify.mjs +5 -5
- package/dist/astro/routes/api/calendar.d.mts +8 -0
- package/dist/astro/routes/api/calendar.d.mts.map +1 -0
- package/dist/astro/routes/api/calendar.mjs +204 -0
- package/dist/astro/routes/api/calendar.mjs.map +1 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +20 -20
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +12 -12
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +31 -31
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +34 -34
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +34 -34
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +24 -24
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/authors.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/index.mjs +16 -16
- package/dist/astro/routes/api/content/_collection_/trash.mjs +16 -16
- package/dist/astro/routes/api/dashboard.mjs +11 -11
- package/dist/astro/routes/api/dev/emails.mjs +6 -6
- package/dist/astro/routes/api/health.mjs +7 -7
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +24 -24
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +13 -13
- package/dist/astro/routes/api/import/wordpress/execute.mjs +23 -23
- package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -12
- package/dist/astro/routes/api/import/wordpress/media.mjs +20 -20
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +18 -18
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -13
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +27 -27
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +24 -24
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -13
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +32 -32
- package/dist/astro/routes/api/manifest.mjs +7 -7
- package/dist/astro/routes/api/mcp.mjs +94 -94
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +19 -19
- package/dist/astro/routes/api/media/_id_/replace.mjs +17 -17
- package/dist/astro/routes/api/media/_id_/upload.mjs +10 -10
- package/dist/astro/routes/api/media/_id_/usage.mjs +31 -31
- package/dist/astro/routes/api/media/_id_.mjs +29 -29
- package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +7 -7
- package/dist/astro/routes/api/media/file/_...key_.mjs +10 -10
- package/dist/astro/routes/api/media/folders/_id_.mjs +17 -17
- package/dist/astro/routes/api/media/folders/index.mjs +17 -17
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +6 -6
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +6 -6
- package/dist/astro/routes/api/media/providers/index.mjs +6 -6
- package/dist/astro/routes/api/media/upload-url.mjs +23 -23
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +37 -36
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +18 -18
- package/dist/astro/routes/api/menus/_name_/items.mjs +18 -18
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +18 -18
- package/dist/astro/routes/api/menus/_name_/translations.mjs +18 -18
- package/dist/astro/routes/api/menus/_name_.mjs +18 -18
- package/dist/astro/routes/api/menus/index.mjs +18 -18
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +9 -9
- package/dist/astro/routes/api/oauth/device/code.mjs +12 -12
- package/dist/astro/routes/api/oauth/device/token.mjs +11 -11
- package/dist/astro/routes/api/oauth/register.mjs +8 -8
- package/dist/astro/routes/api/oauth/token/refresh.mjs +9 -9
- package/dist/astro/routes/api/oauth/token/revoke.mjs +9 -9
- package/dist/astro/routes/api/oauth/token.mjs +9 -9
- package/dist/astro/routes/api/openapi.json.mjs +21 -21
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +12 -12
- package/dist/astro/routes/api/redirects/404s/index.mjs +20 -19
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +20 -19
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +20 -19
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +20 -19
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +34 -34
- package/dist/astro/routes/api/relations/index.mjs +34 -34
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +6 -6
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +17 -17
- package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +70 -69
- package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +70 -69
- package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -1
- package/dist/astro/routes/api/schema/block-types/index.mjs +70 -69
- package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +70 -69
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +70 -69
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +70 -69
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +70 -69
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +70 -69
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +70 -69
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +16 -16
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +70 -69
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +70 -69
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +19 -19
- package/dist/astro/routes/api/search/index.mjs +18 -18
- package/dist/astro/routes/api/search/rebuild.mjs +19 -19
- package/dist/astro/routes/api/search/stats.mjs +9 -9
- package/dist/astro/routes/api/search/suggest.mjs +18 -18
- package/dist/astro/routes/api/sections/_slug_.mjs +18 -18
- package/dist/astro/routes/api/sections/index.mjs +18 -18
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +8 -8
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +8 -8
- package/dist/astro/routes/api/settings/backups/export.mjs +8 -8
- package/dist/astro/routes/api/settings/backups/index.mjs +9 -9
- package/dist/astro/routes/api/settings/email.mjs +7 -7
- package/dist/astro/routes/api/settings.mjs +22 -22
- package/dist/astro/routes/api/setup/admin-verify.mjs +21 -21
- package/dist/astro/routes/api/setup/admin.mjs +19 -19
- package/dist/astro/routes/api/setup/dev-bypass.mjs +42 -42
- package/dist/astro/routes/api/setup/dev-reset.mjs +5 -5
- package/dist/astro/routes/api/setup/index.mjs +54 -49
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +7 -7
- package/dist/astro/routes/api/snapshot.mjs +10 -10
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +22 -22
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +22 -22
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +22 -22
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +22 -22
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +21 -21
- package/dist/astro/routes/api/taxonomies/_name_.mjs +22 -22
- package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +35 -35
- package/dist/astro/routes/api/taxonomies/index.mjs +22 -22
- package/dist/astro/routes/api/themes/preview.mjs +8 -8
- package/dist/astro/routes/api/typegen.mjs +7 -7
- package/dist/astro/routes/api/visual-editing/action-token.mjs +8 -8
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +20 -20
- package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +17 -17
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +19 -19
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +19 -19
- package/dist/astro/routes/api/widget-areas/_name_.mjs +9 -9
- package/dist/astro/routes/api/widget-areas/index.mjs +19 -19
- package/dist/astro/routes/api/widget-components.mjs +6 -6
- package/dist/astro/routes/robots.txt.mjs +7 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
- package/dist/astro/routes/sitemap.xml.mjs +8 -8
- package/dist/astro/types.d.mts +17 -17
- package/dist/audit-CCH3MA4Y.mjs +127 -0
- package/dist/audit-CCH3MA4Y.mjs.map +1 -0
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/auth/providers/microsoft.d.mts +27 -0
- package/dist/auth/providers/microsoft.d.mts.map +1 -0
- package/dist/auth/providers/microsoft.mjs +20 -0
- package/dist/auth/providers/microsoft.mjs.map +1 -0
- package/dist/authoritative-records-dSvXCfzb.mjs +5172 -0
- package/dist/authoritative-records-dSvXCfzb.mjs.map +1 -0
- package/dist/authorize-DeWfk--K.mjs +49 -0
- package/dist/authorize-DeWfk--K.mjs.map +1 -0
- package/dist/backup-CFnbdTSJ.mjs +246 -0
- package/dist/backup-CFnbdTSJ.mjs.map +1 -0
- package/dist/base64-BQBZwXCx.mjs +63 -0
- package/dist/base64-BQBZwXCx.mjs.map +1 -0
- package/dist/block-types-C0YFSfR7.mjs +132 -0
- package/dist/block-types-C0YFSfR7.mjs.map +1 -0
- package/dist/block-values-H4E0XwE8.mjs +175 -0
- package/dist/block-values-H4E0XwE8.mjs.map +1 -0
- package/dist/byline-BhVslYLz.mjs +909 -0
- package/dist/byline-BhVslYLz.mjs.map +1 -0
- package/dist/byline-fields-DHJs0Mja.mjs +280 -0
- package/dist/byline-fields-DHJs0Mja.mjs.map +1 -0
- package/dist/byline-fields-xdxE9kxZ.mjs +123 -0
- package/dist/byline-fields-xdxE9kxZ.mjs.map +1 -0
- package/dist/bylines-B_nN9liM.mjs +234 -0
- package/dist/bylines-B_nN9liM.mjs.map +1 -0
- package/dist/bylines-C71YACkL.mjs +150 -0
- package/dist/bylines-C71YACkL.mjs.map +1 -0
- package/dist/cache-aTYCL9nK.mjs +133 -0
- package/dist/cache-aTYCL9nK.mjs.map +1 -0
- package/dist/canonical-Y1dpjCnp.mjs +174 -0
- package/dist/canonical-Y1dpjCnp.mjs.map +1 -0
- package/dist/capture-triggers-C9JKoIdT.mjs +362 -0
- package/dist/capture-triggers-C9JKoIdT.mjs.map +1 -0
- package/dist/challenge-store-CxZxPqaw.mjs +51 -0
- package/dist/challenge-store-CxZxPqaw.mjs.map +1 -0
- package/dist/checksum-DtH6m5uk.mjs +162 -0
- package/dist/checksum-DtH6m5uk.mjs.map +1 -0
- package/dist/chrome-tags-BIKBMZYO.mjs +24 -0
- package/dist/chrome-tags-BIKBMZYO.mjs.map +1 -0
- package/dist/chunks-zlGB5IQP.mjs +25 -0
- package/dist/chunks-zlGB5IQP.mjs.map +1 -0
- package/dist/cli/index.mjs +47 -47
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +121 -121
- package/dist/client/index.mjs +2 -2
- package/dist/collection-slugs-cache-B2Gs9Jwx.mjs +85 -0
- package/dist/collection-slugs-cache-B2Gs9Jwx.mjs.map +1 -0
- package/dist/comment-Bu08Wvr3.mjs +313 -0
- package/dist/comment-Bu08Wvr3.mjs.map +1 -0
- package/dist/comment-reaction-B8SfT-yM.mjs +85 -0
- package/dist/comment-reaction-B8SfT-yM.mjs.map +1 -0
- package/dist/comments-BRtp992B.mjs +99 -0
- package/dist/comments-BRtp992B.mjs.map +1 -0
- package/dist/comments-DVUYqdyC.mjs +195 -0
- package/dist/comments-DVUYqdyC.mjs.map +1 -0
- package/dist/components-CQY-Sgm-.mjs +108 -0
- package/dist/components-CQY-Sgm-.mjs.map +1 -0
- package/dist/config-B-uePMLj.d.mts +39 -0
- package/dist/config-B-uePMLj.d.mts.map +1 -0
- package/dist/config-B6BDSsz2.d.mts +6712 -0
- package/dist/config-B6BDSsz2.d.mts.map +1 -0
- package/dist/config-BsJHiQxC.mjs +228 -0
- package/dist/config-BsJHiQxC.mjs.map +1 -0
- package/dist/connection-B9pIasFd.mjs +36 -0
- package/dist/connection-B9pIasFd.mjs.map +1 -0
- package/dist/content-Dji9vEiW.mjs +2063 -0
- package/dist/content-Dji9vEiW.mjs.map +1 -0
- package/dist/content-list-query-DiNwyKtd.d.mts +26 -0
- package/dist/content-list-query-DiNwyKtd.d.mts.map +1 -0
- package/dist/content-repair-B7wnbzhN.mjs +442 -0
- package/dist/content-repair-B7wnbzhN.mjs.map +1 -0
- package/dist/context-Ctcxc8Ia.mjs +2026 -0
- package/dist/context-Ctcxc8Ia.mjs.map +1 -0
- package/dist/credits-Bu7dlobo.mjs +110 -0
- package/dist/credits-Bu7dlobo.mjs.map +1 -0
- package/dist/cron-xe5XKqA-.mjs +306 -0
- package/dist/cron-xe5XKqA-.mjs.map +1 -0
- package/dist/dashboard-B5hVHOtf.mjs +151 -0
- package/dist/dashboard-B5hVHOtf.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql-migrations.d.mts +1 -1
- package/dist/db/libsql-migrations.mjs +5 -5
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +1 -1
- package/dist/db/postgres-migrations.mjs +4 -4
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite-migrations.d.mts +1 -1
- package/dist/db/sqlite-migrations.mjs +4 -4
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +1 -1
- package/dist/default-DaoYyFRS.mjs +81 -0
- package/dist/default-DaoYyFRS.mjs.map +1 -0
- package/dist/deferred-tasks-BYcDx2o_.d.mts +9 -0
- package/dist/deferred-tasks-BYcDx2o_.d.mts.map +1 -0
- package/dist/dev-typegen-Dtl8AfS6.mjs +29 -0
- package/dist/dev-typegen-Dtl8AfS6.mjs.map +1 -0
- package/dist/device-flow-Ma5P5d2e.mjs +521 -0
- package/dist/device-flow-Ma5P5d2e.mjs.map +1 -0
- package/dist/digest-xIpR3o64.mjs +194 -0
- package/dist/digest-xIpR3o64.mjs.map +1 -0
- package/dist/direct-executor-DMWwCey3.mjs +104 -0
- package/dist/direct-executor-DMWwCey3.mjs.map +1 -0
- package/dist/email-console-DmqVF5J_.mjs +50 -0
- package/dist/email-console-DmqVF5J_.mjs.map +1 -0
- package/dist/email-locale-Cc5YSXfJ.mjs +25 -0
- package/dist/email-locale-Cc5YSXfJ.mjs.map +1 -0
- package/dist/emdash-runtime-BlybX9Ui.mjs +4249 -0
- package/dist/emdash-runtime-BlybX9Ui.mjs.map +1 -0
- package/dist/enrich-Duu4sV3a.mjs +194 -0
- package/dist/enrich-Duu4sV3a.mjs.map +1 -0
- package/dist/entry-lock-De1L8qUQ.mjs +163 -0
- package/dist/entry-lock-De1L8qUQ.mjs.map +1 -0
- package/dist/entry-lock-esmMbgI9.d.mts +4551 -0
- package/dist/entry-lock-esmMbgI9.d.mts.map +1 -0
- package/dist/entry-lock-sxz2PGAT.mjs +1197 -0
- package/dist/entry-lock-sxz2PGAT.mjs.map +1 -0
- package/dist/env-types-BkjCtN13.mjs +38 -0
- package/dist/env-types-BkjCtN13.mjs.map +1 -0
- package/dist/error-B0rgPgsQ.mjs +90 -0
- package/dist/error-B0rgPgsQ.mjs.map +1 -0
- package/dist/errors-CuHQRiBW.mjs +120 -0
- package/dist/errors-CuHQRiBW.mjs.map +1 -0
- package/dist/errors-DTE6C3K_.mjs +470 -0
- package/dist/errors-DTE6C3K_.mjs.map +1 -0
- package/dist/escape-nJW7dR0s.mjs +9 -0
- package/dist/escape-nJW7dR0s.mjs.map +1 -0
- package/dist/fence-CGw57S4v.mjs +162 -0
- package/dist/fence-CGw57S4v.mjs.map +1 -0
- package/dist/field-defs-cache-Bw8w499w.mjs +77 -0
- package/dist/field-defs-cache-Bw8w499w.mjs.map +1 -0
- package/dist/field-map-hn-D-SyH.mjs +83 -0
- package/dist/field-map-hn-D-SyH.mjs.map +1 -0
- package/dist/fts-manager-DacCYIe4.mjs +443 -0
- package/dist/fts-manager-DacCYIe4.mjs.map +1 -0
- package/dist/gc-BBplHrv6.mjs +75 -0
- package/dist/gc-BBplHrv6.mjs.map +1 -0
- package/dist/hash-C4DFoJ7Z.mjs +37 -0
- package/dist/hash-C4DFoJ7Z.mjs.map +1 -0
- package/dist/hook-result-DSRRxu_-.d.mts +31 -0
- package/dist/hook-result-DSRRxu_-.d.mts.map +1 -0
- package/dist/hreflang-CKPunvU0.mjs +105 -0
- package/dist/hreflang-CKPunvU0.mjs.map +1 -0
- package/dist/hreflang-egumj6WF.d.mts +50 -0
- package/dist/hreflang-egumj6WF.d.mts.map +1 -0
- package/dist/http-route-dispatch-DjNFZcJ2.mjs +339 -0
- package/dist/http-route-dispatch-DjNFZcJ2.mjs.map +1 -0
- package/dist/identity-Dfrx2_Bc.mjs +35 -0
- package/dist/identity-Dfrx2_Bc.mjs.map +1 -0
- package/dist/import-ROBvhbNc.mjs +2021 -0
- package/dist/import-ROBvhbNc.mjs.map +1 -0
- package/dist/index-B4sIdCAT.d.mts +623 -0
- package/dist/index-B4sIdCAT.d.mts.map +1 -0
- package/dist/index.d.mts +24 -24
- package/dist/index.mjs +98 -97
- package/dist/init-lock-BGmew3r1.mjs +56 -0
- package/dist/init-lock-BGmew3r1.mjs.map +1 -0
- package/dist/install-finalization-CPM7Gged.mjs +58 -0
- package/dist/install-finalization-CPM7Gged.mjs.map +1 -0
- package/dist/keys-DxqxcAD4.mjs +61 -0
- package/dist/keys-DxqxcAD4.mjs.map +1 -0
- package/dist/kinds-BQl5gN6e.mjs +974 -0
- package/dist/kinds-BQl5gN6e.mjs.map +1 -0
- package/dist/lifecycle-DGQI7itS.mjs +25 -0
- package/dist/lifecycle-DGQI7itS.mjs.map +1 -0
- package/dist/load-DMxRl4VK.mjs +28 -0
- package/dist/load-DMxRl4VK.mjs.map +1 -0
- package/dist/loader-1DMiOq5s.mjs +1303 -0
- package/dist/loader-1DMiOq5s.mjs.map +1 -0
- package/dist/loops-Dn1i16FP.mjs +205 -0
- package/dist/loops-Dn1i16FP.mjs.map +1 -0
- package/dist/manifest-builder-CTw4FmZk.mjs +189 -0
- package/dist/manifest-builder-CTw4FmZk.mjs.map +1 -0
- package/dist/manifest-schema-gy37bD_D.mjs +496 -0
- package/dist/manifest-schema-gy37bD_D.mjs.map +1 -0
- package/dist/marketplace-tHWSsCUo.mjs +257 -0
- package/dist/marketplace-tHWSsCUo.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +14 -14
- package/dist/media/local-runtime.mjs +7 -7
- package/dist/media-Bw-Q65ve.mjs +294 -0
- package/dist/media-Bw-Q65ve.mjs.map +1 -0
- package/dist/media-BzUDu7nl.mjs +373 -0
- package/dist/media-BzUDu7nl.mjs.map +1 -0
- package/dist/media-aahnKHze.mjs +408 -0
- package/dist/media-aahnKHze.mjs.map +1 -0
- package/dist/media-allowlist-RMrjo8TU.mjs +45 -0
- package/dist/media-allowlist-RMrjo8TU.mjs.map +1 -0
- package/dist/media-folders-kabR1oJf.mjs +210 -0
- package/dist/media-folders-kabR1oJf.mjs.map +1 -0
- package/dist/media-url-DloW2xrP.mjs +43 -0
- package/dist/media-url-DloW2xrP.mjs.map +1 -0
- package/dist/media-usage-CHHpV7ee.mjs +282 -0
- package/dist/media-usage-CHHpV7ee.mjs.map +1 -0
- package/dist/media-usage-activation-BinYqC_r.mjs +78 -0
- package/dist/media-usage-activation-BinYqC_r.mjs.map +1 -0
- package/dist/media-usage-work-C734aq6s.mjs +1015 -0
- package/dist/media-usage-work-C734aq6s.mjs.map +1 -0
- package/dist/media-usage-work-lxvV0-UO.mjs +151 -0
- package/dist/media-usage-work-lxvV0-UO.mjs.map +1 -0
- package/dist/menus-6RHNq4ZK.mjs +753 -0
- package/dist/menus-6RHNq4ZK.mjs.map +1 -0
- package/dist/menus-AuCTaO9W.mjs +3704 -0
- package/dist/menus-AuCTaO9W.mjs.map +1 -0
- package/dist/menus-BQKqf0Up.mjs +92 -0
- package/dist/menus-BQKqf0Up.mjs.map +1 -0
- package/dist/migrations/index.d.mts +1 -1
- package/dist/migrations/index.mjs +3 -3
- package/dist/mime-B5M9R53H.mjs +81 -0
- package/dist/mime-B5M9R53H.mjs.map +1 -0
- package/dist/mode-nh0ppSX5.mjs +23 -0
- package/dist/mode-nh0ppSX5.mjs.map +1 -0
- package/dist/node-sqlite-compat-CDOEcc97.mjs +63 -0
- package/dist/node-sqlite-compat-CDOEcc97.mjs.map +1 -0
- package/dist/oauth-authorization-BtYQ80uU.mjs +274 -0
- package/dist/oauth-authorization-BtYQ80uU.mjs.map +1 -0
- package/dist/oauth-clients-C1S4NQHh.mjs +266 -0
- package/dist/oauth-clients-C1S4NQHh.mjs.map +1 -0
- package/dist/oauth-state-store-CpGi2FZH.mjs +50 -0
- package/dist/oauth-state-store-CpGi2FZH.mjs.map +1 -0
- package/dist/oauth-user-lookup-BNp1aFAr.mjs +26 -0
- package/dist/oauth-user-lookup-BNp1aFAr.mjs.map +1 -0
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/options-B41aLm1I.d.mts +81 -0
- package/dist/options-B41aLm1I.d.mts.map +1 -0
- package/dist/ownership-DoeAIuTg.mjs +12 -0
- package/dist/ownership-DoeAIuTg.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/page/index.mjs +6 -6
- package/dist/parse-CmFDTGzp.mjs +81 -0
- package/dist/parse-CmFDTGzp.mjs.map +1 -0
- package/dist/parse-QVS0t5oR.d.mts +172 -0
- package/dist/parse-QVS0t5oR.d.mts.map +1 -0
- package/dist/passkey-config-C-MxsV22.mjs +46 -0
- package/dist/passkey-config-C-MxsV22.mjs.map +1 -0
- package/dist/paths-kr8hAMMI.mjs +77 -0
- package/dist/paths-kr8hAMMI.mjs.map +1 -0
- package/dist/placeholder-Dn7MMdWr.d.mts +306 -0
- package/dist/placeholder-Dn7MMdWr.d.mts.map +1 -0
- package/dist/plan-CuQOeU9g.mjs +135 -0
- package/dist/plan-CuQOeU9g.mjs.map +1 -0
- package/dist/plugin-test-runtime.d.mts +14 -14
- package/dist/plugin-test-runtime.mjs +110 -109
- package/dist/plugin-types.d.mts +2 -2
- package/dist/plugin-utils.d.mts +12 -12
- package/dist/plugins/adapt-sandbox-entry.d.mts +12 -12
- package/dist/plugins/adapt-sandbox-entry.mjs +3 -3
- package/dist/plugins/host.d.mts +5 -5
- package/dist/plugins/host.mjs +44 -43
- package/dist/plugins-CGb-G98c.mjs +261 -0
- package/dist/plugins-CGb-G98c.mjs.map +1 -0
- package/dist/policy-C5wrDXYV.mjs +45 -0
- package/dist/policy-C5wrDXYV.mjs.map +1 -0
- package/dist/portable-text-BnRuiR1-.mjs +307 -0
- package/dist/portable-text-BnRuiR1-.mjs.map +1 -0
- package/dist/preview-xWNSR1Li.mjs +107 -0
- package/dist/preview-xWNSR1Li.mjs.map +1 -0
- package/dist/protocol-ClxN-m1e.d.mts +61 -0
- package/dist/protocol-ClxN-m1e.d.mts.map +1 -0
- package/dist/public-submission-CkmT2SKv.mjs +250 -0
- package/dist/public-submission-CkmT2SKv.mjs.map +1 -0
- package/dist/public-url-B5a0QAh6.mjs +101 -0
- package/dist/public-url-B5a0QAh6.mjs.map +1 -0
- package/dist/query-DHqFiqqH.mjs +41 -0
- package/dist/query-DHqFiqqH.mjs.map +1 -0
- package/dist/query-DT85BehD.mjs +1125 -0
- package/dist/query-DT85BehD.mjs.map +1 -0
- package/dist/rate-limit-iMvcu3eu.mjs +120 -0
- package/dist/rate-limit-iMvcu3eu.mjs.map +1 -0
- package/dist/receipt-Dpp1uaX9.mjs +43 -0
- package/dist/receipt-Dpp1uaX9.mjs.map +1 -0
- package/dist/redirect-BRoLiWQE.mjs +19 -0
- package/dist/redirect-BRoLiWQE.mjs.map +1 -0
- package/dist/redirect-xZNpBoGp.mjs +417 -0
- package/dist/redirect-xZNpBoGp.mjs.map +1 -0
- package/dist/redirects-D4EWeXAo.mjs +148 -0
- package/dist/redirects-D4EWeXAo.mjs.map +1 -0
- package/dist/redirects-DCKqwiWp.mjs +446 -0
- package/dist/redirects-DCKqwiWp.mjs.map +1 -0
- package/dist/registry/testing.mjs +3 -3
- package/dist/registry-DaXmUsD4.mjs +8369 -0
- package/dist/registry-DaXmUsD4.mjs.map +1 -0
- package/dist/relations-JvBP8nGP.mjs +776 -0
- package/dist/relations-JvBP8nGP.mjs.map +1 -0
- package/dist/request-context.d.mts +2 -2
- package/dist/request-context.mjs.map +1 -1
- package/dist/request-meta-B6blULFY.mjs +141 -0
- package/dist/request-meta-B6blULFY.mjs.map +1 -0
- package/dist/resolve-DAhHi5tn.mjs +100 -0
- package/dist/resolve-DAhHi5tn.mjs.map +1 -0
- package/dist/revision-BWEH2qQB.mjs +368 -0
- package/dist/revision-BWEH2qQB.mjs.map +1 -0
- package/dist/route-wire-DK600v4u.mjs +197 -0
- package/dist/route-wire-DK600v4u.mjs.map +1 -0
- package/dist/routes-BeZyaCzv.mjs +272 -0
- package/dist/routes-BeZyaCzv.mjs.map +1 -0
- package/dist/runner-CskI4Hmq.mjs +6372 -0
- package/dist/runner-CskI4Hmq.mjs.map +1 -0
- package/dist/runner-J3Cj6DGk.d.mts +68 -0
- package/dist/runner-J3Cj6DGk.d.mts.map +1 -0
- package/dist/runtime.d.mts +13 -13
- package/dist/runtime.mjs +3 -3
- package/dist/schema/project-env-types.mjs +37 -37
- package/dist/schema-CK0ihqMR.mjs +815 -0
- package/dist/schema-CK0ihqMR.mjs.map +1 -0
- package/dist/schema-VtaZSrC-.mjs +13 -0
- package/dist/schema-VtaZSrC-.mjs.map +1 -0
- package/dist/scopes-BRnGlL9t.mjs +34 -0
- package/dist/scopes-BRnGlL9t.mjs.map +1 -0
- package/dist/search-D6-XK1Sn.mjs +448 -0
- package/dist/search-D6-XK1Sn.mjs.map +1 -0
- package/dist/secrets-Cc_O3Kp-.mjs +369 -0
- package/dist/secrets-Cc_O3Kp-.mjs.map +1 -0
- package/dist/sections-CBDCf7qR.mjs +346 -0
- package/dist/sections-CBDCf7qR.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +35 -35
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +22 -22
- package/dist/seo-BdeEPDNW.mjs +133 -0
- package/dist/seo-BdeEPDNW.mjs.map +1 -0
- package/dist/seo-C61bnPuV.mjs +114 -0
- package/dist/seo-C61bnPuV.mjs.map +1 -0
- package/dist/service-BuRKLMxa.mjs +150 -0
- package/dist/service-BuRKLMxa.mjs.map +1 -0
- package/dist/session-user-CqkPtrPL.mjs +61 -0
- package/dist/session-user-CqkPtrPL.mjs.map +1 -0
- package/dist/settings-0u0lTthS.mjs +51 -0
- package/dist/settings-0u0lTthS.mjs.map +1 -0
- package/dist/settings-WHr45II1.mjs +61 -0
- package/dist/settings-WHr45II1.mjs.map +1 -0
- package/dist/settings-m5tIYnF0.mjs +443 -0
- package/dist/settings-m5tIYnF0.mjs.map +1 -0
- package/dist/setup-complete-B2_dhZZz.mjs +65 -0
- package/dist/setup-complete-B2_dhZZz.mjs.map +1 -0
- package/dist/setup-nonce-CJV9ftSK.mjs +25 -0
- package/dist/setup-nonce-CJV9ftSK.mjs.map +1 -0
- package/dist/single-flight-cache-D0EaGbN1.mjs +104 -0
- package/dist/single-flight-cache-D0EaGbN1.mjs.map +1 -0
- package/dist/site-settings-KJOqJW6W.mjs +50 -0
- package/dist/site-settings-KJOqJW6W.mjs.map +1 -0
- package/dist/site-url-Ceq_AUK0.mjs +16 -0
- package/dist/site-url-Ceq_AUK0.mjs.map +1 -0
- package/dist/snapshot-Dzsn3NZa.mjs +414 -0
- package/dist/snapshot-Dzsn3NZa.mjs.map +1 -0
- package/dist/ssrf-DsmvZB80.mjs +406 -0
- package/dist/ssrf-DsmvZB80.mjs.map +1 -0
- package/dist/stage-Csnh_yEd.mjs +662 -0
- package/dist/stage-Csnh_yEd.mjs.map +1 -0
- package/dist/staged-Q7pnEVvI.mjs +686 -0
- package/dist/staged-Q7pnEVvI.mjs.map +1 -0
- package/dist/state-kGdOMQRY.mjs +153 -0
- package/dist/state-kGdOMQRY.mjs.map +1 -0
- package/dist/status-D39Fpy8R.mjs +30 -0
- package/dist/status-D39Fpy8R.mjs.map +1 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +2 -2
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/storage-query-C7iy6eUb.d.mts +1615 -0
- package/dist/storage-query-C7iy6eUb.d.mts.map +1 -0
- package/dist/tar-BWiraSUY.mjs +449 -0
- package/dist/tar-BWiraSUY.mjs.map +1 -0
- package/dist/target-DOFs5oUC.mjs +27 -0
- package/dist/target-DOFs5oUC.mjs.map +1 -0
- package/dist/taxonomies-BS40Wxvi.mjs +939 -0
- package/dist/taxonomies-BS40Wxvi.mjs.map +1 -0
- package/dist/taxonomies-DAZU9VDd.mjs +697 -0
- package/dist/taxonomies-DAZU9VDd.mjs.map +1 -0
- package/dist/taxonomy-XzLE95Vx.mjs +525 -0
- package/dist/taxonomy-XzLE95Vx.mjs.map +1 -0
- package/dist/tokens-DSQan4p7.mjs +171 -0
- package/dist/tokens-DSQan4p7.mjs.map +1 -0
- package/dist/transfer-BcCVxDgf.mjs +166 -0
- package/dist/transfer-BcCVxDgf.mjs.map +1 -0
- package/dist/transfer-OMrancOt.mjs +8873 -0
- package/dist/transfer-OMrancOt.mjs.map +1 -0
- package/dist/transfer-download-Dc-ltjZY.mjs +33 -0
- package/dist/transfer-download-Dc-ltjZY.mjs.map +1 -0
- package/dist/transformations-Ddx7UtrY.mjs +196 -0
- package/dist/transformations-Ddx7UtrY.mjs.map +1 -0
- package/dist/transport-flR-Zmds.d.mts +42 -0
- package/dist/transport-flR-Zmds.d.mts.map +1 -0
- package/dist/transport-umzEbgYO.mjs +136 -0
- package/dist/transport-umzEbgYO.mjs.map +1 -0
- package/dist/trusted-proxy-BqfQFs6a.mjs +51 -0
- package/dist/trusted-proxy-BqfQFs6a.mjs.map +1 -0
- package/dist/turnstile-Do60baId.mjs +58 -0
- package/dist/turnstile-Do60baId.mjs.map +1 -0
- package/dist/types-BT1p3uyb.d.mts +1735 -0
- package/dist/types-BT1p3uyb.d.mts.map +1 -0
- package/dist/types-BYaPWs_F.d.mts +198 -0
- package/dist/types-BYaPWs_F.d.mts.map +1 -0
- package/dist/types-C-Xp0CcN.mjs +103 -0
- package/dist/types-C-Xp0CcN.mjs.map +1 -0
- package/dist/types-CLTQ9b_r.d.mts +374 -0
- package/dist/types-CLTQ9b_r.d.mts.map +1 -0
- package/dist/types-CPkQw2T2.d.mts +85 -0
- package/dist/types-CPkQw2T2.d.mts.map +1 -0
- package/dist/types-CQLvuXoZ.d.mts +198 -0
- package/dist/types-CQLvuXoZ.d.mts.map +1 -0
- package/dist/types-CV5xszpF.d.mts +363 -0
- package/dist/types-CV5xszpF.d.mts.map +1 -0
- package/dist/types-CdEMurNh.mjs +77 -0
- package/dist/types-CdEMurNh.mjs.map +1 -0
- package/dist/types-Cj0_yZkn.mjs +18 -0
- package/dist/types-Cj0_yZkn.mjs.map +1 -0
- package/dist/types-CxwtnuSS.mjs +37 -0
- package/dist/types-CxwtnuSS.mjs.map +1 -0
- package/dist/types-DGw8KMX_.mjs +16 -0
- package/dist/types-DGw8KMX_.mjs.map +1 -0
- package/dist/types-DOfVMPoo.d.mts +125 -0
- package/dist/types-DOfVMPoo.d.mts.map +1 -0
- package/dist/types-LtS0SM4F.d.mts +287 -0
- package/dist/types-LtS0SM4F.d.mts.map +1 -0
- package/dist/types-ifgOkcBa.d.mts +916 -0
- package/dist/types-ifgOkcBa.d.mts.map +1 -0
- package/dist/update-check-DOc2aBIU.mjs +161 -0
- package/dist/update-check-DOc2aBIU.mjs.map +1 -0
- package/dist/upload-attempts-nzdc3cay.mjs +32 -0
- package/dist/upload-attempts-nzdc3cay.mjs.map +1 -0
- package/dist/url-pattern-hVbqpXs6.mjs +41 -0
- package/dist/url-pattern-hVbqpXs6.mjs.map +1 -0
- package/dist/user-Dnp53zTq.mjs +171 -0
- package/dist/user-Dnp53zTq.mjs.map +1 -0
- package/dist/utils-CoV-PYhg.mjs +439 -0
- package/dist/utils-CoV-PYhg.mjs.map +1 -0
- package/dist/validate-CfQ9_YlD.d.mts +468 -0
- package/dist/validate-CfQ9_YlD.d.mts.map +1 -0
- package/dist/validate-DBX3DKtL.mjs +516 -0
- package/dist/validate-DBX3DKtL.mjs.map +1 -0
- package/dist/validation-BJMiDhB1.mjs +219 -0
- package/dist/validation-BJMiDhB1.mjs.map +1 -0
- package/dist/version-BaT8yKIT.mjs +7 -0
- package/dist/version-BaT8yKIT.mjs.map +1 -0
- package/dist/version-BwEGkqkx.mjs +9 -0
- package/dist/version-BwEGkqkx.mjs.map +1 -0
- package/dist/virtual-modules-B6vJW15k.mjs +516 -0
- package/dist/virtual-modules-B6vJW15k.mjs.map +1 -0
- package/dist/widgets-BkxBAgZ_.mjs +126 -0
- package/dist/widgets-BkxBAgZ_.mjs.map +1 -0
- package/dist/wxr-taxonomies-D7gAJyOF.mjs +453 -0
- package/dist/wxr-taxonomies-D7gAJyOF.mjs.map +1 -0
- package/dist/wxr-taxonomies-DM5J9AsT.d.mts +34 -0
- package/dist/wxr-taxonomies-DM5J9AsT.d.mts.map +1 -0
- package/dist/zod-generator-BQilAboC.mjs +424 -0
- package/dist/zod-generator-BQilAboC.mjs.map +1 -0
- package/package.json +11 -5
- package/src/api/errors.ts +3 -0
- package/src/api/handlers/bylines.ts +24 -3
- package/src/api/handlers/calendar.ts +246 -0
- package/src/api/handlers/content.ts +12 -5
- package/src/api/handlers/media-allowlist.ts +1 -0
- package/src/api/handlers/media-upload.ts +13 -10
- package/src/api/handlers/plugins.ts +21 -29
- package/src/api/handlers/redirects.ts +6 -6
- package/src/api/schemas/bylines.ts +3 -1
- package/src/api/schemas/content.ts +16 -0
- package/src/astro/integration/index.ts +18 -1
- package/src/astro/integration/routes.ts +5 -0
- package/src/astro/integration/vite-config.ts +11 -0
- package/src/astro/middleware/csp.ts +1 -0
- package/src/astro/middleware/redirect.ts +20 -9
- package/src/astro/middleware/request-context.ts +54 -27
- package/src/astro/middleware.ts +97 -12
- package/src/astro/prefetch.ts +10 -3
- package/src/astro/routes/api/admin/bylines/[id]/translations.ts +0 -5
- package/src/astro/routes/api/admin/bylines/index.ts +1 -1
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +1 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +56 -4
- package/src/astro/routes/api/auth/oauth/[provider].ts +37 -4
- package/src/astro/routes/api/calendar.ts +35 -0
- package/src/astro/routes/api/media/upload-url.ts +8 -4
- package/src/astro/routes/api/media.ts +9 -5
- package/src/astro/routes/api/setup/index.ts +14 -2
- package/src/auth/providers/microsoft-admin.tsx +31 -0
- package/src/auth/providers/microsoft.ts +47 -0
- package/src/comments/turnstile.ts +6 -1
- package/src/components/HtmlBlock.astro +17 -6
- package/src/components/HtmlBlockFrame.astro +63 -0
- package/src/components/Iframe.astro +141 -0
- package/src/components/InlinePortableTextEditor.tsx +8 -7
- package/src/components/WebMcpSearch.astro +80 -0
- package/src/components/index.ts +5 -2
- package/src/components/webmcp-search.ts +142 -0
- package/src/config/secrets.ts +27 -15
- package/src/content/converters/portable-text-identity.ts +1 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +36 -2
- package/src/content/converters/prosemirror-to-portable-text.ts +28 -2
- package/src/content/converters/types.ts +24 -0
- package/src/database/content-datetime.ts +11 -4
- package/src/database/migrations/090_redirect_enable_loop_guard.ts +70 -0
- package/src/database/migrations/091_redirect_artifacts.ts +141 -0
- package/src/database/migrations/runner.ts +4 -0
- package/src/database/repositories/content.ts +81 -26
- package/src/database/repositories/redirect.ts +6 -1
- package/src/database/request-scoped.ts +34 -0
- package/src/database/types.ts +23 -0
- package/src/db/adapters.ts +2 -0
- package/src/emdash-runtime.ts +28 -3
- package/src/import/menus.ts +3 -0
- package/src/index.ts +1 -0
- package/src/loader.ts +226 -55
- package/src/mcp/server.ts +12 -4
- package/src/media/mime.ts +21 -0
- package/src/menus/index.ts +30 -26
- package/src/plugins/hooks.ts +10 -0
- package/src/plugins/manager.ts +12 -2
- package/src/plugins/types.ts +16 -0
- package/src/query.ts +12 -29
- package/src/redirects/artifacts.ts +460 -0
- package/src/redirects/cache.ts +76 -41
- package/src/request-context.ts +1 -1
- package/src/seed/apply.ts +65 -57
- package/src/settings/index.ts +17 -12
- package/src/transfer/format/columns.ts +3 -0
- package/src/transfer/import/rebuild.ts +17 -2
- package/src/transfer/import/scaffold.ts +16 -9
- package/src/ui.ts +1 -0
- package/src/utils/sanitize.ts +14 -9
- package/src/virtual-modules.d.ts +3 -0
- package/dist/action-token-D2KE1Rqf.mjs +0 -26
- package/dist/action-token-D2KE1Rqf.mjs.map +0 -1
- package/dist/activation-DDuqAat2.mjs +0 -387
- package/dist/activation-DDuqAat2.mjs.map +0 -1
- package/dist/adapters-Ds-1Pmkm.d.mts +0 -171
- package/dist/adapters-Ds-1Pmkm.d.mts.map +0 -1
- package/dist/allowed-origins-D78QvbTO.mjs +0 -116
- package/dist/allowed-origins-D78QvbTO.mjs.map +0 -1
- package/dist/api-BBZlif0y.mjs +0 -4974
- package/dist/api-BBZlif0y.mjs.map +0 -1
- package/dist/api-tokens-BGbfWZVO.mjs +0 -166
- package/dist/api-tokens-BGbfWZVO.mjs.map +0 -1
- package/dist/apply-CDNV4JJ-.mjs +0 -1672
- package/dist/apply-CDNV4JJ-.mjs.map +0 -1
- package/dist/approvals-CVvJSEBF.mjs +0 -460
- package/dist/approvals-CVvJSEBF.mjs.map +0 -1
- package/dist/artifact-fetch-CGK0DQLj.mjs +0 -659
- package/dist/artifact-fetch-CGK0DQLj.mjs.map +0 -1
- package/dist/audit-BszbE_BP.mjs +0 -127
- package/dist/audit-BszbE_BP.mjs.map +0 -1
- package/dist/authoritative-records-ChM8dTeV.mjs +0 -5172
- package/dist/authoritative-records-ChM8dTeV.mjs.map +0 -1
- package/dist/authorize-CTGywswM.mjs +0 -49
- package/dist/authorize-CTGywswM.mjs.map +0 -1
- package/dist/backup-CDrPP8fI.mjs +0 -246
- package/dist/backup-CDrPP8fI.mjs.map +0 -1
- package/dist/base64-DvnNgP7e.mjs +0 -63
- package/dist/base64-DvnNgP7e.mjs.map +0 -1
- package/dist/block-types-nNEaPkvx.mjs +0 -132
- package/dist/block-types-nNEaPkvx.mjs.map +0 -1
- package/dist/block-values-Bho1nsZF.mjs +0 -175
- package/dist/block-values-Bho1nsZF.mjs.map +0 -1
- package/dist/byline-02tu_Uc-.mjs +0 -909
- package/dist/byline-02tu_Uc-.mjs.map +0 -1
- package/dist/byline-fields-BYbTptTU.mjs +0 -280
- package/dist/byline-fields-BYbTptTU.mjs.map +0 -1
- package/dist/byline-fields-BjQH-1z9.mjs +0 -123
- package/dist/byline-fields-BjQH-1z9.mjs.map +0 -1
- package/dist/bylines-VytjeHBI.mjs +0 -214
- package/dist/bylines-VytjeHBI.mjs.map +0 -1
- package/dist/bylines-gyWBWUUc.mjs +0 -150
- package/dist/bylines-gyWBWUUc.mjs.map +0 -1
- package/dist/cache-CPanL9IO.mjs +0 -114
- package/dist/cache-CPanL9IO.mjs.map +0 -1
- package/dist/canonical-CAecl1UQ.mjs +0 -174
- package/dist/canonical-CAecl1UQ.mjs.map +0 -1
- package/dist/capture-triggers-CdZfvHtF.mjs +0 -362
- package/dist/capture-triggers-CdZfvHtF.mjs.map +0 -1
- package/dist/challenge-store-VePApay4.mjs +0 -51
- package/dist/challenge-store-VePApay4.mjs.map +0 -1
- package/dist/checksum-BSIhEAYW.mjs +0 -162
- package/dist/checksum-BSIhEAYW.mjs.map +0 -1
- package/dist/chrome-tags-CKELm-bo.mjs +0 -24
- package/dist/chrome-tags-CKELm-bo.mjs.map +0 -1
- package/dist/chunks-DBmkKmBH.mjs +0 -25
- package/dist/chunks-DBmkKmBH.mjs.map +0 -1
- package/dist/collection-slugs-cache-BNRAPNiu.mjs +0 -85
- package/dist/collection-slugs-cache-BNRAPNiu.mjs.map +0 -1
- package/dist/comment-Cq1UYN8_.mjs +0 -313
- package/dist/comment-Cq1UYN8_.mjs.map +0 -1
- package/dist/comment-reaction-B5sZzSoA.mjs +0 -85
- package/dist/comment-reaction-B5sZzSoA.mjs.map +0 -1
- package/dist/comments-B3F-ETyh.mjs +0 -195
- package/dist/comments-B3F-ETyh.mjs.map +0 -1
- package/dist/comments-zrTgWBSg.mjs +0 -99
- package/dist/comments-zrTgWBSg.mjs.map +0 -1
- package/dist/components-Dbf4Eq16.mjs +0 -108
- package/dist/components-Dbf4Eq16.mjs.map +0 -1
- package/dist/config-BCM5sQP7.d.mts +0 -6688
- package/dist/config-BCM5sQP7.d.mts.map +0 -1
- package/dist/config-CXPcy2iY.mjs +0 -228
- package/dist/config-CXPcy2iY.mjs.map +0 -1
- package/dist/config-CZkZBuBK.d.mts +0 -39
- package/dist/config-CZkZBuBK.d.mts.map +0 -1
- package/dist/connection-C5GIciX9.mjs +0 -36
- package/dist/connection-C5GIciX9.mjs.map +0 -1
- package/dist/content-RzEXY3QX.mjs +0 -2020
- package/dist/content-RzEXY3QX.mjs.map +0 -1
- package/dist/content-list-query-B6P37Ug_.d.mts +0 -26
- package/dist/content-list-query-B6P37Ug_.d.mts.map +0 -1
- package/dist/content-repair-BDVsYXWK.mjs +0 -442
- package/dist/content-repair-BDVsYXWK.mjs.map +0 -1
- package/dist/context-BpTIzY5h.mjs +0 -2026
- package/dist/context-BpTIzY5h.mjs.map +0 -1
- package/dist/credits-JOE92Gr7.mjs +0 -110
- package/dist/credits-JOE92Gr7.mjs.map +0 -1
- package/dist/cron-ktyapbdB.mjs +0 -306
- package/dist/cron-ktyapbdB.mjs.map +0 -1
- package/dist/dashboard-BoV4pwPP.mjs +0 -151
- package/dist/dashboard-BoV4pwPP.mjs.map +0 -1
- package/dist/default-B7KnYwtQ.mjs +0 -81
- package/dist/default-B7KnYwtQ.mjs.map +0 -1
- package/dist/deferred-tasks-eaUIqXj8.d.mts +0 -9
- package/dist/deferred-tasks-eaUIqXj8.d.mts.map +0 -1
- package/dist/dev-typegen-8JlYYFno.mjs +0 -29
- package/dist/dev-typegen-8JlYYFno.mjs.map +0 -1
- package/dist/device-flow-BlV_z24g.mjs +0 -521
- package/dist/device-flow-BlV_z24g.mjs.map +0 -1
- package/dist/digest-DiYAoZpf.mjs +0 -194
- package/dist/digest-DiYAoZpf.mjs.map +0 -1
- package/dist/direct-executor-Db8xA3zg.mjs +0 -104
- package/dist/direct-executor-Db8xA3zg.mjs.map +0 -1
- package/dist/email-console-Dm78wSmg.mjs +0 -50
- package/dist/email-console-Dm78wSmg.mjs.map +0 -1
- package/dist/email-locale-B4X9LXe9.mjs +0 -25
- package/dist/email-locale-B4X9LXe9.mjs.map +0 -1
- package/dist/emdash-runtime-BakQ6io7.mjs +0 -4239
- package/dist/emdash-runtime-BakQ6io7.mjs.map +0 -1
- package/dist/enrich-BDdVNeDK.mjs +0 -194
- package/dist/enrich-BDdVNeDK.mjs.map +0 -1
- package/dist/entry-lock-DY8B-xcr.d.mts +0 -4544
- package/dist/entry-lock-DY8B-xcr.d.mts.map +0 -1
- package/dist/entry-lock-DrUAdTzf.mjs +0 -163
- package/dist/entry-lock-DrUAdTzf.mjs.map +0 -1
- package/dist/entry-lock-yIkBduqJ.mjs +0 -1180
- package/dist/entry-lock-yIkBduqJ.mjs.map +0 -1
- package/dist/env-types-DkHYR2pl.mjs +0 -38
- package/dist/env-types-DkHYR2pl.mjs.map +0 -1
- package/dist/error-DIYXAVk4.mjs +0 -90
- package/dist/error-DIYXAVk4.mjs.map +0 -1
- package/dist/errors-CNvATh_7.mjs +0 -469
- package/dist/errors-CNvATh_7.mjs.map +0 -1
- package/dist/errors-CjXd1LgH.mjs +0 -120
- package/dist/errors-CjXd1LgH.mjs.map +0 -1
- package/dist/escape-B1GM0yJU.mjs +0 -9
- package/dist/escape-B1GM0yJU.mjs.map +0 -1
- package/dist/fence-8MaKKWhp.mjs +0 -162
- package/dist/fence-8MaKKWhp.mjs.map +0 -1
- package/dist/field-defs-cache-DEkFYCDE.mjs +0 -77
- package/dist/field-defs-cache-DEkFYCDE.mjs.map +0 -1
- package/dist/field-map-D7ioN0r0.mjs +0 -83
- package/dist/field-map-D7ioN0r0.mjs.map +0 -1
- package/dist/fts-manager-DU03MxWu.mjs +0 -443
- package/dist/fts-manager-DU03MxWu.mjs.map +0 -1
- package/dist/gc-DMiE7ezk.mjs +0 -75
- package/dist/gc-DMiE7ezk.mjs.map +0 -1
- package/dist/hash-CY4QdeMJ.mjs +0 -37
- package/dist/hash-CY4QdeMJ.mjs.map +0 -1
- package/dist/hook-result-Cb67D3P3.d.mts +0 -31
- package/dist/hook-result-Cb67D3P3.d.mts.map +0 -1
- package/dist/hreflang-Bterc3b7.mjs +0 -105
- package/dist/hreflang-Bterc3b7.mjs.map +0 -1
- package/dist/hreflang-uIBOE5yg.d.mts +0 -50
- package/dist/hreflang-uIBOE5yg.d.mts.map +0 -1
- package/dist/http-route-dispatch-DubjTibD.mjs +0 -339
- package/dist/http-route-dispatch-DubjTibD.mjs.map +0 -1
- package/dist/identity-DJ-5AcdG.mjs +0 -35
- package/dist/identity-DJ-5AcdG.mjs.map +0 -1
- package/dist/import-DEfuIgRz.mjs +0 -2020
- package/dist/import-DEfuIgRz.mjs.map +0 -1
- package/dist/index-xCy3j0f6.d.mts +0 -623
- package/dist/index-xCy3j0f6.d.mts.map +0 -1
- package/dist/init-lock-4eRRB4Le.mjs +0 -56
- package/dist/init-lock-4eRRB4Le.mjs.map +0 -1
- package/dist/install-finalization-DeGRbaVa.mjs +0 -58
- package/dist/install-finalization-DeGRbaVa.mjs.map +0 -1
- package/dist/keys-DWwMxdZh.mjs +0 -61
- package/dist/keys-DWwMxdZh.mjs.map +0 -1
- package/dist/kinds-D1A-00-F.mjs +0 -974
- package/dist/kinds-D1A-00-F.mjs.map +0 -1
- package/dist/lifecycle-BWtBGNEn.mjs +0 -25
- package/dist/lifecycle-BWtBGNEn.mjs.map +0 -1
- package/dist/load-BR1NEa_3.mjs +0 -28
- package/dist/load-BR1NEa_3.mjs.map +0 -1
- package/dist/loader-D4edocWH.mjs +0 -1197
- package/dist/loader-D4edocWH.mjs.map +0 -1
- package/dist/loops-DLaHFrvI.mjs +0 -205
- package/dist/loops-DLaHFrvI.mjs.map +0 -1
- package/dist/manifest-builder-8jsSLR7s.mjs +0 -189
- package/dist/manifest-builder-8jsSLR7s.mjs.map +0 -1
- package/dist/manifest-schema-DcZjRNEv.mjs +0 -496
- package/dist/manifest-schema-DcZjRNEv.mjs.map +0 -1
- package/dist/marketplace-CNxjq10S.mjs +0 -257
- package/dist/marketplace-CNxjq10S.mjs.map +0 -1
- package/dist/media-3v7mqAVX.mjs +0 -294
- package/dist/media-3v7mqAVX.mjs.map +0 -1
- package/dist/media-Bs6qmlVl.mjs +0 -408
- package/dist/media-Bs6qmlVl.mjs.map +0 -1
- package/dist/media-CputjGE1.mjs +0 -373
- package/dist/media-CputjGE1.mjs.map +0 -1
- package/dist/media-allowlist-isqF1gAh.mjs +0 -44
- package/dist/media-allowlist-isqF1gAh.mjs.map +0 -1
- package/dist/media-folders-D2VdVBf6.mjs +0 -210
- package/dist/media-folders-D2VdVBf6.mjs.map +0 -1
- package/dist/media-url-DbkMY3y6.mjs +0 -43
- package/dist/media-url-DbkMY3y6.mjs.map +0 -1
- package/dist/media-usage-activation-BaT4AlNI.mjs +0 -78
- package/dist/media-usage-activation-BaT4AlNI.mjs.map +0 -1
- package/dist/media-usage-v43L0kR_.mjs +0 -282
- package/dist/media-usage-v43L0kR_.mjs.map +0 -1
- package/dist/media-usage-work-CY8gIeU-.mjs +0 -151
- package/dist/media-usage-work-CY8gIeU-.mjs.map +0 -1
- package/dist/media-usage-work-VO108Q9E.mjs +0 -1015
- package/dist/media-usage-work-VO108Q9E.mjs.map +0 -1
- package/dist/menus-BYyeRemR.mjs +0 -3647
- package/dist/menus-BYyeRemR.mjs.map +0 -1
- package/dist/menus-mC_XTxD7.mjs +0 -92
- package/dist/menus-mC_XTxD7.mjs.map +0 -1
- package/dist/menus-trvc6_fW.mjs +0 -753
- package/dist/menus-trvc6_fW.mjs.map +0 -1
- package/dist/mime-IZf-LHIS.mjs +0 -36
- package/dist/mime-IZf-LHIS.mjs.map +0 -1
- package/dist/mode-B9-gMXL9.mjs +0 -23
- package/dist/mode-B9-gMXL9.mjs.map +0 -1
- package/dist/node-sqlite-compat-DztcWs4y.mjs +0 -63
- package/dist/node-sqlite-compat-DztcWs4y.mjs.map +0 -1
- package/dist/oauth-authorization-BIHy9SGU.mjs +0 -274
- package/dist/oauth-authorization-BIHy9SGU.mjs.map +0 -1
- package/dist/oauth-clients-BZWZvKXa.mjs +0 -266
- package/dist/oauth-clients-BZWZvKXa.mjs.map +0 -1
- package/dist/oauth-state-store-CxXTjXdI.mjs +0 -50
- package/dist/oauth-state-store-CxXTjXdI.mjs.map +0 -1
- package/dist/oauth-user-lookup-GWojfxRl.mjs +0 -26
- package/dist/oauth-user-lookup-GWojfxRl.mjs.map +0 -1
- package/dist/options-DRF6dlXY.d.mts +0 -81
- package/dist/options-DRF6dlXY.d.mts.map +0 -1
- package/dist/ownership-Bd77AmJz.mjs +0 -12
- package/dist/ownership-Bd77AmJz.mjs.map +0 -1
- package/dist/parse-5Y0W39xR.mjs +0 -81
- package/dist/parse-5Y0W39xR.mjs.map +0 -1
- package/dist/parse-BPblgoa-.d.mts +0 -172
- package/dist/parse-BPblgoa-.d.mts.map +0 -1
- package/dist/passkey-config-PiQ5cBnt.mjs +0 -46
- package/dist/passkey-config-PiQ5cBnt.mjs.map +0 -1
- package/dist/paths-DkS74lBM.mjs +0 -77
- package/dist/paths-DkS74lBM.mjs.map +0 -1
- package/dist/placeholder-Bez2282x.d.mts +0 -306
- package/dist/placeholder-Bez2282x.d.mts.map +0 -1
- package/dist/plan-Lw7iGnGD.mjs +0 -135
- package/dist/plan-Lw7iGnGD.mjs.map +0 -1
- package/dist/plugins-D7xSh1Lu.mjs +0 -277
- package/dist/plugins-D7xSh1Lu.mjs.map +0 -1
- package/dist/policy-DMRiMdLn.mjs +0 -45
- package/dist/policy-DMRiMdLn.mjs.map +0 -1
- package/dist/portable-text-kcyN3F-a.mjs +0 -307
- package/dist/portable-text-kcyN3F-a.mjs.map +0 -1
- package/dist/preview--2VDLaND.mjs +0 -107
- package/dist/preview--2VDLaND.mjs.map +0 -1
- package/dist/protocol-FvwLA8P-.d.mts +0 -61
- package/dist/protocol-FvwLA8P-.d.mts.map +0 -1
- package/dist/public-submission-BRyK76U8.mjs +0 -250
- package/dist/public-submission-BRyK76U8.mjs.map +0 -1
- package/dist/public-url-7j0cpCFc.mjs +0 -101
- package/dist/public-url-7j0cpCFc.mjs.map +0 -1
- package/dist/query-BRNTde3V.mjs +0 -41
- package/dist/query-BRNTde3V.mjs.map +0 -1
- package/dist/query-DyYYWOKk.mjs +0 -1161
- package/dist/query-DyYYWOKk.mjs.map +0 -1
- package/dist/rate-limit-D0BcE76H.mjs +0 -120
- package/dist/rate-limit-D0BcE76H.mjs.map +0 -1
- package/dist/receipt-C4icyHlC.mjs +0 -43
- package/dist/receipt-C4icyHlC.mjs.map +0 -1
- package/dist/redirect-9DbtxSNG.mjs +0 -19
- package/dist/redirect-9DbtxSNG.mjs.map +0 -1
- package/dist/redirect-BvFKh8Sz.mjs +0 -416
- package/dist/redirect-BvFKh8Sz.mjs.map +0 -1
- package/dist/redirects-1nhdEaTq.mjs +0 -148
- package/dist/redirects-1nhdEaTq.mjs.map +0 -1
- package/dist/redirects-D1yMnSR9.mjs +0 -446
- package/dist/redirects-D1yMnSR9.mjs.map +0 -1
- package/dist/registry-CzPJNu3H.mjs +0 -8366
- package/dist/registry-CzPJNu3H.mjs.map +0 -1
- package/dist/relations-C8JpSkU6.mjs +0 -776
- package/dist/relations-C8JpSkU6.mjs.map +0 -1
- package/dist/request-meta-Bs60XW1m.mjs +0 -141
- package/dist/request-meta-Bs60XW1m.mjs.map +0 -1
- package/dist/resolve-BHWgMU_X.mjs +0 -100
- package/dist/resolve-BHWgMU_X.mjs.map +0 -1
- package/dist/revision-CGkQA0e5.mjs +0 -366
- package/dist/revision-CGkQA0e5.mjs.map +0 -1
- package/dist/route-wire-ClUVQduA.mjs +0 -197
- package/dist/route-wire-ClUVQduA.mjs.map +0 -1
- package/dist/routes-D2GFl3B7.mjs +0 -272
- package/dist/routes-D2GFl3B7.mjs.map +0 -1
- package/dist/runner-CLpxmhl8.mjs +0 -6197
- package/dist/runner-CLpxmhl8.mjs.map +0 -1
- package/dist/runner-CWl76CH3.d.mts +0 -68
- package/dist/runner-CWl76CH3.d.mts.map +0 -1
- package/dist/schema-27XefSYG.mjs +0 -13
- package/dist/schema-27XefSYG.mjs.map +0 -1
- package/dist/schema-Btvj3BMh.mjs +0 -815
- package/dist/schema-Btvj3BMh.mjs.map +0 -1
- package/dist/scopes-DGCj6K4p.mjs +0 -34
- package/dist/scopes-DGCj6K4p.mjs.map +0 -1
- package/dist/search-pZRRmWjh.mjs +0 -448
- package/dist/search-pZRRmWjh.mjs.map +0 -1
- package/dist/secrets-CIi0nFiR.mjs +0 -341
- package/dist/secrets-CIi0nFiR.mjs.map +0 -1
- package/dist/sections-CIhqs3Ao.mjs +0 -346
- package/dist/sections-CIhqs3Ao.mjs.map +0 -1
- package/dist/seo-DifeiGdl.mjs +0 -114
- package/dist/seo-DifeiGdl.mjs.map +0 -1
- package/dist/seo-DpYxmGaL.mjs +0 -133
- package/dist/seo-DpYxmGaL.mjs.map +0 -1
- package/dist/service-COVdVpSK.mjs +0 -150
- package/dist/service-COVdVpSK.mjs.map +0 -1
- package/dist/session-user-C6IEjuty.mjs +0 -61
- package/dist/session-user-C6IEjuty.mjs.map +0 -1
- package/dist/settings-CwcrX-Ay.mjs +0 -61
- package/dist/settings-CwcrX-Ay.mjs.map +0 -1
- package/dist/settings-D_cJtT48.mjs +0 -439
- package/dist/settings-D_cJtT48.mjs.map +0 -1
- package/dist/settings-tnPQN3Z2.mjs +0 -51
- package/dist/settings-tnPQN3Z2.mjs.map +0 -1
- package/dist/setup-complete-BOrI10UY.mjs +0 -65
- package/dist/setup-complete-BOrI10UY.mjs.map +0 -1
- package/dist/setup-nonce-0XTBLTsX.mjs +0 -25
- package/dist/setup-nonce-0XTBLTsX.mjs.map +0 -1
- package/dist/single-flight-cache-B5O2qhO1.mjs +0 -104
- package/dist/single-flight-cache-B5O2qhO1.mjs.map +0 -1
- package/dist/site-settings-B7vGFRDT.mjs +0 -50
- package/dist/site-settings-B7vGFRDT.mjs.map +0 -1
- package/dist/site-url-wEYRMHBv.mjs +0 -16
- package/dist/site-url-wEYRMHBv.mjs.map +0 -1
- package/dist/snapshot-W36KbLmc.mjs +0 -414
- package/dist/snapshot-W36KbLmc.mjs.map +0 -1
- package/dist/ssrf-CRJ-xom5.mjs +0 -406
- package/dist/ssrf-CRJ-xom5.mjs.map +0 -1
- package/dist/stage-DA0NrTRZ.mjs +0 -662
- package/dist/stage-DA0NrTRZ.mjs.map +0 -1
- package/dist/staged-Bw5eHbKS.mjs +0 -686
- package/dist/staged-Bw5eHbKS.mjs.map +0 -1
- package/dist/state-BTlprvUF.mjs +0 -153
- package/dist/state-BTlprvUF.mjs.map +0 -1
- package/dist/status-C6IrSLMT.mjs +0 -30
- package/dist/status-C6IrSLMT.mjs.map +0 -1
- package/dist/storage-query-CdVF2bng.d.mts +0 -1614
- package/dist/storage-query-CdVF2bng.d.mts.map +0 -1
- package/dist/tar-BGOZARjy.mjs +0 -449
- package/dist/tar-BGOZARjy.mjs.map +0 -1
- package/dist/target-YhYH_0uc.mjs +0 -27
- package/dist/target-YhYH_0uc.mjs.map +0 -1
- package/dist/taxonomies-DK_XBrmT.mjs +0 -697
- package/dist/taxonomies-DK_XBrmT.mjs.map +0 -1
- package/dist/taxonomies-S1M3rUC0.mjs +0 -939
- package/dist/taxonomies-S1M3rUC0.mjs.map +0 -1
- package/dist/taxonomy-BesdHIwi.mjs +0 -525
- package/dist/taxonomy-BesdHIwi.mjs.map +0 -1
- package/dist/tokens-B_ffPF1q.mjs +0 -171
- package/dist/tokens-B_ffPF1q.mjs.map +0 -1
- package/dist/transfer-9fVSxyrV.mjs +0 -166
- package/dist/transfer-9fVSxyrV.mjs.map +0 -1
- package/dist/transfer-B7LUTaYD.mjs +0 -8855
- package/dist/transfer-B7LUTaYD.mjs.map +0 -1
- package/dist/transfer-download-BdNq4VWN.mjs +0 -33
- package/dist/transfer-download-BdNq4VWN.mjs.map +0 -1
- package/dist/transformations-BtQdvyly.mjs +0 -196
- package/dist/transformations-BtQdvyly.mjs.map +0 -1
- package/dist/transport-CriYXY1c.mjs +0 -136
- package/dist/transport-CriYXY1c.mjs.map +0 -1
- package/dist/transport-SnhS6xku.d.mts +0 -42
- package/dist/transport-SnhS6xku.d.mts.map +0 -1
- package/dist/trusted-proxy-C0eHXPd-.mjs +0 -51
- package/dist/trusted-proxy-C0eHXPd-.mjs.map +0 -1
- package/dist/turnstile-BNPrWd1e.mjs +0 -53
- package/dist/turnstile-BNPrWd1e.mjs.map +0 -1
- package/dist/types-B8sF_kE5.d.mts +0 -287
- package/dist/types-B8sF_kE5.d.mts.map +0 -1
- package/dist/types-BYVIYOlD.d.mts +0 -1720
- package/dist/types-BYVIYOlD.d.mts.map +0 -1
- package/dist/types-Bs4bi95j.d.mts +0 -374
- package/dist/types-Bs4bi95j.d.mts.map +0 -1
- package/dist/types-CGuODCZi.d.mts +0 -198
- package/dist/types-CGuODCZi.d.mts.map +0 -1
- package/dist/types-CVsmpTcU.mjs +0 -18
- package/dist/types-CVsmpTcU.mjs.map +0 -1
- package/dist/types-ClFEExxn.mjs +0 -37
- package/dist/types-ClFEExxn.mjs.map +0 -1
- package/dist/types-D994zUWB.d.mts +0 -85
- package/dist/types-D994zUWB.d.mts.map +0 -1
- package/dist/types-DAj1ByJQ.d.mts +0 -125
- package/dist/types-DAj1ByJQ.d.mts.map +0 -1
- package/dist/types-Dlv6esp4.mjs +0 -77
- package/dist/types-Dlv6esp4.mjs.map +0 -1
- package/dist/types-Dr1LVwL7.d.mts +0 -363
- package/dist/types-Dr1LVwL7.d.mts.map +0 -1
- package/dist/types-MN9bOQ1g.d.mts +0 -198
- package/dist/types-MN9bOQ1g.d.mts.map +0 -1
- package/dist/types-d2Szb1oz.mjs +0 -16
- package/dist/types-d2Szb1oz.mjs.map +0 -1
- package/dist/types-ibtP7GEA.d.mts +0 -896
- package/dist/types-ibtP7GEA.d.mts.map +0 -1
- package/dist/types-j6MBDjlZ.mjs +0 -103
- package/dist/types-j6MBDjlZ.mjs.map +0 -1
- package/dist/update-check-D8No-E5s.mjs +0 -161
- package/dist/update-check-D8No-E5s.mjs.map +0 -1
- package/dist/upload-attempts-BodUoFBu.mjs +0 -32
- package/dist/upload-attempts-BodUoFBu.mjs.map +0 -1
- package/dist/url-pattern-DGAG6ZP2.mjs +0 -41
- package/dist/url-pattern-DGAG6ZP2.mjs.map +0 -1
- package/dist/user-BOvuF1sQ.mjs +0 -171
- package/dist/user-BOvuF1sQ.mjs.map +0 -1
- package/dist/utils-Dr01wRKt.mjs +0 -439
- package/dist/utils-Dr01wRKt.mjs.map +0 -1
- package/dist/validate-BttYU7NK.mjs +0 -516
- package/dist/validate-BttYU7NK.mjs.map +0 -1
- package/dist/validate-Df09inbC.d.mts +0 -468
- package/dist/validate-Df09inbC.d.mts.map +0 -1
- package/dist/validation-DH2WRwWO.mjs +0 -219
- package/dist/validation-DH2WRwWO.mjs.map +0 -1
- package/dist/version-BbygH8CT.mjs +0 -7
- package/dist/version-BbygH8CT.mjs.map +0 -1
- package/dist/version-CseoDDOd.mjs +0 -9
- package/dist/version-CseoDDOd.mjs.map +0 -1
- package/dist/virtual-modules-82GlnQyG.mjs +0 -516
- package/dist/virtual-modules-82GlnQyG.mjs.map +0 -1
- package/dist/widgets-DMAgGcQt.mjs +0 -126
- package/dist/widgets-DMAgGcQt.mjs.map +0 -1
- package/dist/wxr-taxonomies-Bi5kFSxQ.mjs +0 -453
- package/dist/wxr-taxonomies-Bi5kFSxQ.mjs.map +0 -1
- package/dist/wxr-taxonomies-y_FpYlDn.d.mts +0 -34
- package/dist/wxr-taxonomies-y_FpYlDn.d.mts.map +0 -1
- package/dist/zod-generator-DnfG5sGQ.mjs +0 -424
- package/dist/zod-generator-DnfG5sGQ.mjs.map +0 -1
- /package/dist/{api-tokens-B4COohsb.mjs → api-tokens-BLz2MtQs.mjs} +0 -0
- /package/dist/{ssrf-sC4_5rxm.mjs → ssrf-tA45vIPX.mjs} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-D6-XK1Sn.mjs","names":[],"sources":["../src/search/query.ts","../src/search/text-extraction.ts","../src/search/index.ts"],"sourcesContent":["/**\n * Search Query Functions\n *\n * Programmatic API for searching content using FTS5.\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { encodeCursor, decodeCursor, InvalidCursorError } from \"../database/repositories/types.js\";\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport { resolveConfiguredLocale } from \"../i18n/config.js\";\nimport { getDb } from \"../loader.js\";\nimport { FTSManager } from \"./fts-manager.js\";\nimport type {\n\tSearchOptions,\n\tCollectionSearchOptions,\n\tSearchResult,\n\tSearchResponse,\n\tSuggestOptions,\n\tSuggestion,\n\tSearchStats,\n} from \"./types.js\";\n\n/**\n * Marker stored in the `id` slot of a search pagination cursor.\n *\n * Search results are merged from per-collection FTS queries and re-sorted by\n * score, so there is no single stable keyset column to encode (the way\n * `getEmDashCollection` encodes `(orderValue, id)`). We page over the merged,\n * score-sorted set by offset instead, carried as the cursor's `orderValue`.\n * Reusing `encodeCursor`/`decodeCursor` keeps the opaque base64-JSON shape and\n * the `InvalidCursorError` handling consistent with the rest of the API.\n */\nconst SEARCH_CURSOR_MARKER = \"search\";\n\n/** Encode the next-page offset into an opaque search cursor. */\nfunction encodeSearchCursor(offset: number): string {\n\treturn encodeCursor(String(offset), SEARCH_CURSOR_MARKER);\n}\n\n/**\n * Decode a search cursor back to its offset. Throws `InvalidCursorError` for a\n * malformed cursor or one that doesn't carry a non-negative integer offset, so\n * a client pagination bug surfaces immediately rather than silently restarting\n * from the first page.\n */\n/** Upper bound on a decoded search offset, to cap the per-collection row fetch a forged cursor can trigger. */\nconst MAX_SEARCH_OFFSET = 10_000;\n\nfunction decodeSearchOffset(cursor: string): number {\n\tconst { orderValue, id } = decodeCursor(cursor);\n\tif (id !== SEARCH_CURSOR_MARKER) {\n\t\tthrow new InvalidCursorError(cursor);\n\t}\n\tconst offset = Number(orderValue);\n\tif (!Number.isInteger(offset) || offset < 0 || offset > MAX_SEARCH_OFFSET) {\n\t\tthrow new InvalidCursorError(cursor);\n\t}\n\treturn offset;\n}\n\n/** Pattern to split on whitespace for query term extraction */\nconst WHITESPACE_SPLIT_PATTERN = /\\s+/;\nconst FTS_OPERATORS_PATTERN = /\\b(AND|OR|NOT|NEAR)\\b/i;\nconst DOUBLE_QUOTE_PATTERN = /\"/g;\n\n/**\n * Detect FTS5 query syntax errors. Match specifically on the SQLite FTS5\n * error fingerprints rather than a broad \"fts5\" / \"syntax error\" filter\n * (which would also swallow internal table-corruption errors). The two\n * fingerprints we care about are:\n *\n * - \"fts5: syntax error near …\" — unbalanced quotes, stray operators,\n * other malformed user input\n * - \"unknown special query: …\" — bare special tokens like `^*` that\n * parse but don't resolve to a real FTS5 directive\n */\nfunction isFts5SyntaxError(error: unknown): boolean {\n\tif (!(error instanceof Error)) return false;\n\tconst message = error.message.toLowerCase();\n\treturn message.includes(\"fts5: syntax error\") || message.includes(\"unknown special query\");\n}\n\n/**\n * Search across multiple collections\n *\n * Public API that auto-injects the database.\n *\n * @param query - Search query (FTS5 syntax supported)\n * @param options - Search options\n * @returns Search results with pagination\n *\n * @example\n * ```typescript\n * import { search } from \"emdash\";\n *\n * const results = await search(\"hello world\", {\n * collections: [\"posts\", \"pages\"],\n * limit: 20\n * });\n * ```\n */\nexport async function search(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst db = await getDb();\n\treturn searchWithDb(db, query, options);\n}\n\n/**\n * Search across multiple collections (with explicit db)\n *\n * @internal Use `search()` in templates. This variant is for admin routes\n * that already have a database handle.\n *\n * @param db - Kysely database instance\n * @param query - Search query (FTS5 syntax supported)\n * @param options - Search options\n * @returns Search results with pagination\n */\nexport async function searchWithDb(\n\tdb: Kysely<Database>,\n\tquery: string,\n\toptions: SearchOptions = {},\n): Promise<SearchResponse> {\n\tconst ftsManager = new FTSManager(db);\n\tconst limit = options.limit ?? 20;\n\tconst status = options.status ?? \"published\";\n\tconst offset = options.cursor ? decodeSearchOffset(options.cursor) : 0;\n\n\t// Get searchable collections\n\tlet collections = options.collections;\n\tif (!collections || collections.length === 0) {\n\t\tcollections = await getSearchableCollections(db);\n\t}\n\n\tif (collections.length === 0) {\n\t\treturn { items: [] };\n\t}\n\n\t// To rank the merged window [offset, offset + limit) correctly, each\n\t// collection must contribute its own top (offset + limit) rows — in the\n\t// worst case the entire window comes from one collection. The extra +1\n\t// row detects whether a further page exists.\n\tconst perCollectionLimit = offset + limit + 1;\n\n\t// Search each collection and merge results\n\tconst allResults: SearchResult[] = [];\n\tconst titleColumns = await ftsManager.getCollectionsWithTitleColumn(collections);\n\n\tfor (const collection of collections) {\n\t\tconst config = await ftsManager.getSearchConfig(collection);\n\t\tif (!config?.enabled) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst collectionResults = await searchSingleCollection(\n\t\t\tdb,\n\t\t\tcollection,\n\t\t\tquery,\n\t\t\t{\n\t\t\t\tstatus,\n\t\t\t\tlocale: options.locale,\n\t\t\t\tlimit: perCollectionLimit,\n\t\t\t\tscope: options.scope,\n\t\t\t},\n\t\t\tconfig.weights,\n\t\t\ttitleColumns.has(collection),\n\t\t\tconfig.titleField,\n\t\t);\n\n\t\tallResults.push(...collectionResults);\n\t}\n\n\t// Sort by score descending\n\tallResults.sort((a, b) => b.score - a.score);\n\n\t// Page the merged set by offset. A nextCursor is issued only when at least\n\t// one result exists past this page.\n\tconst items = allResults.slice(offset, offset + limit);\n\tconst hasMore = allResults.length > offset + limit;\n\tconst nextCursor = hasMore ? encodeSearchCursor(offset + limit) : undefined;\n\n\treturn { items, nextCursor };\n}\n\n/**\n * Search within a single collection\n *\n * @param db - Kysely database instance\n * @param collection - Collection slug\n * @param query - Search query (FTS5 syntax supported)\n * @param options - Search options\n * @returns Search results with pagination\n *\n * @example\n * ```typescript\n * const results = await searchCollection(db, \"posts\", \"hello world\", {\n * limit: 10\n * });\n * ```\n */\nexport async function searchCollection(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tquery: string,\n\toptions: CollectionSearchOptions = {},\n): Promise<SearchResponse> {\n\tconst ftsManager = new FTSManager(db);\n\tconst config = await ftsManager.getSearchConfig(collection);\n\n\tif (!config?.enabled) {\n\t\treturn { items: [] };\n\t}\n\n\tconst limit = options.limit ?? 20;\n\tconst offset = options.cursor ? decodeSearchOffset(options.cursor) : 0;\n\n\t// Over-fetch the [offset, offset + limit) window plus one row to detect a\n\t// further page, then slice. Keeps the FTS SQL (LIMIT-only) unchanged and\n\t// the cursor contract identical to the cross-collection path.\n\tconst fetched = await searchSingleCollection(\n\t\tdb,\n\t\tcollection,\n\t\tquery,\n\t\t{\n\t\t\tstatus: options.status,\n\t\t\tlocale: options.locale,\n\t\t\tlimit: offset + limit + 1,\n\t\t\tscope: options.scope,\n\t\t},\n\t\tconfig.weights,\n\t\tundefined,\n\t\tconfig.titleField,\n\t);\n\n\tconst items = fetched.slice(offset, offset + limit);\n\tconst hasMore = fetched.length > offset + limit;\n\tconst nextCursor = hasMore ? encodeSearchCursor(offset + limit) : undefined;\n\n\treturn { items, nextCursor };\n}\n\n/**\n * Internal function to search a single collection\n */\nasync function searchSingleCollection(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tquery: string,\n\toptions: CollectionSearchOptions,\n\tweights?: Record<string, number>,\n\thasTitle?: boolean,\n\ttitleField?: string,\n): Promise<SearchResult[]> {\n\t// Validate before any raw SQL interpolation\n\tvalidateIdentifier(collection, \"collection slug\");\n\n\tconst ftsManager = new FTSManager(db);\n\tconst ftsTable = ftsManager.getFtsTableName(collection);\n\tconst contentTable = ftsManager.getContentTableName(collection);\n\tconst limit = options.limit ?? 20;\n\tconst status = options.status ?? \"published\";\n\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\n\t// Check if FTS table exists\n\tif (!(await ftsManager.ftsTableExists(collection))) {\n\t\treturn [];\n\t}\n\n\t// Escape the query for FTS5\n\tconst escapedQuery = escapeQuery(query);\n\tif (!escapedQuery) {\n\t\treturn [];\n\t}\n\n\t// Get searchable fields for snippet generation\n\tconst searchableFields = await ftsManager.getSearchableFields(collection);\n\n\t// Title scope restricts the FTS5 match to the collection's title column.\n\t// The scoped column must be one of the indexed searchable fields; a\n\t// collection whose title is not indexed cannot match by title. Operators\n\t// are disabled so every term stays quoted inside the filter's group.\n\tlet matchQuery = escapedQuery;\n\tif (options.scope === \"title\") {\n\t\tconst titleColumn =\n\t\t\ttitleField && searchableFields.includes(titleField)\n\t\t\t\t? titleField\n\t\t\t\t: searchableFields.includes(\"title\")\n\t\t\t\t\t? \"title\"\n\t\t\t\t\t: undefined;\n\t\tif (!titleColumn) {\n\t\t\treturn [];\n\t\t}\n\t\tvalidateIdentifier(titleColumn, \"title field\");\n\t\tmatchQuery = `${titleColumn} : (${escapeQuery(query, false)})`;\n\t}\n\n\t// `title` is an optional user-defined field, not a system column. Only\n\t// select it when the collection actually has one; otherwise the query\n\t// errors with \"no such column: c.title\". Multi-collection callers\n\t// precompute this in bulk and pass it in; single-collection callers fall\n\t// back to the per-collection check.\n\t// A configured titleField drives the result title so search shows\n\t// the same value as the content list. It's a validated existing field, so\n\t// the column exists; otherwise fall back to the optional `title` column.\n\tlet titleExpr;\n\tif (titleField) {\n\t\tvalidateIdentifier(titleField, \"title field\");\n\t\ttitleExpr = sql`c.${sql.ref(titleField)}`;\n\t} else {\n\t\tconst collectionHasTitle = hasTitle ?? (await ftsManager.hasTitleColumn(collection));\n\t\ttitleExpr = collectionHasTitle ? sql`c.title` : sql`NULL`;\n\t}\n\n\t// Build weight string for bm25 if weights provided\n\t// Format: bm25(table, weight1, weight2, ...)\n\t// First two weights are for 'id' and 'locale' columns (UNINDEXED, so 0)\n\tlet bm25Args = \"\";\n\tif (weights && searchableFields.length > 0) {\n\t\tconst weightValues = [\"0\", \"0\"]; // id column, locale column\n\t\tfor (const field of searchableFields) {\n\t\t\tweightValues.push(String(weights[field] ?? 1));\n\t\t}\n\t\tbm25Args = weightValues.join(\", \");\n\t}\n\n\t// Build and execute the search query\n\t// Using raw SQL because Kysely doesn't have FTS5 support\n\tconst bm25Expr = bm25Args ? `bm25(\"${ftsTable}\", ${bm25Args})` : `bm25(\"${ftsTable}\")`;\n\n\t// Snippet column index is 2 (after id=0, locale=1, first searchable field=2)\n\tlet results;\n\ttry {\n\t\tresults = await sql<{\n\t\t\tid: string;\n\t\t\tslug: string | null;\n\t\t\tlocale: string;\n\t\t\ttitle: string | null;\n\t\t\tsnippet: string | null;\n\t\t\tscore: number;\n\t\t}>`\n\t\tSELECT\n\t\t\tc.id,\n\t\t\tc.slug,\n\t\t\tc.locale,\n\t\t\t${titleExpr} as title,\n\t\t\tsnippet(\"${sql.raw(ftsTable)}\", 2, '<mark>', '</mark>', '...', 32) as snippet,\n\t\t\t${sql.raw(bm25Expr)} as score\n\t\tFROM \"${sql.raw(ftsTable)}\" f\n\t\tJOIN \"${sql.raw(contentTable)}\" c ON f.id = c.id\n\t\tWHERE \"${sql.raw(ftsTable)}\" MATCH ${matchQuery}\n\t\tAND c.status = ${status}\n\t\tAND c.deleted_at IS NULL\n\t\t${locale ? sql`AND c.locale = ${locale}` : sql``}\n\t\tORDER BY score\n\t\tLIMIT ${limit}\n\t`.execute(db);\n\t} catch (error) {\n\t\t// FTS5 returns syntax errors for queries with unbalanced quotes,\n\t\t// stray operators, or other malformed input. Treat these as\n\t\t// \"no matches\" so the user gets an empty result rather than an\n\t\t// internals-leaking error. Other errors (table missing, IO) still\n\t\t// propagate. Intentionally not logged: any anonymous client can\n\t\t// trigger this path, and the underlying error message embeds the\n\t\t// raw query, so logging would be both noisy and a log-injection\n\t\t// vector.\n\t\tif (isFts5SyntaxError(error)) {\n\t\t\treturn [];\n\t\t}\n\t\tthrow error;\n\t}\n\n\treturn results.rows.map((row) => ({\n\t\tcollection,\n\t\tid: row.id,\n\t\tslug: row.slug,\n\t\tlocale: row.locale,\n\t\ttitle: row.title ?? undefined,\n\t\t// SQLite's snippet() returns NULL when the targeted column is\n\t\t// NULL for that row — even if the row matched via a different\n\t\t// searchable column. Skip sanitization in that case so we don't\n\t\t// throw on `null.replace`. The SearchResult.snippet field is\n\t\t// already optional, so omitting it is the documented contract.\n\t\tsnippet: row.snippet === null ? undefined : sanitizeSnippet(row.snippet),\n\t\tscore: Math.abs(row.score), // bm25 returns negative scores\n\t}));\n}\n\n// Module-scope regexes so the engine doesn't recompile per call —\n// snippet sanitization runs on every search result.\nconst SNIPPET_AMP_RE = /&/g;\nconst SNIPPET_LT_RE = /</g;\nconst SNIPPET_GT_RE = />/g;\nconst SNIPPET_QUOT_RE = /\"/g;\nconst SNIPPET_APOS_RE = /'/g;\n\n/**\n * Make an FTS5 snippet safe to render with `set:html` / `innerHTML`.\n *\n * SQLite's `snippet()` function splices literal `<mark>` and `</mark>`\n * markers around matched terms but does not escape the surrounding\n * source text. Posts that legitimately contain `<`, `>`, `&`, `\"` or\n * `'` would render as broken markup, and a `<script>` literal in a\n * title (or any other indexed field) would execute when displayed.\n *\n * The fix: HTML-escape the whole string, which turns the markers into\n * `<mark>` / `</mark>`. Then restore those two patterns to\n * their original tag form. The result is \"the indexed text with all\n * HTML metacharacters escaped, plus a small set of literal `<mark>`\n * highlight tags around matched terms\" — which matches the API's\n * documented contract.\n */\nfunction sanitizeSnippet(snippet: string): string {\n\treturn snippet\n\t\t.replace(SNIPPET_AMP_RE, \"&\")\n\t\t.replace(SNIPPET_LT_RE, \"<\")\n\t\t.replace(SNIPPET_GT_RE, \">\")\n\t\t.replace(SNIPPET_QUOT_RE, \""\")\n\t\t.replace(SNIPPET_APOS_RE, \"'\")\n\t\t.replaceAll(\"<mark>\", \"<mark>\")\n\t\t.replaceAll(\"</mark>\", \"</mark>\");\n}\n\n/**\n * Get search suggestions for autocomplete\n *\n * @param db - Kysely database instance\n * @param query - Partial search query\n * @param options - Suggestion options\n * @returns Array of suggestions\n */\nexport async function getSuggestions(\n\tdb: Kysely<Database>,\n\tquery: string,\n\toptions: SuggestOptions = {},\n): Promise<Suggestion[]> {\n\tconst limit = options.limit ?? 5;\n\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\n\t// Get searchable collections\n\tlet collections = options.collections;\n\tif (!collections || collections.length === 0) {\n\t\tcollections = await getSearchableCollections(db);\n\t}\n\n\tif (collections.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst suggestions: Suggestion[] = [];\n\tconst ftsManager = new FTSManager(db);\n\tconst titleColumns = await ftsManager.getCollectionsWithTitleColumn(collections);\n\n\tfor (const collection of collections) {\n\t\tconst config = await ftsManager.getSearchConfig(collection);\n\t\tif (!config?.enabled) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Suggestions are title-based (Suggestion.title is required). A configured\n\t\t// titleField supplies it -- a validated existing column -- so it\n\t\t// takes precedence and keeps autocomplete consistent with search results.\n\t\t// Otherwise fall back to the optional `title` field; collections with\n\t\t// neither can't produce a suggestion and are skipped (selecting a missing\n\t\t// column would error).\n\t\tlet titleExpr;\n\t\tif (config.titleField) {\n\t\t\tvalidateIdentifier(config.titleField, \"title field\");\n\t\t\ttitleExpr = sql`c.${sql.ref(config.titleField)}`;\n\t\t} else if (titleColumns.has(collection)) {\n\t\t\ttitleExpr = sql`c.title`;\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Validate before raw SQL interpolation\n\t\tvalidateIdentifier(collection, \"collection slug\");\n\n\t\tconst ftsTable = ftsManager.getFtsTableName(collection);\n\t\tconst contentTable = ftsManager.getContentTableName(collection);\n\n\t\t// Use prefix search for autocomplete. `escapeQuery` already appends `*`\n\t\t// to each term for prefix matching, so we must not append another one.\n\t\tconst prefixQuery = escapeQuery(query);\n\t\tif (!prefixQuery) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet results;\n\t\ttry {\n\t\t\tresults = await sql<{\n\t\t\t\tid: string;\n\t\t\t\tslug: string | null;\n\t\t\t\ttitle: string;\n\t\t\t}>`\n\t\t\t\tSELECT\n\t\t\t\t\tc.id,\n\t\t\t\t\tc.slug,\n\t\t\t\t\t${titleExpr} AS title\n\t\t\t\tFROM \"${sql.raw(ftsTable)}\" f\n\t\t\t\tJOIN \"${sql.raw(contentTable)}\" c ON f.id = c.id\n\t\t\t\tWHERE \"${sql.raw(ftsTable)}\" MATCH ${prefixQuery}\n\t\t\t\tAND c.status = 'published'\n\t\t\t\tAND c.deleted_at IS NULL\n\t\t\t\tAND ${titleExpr} IS NOT NULL\n\t\t\t\t${locale ? sql`AND c.locale = ${locale}` : sql``}\n\t\t\t\tORDER BY bm25(\"${sql.raw(ftsTable)}\")\n\t\t\t\tLIMIT ${limit}\n\t\t\t`.execute(db);\n\t\t} catch (error) {\n\t\t\t// Same swallow as searchSingleCollection: malformed prefix\n\t\t\t// queries should yield no suggestions, not surface DB errors.\n\t\t\t// Intentionally not logged (anonymous-triggerable, echoes\n\t\t\t// user input -- see searchSingleCollection for rationale).\n\t\t\tif (isFts5SyntaxError(error)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tfor (const row of results.rows) {\n\t\t\tsuggestions.push({\n\t\t\t\tcollection,\n\t\t\t\tid: row.id,\n\t\t\t\tslug: row.slug,\n\t\t\t\ttitle: row.title,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn suggestions.slice(0, limit);\n}\n\n/**\n * Get search statistics for all collections\n */\nexport async function getSearchStats(db: Kysely<Database>): Promise<SearchStats> {\n\tconst ftsManager = new FTSManager(db);\n\tconst collections = await getSearchableCollections(db);\n\tconst stats: SearchStats = { collections: {} };\n\n\tfor (const collection of collections) {\n\t\tconst collectionStats = await ftsManager.getIndexStats(collection);\n\t\tif (collectionStats) {\n\t\t\tstats.collections[collection] = collectionStats;\n\t\t}\n\t}\n\n\treturn stats;\n}\n\n/**\n * Get list of collections with search enabled\n */\nasync function getSearchableCollections(db: Kysely<Database>): Promise<string[]> {\n\tconst results = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select([\"slug\", \"search_config\"])\n\t\t.execute();\n\n\treturn results\n\t\t.filter((r) => {\n\t\t\tif (!r.search_config) return false;\n\t\t\ttry {\n\t\t\t\tconst config = JSON.parse(r.search_config);\n\t\t\t\treturn config.enabled === true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})\n\t\t.map((r) => r.slug);\n}\n\n/**\n * Escape a query string for FTS5\n *\n * Handles special characters and prevents injection.\n */\nfunction escapeQuery(query: string, allowOperators = true): string {\n\tif (!query || typeof query !== \"string\") {\n\t\treturn \"\";\n\t}\n\n\t// Trim whitespace\n\tquery = query.trim();\n\n\tif (query.length === 0) {\n\t\treturn \"\";\n\t}\n\n\t// If already a quoted phrase, escape only interior quotes and preserve phrase syntax\n\tif (query.startsWith('\"') && query.endsWith('\"') && query.length >= 2) {\n\t\tconst inner = query.slice(1, -1);\n\t\treturn `\"${inner.replace(DOUBLE_QUOTE_PATTERN, '\"\"')}\"`;\n\t}\n\n\t// Escape any existing quotes\n\tconst escaped = query.replace(DOUBLE_QUOTE_PATTERN, '\"\"');\n\n\t// If the query contains FTS5 operators (AND, OR, NOT, NEAR),\n\t// pass through with quotes escaped but operators preserved. Not allowed\n\t// when the match string is embedded in a column filter: unquoted parens\n\t// would let a clause escape the filter's group.\n\tif (allowOperators && FTS_OPERATORS_PATTERN.test(query)) {\n\t\treturn escaped;\n\t}\n\n\t// For simple queries, wrap each word to handle special chars\n\tconst terms = escaped.split(WHITESPACE_SPLIT_PATTERN).filter((t) => t.length > 0);\n\tif (terms.length === 0) {\n\t\treturn \"\";\n\t}\n\n\t// Join with implicit AND, add prefix matching (*) to all terms\n\t// This allows \"hel wor\" to match \"hello world\"\n\treturn terms.map((t) => `\"${t}\"*`).join(\" \");\n}\n","/**\n * Text Extraction\n *\n * Extracts plain text from Portable Text blocks for FTS indexing.\n * Uses @portabletext/toolkit as base with extensions for custom block types.\n */\n\nimport { toPlainText } from \"@portabletext/toolkit\";\n\nimport type { PortableTextBlock } from \"../content/converters/types.js\";\n\n/**\n * Validate that a value looks like a Portable Text block array.\n * Each element must have at least a `_type` string property.\n */\nfunction isPortableTextArray(value: unknown[]): value is PortableTextBlock[] {\n\treturn value.every(\n\t\t(item) =>\n\t\t\ttypeof item === \"object\" &&\n\t\t\titem !== null &&\n\t\t\t\"_type\" in item &&\n\t\t\ttypeof item._type === \"string\",\n\t);\n}\n\n/**\n * Extract additional text from custom block types that toPlainText doesn't handle\n */\nfunction extractCustomBlockText(block: PortableTextBlock): string {\n\t// Code blocks - include the code content\n\tif (block._type === \"code\" && \"code\" in block && typeof block.code === \"string\") {\n\t\treturn block.code;\n\t}\n\n\t// Image blocks - include alt text and caption\n\tif (block._type === \"image\") {\n\t\tconst parts: string[] = [];\n\t\tif (\"alt\" in block && typeof block.alt === \"string\" && block.alt) {\n\t\t\tparts.push(block.alt);\n\t\t}\n\t\tif (\"caption\" in block && typeof block.caption === \"string\" && block.caption) {\n\t\t\tparts.push(block.caption);\n\t\t}\n\t\treturn parts.join(\" \");\n\t}\n\n\treturn \"\";\n}\n\n/**\n * Extract plain text from Portable Text blocks\n *\n * Uses @portabletext/toolkit's toPlainText for standard blocks,\n * plus extracts text from custom block types (code, images with alt/caption).\n *\n * @param blocks - Array of Portable Text blocks (or a JSON string)\n * @returns Plain text content\n *\n * @example\n * ```typescript\n * const text = extractPlainText([\n * {\n * _type: \"block\",\n * _key: \"abc\",\n * children: [{ _type: \"span\", _key: \"s1\", text: \"Hello World\" }]\n * }\n * ]);\n * // Returns: \"Hello World\"\n * ```\n */\nexport function extractPlainText(blocks: PortableTextBlock[] | string | null | undefined): string {\n\tif (!blocks) {\n\t\treturn \"\";\n\t}\n\n\t// Handle JSON string input\n\tlet parsedBlocks: PortableTextBlock[];\n\tif (typeof blocks === \"string\") {\n\t\ttry {\n\t\t\tparsedBlocks = JSON.parse(blocks);\n\t\t} catch {\n\t\t\t// If it's not valid JSON, treat as plain text\n\t\t\treturn blocks;\n\t\t}\n\t} else {\n\t\tparsedBlocks = blocks;\n\t}\n\n\tif (!Array.isArray(parsedBlocks)) {\n\t\treturn \"\";\n\t}\n\n\t// Use official toPlainText for standard blocks.\n\t// toPlainText expects `{ _type: string; [key: string]: any }[]` but our blocks use\n\t// `unknown` index sigs. They're structurally compatible at runtime — spread each block\n\t// to satisfy the wider index signature without an unsafe cast.\n\tconst toolkitBlocks = parsedBlocks.map((b) => {\n\t\tconst obj: Record<string, unknown> & { _type: string } = { _type: b._type };\n\t\tfor (const [key, val] of Object.entries(b)) {\n\t\t\tobj[key] = val;\n\t\t}\n\t\treturn obj;\n\t});\n\tconst standardText = toPlainText(toolkitBlocks);\n\n\t// Extract text from custom block types that toPlainText doesn't handle\n\tconst customTexts = parsedBlocks.map(extractCustomBlockText).filter((text) => text.length > 0);\n\n\t// Combine both\n\tconst allTexts = [standardText, ...customTexts].filter((t) => t.length > 0);\n\treturn allTexts.join(\"\\n\");\n}\n\n/**\n * Extract searchable text from a content entry\n *\n * Extracts text from specified fields, handling both plain text and Portable Text.\n *\n * @param entry - Content entry data\n * @param fields - Field names to extract text from\n * @returns Object mapping field names to extracted text\n */\nexport function extractSearchableFields(\n\tentry: Record<string, unknown>,\n\tfields: string[],\n): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\n\tfor (const field of fields) {\n\t\tconst value = entry[field];\n\n\t\tif (value === null || value === undefined) {\n\t\t\tresult[field] = \"\";\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (typeof value === \"string\") {\n\t\t\t// Could be plain text or JSON Portable Text\n\t\t\tif (value.startsWith(\"[\")) {\n\t\t\t\tresult[field] = extractPlainText(value);\n\t\t\t} else {\n\t\t\t\tresult[field] = value;\n\t\t\t}\n\t\t} else if (Array.isArray(value)) {\n\t\t\t// Validate the array looks like Portable Text before treating it as such\n\t\t\tif (isPortableTextArray(value)) {\n\t\t\t\tresult[field] = extractPlainText(value);\n\t\t\t} else {\n\t\t\t\tresult[field] = JSON.stringify(value);\n\t\t\t}\n\t\t} else if (typeof value === \"object\") {\n\t\t\t// Object — serialize to JSON for searchable text\n\t\t\tresult[field] = JSON.stringify(value);\n\t\t} else if (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\tresult[field] = `${value}`;\n\t\t} else {\n\t\t\tresult[field] = \"\";\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * Search Module\n *\n * Full-text search for EmDash using SQLite FTS5.\n */\n\n// Types\nexport type {\n\tSearchConfig,\n\tSearchTokenizer,\n\tSearchOptions,\n\tCollectionSearchOptions,\n\tSearchResult,\n\tSearchResponse,\n\tSuggestOptions,\n\tSuggestion,\n\tSearchStats,\n} from \"./types.js\";\nexport { SEARCH_TOKENIZERS } from \"./types.js\";\n\n// FTS Manager\nexport { FTSManager } from \"./fts-manager.js\";\n\n// Query functions (public API uses getDb() internally)\nexport { search, searchWithDb, searchCollection, getSuggestions, getSearchStats } from \"./query.js\";\n\n// Text extraction\nexport { extractPlainText, extractSearchableFields } from \"./text-extraction.js\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { t as after } from "./after-PcEvMY_6.mjs";
|
|
2
|
+
import { t as OptionsRepository } from "./options-DhrI7eMv.mjs";
|
|
3
|
+
import { a as encodeBase64url, r as decodeBase64url } from "./base64-BQBZwXCx.mjs";
|
|
4
|
+
import { r as singleFlightCached, t as createSingleFlightCache } from "./single-flight-cache-D0EaGbN1.mjs";
|
|
5
|
+
import { sha256 } from "@oslojs/crypto/sha2";
|
|
6
|
+
import { encodeHexLowerCase } from "@oslojs/encoding";
|
|
7
|
+
|
|
8
|
+
//#region src/database/request-scoped.ts
|
|
9
|
+
const REQUEST_SCOPED_DBS_KEY = Symbol.for("emdash:request-scoped-dbs");
|
|
10
|
+
function getRegistry() {
|
|
11
|
+
const holder = globalThis;
|
|
12
|
+
let registry = holder[REQUEST_SCOPED_DBS_KEY];
|
|
13
|
+
if (!registry) {
|
|
14
|
+
registry = /* @__PURE__ */ new WeakSet();
|
|
15
|
+
holder[REQUEST_SCOPED_DBS_KEY] = registry;
|
|
16
|
+
}
|
|
17
|
+
return registry;
|
|
18
|
+
}
|
|
19
|
+
function markRequestScopedDb(db) {
|
|
20
|
+
getRegistry().add(db);
|
|
21
|
+
}
|
|
22
|
+
function isRequestScopedDb(db) {
|
|
23
|
+
return getRegistry().has(db);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/config/secrets.ts
|
|
28
|
+
/**
|
|
29
|
+
* Centralized secrets module
|
|
30
|
+
*
|
|
31
|
+
* Single source of truth for site-level cryptographic secrets:
|
|
32
|
+
*
|
|
33
|
+
* - `EMDASH_ENCRYPTION_KEY` — primary key for encrypting plugin secrets at
|
|
34
|
+
* rest. A comma-separated list supports rotation: the first key encrypts
|
|
35
|
+
* new values and every listed key remains available for decryption. Format:
|
|
36
|
+
* `emdash_enc_v1_<43 base64url chars>` representing
|
|
37
|
+
* 32 random bytes. **Operator-provided; never stored in the database.**
|
|
38
|
+
* Losing the key means losing every secret encrypted with it. Validated
|
|
39
|
+
* at runtime startup via `validateEncryptionKeyAtStartup` — request-time
|
|
40
|
+
* resolution does not depend on it, so a malformed key can't 500 the
|
|
41
|
+
* preview/comment hot paths for unrelated visitors.
|
|
42
|
+
* - `EMDASH_IP_SALT` (optional) / DB-stored `emdash:ip_salt` — site-specific
|
|
43
|
+
* salt for hashing commenter IPs. Generated and persisted on first need
|
|
44
|
+
* if no env override is set. Replaces the previous hardcoded
|
|
45
|
+
* `"emdash-ip-salt"` constant which was correlatable across installs.
|
|
46
|
+
* - `EMDASH_PREVIEW_SECRET` (optional) / DB-stored `emdash:preview_secret` —
|
|
47
|
+
* HMAC secret for signing preview URLs. Generated and persisted on first
|
|
48
|
+
* need if no env override is set. Replaces the previous empty-string
|
|
49
|
+
* fallback which silently disabled preview-token verification.
|
|
50
|
+
*
|
|
51
|
+
* The `EMDASH_AUTH_SECRET` env var is consulted only as a legacy fallback
|
|
52
|
+
* source for the IP salt — that's the only path the prior code actually
|
|
53
|
+
* read it from. New deployments don't need to set it.
|
|
54
|
+
*
|
|
55
|
+
* Modeled on `resolveS3Config` in `../storage/s3.ts`.
|
|
56
|
+
*/
|
|
57
|
+
/** v1 encryption key prefix. Bumping requires a separate KDF version. */
|
|
58
|
+
const ENCRYPTION_KEY_PREFIX = "emdash_enc_v1_";
|
|
59
|
+
/** 32 random bytes encoded as unpadded base64url = 43 chars. */
|
|
60
|
+
const ENCRYPTION_KEY_BODY_LENGTH = 43;
|
|
61
|
+
/**
|
|
62
|
+
* Built from the prefix constant via interpolation. The prefix has no regex
|
|
63
|
+
* metacharacters today (`emdash_enc_v1_`), but escaping is cheap defense
|
|
64
|
+
* against anyone changing the prefix in a future bump without remembering.
|
|
65
|
+
*/
|
|
66
|
+
const ENCRYPTION_KEY_PATTERN = new RegExp(`^${ENCRYPTION_KEY_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[A-Za-z0-9_-]{${ENCRYPTION_KEY_BODY_LENGTH}}$`);
|
|
67
|
+
/** Options-table key for the persisted commenter-IP salt. */
|
|
68
|
+
const IP_SALT_OPTION_KEY = "emdash:ip_salt";
|
|
69
|
+
/** Options-table key for the persisted preview HMAC secret. */
|
|
70
|
+
const PREVIEW_SECRET_OPTION_KEY = "emdash:preview_secret";
|
|
71
|
+
/** Length in bytes of generated values. 32 bytes = 256 bits. */
|
|
72
|
+
const GENERATED_SECRET_BYTES = 32;
|
|
73
|
+
/**
|
|
74
|
+
* Class of validation failures raised by this module.
|
|
75
|
+
*
|
|
76
|
+
* Errors here are operator-facing config problems (malformed key, etc.).
|
|
77
|
+
* They are thrown rather than soft-skipped so misconfiguration fails loudly
|
|
78
|
+
* at startup instead of silently degrading at request time.
|
|
79
|
+
*/
|
|
80
|
+
var EmDashSecretsError = class extends Error {
|
|
81
|
+
name = "EmDashSecretsError";
|
|
82
|
+
code;
|
|
83
|
+
constructor(message, code) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.code = code;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Parse the `EMDASH_ENCRYPTION_KEY` env var.
|
|
90
|
+
*
|
|
91
|
+
* Accepts a single key or a comma-separated list. The first entry is the
|
|
92
|
+
* primary (used for new writes); all entries are tried for decryption,
|
|
93
|
+
* matched by `kid`. Whitespace around commas is tolerated. Empty entries
|
|
94
|
+
* (e.g. trailing comma) are ignored.
|
|
95
|
+
*
|
|
96
|
+
* Returns `null` for an unset/empty input. Throws `EmDashSecretsError` on
|
|
97
|
+
* any malformed entry — silent skipping would mask deployment mistakes.
|
|
98
|
+
*/
|
|
99
|
+
async function parseEncryptionKeys(raw) {
|
|
100
|
+
if (!raw) return null;
|
|
101
|
+
const entries = raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
102
|
+
if (entries.length === 0) return null;
|
|
103
|
+
const parsed = [];
|
|
104
|
+
const seenKids = /* @__PURE__ */ new Set();
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
if (!ENCRYPTION_KEY_PATTERN.test(entry)) throw new EmDashSecretsError(`EMDASH_ENCRYPTION_KEY entry is malformed (expected "${ENCRYPTION_KEY_PREFIX}" followed by ${ENCRYPTION_KEY_BODY_LENGTH} base64url chars). Generate one with \`emdash secrets generate\`.`, "INVALID_ENCRYPTION_KEY");
|
|
107
|
+
const body = entry.slice(14);
|
|
108
|
+
const key = decodeBase64urlStrict(body);
|
|
109
|
+
if (!key) throw new EmDashSecretsError("EMDASH_ENCRYPTION_KEY body is not valid base64url", "INVALID_ENCRYPTION_KEY");
|
|
110
|
+
if (key.length !== GENERATED_SECRET_BYTES) throw new EmDashSecretsError(`EMDASH_ENCRYPTION_KEY must decode to ${GENERATED_SECRET_BYTES} bytes, got ${key.length}`, "INVALID_ENCRYPTION_KEY");
|
|
111
|
+
if (encodeBase64url(key) !== body) throw new EmDashSecretsError("EMDASH_ENCRYPTION_KEY body is not canonical base64url. Generate one with `emdash secrets generate`.", "INVALID_ENCRYPTION_KEY");
|
|
112
|
+
const kid = fingerprintKeyBytes(key);
|
|
113
|
+
if (seenKids.has(kid)) continue;
|
|
114
|
+
seenKids.add(kid);
|
|
115
|
+
parsed.push({
|
|
116
|
+
kid,
|
|
117
|
+
key,
|
|
118
|
+
raw: entry
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve the encryption keys used for plugin secret settings.
|
|
125
|
+
*
|
|
126
|
+
* This is deliberately separate from `resolveSecrets`: plugin settings are
|
|
127
|
+
* not on the anonymous request path, and a missing or malformed key must only
|
|
128
|
+
* fail operations that need encrypted plugin settings.
|
|
129
|
+
*/
|
|
130
|
+
function resolvePluginEncryptionKeys(env) {
|
|
131
|
+
return parseEncryptionKeys((env ?? readDefaultEnv()).EMDASH_ENCRYPTION_KEY);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Compute the kid for a raw key string (the env-var form including the
|
|
135
|
+
* `emdash_enc_v1_` prefix). Public so the CLI's `fingerprint` subcommand
|
|
136
|
+
* and admin endpoints can show kids without exposing raw keys.
|
|
137
|
+
*
|
|
138
|
+
* The kid is derived from the decoded key **bytes**, not the raw string,
|
|
139
|
+
* so admin endpoints / future rotation flows can match envelope kids
|
|
140
|
+
* against bytes regardless of how the env var was originally spelled.
|
|
141
|
+
*
|
|
142
|
+
* Validates the same shape as `parseEncryptionKeys` — including canonical
|
|
143
|
+
* base64url — so the CLI can't print a kid for a key the runtime would
|
|
144
|
+
* later refuse to load.
|
|
145
|
+
*
|
|
146
|
+
* Throws `EmDashSecretsError` for malformed or non-canonical input.
|
|
147
|
+
*/
|
|
148
|
+
async function fingerprintKey(raw) {
|
|
149
|
+
if (!ENCRYPTION_KEY_PATTERN.test(raw)) throw new EmDashSecretsError(`Key must match "${ENCRYPTION_KEY_PREFIX}" followed by ${ENCRYPTION_KEY_BODY_LENGTH} base64url chars`, "INVALID_ENCRYPTION_KEY");
|
|
150
|
+
const body = raw.slice(14);
|
|
151
|
+
const bytes = decodeBase64urlStrict(body);
|
|
152
|
+
if (!bytes || bytes.length !== GENERATED_SECRET_BYTES || encodeBase64url(bytes) !== body) throw new EmDashSecretsError(`Key body must decode to ${GENERATED_SECRET_BYTES} canonical base64url bytes`, "INVALID_ENCRYPTION_KEY");
|
|
153
|
+
return fingerprintKeyBytes(bytes);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Internal: kid derivation from raw key bytes. The single source of truth
|
|
157
|
+
* for what makes two keys "the same key" — used by both `parseEncryptionKeys`
|
|
158
|
+
* and `fingerprintKey`.
|
|
159
|
+
*/
|
|
160
|
+
function fingerprintKeyBytes(key) {
|
|
161
|
+
return encodeHexLowerCase(sha256(key)).slice(0, 8);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Generate a fresh `EMDASH_ENCRYPTION_KEY` value. Used by the CLI's
|
|
165
|
+
* `secrets generate` subcommand and by `create-emdash` scaffolding.
|
|
166
|
+
*/
|
|
167
|
+
function generateEncryptionKey() {
|
|
168
|
+
const bytes = new Uint8Array(GENERATED_SECRET_BYTES);
|
|
169
|
+
crypto.getRandomValues(bytes);
|
|
170
|
+
return `${ENCRYPTION_KEY_PREFIX}${encodeBase64url(bytes)}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve site secrets. Reads env vars; for IP salt and preview secret,
|
|
174
|
+
* falls back to a DB-stored value, generating one atomically on first need.
|
|
175
|
+
*
|
|
176
|
+
* Idempotent. Concurrent callers race on the atomic `setIfAbsent`; whichever
|
|
177
|
+
* wins, all callers converge on the same stored value.
|
|
178
|
+
*
|
|
179
|
+
* Note: `EMDASH_ENCRYPTION_KEY` is **not** consumed here. It's validated
|
|
180
|
+
* separately at runtime startup (see `validateEncryptionKeyAtStartup`) so a
|
|
181
|
+
* malformed key can't take down preview-token verification or comment
|
|
182
|
+
* submission for unrelated visitors. Plugin-secret encryption reads it
|
|
183
|
+
* separately through `resolvePluginEncryptionKeys`.
|
|
184
|
+
*/
|
|
185
|
+
async function resolveSecrets(options) {
|
|
186
|
+
const env = options.env ?? readDefaultEnv();
|
|
187
|
+
const repo = options._repo ?? new OptionsRepository(options.db);
|
|
188
|
+
const previewEnvOverride = pickFirstNonEmpty(env.EMDASH_PREVIEW_SECRET, env.PREVIEW_SECRET);
|
|
189
|
+
const ipSaltEnvOverride = pickFirstNonEmpty(env.EMDASH_IP_SALT, env.EMDASH_AUTH_SECRET, env.AUTH_SECRET);
|
|
190
|
+
const [previewSecret, ipSalt] = await Promise.all([previewEnvOverride !== null ? Promise.resolve({
|
|
191
|
+
value: previewEnvOverride,
|
|
192
|
+
source: "env"
|
|
193
|
+
}) : ensureGeneratedOption(repo, PREVIEW_SECRET_OPTION_KEY), ipSaltEnvOverride !== null ? Promise.resolve({
|
|
194
|
+
value: ipSaltEnvOverride,
|
|
195
|
+
source: "env"
|
|
196
|
+
}) : ensureGeneratedOption(repo, IP_SALT_OPTION_KEY)]);
|
|
197
|
+
return {
|
|
198
|
+
previewSecret: previewSecret.value,
|
|
199
|
+
previewSecretSource: previewSecret.source,
|
|
200
|
+
ipSalt: ipSalt.value,
|
|
201
|
+
ipSaltSource: ipSalt.source
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Validate `EMDASH_ENCRYPTION_KEY` once at runtime startup. Logs an
|
|
206
|
+
* operator-facing error if the value is malformed but does **not** throw —
|
|
207
|
+
* plugin secret-setting operations fail closed at their own boundary when
|
|
208
|
+
* the key is invalid. This way, deployment mistakes surface immediately in
|
|
209
|
+
* startup logs without wedging unrelated request paths such as preview-token
|
|
210
|
+
* verification or comment submission.
|
|
211
|
+
*
|
|
212
|
+
* Returns `true` if the key is unset or valid, `false` if it was malformed.
|
|
213
|
+
*/
|
|
214
|
+
async function validateEncryptionKeyAtStartup(env) {
|
|
215
|
+
const resolved = env ?? readDefaultEnv();
|
|
216
|
+
try {
|
|
217
|
+
await parseEncryptionKeys(resolved.EMDASH_ENCRYPTION_KEY);
|
|
218
|
+
return true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error instanceof EmDashSecretsError) {
|
|
221
|
+
console.error(`[emdash] EMDASH_ENCRYPTION_KEY is invalid: ${error.message} Plugin secret settings are unavailable until this is fixed. Generate a fresh key with \`emdash secrets generate\`.`);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Per-DB cache of resolved secrets. Request-scoped views of the configured
|
|
229
|
+
* database (see `database/request-scoped.ts`) share one entry; any other
|
|
230
|
+
* Kysely instance is keyed by its identity.
|
|
231
|
+
*
|
|
232
|
+
* The resolved values are stable for the lifetime of the deployment (env
|
|
233
|
+
* vars don't change without a restart, and DB-stored values are written
|
|
234
|
+
* once via `setIfAbsent`). Caching avoids one options-table read per
|
|
235
|
+
* request on the hot paths (preview verification, comment hashing). A
|
|
236
|
+
* deleted row is regenerated only by a process or isolate that has not
|
|
237
|
+
* cached the old value, so rotating a DB-stored secret needs a restart or
|
|
238
|
+
* redeploy.
|
|
239
|
+
*
|
|
240
|
+
* Lives on `globalThis` so module-duplication during SSR bundling can't
|
|
241
|
+
* fragment the cache. See `request-context.ts` for the same pattern.
|
|
242
|
+
*
|
|
243
|
+
* Each entry is a poison-immune single-flight cache (see
|
|
244
|
+
* `utils/single-flight-cache.ts`): the resolved *value* is cached, never an
|
|
245
|
+
* in-flight promise, so a request cancelled mid-resolve can't strand later
|
|
246
|
+
* preview/comment requests on the isolate.
|
|
247
|
+
*/
|
|
248
|
+
const SECRETS_CACHE_KEY = Symbol.for("@emdash-cms/core/secrets-cache@2");
|
|
249
|
+
function getSecretsCache(db) {
|
|
250
|
+
const holder = globalThis;
|
|
251
|
+
let entry = holder[SECRETS_CACHE_KEY];
|
|
252
|
+
if (!entry) {
|
|
253
|
+
entry = { cache: /* @__PURE__ */ new WeakMap() };
|
|
254
|
+
holder[SECRETS_CACHE_KEY] = entry;
|
|
255
|
+
}
|
|
256
|
+
if (isRequestScopedDb(db)) {
|
|
257
|
+
entry.requestScoped ??= createSingleFlightCache();
|
|
258
|
+
return entry.requestScoped;
|
|
259
|
+
}
|
|
260
|
+
let cache = entry.cache.get(db);
|
|
261
|
+
if (!cache) {
|
|
262
|
+
cache = createSingleFlightCache();
|
|
263
|
+
entry.cache.set(db, cache);
|
|
264
|
+
}
|
|
265
|
+
return cache;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Memoized wrapper around `resolveSecrets`. Use this from request-time hot
|
|
269
|
+
* paths (preview verification, comment IP hashing) so they don't reread
|
|
270
|
+
* env / re-query options on every request.
|
|
271
|
+
*
|
|
272
|
+
* Request-scoped views of the configured database share one resolution;
|
|
273
|
+
* playground / DO preview / per-test databases each get their own.
|
|
274
|
+
* Concurrent cold callers coalesce onto one resolution via the single-flight
|
|
275
|
+
* lock; a failed resolution propagates to the caller and releases the lock so
|
|
276
|
+
* the next caller retries.
|
|
277
|
+
*/
|
|
278
|
+
function resolveSecretsCached(db) {
|
|
279
|
+
return singleFlightCached(getSecretsCache(db), () => resolveSecrets({ db }), {
|
|
280
|
+
anchor: (promise) => after(() => promise),
|
|
281
|
+
ownerTimeoutMs: 3e4
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Read or generate-and-persist a random base64url secret stored in the
|
|
286
|
+
* options table.
|
|
287
|
+
*
|
|
288
|
+
* Concurrency: `setIfAbsent` is an atomic INSERT...ON CONFLICT DO NOTHING.
|
|
289
|
+
* On race, the loser re-reads to converge on the winner's value.
|
|
290
|
+
*/
|
|
291
|
+
async function ensureGeneratedOption(repo, optionKey) {
|
|
292
|
+
const existing = await repo.get(optionKey);
|
|
293
|
+
if (typeof existing === "string" && existing.length > 0) return {
|
|
294
|
+
value: existing,
|
|
295
|
+
source: "db"
|
|
296
|
+
};
|
|
297
|
+
const generated = generateRandomSecret();
|
|
298
|
+
if (await repo.setIfAbsent(optionKey, generated)) return {
|
|
299
|
+
value: generated,
|
|
300
|
+
source: "db"
|
|
301
|
+
};
|
|
302
|
+
const winner = await repo.get(optionKey);
|
|
303
|
+
if (typeof winner !== "string" || winner.length === 0) throw new EmDashSecretsError(`Failed to persist generated secret for "${optionKey}"`, "SECRET_PERSIST_FAILED");
|
|
304
|
+
return {
|
|
305
|
+
value: winner,
|
|
306
|
+
source: "db"
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/** Generate 32 random bytes encoded as unpadded base64url. */
|
|
310
|
+
function generateRandomSecret() {
|
|
311
|
+
const bytes = new Uint8Array(GENERATED_SECRET_BYTES);
|
|
312
|
+
crypto.getRandomValues(bytes);
|
|
313
|
+
return encodeBase64url(bytes);
|
|
314
|
+
}
|
|
315
|
+
/** Return the first non-empty string from `values`, or `null` if all are empty. */
|
|
316
|
+
function pickFirstNonEmpty(...values) {
|
|
317
|
+
for (const value of values) if (typeof value === "string" && value.length > 0) return value;
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const BASE64URL_CHARSET_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
321
|
+
/**
|
|
322
|
+
* Validate base64url shape and decode. Returns `null` on malformed input
|
|
323
|
+
* (rather than throwing) so the caller can produce a config-specific error.
|
|
324
|
+
*/
|
|
325
|
+
function decodeBase64urlStrict(input) {
|
|
326
|
+
if (!BASE64URL_CHARSET_PATTERN.test(input)) return null;
|
|
327
|
+
try {
|
|
328
|
+
return decodeBase64url(input);
|
|
329
|
+
} catch {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Default env reader.
|
|
335
|
+
*
|
|
336
|
+
* Reads **only** `process.env`. This module must never reference
|
|
337
|
+
* `import.meta.env`: in an Astro/Vite production build the bare
|
|
338
|
+
* `import.meta.env` expression is statically replaced with an object
|
|
339
|
+
* literal built from the env loaded at build time, which (a) embeds any
|
|
340
|
+
* secret present in `.env` on the build machine into the shipped server
|
|
341
|
+
* bundle, and (b) makes that stale build-time value silently shadow the
|
|
342
|
+
* real runtime secret configured on the deployment platform (e.g. via
|
|
343
|
+
* `wrangler secret put`). See #2139.
|
|
344
|
+
*
|
|
345
|
+
* `process.env` is the runtime source on Node/container deployments and
|
|
346
|
+
* on Cloudflare Workers with Node compatibility (the same source
|
|
347
|
+
* `resolveS3Config` in `../storage/s3.ts` uses). The CLI surface
|
|
348
|
+
* (`cli/commands/secrets.ts`) runs outside the bundle where
|
|
349
|
+
* `process.env` is native.
|
|
350
|
+
*
|
|
351
|
+
* The convention documented in AGENTS.md ("import.meta.env.EMDASH_X ||
|
|
352
|
+
* import.meta.env.X") is for public, build-time config in route
|
|
353
|
+
* handlers; secrets are deliberately excluded from it.
|
|
354
|
+
*/
|
|
355
|
+
function readDefaultEnv() {
|
|
356
|
+
const proc = typeof process !== "undefined" && process.env ? process.env : {};
|
|
357
|
+
return {
|
|
358
|
+
EMDASH_ENCRYPTION_KEY: proc.EMDASH_ENCRYPTION_KEY,
|
|
359
|
+
EMDASH_PREVIEW_SECRET: proc.EMDASH_PREVIEW_SECRET,
|
|
360
|
+
PREVIEW_SECRET: proc.PREVIEW_SECRET,
|
|
361
|
+
EMDASH_IP_SALT: proc.EMDASH_IP_SALT,
|
|
362
|
+
EMDASH_AUTH_SECRET: proc.EMDASH_AUTH_SECRET,
|
|
363
|
+
AUTH_SECRET: proc.AUTH_SECRET
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
export { resolveSecretsCached as a, resolvePluginEncryptionKeys as i, fingerprintKey as n, validateEncryptionKeyAtStartup as o, generateEncryptionKey as r, markRequestScopedDb as s, EmDashSecretsError as t };
|
|
369
|
+
//# sourceMappingURL=secrets-Cc_O3Kp-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets-Cc_O3Kp-.mjs","names":[],"sources":["../src/database/request-scoped.ts","../src/config/secrets.ts"],"sourcesContent":["/**\n * Handles returned by the adapter's `createRequestScopedDb`. Some adapters\n * (D1 with sessions, Hyperdrive, Durable Object SQL with sessions or on\n * writes) build a new Kysely instance for each request or event, and every\n * one addresses the configured database, so a cache keyed by instance uses\n * `isRequestScopedDb` to treat them as one database. A database that other\n * code puts in the request context (playground, Durable Object preview) is\n * not registered.\n */\n\nimport type { Kysely } from \"kysely\";\n\n// On globalThis so Vite SSR chunk duplication cannot split the registry\n// between the middleware that registers handles and the caches that read it.\nconst REQUEST_SCOPED_DBS_KEY = Symbol.for(\"emdash:request-scoped-dbs\");\n\nfunction getRegistry(): WeakSet<object> {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern\n\tconst holder = globalThis as Record<symbol, WeakSet<object> | undefined>;\n\tlet registry = holder[REQUEST_SCOPED_DBS_KEY];\n\tif (!registry) {\n\t\tregistry = new WeakSet();\n\t\tholder[REQUEST_SCOPED_DBS_KEY] = registry;\n\t}\n\treturn registry;\n}\n\nexport function markRequestScopedDb<DB>(db: Kysely<DB>): void {\n\tgetRegistry().add(db);\n}\n\nexport function isRequestScopedDb<DB>(db: Kysely<DB>): boolean {\n\treturn getRegistry().has(db);\n}\n","/**\n * Centralized secrets module\n *\n * Single source of truth for site-level cryptographic secrets:\n *\n * - `EMDASH_ENCRYPTION_KEY` — primary key for encrypting plugin secrets at\n * rest. A comma-separated list supports rotation: the first key encrypts\n * new values and every listed key remains available for decryption. Format:\n * `emdash_enc_v1_<43 base64url chars>` representing\n * 32 random bytes. **Operator-provided; never stored in the database.**\n * Losing the key means losing every secret encrypted with it. Validated\n * at runtime startup via `validateEncryptionKeyAtStartup` — request-time\n * resolution does not depend on it, so a malformed key can't 500 the\n * preview/comment hot paths for unrelated visitors.\n * - `EMDASH_IP_SALT` (optional) / DB-stored `emdash:ip_salt` — site-specific\n * salt for hashing commenter IPs. Generated and persisted on first need\n * if no env override is set. Replaces the previous hardcoded\n * `\"emdash-ip-salt\"` constant which was correlatable across installs.\n * - `EMDASH_PREVIEW_SECRET` (optional) / DB-stored `emdash:preview_secret` —\n * HMAC secret for signing preview URLs. Generated and persisted on first\n * need if no env override is set. Replaces the previous empty-string\n * fallback which silently disabled preview-token verification.\n *\n * The `EMDASH_AUTH_SECRET` env var is consulted only as a legacy fallback\n * source for the IP salt — that's the only path the prior code actually\n * read it from. New deployments don't need to set it.\n *\n * Modeled on `resolveS3Config` in `../storage/s3.ts`.\n */\n\nimport { sha256 } from \"@oslojs/crypto/sha2\";\nimport { encodeHexLowerCase } from \"@oslojs/encoding\";\nimport type { Kysely } from \"kysely\";\n\nimport { after } from \"../after.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport { isRequestScopedDb } from \"../database/request-scoped.js\";\nimport type { Database } from \"../database/types.js\";\nimport { decodeBase64url, encodeBase64url } from \"../utils/base64.js\";\nimport {\n\tcreateSingleFlightCache,\n\ttype SingleFlightCache,\n\tsingleFlightCached,\n} from \"../utils/single-flight-cache.js\";\n\n/** v1 encryption key prefix. Bumping requires a separate KDF version. */\nexport const ENCRYPTION_KEY_PREFIX = \"emdash_enc_v1_\";\n\n/** 32 random bytes encoded as unpadded base64url = 43 chars. */\nconst ENCRYPTION_KEY_BODY_LENGTH = 43;\n\nconst REGEX_META_PATTERN = /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Built from the prefix constant via interpolation. The prefix has no regex\n * metacharacters today (`emdash_enc_v1_`), but escaping is cheap defense\n * against anyone changing the prefix in a future bump without remembering.\n */\nconst ENCRYPTION_KEY_PATTERN = new RegExp(\n\t`^${ENCRYPTION_KEY_PREFIX.replace(REGEX_META_PATTERN, \"\\\\$&\")}[A-Za-z0-9_-]{${ENCRYPTION_KEY_BODY_LENGTH}}$`,\n);\n\n/** Options-table key for the persisted commenter-IP salt. */\nexport const IP_SALT_OPTION_KEY = \"emdash:ip_salt\";\n\n/** Options-table key for the persisted preview HMAC secret. */\nexport const PREVIEW_SECRET_OPTION_KEY = \"emdash:preview_secret\";\n\n/** Length in bytes of generated values. 32 bytes = 256 bits. */\nconst GENERATED_SECRET_BYTES = 32;\n\n/**\n * A parsed encryption key with its kid (key id) fingerprint.\n *\n * `kid` is the first 8 chars of the SHA-256 hash of the decoded key bytes\n * (lowercase hex), used to tag envelopes so the decryptor can pick the right\n * key during rotation.\n */\nexport interface ParsedEncryptionKey {\n\t/** 8-char lowercase hex fingerprint derived from the decoded key bytes. */\n\tkid: string;\n\t/** The 32 raw key bytes, ready for `crypto.subtle.importKey`. */\n\tkey: Uint8Array;\n\t/** The original env-var-formatted string (kept for re-emit; never log). */\n\traw: string;\n}\n\n/** Resolved site secrets. */\nexport interface ResolvedSecrets {\n\t/** HMAC secret for preview URLs. Always non-empty after resolution. */\n\tpreviewSecret: string;\n\t/**\n\t * Source of `previewSecret`. Useful for diagnostics; never expose the\n\t * value itself, only the source.\n\t */\n\tpreviewSecretSource: \"env\" | \"db\";\n\t/** Salt for hashing commenter IPs. Always non-empty after resolution. */\n\tipSalt: string;\n\t/** Source of `ipSalt`. */\n\tipSaltSource: \"env\" | \"db\";\n}\n\n/** Inputs for `resolveSecrets`. */\nexport interface ResolveSecretsOptions {\n\t/**\n\t * The Kysely DB used to persist (and read back) generated salt/preview\n\t * secret values. Required — these values must be stable across requests\n\t * within a deployment.\n\t */\n\tdb: Kysely<Database>;\n\t/**\n\t * Optional explicit env override map. When omitted, falls back to\n\t * `process.env` via `readDefaultEnv` below (never `import.meta.env` —\n\t * see that function's docstring). Tests pass an explicit map to avoid\n\t * leaking process state.\n\t */\n\tenv?: SecretsEnv;\n\t/**\n\t * @internal Test seam: inject a custom OptionsRepository to exercise\n\t * the lost-race re-read branch. Production callers never set this.\n\t */\n\t_repo?: OptionsRepository;\n}\n\n/** Environment-variable shape consulted by the resolver. */\nexport interface SecretsEnv {\n\t/**\n\t * Read by `validateEncryptionKeyAtStartup` and the plugin-secret encryption\n\t * layer. **Not** consulted by `resolveSecrets`,\n\t * so a malformed value can't 500 the preview/comment hot paths.\n\t */\n\tEMDASH_ENCRYPTION_KEY?: string;\n\tEMDASH_PREVIEW_SECRET?: string;\n\t/** Legacy alias; new docs point at EMDASH_PREVIEW_SECRET. */\n\tPREVIEW_SECRET?: string;\n\tEMDASH_IP_SALT?: string;\n\t/**\n\t * Legacy fallback. Prior code derived the IP salt from\n\t * `EMDASH_AUTH_SECRET || AUTH_SECRET || \"emdash-ip-salt\"`. We preserve\n\t * the env-var fallback (so existing installs keep their stable salt)\n\t * but no longer read it from `import.meta.env` in route handlers.\n\t */\n\tEMDASH_AUTH_SECRET?: string;\n\t/** Legacy alias. */\n\tAUTH_SECRET?: string;\n}\n\n/**\n * Class of validation failures raised by this module.\n *\n * Errors here are operator-facing config problems (malformed key, etc.).\n * They are thrown rather than soft-skipped so misconfiguration fails loudly\n * at startup instead of silently degrading at request time.\n */\nexport class EmDashSecretsError extends Error {\n\toverride readonly name = \"EmDashSecretsError\";\n\treadonly code: string;\n\n\tconstructor(message: string, code: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Encryption key parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse the `EMDASH_ENCRYPTION_KEY` env var.\n *\n * Accepts a single key or a comma-separated list. The first entry is the\n * primary (used for new writes); all entries are tried for decryption,\n * matched by `kid`. Whitespace around commas is tolerated. Empty entries\n * (e.g. trailing comma) are ignored.\n *\n * Returns `null` for an unset/empty input. Throws `EmDashSecretsError` on\n * any malformed entry — silent skipping would mask deployment mistakes.\n */\nexport async function parseEncryptionKeys(\n\traw: string | undefined,\n): Promise<ParsedEncryptionKey[] | null> {\n\tif (!raw) return null;\n\n\tconst entries = raw\n\t\t.split(\",\")\n\t\t.map((entry) => entry.trim())\n\t\t.filter((entry) => entry.length > 0);\n\n\tif (entries.length === 0) return null;\n\n\tconst parsed: ParsedEncryptionKey[] = [];\n\tconst seenKids = new Set<string>();\n\n\tfor (const entry of entries) {\n\t\tif (!ENCRYPTION_KEY_PATTERN.test(entry)) {\n\t\t\tthrow new EmDashSecretsError(\n\t\t\t\t`EMDASH_ENCRYPTION_KEY entry is malformed (expected \"${ENCRYPTION_KEY_PREFIX}\" followed by ${ENCRYPTION_KEY_BODY_LENGTH} base64url chars). Generate one with \\`emdash secrets generate\\`.`,\n\t\t\t\t\"INVALID_ENCRYPTION_KEY\",\n\t\t\t);\n\t\t}\n\n\t\tconst body = entry.slice(ENCRYPTION_KEY_PREFIX.length);\n\t\tconst key = decodeBase64urlStrict(body);\n\t\tif (!key) {\n\t\t\tthrow new EmDashSecretsError(\n\t\t\t\t\"EMDASH_ENCRYPTION_KEY body is not valid base64url\",\n\t\t\t\t\"INVALID_ENCRYPTION_KEY\",\n\t\t\t);\n\t\t}\n\t\tif (key.length !== GENERATED_SECRET_BYTES) {\n\t\t\tthrow new EmDashSecretsError(\n\t\t\t\t`EMDASH_ENCRYPTION_KEY must decode to ${GENERATED_SECRET_BYTES} bytes, got ${key.length}`,\n\t\t\t\t\"INVALID_ENCRYPTION_KEY\",\n\t\t\t);\n\t\t}\n\n\t\t// Reject non-canonical base64url. 43 chars decode to 32 bytes but\n\t\t// the last char only carries 2 information bits — multiple raw\n\t\t// strings can decode to the same bytes. Forcing canonical form\n\t\t// guarantees `kid` (derived from bytes) is stable per key\n\t\t// material, regardless of how the operator pasted it.\n\t\tconst canonical = encodeBase64url(key);\n\t\tif (canonical !== body) {\n\t\t\tthrow new EmDashSecretsError(\n\t\t\t\t\"EMDASH_ENCRYPTION_KEY body is not canonical base64url. Generate one with `emdash secrets generate`.\",\n\t\t\t\t\"INVALID_ENCRYPTION_KEY\",\n\t\t\t);\n\t\t}\n\n\t\tconst kid = fingerprintKeyBytes(key);\n\t\tif (seenKids.has(kid)) {\n\t\t\t// Duplicate keys are user error (paste mistake during rotation).\n\t\t\t// We dedupe rather than throw — the rotation flow is forgiving.\n\t\t\tcontinue;\n\t\t}\n\t\tseenKids.add(kid);\n\t\tparsed.push({ kid, key, raw: entry });\n\t}\n\n\t// `parsed` always has at least one entry here: `entries` was non-empty\n\t// after filtering, the loop runs at least once, the first iteration\n\t// always passes the empty-`seenKids` check.\n\treturn parsed;\n}\n\n/**\n * Resolve the encryption keys used for plugin secret settings.\n *\n * This is deliberately separate from `resolveSecrets`: plugin settings are\n * not on the anonymous request path, and a missing or malformed key must only\n * fail operations that need encrypted plugin settings.\n */\nexport function resolvePluginEncryptionKeys(\n\tenv?: SecretsEnv,\n): Promise<ParsedEncryptionKey[] | null> {\n\treturn parseEncryptionKeys((env ?? readDefaultEnv()).EMDASH_ENCRYPTION_KEY);\n}\n\n/**\n * Compute the kid for a raw key string (the env-var form including the\n * `emdash_enc_v1_` prefix). Public so the CLI's `fingerprint` subcommand\n * and admin endpoints can show kids without exposing raw keys.\n *\n * The kid is derived from the decoded key **bytes**, not the raw string,\n * so admin endpoints / future rotation flows can match envelope kids\n * against bytes regardless of how the env var was originally spelled.\n *\n * Validates the same shape as `parseEncryptionKeys` — including canonical\n * base64url — so the CLI can't print a kid for a key the runtime would\n * later refuse to load.\n *\n * Throws `EmDashSecretsError` for malformed or non-canonical input.\n */\nexport async function fingerprintKey(raw: string): Promise<string> {\n\tif (!ENCRYPTION_KEY_PATTERN.test(raw)) {\n\t\tthrow new EmDashSecretsError(\n\t\t\t`Key must match \"${ENCRYPTION_KEY_PREFIX}\" followed by ${ENCRYPTION_KEY_BODY_LENGTH} base64url chars`,\n\t\t\t\"INVALID_ENCRYPTION_KEY\",\n\t\t);\n\t}\n\tconst body = raw.slice(ENCRYPTION_KEY_PREFIX.length);\n\tconst bytes = decodeBase64urlStrict(body);\n\tif (!bytes || bytes.length !== GENERATED_SECRET_BYTES || encodeBase64url(bytes) !== body) {\n\t\tthrow new EmDashSecretsError(\n\t\t\t`Key body must decode to ${GENERATED_SECRET_BYTES} canonical base64url bytes`,\n\t\t\t\"INVALID_ENCRYPTION_KEY\",\n\t\t);\n\t}\n\treturn fingerprintKeyBytes(bytes);\n}\n\n/**\n * Internal: kid derivation from raw key bytes. The single source of truth\n * for what makes two keys \"the same key\" — used by both `parseEncryptionKeys`\n * and `fingerprintKey`.\n */\nfunction fingerprintKeyBytes(key: Uint8Array): string {\n\treturn encodeHexLowerCase(sha256(key)).slice(0, 8);\n}\n\n/**\n * Generate a fresh `EMDASH_ENCRYPTION_KEY` value. Used by the CLI's\n * `secrets generate` subcommand and by `create-emdash` scaffolding.\n */\nexport function generateEncryptionKey(): string {\n\tconst bytes = new Uint8Array(GENERATED_SECRET_BYTES);\n\tcrypto.getRandomValues(bytes);\n\treturn `${ENCRYPTION_KEY_PREFIX}${encodeBase64url(bytes)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Site-secret resolution (DB-backed with env override)\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve site secrets. Reads env vars; for IP salt and preview secret,\n * falls back to a DB-stored value, generating one atomically on first need.\n *\n * Idempotent. Concurrent callers race on the atomic `setIfAbsent`; whichever\n * wins, all callers converge on the same stored value.\n *\n * Note: `EMDASH_ENCRYPTION_KEY` is **not** consumed here. It's validated\n * separately at runtime startup (see `validateEncryptionKeyAtStartup`) so a\n * malformed key can't take down preview-token verification or comment\n * submission for unrelated visitors. Plugin-secret encryption reads it\n * separately through `resolvePluginEncryptionKeys`.\n */\nexport async function resolveSecrets(options: ResolveSecretsOptions): Promise<ResolvedSecrets> {\n\tconst env = options.env ?? readDefaultEnv();\n\tconst repo = options._repo ?? new OptionsRepository(options.db);\n\n\tconst previewEnvOverride = pickFirstNonEmpty(env.EMDASH_PREVIEW_SECRET, env.PREVIEW_SECRET);\n\tconst ipSaltEnvOverride = pickFirstNonEmpty(\n\t\tenv.EMDASH_IP_SALT,\n\t\tenv.EMDASH_AUTH_SECRET,\n\t\tenv.AUTH_SECRET,\n\t);\n\n\tconst [previewSecret, ipSalt] = await Promise.all([\n\t\tpreviewEnvOverride !== null\n\t\t\t? Promise.resolve({ value: previewEnvOverride, source: \"env\" as const })\n\t\t\t: ensureGeneratedOption(repo, PREVIEW_SECRET_OPTION_KEY),\n\t\tipSaltEnvOverride !== null\n\t\t\t? Promise.resolve({ value: ipSaltEnvOverride, source: \"env\" as const })\n\t\t\t: ensureGeneratedOption(repo, IP_SALT_OPTION_KEY),\n\t]);\n\n\treturn {\n\t\tpreviewSecret: previewSecret.value,\n\t\tpreviewSecretSource: previewSecret.source,\n\t\tipSalt: ipSalt.value,\n\t\tipSaltSource: ipSalt.source,\n\t};\n}\n\n/**\n * Validate `EMDASH_ENCRYPTION_KEY` once at runtime startup. Logs an\n * operator-facing error if the value is malformed but does **not** throw —\n * plugin secret-setting operations fail closed at their own boundary when\n * the key is invalid. This way, deployment mistakes surface immediately in\n * startup logs without wedging unrelated request paths such as preview-token\n * verification or comment submission.\n *\n * Returns `true` if the key is unset or valid, `false` if it was malformed.\n */\nexport async function validateEncryptionKeyAtStartup(env?: SecretsEnv): Promise<boolean> {\n\tconst resolved = env ?? readDefaultEnv();\n\ttry {\n\t\tawait parseEncryptionKeys(resolved.EMDASH_ENCRYPTION_KEY);\n\t\treturn true;\n\t} catch (error) {\n\t\tif (error instanceof EmDashSecretsError) {\n\t\t\tconsole.error(\n\t\t\t\t`[emdash] EMDASH_ENCRYPTION_KEY is invalid: ${error.message} ` +\n\t\t\t\t\t\"Plugin secret settings are unavailable until this is fixed. \" +\n\t\t\t\t\t\"Generate a fresh key with `emdash secrets generate`.\",\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Per-DB cache of resolved secrets. Request-scoped views of the configured\n * database (see `database/request-scoped.ts`) share one entry; any other\n * Kysely instance is keyed by its identity.\n *\n * The resolved values are stable for the lifetime of the deployment (env\n * vars don't change without a restart, and DB-stored values are written\n * once via `setIfAbsent`). Caching avoids one options-table read per\n * request on the hot paths (preview verification, comment hashing). A\n * deleted row is regenerated only by a process or isolate that has not\n * cached the old value, so rotating a DB-stored secret needs a restart or\n * redeploy.\n *\n * Lives on `globalThis` so module-duplication during SSR bundling can't\n * fragment the cache. See `request-context.ts` for the same pattern.\n *\n * Each entry is a poison-immune single-flight cache (see\n * `utils/single-flight-cache.ts`): the resolved *value* is cached, never an\n * in-flight promise, so a request cancelled mid-resolve can't strand later\n * preview/comment requests on the isolate.\n */\n// Versioned to prevent cache fragmentation if `ResolvedSecrets`'s shape\n// ever changes. Bump the suffix on incompatible changes so a co-resident\n// older build doesn't read a newer-shape value. Bumped to @2 when the cached\n// value changed from a bare promise to a single-flight cache.\nconst SECRETS_CACHE_KEY = Symbol.for(\"@emdash-cms/core/secrets-cache@2\");\n\ninterface SecretsCacheHolder {\n\tcache: WeakMap<Kysely<Database>, SingleFlightCache<ResolvedSecrets>>;\n\trequestScoped?: SingleFlightCache<ResolvedSecrets>;\n}\n\nfunction getSecretsCache(db: Kysely<Database>): SingleFlightCache<ResolvedSecrets> {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern\n\tconst holder = globalThis as Record<symbol, SecretsCacheHolder | undefined>;\n\tlet entry = holder[SECRETS_CACHE_KEY];\n\tif (!entry) {\n\t\tentry = { cache: new WeakMap() };\n\t\tholder[SECRETS_CACHE_KEY] = entry;\n\t}\n\tif (isRequestScopedDb(db)) {\n\t\tentry.requestScoped ??= createSingleFlightCache<ResolvedSecrets>();\n\t\treturn entry.requestScoped;\n\t}\n\tlet cache = entry.cache.get(db);\n\tif (!cache) {\n\t\tcache = createSingleFlightCache<ResolvedSecrets>();\n\t\tentry.cache.set(db, cache);\n\t}\n\treturn cache;\n}\n\n/**\n * Memoized wrapper around `resolveSecrets`. Use this from request-time hot\n * paths (preview verification, comment IP hashing) so they don't reread\n * env / re-query options on every request.\n *\n * Request-scoped views of the configured database share one resolution;\n * playground / DO preview / per-test databases each get their own.\n * Concurrent cold callers coalesce onto one resolution via the single-flight\n * lock; a failed resolution propagates to the caller and releases the lock so\n * the next caller retries.\n */\nexport function resolveSecretsCached(db: Kysely<Database>): Promise<ResolvedSecrets> {\n\tconst cache = getSecretsCache(db);\n\treturn singleFlightCached(cache, () => resolveSecrets({ db }), {\n\t\tanchor: (promise) => after(() => promise),\n\t\townerTimeoutMs: 30_000,\n\t});\n}\n\n/**\n * Test-only helper: clear the secrets cache. Tests that mutate env between\n * cases need this so a stale resolution doesn't leak across cases.\n *\n * @internal\n */\nexport function _clearSecretsCacheForTesting(): void {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern\n\tconst holder = globalThis as Record<symbol, SecretsCacheHolder | undefined>;\n\tholder[SECRETS_CACHE_KEY] = undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/**\n * Read or generate-and-persist a random base64url secret stored in the\n * options table.\n *\n * Concurrency: `setIfAbsent` is an atomic INSERT...ON CONFLICT DO NOTHING.\n * On race, the loser re-reads to converge on the winner's value.\n */\nasync function ensureGeneratedOption(\n\trepo: OptionsRepository,\n\toptionKey: string,\n): Promise<{ value: string; source: \"db\" }> {\n\tconst existing = await repo.get<string>(optionKey);\n\tif (typeof existing === \"string\" && existing.length > 0) {\n\t\treturn { value: existing, source: \"db\" };\n\t}\n\n\tconst generated = generateRandomSecret();\n\tconst inserted = await repo.setIfAbsent(optionKey, generated);\n\tif (inserted) {\n\t\treturn { value: generated, source: \"db\" };\n\t}\n\n\t// Lost the race — another process inserted first. Re-read to pick up\n\t// the winner. If the row is somehow still missing or empty, treat that\n\t// as a real error rather than looping.\n\tconst winner = await repo.get<string>(optionKey);\n\tif (typeof winner !== \"string\" || winner.length === 0) {\n\t\tthrow new EmDashSecretsError(\n\t\t\t`Failed to persist generated secret for \"${optionKey}\"`,\n\t\t\t\"SECRET_PERSIST_FAILED\",\n\t\t);\n\t}\n\treturn { value: winner, source: \"db\" };\n}\n\n/** Generate 32 random bytes encoded as unpadded base64url. */\nfunction generateRandomSecret(): string {\n\tconst bytes = new Uint8Array(GENERATED_SECRET_BYTES);\n\tcrypto.getRandomValues(bytes);\n\treturn encodeBase64url(bytes);\n}\n\n/** Return the first non-empty string from `values`, or `null` if all are empty. */\nfunction pickFirstNonEmpty(...values: (string | undefined)[]): string | null {\n\tfor (const value of values) {\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn null;\n}\n\nconst BASE64URL_CHARSET_PATTERN = /^[A-Za-z0-9_-]+$/;\n\n/**\n * Validate base64url shape and decode. Returns `null` on malformed input\n * (rather than throwing) so the caller can produce a config-specific error.\n */\nfunction decodeBase64urlStrict(input: string): Uint8Array | null {\n\t// `decodeBase64url` accepts padded input too; the env-var format is\n\t// strictly unpadded base64url, so we do a charset check first.\n\tif (!BASE64URL_CHARSET_PATTERN.test(input)) return null;\n\ttry {\n\t\treturn decodeBase64url(input);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Default env reader.\n *\n * Reads **only** `process.env`. This module must never reference\n * `import.meta.env`: in an Astro/Vite production build the bare\n * `import.meta.env` expression is statically replaced with an object\n * literal built from the env loaded at build time, which (a) embeds any\n * secret present in `.env` on the build machine into the shipped server\n * bundle, and (b) makes that stale build-time value silently shadow the\n * real runtime secret configured on the deployment platform (e.g. via\n * `wrangler secret put`). See #2139.\n *\n * `process.env` is the runtime source on Node/container deployments and\n * on Cloudflare Workers with Node compatibility (the same source\n * `resolveS3Config` in `../storage/s3.ts` uses). The CLI surface\n * (`cli/commands/secrets.ts`) runs outside the bundle where\n * `process.env` is native.\n *\n * The convention documented in AGENTS.md (\"import.meta.env.EMDASH_X ||\n * import.meta.env.X\") is for public, build-time config in route\n * handlers; secrets are deliberately excluded from it.\n */\nfunction readDefaultEnv(): SecretsEnv {\n\tconst proc = typeof process !== \"undefined\" && process.env ? process.env : {};\n\n\treturn {\n\t\tEMDASH_ENCRYPTION_KEY: proc.EMDASH_ENCRYPTION_KEY,\n\t\tEMDASH_PREVIEW_SECRET: proc.EMDASH_PREVIEW_SECRET,\n\t\tPREVIEW_SECRET: proc.PREVIEW_SECRET,\n\t\tEMDASH_IP_SALT: proc.EMDASH_IP_SALT,\n\t\tEMDASH_AUTH_SECRET: proc.EMDASH_AUTH_SECRET,\n\t\tAUTH_SECRET: proc.AUTH_SECRET,\n\t};\n}\n"],"mappings":"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"}
|