emdash 0.32.0 → 0.33.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/{adapters-C0ONzkUP.d.mts → adapters-BMyNSSZi.d.mts} +9 -1
- package/dist/adapters-BMyNSSZi.d.mts.map +1 -0
- package/dist/after-CBU2vkoO.mjs +96 -0
- package/dist/after-CBU2vkoO.mjs.map +1 -0
- package/dist/{allowed-origins-CCEi9bPI.mjs → allowed-origins-CG1pACtS.mjs} +2 -2
- package/dist/{allowed-origins-CCEi9bPI.mjs.map → allowed-origins-CG1pACtS.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -4
- package/dist/api/route-utils.d.mts.map +1 -1
- package/dist/api/route-utils.mjs +22 -22
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -5
- package/dist/{api-DgPbMlng.mjs → api-D2hunekQ.mjs} +247 -45
- package/dist/api-D2hunekQ.mjs.map +1 -0
- package/dist/{api-tokens-p-iMzvXR.mjs → api-tokens-CSND0i1z.mjs} +2 -2
- package/dist/{api-tokens-p-iMzvXR.mjs.map → api-tokens-CSND0i1z.mjs.map} +1 -1
- package/dist/{apply-1_6ra7NP.mjs → apply-kwNEJW2l.mjs} +35 -22
- package/dist/apply-kwNEJW2l.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.mjs +13 -3
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.mjs +8 -8
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +8 -8
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +4 -4
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +10 -10
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +344 -173
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -7
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -7
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +10 -10
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +10 -10
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +10 -10
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +18 -18
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +18 -18
- package/dist/astro/routes/api/admin/bylines/index.mjs +18 -18
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +16 -16
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -12
- package/dist/astro/routes/api/admin/comments/counts.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/index.mjs +12 -12
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +12 -12
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +46 -45
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +46 -45
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +45 -44
- 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 +45 -44
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +45 -44
- 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 +46 -45
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +46 -45
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +45 -44
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +45 -44
- 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 +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +45 -44
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +8 -8
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/index.mjs +7 -7
- package/dist/astro/routes/api/auth/dev-bypass.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +12 -12
- package/dist/astro/routes/api/auth/invite/index.mjs +10 -10
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -11
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -12
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +8 -8
- package/dist/astro/routes/api/auth/mode.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +7 -7
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +13 -13
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -11
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -12
- package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -12
- package/dist/astro/routes/api/auth/signup/complete.mjs +12 -12
- package/dist/astro/routes/api/auth/signup/request.mjs +12 -12
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +18 -18
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -13
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +18 -18
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -18
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +16 -16
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/index.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs +8 -8
- package/dist/astro/routes/api/dashboard.mjs +9 -9
- package/dist/astro/routes/api/dev/emails.mjs +3 -3
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +19 -19
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +15 -15
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +10 -10
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -12
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +19 -19
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +27 -27
- package/dist/astro/routes/api/manifest.mjs +5 -5
- package/dist/astro/routes/api/mcp.mjs +42 -42
- package/dist/astro/routes/api/media/_id_/confirm.mjs +10 -10
- package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
- package/dist/astro/routes/api/media/_id_/usage.mjs +14 -14
- package/dist/astro/routes/api/media/_id_.mjs +13 -13
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.mjs +9 -9
- package/dist/astro/routes/api/media.mjs +13 -13
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/items.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/translations.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_.mjs +11 -11
- package/dist/astro/routes/api/menus/index.mjs +11 -11
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
- package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
- package/dist/astro/routes/api/oauth/register.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +36 -5
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +5 -5
- package/dist/astro/routes/api/redirects/404s/index.mjs +12 -12
- package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -12
- package/dist/astro/routes/api/redirects/_id_.mjs +14 -13
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +14 -13
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +18 -18
- package/dist/astro/routes/api/relations/_id_/translations.mjs +13 -13
- package/dist/astro/routes/api/relations/index.mjs +18 -18
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +4 -4
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +45 -44
- 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 +45 -44
- 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 +45 -44
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +45 -44
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +45 -44
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.d.mts +8 -0
- package/dist/astro/routes/api/schema/collections/reorder.d.mts.map +1 -0
- package/dist/astro/routes/api/schema/collections/reorder.mjs +66 -0
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -0
- package/dist/astro/routes/api/schema/index.mjs +11 -9
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +45 -44
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +45 -44
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.d.mts +1 -0
- package/dist/astro/routes/api/search/enable.d.mts.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +17 -13
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +11 -11
- package/dist/astro/routes/api/search/rebuild.mjs +13 -13
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +7 -7
- package/dist/astro/routes/api/search/suggest.mjs +11 -11
- package/dist/astro/routes/api/sections/_slug_.mjs +11 -11
- package/dist/astro/routes/api/sections/index.mjs +11 -11
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +16 -16
- package/dist/astro/routes/api/setup/admin-verify.mjs +13 -13
- package/dist/astro/routes/api/setup/admin.mjs +12 -12
- package/dist/astro/routes/api/setup/dev-bypass.mjs +30 -30
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +32 -32
- package/dist/astro/routes/api/setup/status.mjs +4 -4
- package/dist/astro/routes/api/snapshot.mjs +9 -9
- package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts +8 -0
- package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +43 -0
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -16
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -16
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +16 -16
- package/dist/astro/routes/api/taxonomies/index.mjs +16 -16
- package/dist/astro/routes/api/themes/preview.mjs +7 -7
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- 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 +8 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +11 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +11 -11
- package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/index.mjs +11 -11
- package/dist/astro/routes/api/widget-components.mjs +3 -3
- package/dist/astro/routes/robots.txt.mjs +9 -9
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +12 -12
- package/dist/astro/routes/sitemap.xml.mjs +10 -10
- package/dist/astro/types.d.mts +19 -13
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-R5iYYrpa.mjs → authorize-qRhWQnel.mjs} +2 -2
- package/dist/{authorize-R5iYYrpa.mjs.map → authorize-qRhWQnel.mjs.map} +1 -1
- package/dist/{backup-LpQ1V_sR.mjs → backup-CtA6LDNw.mjs} +5 -5
- package/dist/{backup-LpQ1V_sR.mjs.map → backup-CtA6LDNw.mjs.map} +1 -1
- package/dist/{byline-C5TAqs8N.mjs → byline-Cp6csL84.mjs} +9 -9
- package/dist/{byline-C5TAqs8N.mjs.map → byline-Cp6csL84.mjs.map} +1 -1
- package/dist/{byline-fields-T9ZLZMxJ.mjs → byline-fields-CNc0Yq5i.mjs} +2 -2
- package/dist/{byline-fields-T9ZLZMxJ.mjs.map → byline-fields-CNc0Yq5i.mjs.map} +1 -1
- package/dist/{byline-registry-BCuOp4UF.mjs → byline-registry-ZkPKqEwC.mjs} +4 -4
- package/dist/{byline-registry-BCuOp4UF.mjs.map → byline-registry-ZkPKqEwC.mjs.map} +1 -1
- package/dist/{bylines-B8-WGdja.mjs → bylines-CEj5CdT4.mjs} +7 -7
- package/dist/{bylines-B8-WGdja.mjs.map → bylines-CEj5CdT4.mjs.map} +1 -1
- package/dist/{bylines-DGuqBLjV.mjs → bylines-cf-C9lmF.mjs} +4 -4
- package/dist/{bylines-DGuqBLjV.mjs.map → bylines-cf-C9lmF.mjs.map} +1 -1
- package/dist/cache-C9FptTFR.mjs +108 -0
- package/dist/cache-C9FptTFR.mjs.map +1 -0
- package/dist/{challenge-store-BFzgFRog.mjs → challenge-store-D9VNKELo.mjs} +1 -1
- package/dist/{challenge-store-BFzgFRog.mjs.map → challenge-store-D9VNKELo.mjs.map} +1 -1
- package/dist/{chunks-D5dlPeRb.mjs → chunks-DbYzib_k.mjs} +2 -2
- package/dist/{chunks-D5dlPeRb.mjs.map → chunks-DbYzib_k.mjs.map} +1 -1
- package/dist/cli/index.mjs +40 -32
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/{comment-DnTxxVHv.mjs → comment-BIkJ6Fil.mjs} +3 -3
- package/dist/{comment-DnTxxVHv.mjs.map → comment-BIkJ6Fil.mjs.map} +1 -1
- package/dist/{comment-reaction-BUlUz2bh.mjs → comment-reaction-D-laEceb.mjs} +2 -2
- package/dist/{comment-reaction-BUlUz2bh.mjs.map → comment-reaction-D-laEceb.mjs.map} +1 -1
- package/dist/{comments-QPKgK3nk.mjs → comments-0_w6KChA.mjs} +3 -3
- package/dist/{comments-QPKgK3nk.mjs.map → comments-0_w6KChA.mjs.map} +1 -1
- package/dist/{components-DZkL4Fx0.mjs → components-BPxrRjpE.mjs} +1 -1
- package/dist/{components-DZkL4Fx0.mjs.map → components-BPxrRjpE.mjs.map} +1 -1
- package/dist/{content-CpfKV9QE.mjs → content-CJ94NEx5.mjs} +165 -112
- package/dist/content-CJ94NEx5.mjs.map +1 -0
- package/dist/{content-refresh-DgqUBeTv.mjs → content-refresh-Xw7aKmou.mjs} +6 -6
- package/dist/{content-refresh-DgqUBeTv.mjs.map → content-refresh-Xw7aKmou.mjs.map} +1 -1
- package/dist/{context-kO4YZB03.mjs → context-C_1nrqnW.mjs} +14 -14
- package/dist/{context-kO4YZB03.mjs.map → context-C_1nrqnW.mjs.map} +1 -1
- package/dist/{cron-BlKIMD_e.mjs → cron-DRV-UC4K.mjs} +1 -1
- package/dist/{cron-BlKIMD_e.mjs.map → cron-DRV-UC4K.mjs.map} +1 -1
- package/dist/{dashboard-DoBwPnON.mjs → dashboard-DINnNVVU.mjs} +5 -5
- package/dist/{dashboard-DoBwPnON.mjs.map → dashboard-DINnNVVU.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{default-C4v9KP56.mjs → default-BD2FG0xV.mjs} +1 -1
- package/dist/{default-C4v9KP56.mjs.map → default-BD2FG0xV.mjs.map} +1 -1
- package/dist/deferred-tasks-C-WRCcKo.d.mts +9 -0
- package/dist/deferred-tasks-C-WRCcKo.d.mts.map +1 -0
- package/dist/{device-flow-DhUsyJU6.mjs → device-flow-CTqd8mud.mjs} +5 -5
- package/dist/{device-flow-DhUsyJU6.mjs.map → device-flow-CTqd8mud.mjs.map} +1 -1
- package/dist/{dialect-helpers-DFwyWjTM.mjs → dialect-helpers-BSbfaO3F.mjs} +24 -16
- package/dist/dialect-helpers-BSbfaO3F.mjs.map +1 -0
- package/dist/{email-console-C-9Ng8DM.mjs → email-console-C6WdcWbL.mjs} +1 -1
- package/dist/{email-console-C-9Ng8DM.mjs.map → email-console-C6WdcWbL.mjs.map} +1 -1
- package/dist/{error-DmmN74gW.mjs → error-oh_9i6bR.mjs} +3 -3
- package/dist/{error-DmmN74gW.mjs.map → error-oh_9i6bR.mjs.map} +1 -1
- package/dist/{errors-DtEXIQQV.mjs → errors-BucQ8VYz.mjs} +2 -1
- package/dist/errors-BucQ8VYz.mjs.map +1 -0
- package/dist/{escape-CdW31WsG.mjs → escape-CXps5F_v.mjs} +1 -1
- package/dist/{escape-CdW31WsG.mjs.map → escape-CXps5F_v.mjs.map} +1 -1
- package/dist/{field-defs-cache-QMVnzTH6.mjs → field-defs-cache-WD82dMQq.mjs} +4 -4
- package/dist/{field-defs-cache-QMVnzTH6.mjs.map → field-defs-cache-WD82dMQq.mjs.map} +1 -1
- package/dist/{fts-manager-vncsZIJm.mjs → fts-manager-BITakW_R.mjs} +37 -13
- package/dist/fts-manager-BITakW_R.mjs.map +1 -0
- package/dist/{hash-Cb7U1b5M.mjs → hash-Cg0TXJPV.mjs} +1 -1
- package/dist/{hash-Cb7U1b5M.mjs.map → hash-Cg0TXJPV.mjs.map} +1 -1
- package/dist/{hreflang-CZbSx6Bg.d.mts → hreflang-BaLCNcDx.d.mts} +2 -2
- package/dist/{hreflang-CZbSx6Bg.d.mts.map → hreflang-BaLCNcDx.d.mts.map} +1 -1
- package/dist/{hreflang-Bg-zMpkr.mjs → hreflang-BdZuVBhL.mjs} +6 -6
- package/dist/{hreflang-Bg-zMpkr.mjs.map → hreflang-BdZuVBhL.mjs.map} +1 -1
- package/dist/{import-PKhLeXvn.mjs → import-C-9uqyT3.mjs} +6 -6
- package/dist/{import-PKhLeXvn.mjs.map → import-C-9uqyT3.mjs.map} +1 -1
- package/dist/{index-j9rTh3cp.d.mts → index-M37iz29I.d.mts} +75 -35
- package/dist/index-M37iz29I.d.mts.map +1 -0
- package/dist/{index-BeNto_Vs.d.mts → index-MGw47uWq.d.mts} +3 -3
- package/dist/{index-BeNto_Vs.d.mts.map → index-MGw47uWq.d.mts.map} +1 -1
- package/dist/index.d.mts +19 -19
- package/dist/index.mjs +68 -66
- package/dist/{load-BwTdWE8B.mjs → load-DV1DZuUJ.mjs} +2 -2
- package/dist/{load-BwTdWE8B.mjs.map → load-DV1DZuUJ.mjs.map} +1 -1
- package/dist/{loader-C1XOLV5b.mjs → loader-CGugY7Zl.mjs} +29 -15
- package/dist/loader-CGugY7Zl.mjs.map +1 -0
- package/dist/{manifest-schema-bCq54i7F.mjs → manifest-schema-Da7kj23y.mjs} +1 -1
- package/dist/{manifest-schema-bCq54i7F.mjs.map → manifest-schema-Da7kj23y.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/local-runtime.d.mts +12 -12
- package/dist/media/local-runtime.mjs +8 -8
- package/dist/{media-tQDZEdu7.mjs → media-BP7S-nHT.mjs} +2 -2
- package/dist/{media-tQDZEdu7.mjs.map → media-BP7S-nHT.mjs.map} +1 -1
- package/dist/{media-DWIzoVFL.mjs → media-OhYL-7Io.mjs} +1 -1
- package/dist/{media-DWIzoVFL.mjs.map → media-OhYL-7Io.mjs.map} +1 -1
- package/dist/{media-allowlist-B7TPD6aq.mjs → media-allowlist-D-peH1H5.mjs} +1 -1
- package/dist/{media-allowlist-B7TPD6aq.mjs.map → media-allowlist-D-peH1H5.mjs.map} +1 -1
- package/dist/{media-upload-Bc9Nc5Zf.mjs → media-upload-DtrwU2B0.mjs} +7 -7
- package/dist/{media-upload-Bc9Nc5Zf.mjs.map → media-upload-DtrwU2B0.mjs.map} +1 -1
- package/dist/{media-url-BCm5vBn6.mjs → media-url-DYs_YLOx.mjs} +1 -1
- package/dist/{media-url-BCm5vBn6.mjs.map → media-url-DYs_YLOx.mjs.map} +1 -1
- package/dist/{media-usage-DPJcNaAq.mjs → media-usage-BlxfTkN0.mjs} +8 -8
- package/dist/{media-usage-DPJcNaAq.mjs.map → media-usage-BlxfTkN0.mjs.map} +1 -1
- package/dist/{menus-BxPLFfIc.mjs → menus-BcQWxeXu.mjs} +4 -4
- package/dist/{menus-BxPLFfIc.mjs.map → menus-BcQWxeXu.mjs.map} +1 -1
- package/dist/{menus-C6qhqP4X.mjs → menus-Y54RDPIN.mjs} +215 -38
- package/dist/menus-Y54RDPIN.mjs.map +1 -0
- package/dist/{mode-fiXRMfeA.mjs → mode-BWyTBe3I.mjs} +1 -1
- package/dist/{mode-fiXRMfeA.mjs.map → mode-BWyTBe3I.mjs.map} +1 -1
- package/dist/{oauth-authorization-C9tFinly.mjs → oauth-authorization-ClvktmrL.mjs} +5 -5
- package/dist/{oauth-authorization-C9tFinly.mjs.map → oauth-authorization-ClvktmrL.mjs.map} +1 -1
- package/dist/{oauth-clients-BwTxE1aE.mjs → oauth-clients-8ao-U66l.mjs} +1 -1
- package/dist/{oauth-clients-BwTxE1aE.mjs.map → oauth-clients-8ao-U66l.mjs.map} +1 -1
- package/dist/{oauth-state-store-DaRUwPWO.mjs → oauth-state-store-B1ofdTiQ.mjs} +1 -1
- package/dist/{oauth-state-store-DaRUwPWO.mjs.map → oauth-state-store-B1ofdTiQ.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-CzuzOSlF.mjs → oauth-user-lookup-CscEuIiR.mjs} +1 -1
- package/dist/{oauth-user-lookup-CzuzOSlF.mjs.map → oauth-user-lookup-CscEuIiR.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{object-cache-Bok5j2ae.mjs → object-cache-71tPfV4A.mjs} +106 -5
- package/dist/object-cache-71tPfV4A.mjs.map +1 -0
- package/dist/{options-aeIs8WFu.d.mts → options-CA4eVX6P.d.mts} +3 -3
- package/dist/{options-aeIs8WFu.d.mts.map → options-CA4eVX6P.d.mts.map} +1 -1
- package/dist/{options-BlmBHTvX.mjs → options-CBXIHUoT.mjs} +1 -1
- package/dist/{options-BlmBHTvX.mjs.map → options-CBXIHUoT.mjs.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-BL49yb9D.mjs → parse-CJKNuewM.mjs} +2 -2
- package/dist/{parse-BL49yb9D.mjs.map → parse-CJKNuewM.mjs.map} +1 -1
- package/dist/{passkey-config-KMhgFjdQ.mjs → passkey-config-D-zrzMJI.mjs} +1 -1
- package/dist/{passkey-config-KMhgFjdQ.mjs.map → passkey-config-D-zrzMJI.mjs.map} +1 -1
- package/dist/{placeholder-Dxo5Zt58.d.mts → placeholder-DaeNS-VG.d.mts} +1 -1
- package/dist/{placeholder-Dxo5Zt58.d.mts.map → placeholder-DaeNS-VG.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{portable-text-CNahFfj6.mjs → portable-text-B_kqBmXB.mjs} +1 -1
- package/dist/{portable-text-CNahFfj6.mjs.map → portable-text-B_kqBmXB.mjs.map} +1 -1
- package/dist/{preview-D4Jnbfx7.mjs → preview-BU6PaLpM.mjs} +2 -2
- package/dist/{preview-D4Jnbfx7.mjs.map → preview-BU6PaLpM.mjs.map} +1 -1
- package/dist/{public-url-DSGTnJFw.mjs → public-url-Ox9oOlgp.mjs} +14 -3
- package/dist/public-url-Ox9oOlgp.mjs.map +1 -0
- package/dist/{query-DCiXI7OZ.mjs → query-C5w7PsRQ.mjs} +20 -41
- package/dist/query-C5w7PsRQ.mjs.map +1 -0
- package/dist/{query-1xOcEy18.mjs → query-rC9HKv1N.mjs} +4 -4
- package/dist/{query-1xOcEy18.mjs.map → query-rC9HKv1N.mjs.map} +1 -1
- package/dist/{rate-limit-Vkp9gQ1Y.mjs → rate-limit-kug6q99V.mjs} +2 -2
- package/dist/{rate-limit-Vkp9gQ1Y.mjs.map → rate-limit-kug6q99V.mjs.map} +1 -1
- package/dist/{redirect-BJ6d1yHh.mjs → redirect-DIJxXKOG.mjs} +3 -3
- package/dist/{redirect-BJ6d1yHh.mjs.map → redirect-DIJxXKOG.mjs.map} +1 -1
- package/dist/{redirect-CDgfv9vh.mjs → redirect-Egf2R9yP.mjs} +1 -1
- package/dist/{redirect-CDgfv9vh.mjs.map → redirect-Egf2R9yP.mjs.map} +1 -1
- package/dist/{redirects-Cwkq2H-G.mjs → redirects-Bzm812bY.mjs} +5 -5
- package/dist/{redirects-Cwkq2H-G.mjs.map → redirects-Bzm812bY.mjs.map} +1 -1
- package/dist/{redirects-BOn2DIUs.mjs → redirects-G8wAhWAf.mjs} +20 -6
- package/dist/redirects-G8wAhWAf.mjs.map +1 -0
- package/dist/{registry-BP1JK2xh.mjs → registry-Dbodb4Uo.mjs} +59 -14
- package/dist/registry-Dbodb4Uo.mjs.map +1 -0
- package/dist/{relation-DJ5DZh7B.mjs → relation-Cbe3Sd3m.mjs} +3 -3
- package/dist/{relation-DJ5DZh7B.mjs.map → relation-Cbe3Sd3m.mjs.map} +1 -1
- package/dist/{relations-C4duJnwI.mjs → relations-Bw_E1TXo.mjs} +3 -3
- package/dist/{relations-C4duJnwI.mjs.map → relations-Bw_E1TXo.mjs.map} +1 -1
- package/dist/{relations-DHFQGX0h.d.mts → relations-G2ta7GIl.d.mts} +36 -9
- package/dist/relations-G2ta7GIl.d.mts.map +1 -0
- package/dist/{relations-DpTjftCl.mjs → relations-qHwpUe66.mjs} +5 -5
- package/dist/{relations-DpTjftCl.mjs.map → relations-qHwpUe66.mjs.map} +1 -1
- package/dist/request-context.d.mts +3 -0
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{request-meta-DzXYYI-n.mjs → request-meta-Df-jFbAd.mjs} +2 -2
- package/dist/{request-meta-DzXYYI-n.mjs.map → request-meta-Df-jFbAd.mjs.map} +1 -1
- package/dist/{resolve-BUvFE0Lr.mjs → resolve-DZzG4HqM.mjs} +1 -1
- package/dist/{resolve-BUvFE0Lr.mjs.map → resolve-DZzG4HqM.mjs.map} +1 -1
- package/dist/{runner-BsI18UgP.mjs → runner-BFTrkYhu.mjs} +482 -216
- package/dist/runner-BFTrkYhu.mjs.map +1 -0
- package/dist/{runner-CHdkERbt.d.mts → runner-BaADli8l.d.mts} +2 -2
- package/dist/{runner-CHdkERbt.d.mts.map → runner-BaADli8l.d.mts.map} +1 -1
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +3 -3
- package/dist/{schema-eqZhkR0S.mjs → schema-CiQXr6_F.mjs} +3 -3
- package/dist/{schema-eqZhkR0S.mjs.map → schema-CiQXr6_F.mjs.map} +1 -1
- package/dist/{scopes-Be2d8bSb.mjs → scopes-CNJVT5i5.mjs} +3 -3
- package/dist/{scopes-Be2d8bSb.mjs.map → scopes-CNJVT5i5.mjs.map} +1 -1
- package/dist/{search-CeO678cp.mjs → search-FZhx5dDU.mjs} +6 -6
- package/dist/{search-CeO678cp.mjs.map → search-FZhx5dDU.mjs.map} +1 -1
- package/dist/{secrets-870d-7yA.mjs → secrets-DLd8RXee.mjs} +4 -4
- package/dist/{secrets-870d-7yA.mjs.map → secrets-DLd8RXee.mjs.map} +1 -1
- package/dist/{sections-DSuEfirn.mjs → sections-BrIrMUCn.mjs} +3 -3
- package/dist/{sections-DSuEfirn.mjs.map → sections-BrIrMUCn.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +24 -24
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +17 -17
- package/dist/{seo-C3GDfT0V.mjs → seo-CobNfOvb.mjs} +2 -2
- package/dist/{seo-C3GDfT0V.mjs.map → seo-CobNfOvb.mjs.map} +1 -1
- package/dist/{seo-BAPKiED8.mjs → seo-DTHRntNC.mjs} +3 -3
- package/dist/{seo-BAPKiED8.mjs.map → seo-DTHRntNC.mjs.map} +1 -1
- package/dist/{service-DyFT-k-R.mjs → service-B-pYGPgR.mjs} +4 -4
- package/dist/{service-DyFT-k-R.mjs.map → service-B-pYGPgR.mjs.map} +1 -1
- package/dist/{session-user-BrK2zz7S.mjs → session-user-BvM9-zNl.mjs} +2 -2
- package/dist/{session-user-BrK2zz7S.mjs.map → session-user-BvM9-zNl.mjs.map} +1 -1
- package/dist/{settings-CREKyj8I.mjs → settings-DPHau-WH.mjs} +3 -3
- package/dist/{settings-CREKyj8I.mjs.map → settings-DPHau-WH.mjs.map} +1 -1
- package/dist/{settings-DgKouY2S.mjs → settings-kJhGiQ4z.mjs} +7 -7
- package/dist/{settings-DgKouY2S.mjs.map → settings-kJhGiQ4z.mjs.map} +1 -1
- package/dist/{setup-complete-L7bMK2VI.mjs → setup-complete-u50Qm8rY.mjs} +2 -2
- package/dist/{setup-complete-L7bMK2VI.mjs.map → setup-complete-u50Qm8rY.mjs.map} +1 -1
- package/dist/{setup-nonce-Ct5tjWcq.mjs → setup-nonce-DCjiiOD2.mjs} +1 -1
- package/dist/{setup-nonce-Ct5tjWcq.mjs.map → setup-nonce-DCjiiOD2.mjs.map} +1 -1
- package/dist/{single-flight-cache-C2exrGAi.mjs → single-flight-cache-DW3w11Pu.mjs} +1 -1
- package/dist/{single-flight-cache-C2exrGAi.mjs.map → single-flight-cache-DW3w11Pu.mjs.map} +1 -1
- package/dist/site-url-I-lJ5kma.mjs +16 -0
- package/dist/site-url-I-lJ5kma.mjs.map +1 -0
- package/dist/{snapshot-BdpUJKD-.mjs → snapshot-I_fjLDaD.mjs} +2 -2
- package/dist/{snapshot-BdpUJKD-.mjs.map → snapshot-I_fjLDaD.mjs.map} +1 -1
- package/dist/{ssrf-CviKqWmq.mjs → ssrf-CTa6v21i.mjs} +1 -1
- package/dist/{ssrf-CviKqWmq.mjs.map → ssrf-CTa6v21i.mjs.map} +1 -1
- package/dist/{state-xxv6ZTMv.mjs → state-CQvoefYP.mjs} +1 -1
- package/dist/{state-xxv6ZTMv.mjs.map → state-CQvoefYP.mjs.map} +1 -1
- package/dist/{status-COfycGIU.mjs → status-CIsmlC1E.mjs} +1 -1
- package/dist/{status-COfycGIU.mjs.map → status-CIsmlC1E.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-Dbdki3Un.mjs → taxonomies-CKuaZER5.mjs} +85 -5
- package/dist/taxonomies-CKuaZER5.mjs.map +1 -0
- package/dist/{taxonomies-B61CRSha.mjs → taxonomies-CLN1N92E.mjs} +16 -17
- package/dist/taxonomies-CLN1N92E.mjs.map +1 -0
- package/dist/{taxonomy-DvwWAPvA.mjs → taxonomy-pOrB2kZe.mjs} +142 -15
- package/dist/taxonomy-pOrB2kZe.mjs.map +1 -0
- package/dist/{tokens-DVltpO2D.mjs → tokens-DagaJGK8.mjs} +1 -1
- package/dist/{tokens-DVltpO2D.mjs.map → tokens-DagaJGK8.mjs.map} +1 -1
- package/dist/{transaction-D0FOsb3X.mjs → transaction-NQj4VJ7Z.mjs} +1 -1
- package/dist/{transaction-D0FOsb3X.mjs.map → transaction-NQj4VJ7Z.mjs.map} +1 -1
- package/dist/{transport-DokJOScS.d.mts → transport-CAwTaTp_.d.mts} +1 -1
- package/dist/{transport-DokJOScS.d.mts.map → transport-CAwTaTp_.d.mts.map} +1 -1
- package/dist/{transport-BWQo0Foy.mjs → transport-DVsa5YYo.mjs} +1 -1
- package/dist/{transport-BWQo0Foy.mjs.map → transport-DVsa5YYo.mjs.map} +1 -1
- package/dist/{trusted-proxy-CwjQj0YG.mjs → trusted-proxy-D20_7fyX.mjs} +1 -1
- package/dist/{trusted-proxy-CwjQj0YG.mjs.map → trusted-proxy-D20_7fyX.mjs.map} +1 -1
- package/dist/{types-DqZVPILm.d.mts → types-BGWPMiBO.d.mts} +2 -2
- package/dist/{types-DqZVPILm.d.mts.map → types-BGWPMiBO.d.mts.map} +1 -1
- package/dist/{types-DsAMyD_o.d.mts → types-BK6U01Ro.d.mts} +1 -1
- package/dist/{types-DsAMyD_o.d.mts.map → types-BK6U01Ro.d.mts.map} +1 -1
- package/dist/{types-BD40g7zz.mjs → types-BjxTKxrZ.mjs} +1 -1
- package/dist/{types-BD40g7zz.mjs.map → types-BjxTKxrZ.mjs.map} +1 -1
- package/dist/{types-jISEalTX.d.mts → types-BnFP1M3J.d.mts} +22 -1
- package/dist/types-BnFP1M3J.d.mts.map +1 -0
- package/dist/{types-Dr2GLlwu.d.mts → types-C8oz_ayJ.d.mts} +1 -1
- package/dist/{types-Dr2GLlwu.d.mts.map → types-C8oz_ayJ.d.mts.map} +1 -1
- package/dist/{types-C9P3ysxq.d.mts → types-CbUcuxxc.d.mts} +1 -1
- package/dist/{types-C9P3ysxq.d.mts.map → types-CbUcuxxc.d.mts.map} +1 -1
- package/dist/{types-XrQQ-Aex.mjs → types-Cd8iuS1J.mjs} +10 -3
- package/dist/{types-XrQQ-Aex.mjs.map → types-Cd8iuS1J.mjs.map} +1 -1
- package/dist/types-CtzytVXn.mjs +18 -0
- package/dist/types-CtzytVXn.mjs.map +1 -0
- package/dist/{types-BQY7VYw6.d.mts → types-Cz8hNch2.d.mts} +5 -5
- package/dist/{types-BQY7VYw6.d.mts.map → types-Cz8hNch2.d.mts.map} +1 -1
- package/dist/{types-CcSZCcF1.d.mts → types-DLG4IFEQ.d.mts} +1 -1
- package/dist/{types-CcSZCcF1.d.mts.map → types-DLG4IFEQ.d.mts.map} +1 -1
- package/dist/{types-84Bque3-.d.mts → types-Dc4ntQjQ.d.mts} +10 -1
- package/dist/types-Dc4ntQjQ.d.mts.map +1 -0
- package/dist/{types-DQLZ58GV.d.mts → types-LoagAGzH.d.mts} +1 -1
- package/dist/{types-DQLZ58GV.d.mts.map → types-LoagAGzH.d.mts.map} +1 -1
- package/dist/{types-o7xo7VgH.mjs → types-NBI3rF-_.mjs} +3 -2
- package/dist/types-NBI3rF-_.mjs.map +1 -0
- package/dist/{upload-attempts-C5yd6Gae.mjs → upload-attempts-D8QAFLUJ.mjs} +1 -1
- package/dist/{upload-attempts-C5yd6Gae.mjs.map → upload-attempts-D8QAFLUJ.mjs.map} +1 -1
- package/dist/{user-BAumEmpA.mjs → user-DBrq4UVq.mjs} +3 -3
- package/dist/{user-BAumEmpA.mjs.map → user-DBrq4UVq.mjs.map} +1 -1
- package/dist/{utils-BbUgmeZG.mjs → utils-hLzi_2Fn.mjs} +2 -2
- package/dist/{utils-BbUgmeZG.mjs.map → utils-hLzi_2Fn.mjs.map} +1 -1
- package/dist/{validate-VPnKoIzW.mjs → validate-BpwoDOgt.mjs} +25 -2
- package/dist/validate-BpwoDOgt.mjs.map +1 -0
- package/dist/{validate-CdYcY-Ln.d.mts → validate-C3oSmA7F.d.mts} +15 -5
- package/dist/validate-C3oSmA7F.d.mts.map +1 -0
- package/dist/{validate-Bs_wT2ul.mjs → validate-mKo3RxBM.mjs} +3 -3
- package/dist/{validate-Bs_wT2ul.mjs.map → validate-mKo3RxBM.mjs.map} +1 -1
- package/dist/{validation-BngR0nit.mjs → validation-BgpucsvF.mjs} +10 -10
- package/dist/{validation-BngR0nit.mjs.map → validation-BgpucsvF.mjs.map} +1 -1
- package/dist/version-C4PnpmoV.mjs +7 -0
- package/dist/{version-Bdfb34kk.mjs.map → version-C4PnpmoV.mjs.map} +1 -1
- package/dist/{widgets-L-VndTKD.mjs → widgets-CJJtkiKh.mjs} +3 -3
- package/dist/{widgets-L-VndTKD.mjs.map → widgets-CJJtkiKh.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-BexjtNid.mjs → wxr-taxonomies-9PPSy8X6.mjs} +4 -4
- package/dist/{wxr-taxonomies-BexjtNid.mjs.map → wxr-taxonomies-9PPSy8X6.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-DueaLFd9.d.mts → wxr-taxonomies-Bkpp_2tK.d.mts} +1 -1
- package/dist/{wxr-taxonomies-DueaLFd9.d.mts.map → wxr-taxonomies-Bkpp_2tK.d.mts.map} +1 -1
- package/dist/{zod-generator-DMzfga3g.mjs → zod-generator-CG64Hnbh.mjs} +3 -3
- package/dist/{zod-generator-DMzfga3g.mjs.map → zod-generator-CG64Hnbh.mjs.map} +1 -1
- package/package.json +11 -8
- package/src/after.ts +6 -1
- package/src/api/errors.ts +1 -0
- package/src/api/handlers/content.ts +45 -4
- package/src/api/handlers/marketplace.ts +14 -0
- package/src/api/handlers/registry.ts +14 -0
- package/src/api/handlers/revision.ts +19 -5
- package/src/api/handlers/schema.ts +37 -0
- package/src/api/handlers/snapshot.ts +1 -1
- package/src/api/handlers/taxonomies.ts +101 -1
- package/src/api/openapi/document.ts +48 -0
- package/src/api/public-url.ts +14 -2
- package/src/api/schemas/schema.ts +13 -0
- package/src/api/schemas/search.ts +2 -0
- package/src/api/schemas/taxonomies.ts +18 -0
- package/src/api/site-url.ts +16 -6
- package/src/astro/integration/routes.ts +14 -0
- package/src/astro/integration/runtime.ts +13 -0
- package/src/astro/integration/virtual-modules.ts +2 -0
- package/src/astro/middleware/redirect.ts +2 -10
- package/src/astro/middleware/scoped-db.ts +35 -4
- package/src/astro/middleware.ts +56 -22
- package/src/astro/routes/api/admin/comments/[id]/status.ts +1 -1
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +2 -2
- package/src/astro/routes/api/auth/invite/index.ts +2 -2
- package/src/astro/routes/api/auth/magic-link/send.ts +2 -2
- package/src/astro/routes/api/auth/signup/request.ts +2 -2
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/reorder.ts +31 -0
- package/src/astro/routes/api/schema/index.ts +2 -0
- package/src/astro/routes/api/search/enable.ts +5 -1
- package/src/astro/routes/api/search/rebuild.ts +6 -1
- package/src/astro/routes/api/taxonomies/[name]/reorder.ts +41 -0
- package/src/astro/types.ts +6 -0
- package/src/cleanup.ts +31 -26
- package/src/cli/commands/dev.ts +13 -3
- package/src/cli/commands/export-seed.ts +2 -0
- package/src/cli/index.ts +1 -1
- package/src/components/CommentForm.astro +1 -1
- package/src/components/Comments.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +171 -31
- package/src/components/OrderedList.astro +14 -0
- package/src/components/PortableText.astro +26 -4
- package/src/components/index.ts +12 -1
- package/src/components/ordered-list.ts +549 -0
- package/src/content/converters/numbered-list.ts +148 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +59 -9
- package/src/content/converters/prosemirror-to-portable-text.ts +23 -12
- package/src/content/converters/types.ts +2 -0
- package/src/content/portable-text-lists.ts +205 -0
- package/src/database/dialect-helpers.ts +3 -22
- package/src/database/migrations/055_content_translation_group_locale_index.ts +67 -0
- package/src/database/migrations/056_taxonomy_term_sort_order.ts +189 -0
- package/src/database/migrations/057_collection_hidden.ts +24 -0
- package/src/database/migrations/058_collection_sort_order.ts +20 -0
- package/src/database/migrations/059_revision_prune_queue.ts +36 -0
- package/src/database/migrations/runner.ts +10 -0
- package/src/database/repositories/content.ts +191 -178
- package/src/database/repositories/revision.ts +82 -17
- package/src/database/repositories/taxonomy.ts +202 -7
- package/src/database/repositories/types.ts +7 -0
- package/src/database/types.ts +13 -1
- package/src/database/validate.ts +39 -0
- package/src/db/adapters.ts +8 -0
- package/src/deferred-tasks.ts +71 -0
- package/src/emdash-runtime.ts +268 -92
- package/src/index.ts +2 -0
- package/src/loader.ts +46 -24
- package/src/media/usage/gc.ts +50 -0
- package/src/object-cache/index.ts +160 -3
- package/src/plugins/routes.ts +34 -0
- package/src/plugins/storage-indexes.ts +50 -11
- package/src/query.ts +21 -42
- package/src/redirects/cache.ts +79 -19
- package/src/request-context.ts +3 -0
- package/src/scheduled-publish.ts +6 -1
- package/src/schema/registry.ts +95 -3
- package/src/schema/types.ts +24 -0
- package/src/search/fts-manager.ts +50 -10
- package/src/search/index.ts +2 -0
- package/src/search/types.ts +6 -0
- package/src/seed/apply.ts +22 -2
- package/src/seed/types.ts +10 -0
- package/src/taxonomies/index.ts +11 -7
- package/src/taxonomies/term-counts.ts +2 -4
- package/src/ui-comments.ts +19 -0
- package/src/ui.ts +11 -3
- package/src/virtual-modules.d.ts +8 -0
- package/dist/adapters-C0ONzkUP.d.mts.map +0 -1
- package/dist/after-B1IIdH3Y.mjs +0 -29
- package/dist/after-B1IIdH3Y.mjs.map +0 -1
- package/dist/api-DgPbMlng.mjs.map +0 -1
- package/dist/apply-1_6ra7NP.mjs.map +0 -1
- package/dist/cache-B26cufFd.mjs +0 -65
- package/dist/cache-B26cufFd.mjs.map +0 -1
- package/dist/content-CpfKV9QE.mjs.map +0 -1
- package/dist/dialect-helpers-DFwyWjTM.mjs.map +0 -1
- package/dist/errors-DtEXIQQV.mjs.map +0 -1
- package/dist/fts-manager-vncsZIJm.mjs.map +0 -1
- package/dist/index-j9rTh3cp.d.mts.map +0 -1
- package/dist/loader-C1XOLV5b.mjs.map +0 -1
- package/dist/menus-C6qhqP4X.mjs.map +0 -1
- package/dist/object-cache-Bok5j2ae.mjs.map +0 -1
- package/dist/public-url-DSGTnJFw.mjs.map +0 -1
- package/dist/query-DCiXI7OZ.mjs.map +0 -1
- package/dist/redirects-BOn2DIUs.mjs.map +0 -1
- package/dist/registry-BP1JK2xh.mjs.map +0 -1
- package/dist/relations-DHFQGX0h.d.mts.map +0 -1
- package/dist/runner-BsI18UgP.mjs.map +0 -1
- package/dist/site-url-8N3kSyVZ.mjs +0 -13
- package/dist/site-url-8N3kSyVZ.mjs.map +0 -1
- package/dist/taxonomies-B61CRSha.mjs.map +0 -1
- package/dist/taxonomies-Dbdki3Un.mjs.map +0 -1
- package/dist/taxonomy-DvwWAPvA.mjs.map +0 -1
- package/dist/types-84Bque3-.d.mts.map +0 -1
- package/dist/types-jISEalTX.d.mts.map +0 -1
- package/dist/types-o7xo7VgH.mjs.map +0 -1
- package/dist/validate-CdYcY-Ln.d.mts.map +0 -1
- package/dist/validate-VPnKoIzW.mjs.map +0 -1
- package/dist/version-Bdfb34kk.mjs +0 -7
- /package/dist/{api-tokens-CEsW_jCg.mjs → api-tokens-Dai9dND0.mjs} +0 -0
- /package/dist/{ssrf-BB__xfNM.mjs → ssrf-Cj568Dgs.mjs} +0 -0
- /package/dist/{types-CK0LxPUQ.mjs → types-DKuWPYV5.mjs} +0 -0
package/dist/db/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../types-
|
|
2
|
-
import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-
|
|
3
|
-
import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-
|
|
1
|
+
import "../types-Dc4ntQjQ.mjs";
|
|
2
|
+
import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-BaADli8l.mjs";
|
|
3
|
+
import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-BMyNSSZi.mjs";
|
|
4
4
|
export { type DatabaseDescriptor, type DatabaseDialectType, type LibsqlConfig, type MigrationStatus, type PostgresConfig, type SqliteConfig, getMigrationStatus, libsql, postgres, rollbackMigration, runMigrations, sqlite };
|
package/dist/db/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as runMigrations, i as rollbackMigration, r as getMigrationStatus } from "../runner-
|
|
2
|
-
import "../dialect-helpers-
|
|
1
|
+
import { a as runMigrations, i as rollbackMigration, r as getMigrationStatus } from "../runner-BFTrkYhu.mjs";
|
|
2
|
+
import "../dialect-helpers-BSbfaO3F.mjs";
|
|
3
3
|
import "../config-DyF3OKg_.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/db/adapters.ts
|
package/dist/db/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/db/adapters.ts"],"sourcesContent":["/**\n * Database Adapter Functions\n *\n * These run at config time (astro.config.mjs) and return serializable descriptors.\n * The actual dialect is created at runtime by loading the entrypoint.\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import emdash from \"emdash/astro\";\n * import { sqlite } from \"emdash/db\";\n *\n * export default defineConfig({\n * integrations: [\n * emdash({\n * database: sqlite({ url: \"file:./data.db\" }),\n * }),\n * ],\n * });\n * ```\n */\n\n/**\n * Dialect family identifier.\n * Used at runtime to select dialect-specific SQL fragments.\n */\nexport type DatabaseDialectType = \"sqlite\" | \"postgres\";\n\n/**\n * Database descriptor - serializable config for virtual modules\n */\nexport interface DatabaseDescriptor {\n\tentrypoint: string;\n\tconfig: unknown;\n\ttype: DatabaseDialectType;\n\t/**\n\t * When true, the adapter's runtime entrypoint MUST export a named\n\t * `createRequestScopedDb` function matching the signature declared in\n\t * `virtual:emdash/dialect`. The virtual-module generator re-exports it\n\t * by name, so a missing export becomes a build-time bundler error.\n\t *\n\t * The function is called once per request and decides — based on its own\n\t * runtime config (e.g. whether the user opted into D1 sessions) — whether\n\t * to return a per-request Kysely or null. Use this for features like D1\n\t * read-replica sessions, bookmark cookies, or any per-request DB handle.\n\t *\n\t * When false or absent, the generator emits a stub that returns null and\n\t * the middleware takes its default (singleton) path.\n\t */\n\tsupportsRequestScope?: boolean;\n\t/**\n\t * When true, the adapter's runtime entrypoint MUST export a named\n\t * `createCoalescingDialect` function. The runtime uses this fresh dialect\n\t * only for its cold-start read batch.\n\t *\n\t * When false or absent, the virtual module exports `undefined` without\n\t * inspecting an optional entrypoint export.\n\t */\n\tsupportsCoalescing?: boolean;\n}\n\nexport interface SqliteConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\")\n\t */\n\turl: string;\n}\n\nexport interface LibsqlConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\" or \"libsql://...\")\n\t */\n\turl: string;\n\t/**\n\t * Auth token for remote libSQL\n\t */\n\tauthToken?: string;\n}\n\n/**\n * SQLite database adapter (better-sqlite3)\n *\n * For local development and Node.js deployments.\n *\n * @example\n * ```ts\n * database: sqlite({ url: \"file:./data.db\" })\n * ```\n */\nexport function sqlite(config: SqliteConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/sqlite\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * libSQL database adapter (Turso)\n *\n * For Turso hosted databases or local libSQL.\n *\n * @example\n * ```ts\n * database: libsql({\n * url: \"libsql://my-db.turso.io\",\n * authToken: process.env.TURSO_AUTH_TOKEN,\n * })\n * ```\n */\nexport function libsql(config: LibsqlConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/libsql\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * PostgreSQL connection configuration\n */\nexport interface PostgresConfig {\n\tconnectionString?: string;\n\thost?: string;\n\tport?: number;\n\tdatabase?: string;\n\tuser?: string;\n\tpassword?: string;\n\tssl?: boolean;\n\tpool?: { min?: number; max?: number };\n}\n\n/**\n * PostgreSQL database adapter\n *\n * For PostgreSQL deployments with connection pooling.\n *\n * @example\n * ```ts\n * database: postgres({ connectionString: process.env.DATABASE_URL })\n * ```\n */\nexport function postgres(config: PostgresConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/postgres\",\n\t\tconfig,\n\t\ttype: \"postgres\",\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/db/adapters.ts"],"sourcesContent":["/**\n * Database Adapter Functions\n *\n * These run at config time (astro.config.mjs) and return serializable descriptors.\n * The actual dialect is created at runtime by loading the entrypoint.\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import emdash from \"emdash/astro\";\n * import { sqlite } from \"emdash/db\";\n *\n * export default defineConfig({\n * integrations: [\n * emdash({\n * database: sqlite({ url: \"file:./data.db\" }),\n * }),\n * ],\n * });\n * ```\n */\n\n/**\n * Dialect family identifier.\n * Used at runtime to select dialect-specific SQL fragments.\n */\nexport type DatabaseDialectType = \"sqlite\" | \"postgres\";\n\n/**\n * Database descriptor - serializable config for virtual modules\n */\nexport interface DatabaseDescriptor {\n\tentrypoint: string;\n\tconfig: unknown;\n\ttype: DatabaseDialectType;\n\t/**\n\t * When true, the adapter's runtime entrypoint MUST export a named\n\t * `createRequestScopedDb` function matching the signature declared in\n\t * `virtual:emdash/dialect`. The virtual-module generator re-exports it\n\t * by name, so a missing export becomes a build-time bundler error.\n\t *\n\t * The function is called once per request and decides — based on its own\n\t * runtime config (e.g. whether the user opted into D1 sessions) — whether\n\t * to return a per-request Kysely or null. Use this for features like D1\n\t * read-replica sessions, bookmark cookies, or any per-request DB handle.\n\t *\n\t * When false or absent, the generator emits a stub that returns null and\n\t * the middleware takes its default (singleton) path.\n\t */\n\tsupportsRequestScope?: boolean;\n\t/**\n\t * When true, request middleware resolves the last content-namespace\n\t * invalidation timestamp and passes it to `createRequestScopedDb`.\n\t *\n\t * Keep this unset unless request routing depends on that timestamp: reading\n\t * it may require an object-cache backend round trip.\n\t */\n\tneedsLastContentWriteAt?: boolean;\n\t/**\n\t * When true, the adapter's runtime entrypoint MUST export a named\n\t * `createCoalescingDialect` function. The runtime uses this fresh dialect\n\t * only for its cold-start read batch.\n\t *\n\t * When false or absent, the virtual module exports `undefined` without\n\t * inspecting an optional entrypoint export.\n\t */\n\tsupportsCoalescing?: boolean;\n}\n\nexport interface SqliteConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\")\n\t */\n\turl: string;\n}\n\nexport interface LibsqlConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\" or \"libsql://...\")\n\t */\n\turl: string;\n\t/**\n\t * Auth token for remote libSQL\n\t */\n\tauthToken?: string;\n}\n\n/**\n * SQLite database adapter (better-sqlite3)\n *\n * For local development and Node.js deployments.\n *\n * @example\n * ```ts\n * database: sqlite({ url: \"file:./data.db\" })\n * ```\n */\nexport function sqlite(config: SqliteConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/sqlite\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * libSQL database adapter (Turso)\n *\n * For Turso hosted databases or local libSQL.\n *\n * @example\n * ```ts\n * database: libsql({\n * url: \"libsql://my-db.turso.io\",\n * authToken: process.env.TURSO_AUTH_TOKEN,\n * })\n * ```\n */\nexport function libsql(config: LibsqlConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/libsql\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * PostgreSQL connection configuration\n */\nexport interface PostgresConfig {\n\tconnectionString?: string;\n\thost?: string;\n\tport?: number;\n\tdatabase?: string;\n\tuser?: string;\n\tpassword?: string;\n\tssl?: boolean;\n\tpool?: { min?: number; max?: number };\n}\n\n/**\n * PostgreSQL database adapter\n *\n * For PostgreSQL deployments with connection pooling.\n *\n * @example\n * ```ts\n * database: postgres({ connectionString: process.env.DATABASE_URL })\n * ```\n */\nexport function postgres(config: PostgresConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/postgres\",\n\t\tconfig,\n\t\ttype: \"postgres\",\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiGA,SAAgB,OAAO,QAA0C;AAChE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN;;;;;;;;;;;;;;;AAgBF,SAAgB,OAAO,QAA0C;AAChE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN;;;;;;;;;;;;AA2BF,SAAgB,SAAS,QAA4C;AACpE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN"}
|
package/dist/db/libsql.d.mts
CHANGED
package/dist/db/postgres.d.mts
CHANGED
package/dist/db/sqlite.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-
|
|
1
|
+
{"version":3,"file":"default-BD2FG0xV.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/deferred-tasks.d.ts
|
|
2
|
+
interface DeferredTaskTracker {
|
|
3
|
+
settled: Promise<void>;
|
|
4
|
+
track<T>(promise: Promise<T>): Promise<T>;
|
|
5
|
+
settle(): void;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { DeferredTaskTracker as t };
|
|
9
|
+
//# sourceMappingURL=deferred-tasks-C-WRCcKo.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-tasks-C-WRCcKo.d.mts","names":[],"sources":["../src/deferred-tasks.ts"],"mappings":";UAAiB,mBAAA;EAChB,OAAA,EAAS,OAAA;EACT,KAAA,IAAS,OAAA,EAAS,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EACvC,MAAA;AAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as withTransaction } from "./transaction-
|
|
2
|
-
import { a as hashApiToken, o as isValidScope, r as generatePrefixedToken, t as TOKEN_PREFIXES } from "./api-tokens-
|
|
3
|
-
import { o as lookupOAuthClient } from "./oauth-clients-
|
|
4
|
-
import { t as lookupUserRoleAndStatus } from "./oauth-user-lookup-
|
|
1
|
+
import { t as withTransaction } from "./transaction-NQj4VJ7Z.mjs";
|
|
2
|
+
import { a as hashApiToken, o as isValidScope, r as generatePrefixedToken, t as TOKEN_PREFIXES } from "./api-tokens-Dai9dND0.mjs";
|
|
3
|
+
import { o as lookupOAuthClient } from "./oauth-clients-8ao-U66l.mjs";
|
|
4
|
+
import { t as lookupUserRoleAndStatus } from "./oauth-user-lookup-CscEuIiR.mjs";
|
|
5
5
|
import { clampScopes } from "@emdash-cms/auth";
|
|
6
6
|
import { generateCodeVerifier } from "arctic";
|
|
7
7
|
|
|
@@ -463,4 +463,4 @@ async function handleTokenRevoke(db, input) {
|
|
|
463
463
|
|
|
464
464
|
//#endregion
|
|
465
465
|
export { handleTokenRevoke as a, handleTokenRefresh as i, handleDeviceCodeRequest as n, handleDeviceTokenExchange as r, handleDeviceAuthorize as t };
|
|
466
|
-
//# sourceMappingURL=device-flow-
|
|
466
|
+
//# sourceMappingURL=device-flow-CTqd8mud.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-flow-DhUsyJU6.mjs","names":[],"sources":["../src/api/handlers/device-flow.ts"],"sourcesContent":["/**\n * OAuth Device Flow handlers (RFC 8628).\n *\n * EmDash acts as an OAuth 2.0 authorization server. The CLI requests\n * a device code, displays a URL + user code, and polls for a token.\n * The user opens a browser, logs in, enters the code, and the CLI gets\n * an access + refresh token pair.\n *\n * Uses arctic for code generation and @emdash-cms/auth for token utilities.\n */\n\nimport { clampScopes } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tisValidScope,\n\tTOKEN_PREFIXES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Device codes expire after 15 minutes */\nconst DEVICE_CODE_TTL_SECONDS = 15 * 60;\n\n/** Default polling interval in seconds */\nconst DEFAULT_INTERVAL = 5;\n\n/** RFC 8628 §3.5: interval increase on slow_down */\nconst SLOW_DOWN_INCREMENT = 5;\n\n/** Maximum slow_down interval cap (seconds) */\nconst MAX_SLOW_DOWN_INTERVAL = 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n/** Default scopes for CLI login */\nconst DEFAULT_SCOPES = [\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"schema:read\",\n] as const;\n\n/** Pattern to normalize user codes (strip hyphens) */\nconst HYPHEN_PATTERN = /-/g;\n\n/** Characters for user codes (uppercase, no ambiguous chars like 0/O, 1/I) */\nconst USER_CODE_CHARS = \"ABCDEFGHJKLMNPQRSTUVWXYZ23456789\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// RFC 8628 error codes\nexport type DeviceFlowError =\n\t| \"authorization_pending\"\n\t| \"slow_down\"\n\t| \"expired_token\"\n\t| \"access_denied\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Generate a short human-readable user code (XXXX-XXXX) */\nfunction generateUserCode(): string {\n\tconst bytes = new Uint8Array(8);\n\tcrypto.getRandomValues(bytes);\n\tconst chars = Array.from(bytes, (b) => USER_CODE_CHARS[b % USER_CODE_CHARS.length]).join(\"\");\n\treturn `${chars.slice(0, 4)}-${chars.slice(4, 8)}`;\n}\n\n/** Get an ISO datetime string offset from now */\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\n/** Validate and normalize scopes. Returns validated scope list. */\nfunction normalizeScopes(requested?: string[]): string[] {\n\tif (!requested || requested.length === 0) {\n\t\treturn [...DEFAULT_SCOPES];\n\t}\n\treturn requested.filter(isValidScope);\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * POST /oauth/device/code\n *\n * Issue a device code + user code. The CLI displays the user code\n * and tells the user to open the verification URI.\n */\nexport async function handleDeviceCodeRequest(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tclient_id?: string;\n\t\tscope?: string;\n\t},\n\tverificationUri: string,\n): Promise<ApiResult<DeviceCodeResponse>> {\n\ttry {\n\t\t// Note: client_id is accepted but not validated against _emdash_oauth_clients\n\t\t// because the CLI uses a well-known built-in client ID (\"emdash-cli\") that\n\t\t// isn't stored in the DB. Full client_id validation + scope clamping for the\n\t\t// device flow is tracked as a follow-up.\n\n\t\t// Parse and validate scopes\n\t\tconst requestedScopes = input.scope ? input.scope.split(\" \").filter(Boolean) : [];\n\t\tconst scopes = normalizeScopes(requestedScopes);\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\tconst deviceCode = generateCodeVerifier();\n\t\tconst userCode = generateUserCode();\n\t\tconst expires = expiresAt(DEVICE_CODE_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_device_codes\")\n\t\t\t.values({\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tstatus: \"pending\",\n\t\t\t\texpires_at: expires,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tverification_uri: verificationUri,\n\t\t\t\texpires_in: DEVICE_CODE_TTL_SECONDS,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"DEVICE_CODE_ERROR\",\n\t\t\t\tmessage: \"Failed to create device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/token\n *\n * CLI polls this endpoint with the device_code. Returns:\n * - 200 with tokens if authorized\n * - 400 with error \"authorization_pending\" while waiting\n * - 400 with error \"slow_down\" if polling too fast\n * - 400 with error \"expired_token\" if the code expired\n * - 400 with error \"access_denied\" if the user denied\n */\nexport async function handleDeviceTokenExchange(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tdevice_code: string;\n\t\tgrant_type: string;\n\t},\n): Promise<\n\tApiResult<TokenResponse> & { deviceFlowError?: DeviceFlowError; deviceFlowInterval?: number }\n> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (input.grant_type !== \"urn:ietf:params:oauth:grant-type:device_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the device code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date();\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < now) {\n\t\t\t// Clean up expired code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"expired_token\",\n\t\t\t\terror: { code: \"expired_token\", message: \"The device code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check status\n\t\tif (row.status === \"denied\") {\n\t\t\t// Clean up denied code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"access_denied\",\n\t\t\t\terror: { code: \"access_denied\", message: \"The user denied the request\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status === \"pending\") {\n\t\t\t// RFC 8628 §3.5: slow_down enforcement during polling phase.\n\t\t\t// Only applies while waiting for authorization — once authorized,\n\t\t\t// the final exchange proceeds without throttling.\n\t\t\tif (row.last_polled_at) {\n\t\t\t\tconst lastPolled = new Date(row.last_polled_at);\n\t\t\t\tconst elapsedSeconds = (now.getTime() - lastPolled.getTime()) / 1000;\n\n\t\t\t\tif (elapsedSeconds < row.interval) {\n\t\t\t\t\t// Too fast — increase interval by 5s per RFC 8628 §3.5, capped at 60s\n\t\t\t\t\tconst newInterval = Math.min(row.interval + SLOW_DOWN_INCREMENT, MAX_SLOW_DOWN_INTERVAL);\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\tinterval: newInterval,\n\t\t\t\t\t\t\tlast_polled_at: now.toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t\t\t.execute();\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tdeviceFlowError: \"slow_down\",\n\t\t\t\t\t\tdeviceFlowInterval: newInterval,\n\t\t\t\t\t\terror: { code: \"slow_down\", message: \"Polling too fast\" },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update last_polled_at for future slow_down checks\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ last_polled_at: now.toISOString() })\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"authorization_pending\",\n\t\t\t\terror: { code: \"authorization_pending\", message: \"Authorization pending\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status !== \"authorized\" || !row.user_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code state\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate tokens before consuming the device code so that if\n\t\t// generation fails, the code is still available for retry.\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically consume the device code and create tokens in a single\n\t\t// transaction. DELETE...RETURNING prevents TOCTOU: two concurrent\n\t\t// requests race on the DELETE, only one gets a row back. Wrapping\n\t\t// in a transaction ensures the code isn't consumed if token storage fails.\n\t\tconst result = await withTransaction(db, async (trx) => {\n\t\t\tconst consumed = await trx\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.where(\"status\", \"=\", \"authorized\")\n\t\t\t\t.returningAll()\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!consumed) return null;\n\n\t\t\tif (!consumed.user_id) return null;\n\n\t\t\tconst scopes = JSON.parse(consumed.scopes) as string[];\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\treturn { scopes };\n\t\t});\n\n\t\tif (!result) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Device code already consumed\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: result.scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/authorize\n *\n * The user submits the user_code after logging in via the browser.\n * This authorizes the device code, allowing the CLI to exchange it for tokens.\n *\n * Scopes are clamped to the user's role at this point. The stored scopes\n * are replaced with the intersection of requested scopes and the scopes\n * the user's role permits. This prevents scope escalation.\n */\nexport async function handleDeviceAuthorize(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tinput: {\n\t\tuser_code: string;\n\t\taction?: \"approve\" | \"deny\";\n\t},\n): Promise<ApiResult<{ authorized: boolean }>> {\n\ttry {\n\t\t// Normalize user code (strip hyphens, uppercase)\n\t\tconst normalizedCode = input.user_code.replace(HYPHEN_PATTERN, \"\").toUpperCase();\n\n\t\t// Look up the device code by user_code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.execute();\n\n\t\t// Find the matching code (strip hyphens for comparison)\n\t\tconst match = row.find(\n\t\t\t(r) => r.user_code.replace(HYPHEN_PATTERN, \"\").toUpperCase() === normalizedCode,\n\t\t);\n\n\t\tif (!match) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CODE\", message: \"Invalid or expired code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(match.expires_at) < new Date()) {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"EXPIRED_CODE\", message: \"This code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\tconst action = input.action ?? \"approve\";\n\n\t\tif (action === \"deny\") {\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ status: \"denied\" })\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn { success: true, data: { authorized: false } };\n\t\t}\n\n\t\t// Clamp requested scopes to those the user's role permits.\n\t\t// effective_scopes = requested_scopes ∩ scopesForRole(user.role)\n\t\tconst requestedScopes = JSON.parse(match.scopes) as string[];\n\t\tconst effectiveScopes = clampScopes(requestedScopes, userRole);\n\n\t\tif (effectiveScopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INSUFFICIENT_ROLE\",\n\t\t\t\t\tmessage: \"Your role does not permit any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Approve: set user_id, status, and clamped scopes\n\t\tawait db\n\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t.set({\n\t\t\t\tstatus: \"authorized\",\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(effectiveScopes),\n\t\t\t})\n\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t.execute();\n\n\t\treturn { success: true, data: { authorized: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZE_ERROR\",\n\t\t\t\tmessage: \"Failed to authorize device\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/refresh\n *\n * Exchange a refresh token for a new access token.\n * The refresh token itself is not rotated (per spec: optional rotation).\n */\nexport async function handleTokenRefresh(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\trefresh_token: string;\n\t\tgrant_type: string;\n\t},\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\tif (input.grant_type !== \"refresh_token\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\tif (!input.refresh_token.startsWith(TOKEN_PREFIXES.OAUTH_REFRESH)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token format\" },\n\t\t\t};\n\t\t}\n\n\t\tconst refreshHash = hashApiToken(input.refresh_token);\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.selectAll()\n\t\t\t.where(\"token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"refresh\")\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\t// Clean up expired refresh token and its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Refresh token expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-42: Revalidate user role before issuing new access token.\n\t\t// SEC-43: Reject refresh if user is disabled or deleted.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\t// User no longer exists — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\t// User is disabled — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate stored scopes against the user's current role.\n\t\t// A demoted user's refresh token may carry stale elevated scopes.\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// SEC-41: Intersect with the client's registered scopes (if any).\n\t\t// Same check as the approval path — a client registered with limited\n\t\t// scopes should never receive elevated scopes on refresh, even if the\n\t\t// user's role would allow them.\n\t\tif (row.client_id) {\n\t\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\t\tif (client?.scopes?.length) {\n\t\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t\t}\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\t// User's role no longer supports any of the token's scopes — revoke\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_GRANT\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the token's scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Delete old access tokens for this refresh token\n\t\tawait db\n\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"access\")\n\t\t\t.execute();\n\n\t\t// Generate new access token\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t.values({\n\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\ttoken_type: \"access\",\n\t\t\t\tuser_id: row.user_id,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tclient_type: row.client_type,\n\t\t\t\texpires_at: accessExpires,\n\t\t\t\trefresh_token_hash: refreshHash,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: input.refresh_token, // Return same refresh token\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REFRESH_ERROR\",\n\t\t\t\tmessage: \"Failed to refresh token\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/revoke\n *\n * Revoke an access or refresh token. If a refresh token is revoked,\n * also revoke all associated access tokens.\n *\n * Per RFC 7009, this endpoint always returns 200 (even for invalid tokens).\n */\nexport async function handleTokenRevoke(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\ttoken: string;\n\t},\n): Promise<ApiResult<{ revoked: boolean }>> {\n\ttry {\n\t\tconst hash = hashApiToken(input.token);\n\n\t\t// Look up the token\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.select([\"token_hash\", \"token_type\", \"refresh_token_hash\"])\n\t\t\t.where(\"token_hash\", \"=\", hash)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\t// Per RFC 7009: always 200, even for invalid tokens\n\t\t\treturn { success: true, data: { revoked: true } };\n\t\t}\n\n\t\tif (row.token_type === \"refresh\") {\n\t\t\t// Revoke refresh token and all its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"refresh_token_hash\", \"=\", hash).execute();\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t} else {\n\t\t\t// Revoke just the access token\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t}\n\n\t\treturn { success: true, data: { revoked: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REVOKE_ERROR\",\n\t\t\t\tmessage: \"Failed to revoke token\",\n\t\t\t},\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"device-flow-CTqd8mud.mjs","names":[],"sources":["../src/api/handlers/device-flow.ts"],"sourcesContent":["/**\n * OAuth Device Flow handlers (RFC 8628).\n *\n * EmDash acts as an OAuth 2.0 authorization server. The CLI requests\n * a device code, displays a URL + user code, and polls for a token.\n * The user opens a browser, logs in, enters the code, and the CLI gets\n * an access + refresh token pair.\n *\n * Uses arctic for code generation and @emdash-cms/auth for token utilities.\n */\n\nimport { clampScopes } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tisValidScope,\n\tTOKEN_PREFIXES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Device codes expire after 15 minutes */\nconst DEVICE_CODE_TTL_SECONDS = 15 * 60;\n\n/** Default polling interval in seconds */\nconst DEFAULT_INTERVAL = 5;\n\n/** RFC 8628 §3.5: interval increase on slow_down */\nconst SLOW_DOWN_INCREMENT = 5;\n\n/** Maximum slow_down interval cap (seconds) */\nconst MAX_SLOW_DOWN_INTERVAL = 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n/** Default scopes for CLI login */\nconst DEFAULT_SCOPES = [\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"schema:read\",\n] as const;\n\n/** Pattern to normalize user codes (strip hyphens) */\nconst HYPHEN_PATTERN = /-/g;\n\n/** Characters for user codes (uppercase, no ambiguous chars like 0/O, 1/I) */\nconst USER_CODE_CHARS = \"ABCDEFGHJKLMNPQRSTUVWXYZ23456789\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// RFC 8628 error codes\nexport type DeviceFlowError =\n\t| \"authorization_pending\"\n\t| \"slow_down\"\n\t| \"expired_token\"\n\t| \"access_denied\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Generate a short human-readable user code (XXXX-XXXX) */\nfunction generateUserCode(): string {\n\tconst bytes = new Uint8Array(8);\n\tcrypto.getRandomValues(bytes);\n\tconst chars = Array.from(bytes, (b) => USER_CODE_CHARS[b % USER_CODE_CHARS.length]).join(\"\");\n\treturn `${chars.slice(0, 4)}-${chars.slice(4, 8)}`;\n}\n\n/** Get an ISO datetime string offset from now */\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\n/** Validate and normalize scopes. Returns validated scope list. */\nfunction normalizeScopes(requested?: string[]): string[] {\n\tif (!requested || requested.length === 0) {\n\t\treturn [...DEFAULT_SCOPES];\n\t}\n\treturn requested.filter(isValidScope);\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * POST /oauth/device/code\n *\n * Issue a device code + user code. The CLI displays the user code\n * and tells the user to open the verification URI.\n */\nexport async function handleDeviceCodeRequest(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tclient_id?: string;\n\t\tscope?: string;\n\t},\n\tverificationUri: string,\n): Promise<ApiResult<DeviceCodeResponse>> {\n\ttry {\n\t\t// Note: client_id is accepted but not validated against _emdash_oauth_clients\n\t\t// because the CLI uses a well-known built-in client ID (\"emdash-cli\") that\n\t\t// isn't stored in the DB. Full client_id validation + scope clamping for the\n\t\t// device flow is tracked as a follow-up.\n\n\t\t// Parse and validate scopes\n\t\tconst requestedScopes = input.scope ? input.scope.split(\" \").filter(Boolean) : [];\n\t\tconst scopes = normalizeScopes(requestedScopes);\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\tconst deviceCode = generateCodeVerifier();\n\t\tconst userCode = generateUserCode();\n\t\tconst expires = expiresAt(DEVICE_CODE_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_device_codes\")\n\t\t\t.values({\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tstatus: \"pending\",\n\t\t\t\texpires_at: expires,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tverification_uri: verificationUri,\n\t\t\t\texpires_in: DEVICE_CODE_TTL_SECONDS,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"DEVICE_CODE_ERROR\",\n\t\t\t\tmessage: \"Failed to create device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/token\n *\n * CLI polls this endpoint with the device_code. Returns:\n * - 200 with tokens if authorized\n * - 400 with error \"authorization_pending\" while waiting\n * - 400 with error \"slow_down\" if polling too fast\n * - 400 with error \"expired_token\" if the code expired\n * - 400 with error \"access_denied\" if the user denied\n */\nexport async function handleDeviceTokenExchange(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tdevice_code: string;\n\t\tgrant_type: string;\n\t},\n): Promise<\n\tApiResult<TokenResponse> & { deviceFlowError?: DeviceFlowError; deviceFlowInterval?: number }\n> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (input.grant_type !== \"urn:ietf:params:oauth:grant-type:device_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the device code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date();\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < now) {\n\t\t\t// Clean up expired code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"expired_token\",\n\t\t\t\terror: { code: \"expired_token\", message: \"The device code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check status\n\t\tif (row.status === \"denied\") {\n\t\t\t// Clean up denied code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"access_denied\",\n\t\t\t\terror: { code: \"access_denied\", message: \"The user denied the request\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status === \"pending\") {\n\t\t\t// RFC 8628 §3.5: slow_down enforcement during polling phase.\n\t\t\t// Only applies while waiting for authorization — once authorized,\n\t\t\t// the final exchange proceeds without throttling.\n\t\t\tif (row.last_polled_at) {\n\t\t\t\tconst lastPolled = new Date(row.last_polled_at);\n\t\t\t\tconst elapsedSeconds = (now.getTime() - lastPolled.getTime()) / 1000;\n\n\t\t\t\tif (elapsedSeconds < row.interval) {\n\t\t\t\t\t// Too fast — increase interval by 5s per RFC 8628 §3.5, capped at 60s\n\t\t\t\t\tconst newInterval = Math.min(row.interval + SLOW_DOWN_INCREMENT, MAX_SLOW_DOWN_INTERVAL);\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\tinterval: newInterval,\n\t\t\t\t\t\t\tlast_polled_at: now.toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t\t\t.execute();\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tdeviceFlowError: \"slow_down\",\n\t\t\t\t\t\tdeviceFlowInterval: newInterval,\n\t\t\t\t\t\terror: { code: \"slow_down\", message: \"Polling too fast\" },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update last_polled_at for future slow_down checks\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ last_polled_at: now.toISOString() })\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"authorization_pending\",\n\t\t\t\terror: { code: \"authorization_pending\", message: \"Authorization pending\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status !== \"authorized\" || !row.user_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code state\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate tokens before consuming the device code so that if\n\t\t// generation fails, the code is still available for retry.\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically consume the device code and create tokens in a single\n\t\t// transaction. DELETE...RETURNING prevents TOCTOU: two concurrent\n\t\t// requests race on the DELETE, only one gets a row back. Wrapping\n\t\t// in a transaction ensures the code isn't consumed if token storage fails.\n\t\tconst result = await withTransaction(db, async (trx) => {\n\t\t\tconst consumed = await trx\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.where(\"status\", \"=\", \"authorized\")\n\t\t\t\t.returningAll()\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!consumed) return null;\n\n\t\t\tif (!consumed.user_id) return null;\n\n\t\t\tconst scopes = JSON.parse(consumed.scopes) as string[];\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\treturn { scopes };\n\t\t});\n\n\t\tif (!result) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Device code already consumed\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: result.scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/authorize\n *\n * The user submits the user_code after logging in via the browser.\n * This authorizes the device code, allowing the CLI to exchange it for tokens.\n *\n * Scopes are clamped to the user's role at this point. The stored scopes\n * are replaced with the intersection of requested scopes and the scopes\n * the user's role permits. This prevents scope escalation.\n */\nexport async function handleDeviceAuthorize(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tinput: {\n\t\tuser_code: string;\n\t\taction?: \"approve\" | \"deny\";\n\t},\n): Promise<ApiResult<{ authorized: boolean }>> {\n\ttry {\n\t\t// Normalize user code (strip hyphens, uppercase)\n\t\tconst normalizedCode = input.user_code.replace(HYPHEN_PATTERN, \"\").toUpperCase();\n\n\t\t// Look up the device code by user_code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.execute();\n\n\t\t// Find the matching code (strip hyphens for comparison)\n\t\tconst match = row.find(\n\t\t\t(r) => r.user_code.replace(HYPHEN_PATTERN, \"\").toUpperCase() === normalizedCode,\n\t\t);\n\n\t\tif (!match) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CODE\", message: \"Invalid or expired code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(match.expires_at) < new Date()) {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"EXPIRED_CODE\", message: \"This code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\tconst action = input.action ?? \"approve\";\n\n\t\tif (action === \"deny\") {\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ status: \"denied\" })\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn { success: true, data: { authorized: false } };\n\t\t}\n\n\t\t// Clamp requested scopes to those the user's role permits.\n\t\t// effective_scopes = requested_scopes ∩ scopesForRole(user.role)\n\t\tconst requestedScopes = JSON.parse(match.scopes) as string[];\n\t\tconst effectiveScopes = clampScopes(requestedScopes, userRole);\n\n\t\tif (effectiveScopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INSUFFICIENT_ROLE\",\n\t\t\t\t\tmessage: \"Your role does not permit any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Approve: set user_id, status, and clamped scopes\n\t\tawait db\n\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t.set({\n\t\t\t\tstatus: \"authorized\",\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(effectiveScopes),\n\t\t\t})\n\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t.execute();\n\n\t\treturn { success: true, data: { authorized: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZE_ERROR\",\n\t\t\t\tmessage: \"Failed to authorize device\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/refresh\n *\n * Exchange a refresh token for a new access token.\n * The refresh token itself is not rotated (per spec: optional rotation).\n */\nexport async function handleTokenRefresh(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\trefresh_token: string;\n\t\tgrant_type: string;\n\t},\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\tif (input.grant_type !== \"refresh_token\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\tif (!input.refresh_token.startsWith(TOKEN_PREFIXES.OAUTH_REFRESH)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token format\" },\n\t\t\t};\n\t\t}\n\n\t\tconst refreshHash = hashApiToken(input.refresh_token);\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.selectAll()\n\t\t\t.where(\"token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"refresh\")\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\t// Clean up expired refresh token and its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Refresh token expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-42: Revalidate user role before issuing new access token.\n\t\t// SEC-43: Reject refresh if user is disabled or deleted.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\t// User no longer exists — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\t// User is disabled — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate stored scopes against the user's current role.\n\t\t// A demoted user's refresh token may carry stale elevated scopes.\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// SEC-41: Intersect with the client's registered scopes (if any).\n\t\t// Same check as the approval path — a client registered with limited\n\t\t// scopes should never receive elevated scopes on refresh, even if the\n\t\t// user's role would allow them.\n\t\tif (row.client_id) {\n\t\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\t\tif (client?.scopes?.length) {\n\t\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t\t}\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\t// User's role no longer supports any of the token's scopes — revoke\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_GRANT\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the token's scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Delete old access tokens for this refresh token\n\t\tawait db\n\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"access\")\n\t\t\t.execute();\n\n\t\t// Generate new access token\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t.values({\n\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\ttoken_type: \"access\",\n\t\t\t\tuser_id: row.user_id,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tclient_type: row.client_type,\n\t\t\t\texpires_at: accessExpires,\n\t\t\t\trefresh_token_hash: refreshHash,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: input.refresh_token, // Return same refresh token\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REFRESH_ERROR\",\n\t\t\t\tmessage: \"Failed to refresh token\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/revoke\n *\n * Revoke an access or refresh token. If a refresh token is revoked,\n * also revoke all associated access tokens.\n *\n * Per RFC 7009, this endpoint always returns 200 (even for invalid tokens).\n */\nexport async function handleTokenRevoke(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\ttoken: string;\n\t},\n): Promise<ApiResult<{ revoked: boolean }>> {\n\ttry {\n\t\tconst hash = hashApiToken(input.token);\n\n\t\t// Look up the token\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.select([\"token_hash\", \"token_type\", \"refresh_token_hash\"])\n\t\t\t.where(\"token_hash\", \"=\", hash)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\t// Per RFC 7009: always 200, even for invalid tokens\n\t\t\treturn { success: true, data: { revoked: true } };\n\t\t}\n\n\t\tif (row.token_type === \"refresh\") {\n\t\t\t// Revoke refresh token and all its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"refresh_token_hash\", \"=\", hash).execute();\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t} else {\n\t\t\t// Revoke just the access token\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t}\n\n\t\treturn { success: true, data: { revoked: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REVOKE_ERROR\",\n\t\t\t\tmessage: \"Failed to revoke token\",\n\t\t\t},\n\t\t};\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,MAAM,0BAA0B;;AAGhC,MAAM,mBAAmB;;AAGzB,MAAM,sBAAsB;;AAG5B,MAAM,yBAAyB;;AAG/B,MAAM,2BAA2B;;AAGjC,MAAM,4BAA4B,OAAU,KAAK;;AAGjD,MAAM,iBAAiB;CACtB;CACA;CACA;CACA;CACA;CACA;;AAGD,MAAM,iBAAiB;;AAGvB,MAAM,kBAAkB;;AAkCxB,SAAS,mBAA2B;CACnC,MAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,QAAO,gBAAgB,MAAM;CAC7B,MAAM,QAAQ,MAAM,KAAK,QAAQ,MAAM,gBAAgB,IAAI,IAAwB,CAAC,KAAK,GAAG;AAC5F,QAAO,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,MAAM,GAAG,EAAE;;;AAIjD,SAAS,UAAU,SAAyB;AAC3C,QAAO,IAAI,KAAK,KAAK,KAAK,GAAG,UAAU,IAAK,CAAC,aAAa;;;AAI3D,SAAS,gBAAgB,WAAgC;AACxD,KAAI,CAAC,aAAa,UAAU,WAAW,EACtC,QAAO,CAAC,GAAG,eAAe;AAE3B,QAAO,UAAU,OAAO,aAAa;;;;;;;;AAatC,eAAsB,wBACrB,IACA,OAIA,iBACyC;AACzC,KAAI;EAQH,MAAM,SAAS,gBADS,MAAM,QAAQ,MAAM,MAAM,MAAM,IAAI,CAAC,OAAO,QAAQ,GAAG,EAAE,CAClC;AAE/C,MAAI,OAAO,WAAW,EACrB,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA6B;GACtE;EAGF,MAAM,aAAa,sBAAsB;EACzC,MAAM,WAAW,kBAAkB;EACnC,MAAM,UAAU,UAAU,wBAAwB;AAElD,QAAM,GACJ,WAAW,uBAAuB,CAClC,OAAO;GACP,aAAa;GACb,WAAW;GACX,QAAQ,KAAK,UAAU,OAAO;GAC9B,QAAQ;GACR,YAAY;GACZ,UAAU;GACV,CAAC,CACD,SAAS;AAEX,SAAO;GACN,SAAS;GACT,MAAM;IACL,aAAa;IACb,WAAW;IACX,kBAAkB;IAClB,YAAY;IACZ,UAAU;IACV;GACD;SACM;AACP,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;;;;;;;;;;;;;AAcH,eAAsB,0BACrB,IACA,OAMC;AACD,KAAI;AAEH,MAAI,MAAM,eAAe,+CACxB,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAA0B,SAAS;IAAsB;GACxE;EAIF,MAAM,MAAM,MAAM,GAChB,WAAW,uBAAuB,CAClC,WAAW,CACX,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,kBAAkB;AAEpB,MAAI,CAAC,IACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAuB;GAChE;EAGF,MAAM,sBAAM,IAAI,MAAM;AAGtB,MAAI,IAAI,KAAK,IAAI,WAAW,GAAG,KAAK;AAEnC,SAAM,GACJ,WAAW,uBAAuB,CAClC,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,UAAO;IACN,SAAS;IACT,iBAAiB;IACjB,OAAO;KAAE,MAAM;KAAiB,SAAS;KAA+B;IACxE;;AAIF,MAAI,IAAI,WAAW,UAAU;AAE5B,SAAM,GACJ,WAAW,uBAAuB,CAClC,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,UAAO;IACN,SAAS;IACT,iBAAiB;IACjB,OAAO;KAAE,MAAM;KAAiB,SAAS;KAA+B;IACxE;;AAGF,MAAI,IAAI,WAAW,WAAW;AAI7B,OAAI,IAAI,gBAAgB;IACvB,MAAM,aAAa,IAAI,KAAK,IAAI,eAAe;AAG/C,SAFwB,IAAI,SAAS,GAAG,WAAW,SAAS,IAAI,MAE3C,IAAI,UAAU;KAElC,MAAM,cAAc,KAAK,IAAI,IAAI,WAAW,qBAAqB,uBAAuB;AACxF,WAAM,GACJ,YAAY,uBAAuB,CACnC,IAAI;MACJ,UAAU;MACV,gBAAgB,IAAI,aAAa;MACjC,CAAC,CACD,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,YAAO;MACN,SAAS;MACT,iBAAiB;MACjB,oBAAoB;MACpB,OAAO;OAAE,MAAM;OAAa,SAAS;OAAoB;MACzD;;;AAKH,SAAM,GACJ,YAAY,uBAAuB,CACnC,IAAI,EAAE,gBAAgB,IAAI,aAAa,EAAE,CAAC,CAC1C,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,UAAO;IACN,SAAS;IACT,iBAAiB;IACjB,OAAO;KAAE,MAAM;KAAyB,SAAS;KAAyB;IAC1E;;AAGF,MAAI,IAAI,WAAW,gBAAgB,CAAC,IAAI,QACvC,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA6B;GACtE;EAKF,MAAM,cAAc,sBAAsB,eAAe,aAAa;EACtE,MAAM,gBAAgB,UAAU,yBAAyB;EACzD,MAAM,eAAe,sBAAsB,eAAe,cAAc;EACxE,MAAM,iBAAiB,UAAU,0BAA0B;EAM3D,MAAM,SAAS,MAAM,gBAAgB,IAAI,OAAO,QAAQ;GACvD,MAAM,WAAW,MAAM,IACrB,WAAW,uBAAuB,CAClC,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,MAAM,UAAU,KAAK,aAAa,CAClC,cAAc,CACd,kBAAkB;AAEpB,OAAI,CAAC,SAAU,QAAO;AAEtB,OAAI,CAAC,SAAS,QAAS,QAAO;GAE9B,MAAM,SAAS,KAAK,MAAM,SAAS,OAAO;AAE1C,SAAM,IACJ,WAAW,uBAAuB,CAClC,OAAO;IACP,YAAY,YAAY;IACxB,YAAY;IACZ,SAAS,SAAS;IAClB,QAAQ,KAAK,UAAU,OAAO;IAC9B,aAAa;IACb,YAAY;IACZ,oBAAoB,aAAa;IACjC,CAAC,CACD,SAAS;AAEX,SAAM,IACJ,WAAW,uBAAuB,CAClC,OAAO;IACP,YAAY,aAAa;IACzB,YAAY;IACZ,SAAS,SAAS;IAClB,QAAQ,KAAK,UAAU,OAAO;IAC9B,aAAa;IACb,YAAY;IACZ,oBAAoB;IACpB,CAAC,CACD,SAAS;AAEX,UAAO,EAAE,QAAQ;IAChB;AAEF,MAAI,CAAC,OACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAgC;GACzE;AAGF,SAAO;GACN,SAAS;GACT,MAAM;IACL,cAAc,YAAY;IAC1B,eAAe,aAAa;IAC5B,YAAY;IACZ,YAAY;IACZ,OAAO,OAAO,OAAO,KAAK,IAAI;IAC9B;GACD;SACM;AACP,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;;;;;;;;;;;;;AAcH,eAAsB,sBACrB,IACA,QACA,UACA,OAI8C;AAC9C,KAAI;EAEH,MAAM,iBAAiB,MAAM,UAAU,QAAQ,gBAAgB,GAAG,CAAC,aAAa;EAUhF,MAAM,SAPM,MAAM,GAChB,WAAW,uBAAuB,CAClC,WAAW,CACX,MAAM,UAAU,KAAK,UAAU,CAC/B,SAAS,EAGO,MAChB,MAAM,EAAE,UAAU,QAAQ,gBAAgB,GAAG,CAAC,aAAa,KAAK,eACjE;AAED,MAAI,CAAC,MACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAgB,SAAS;IAA2B;GACnE;AAIF,MAAI,IAAI,KAAK,MAAM,WAAW,mBAAG,IAAI,MAAM,EAAE;AAC5C,SAAM,GACJ,WAAW,uBAAuB,CAClC,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,UAAO;IACN,SAAS;IACT,OAAO;KAAE,MAAM;KAAgB,SAAS;KAAyB;IACjE;;AAKF,OAFe,MAAM,UAAU,eAEhB,QAAQ;AACtB,SAAM,GACJ,YAAY,uBAAuB,CACnC,IAAI,EAAE,QAAQ,UAAU,CAAC,CACzB,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,UAAO;IAAE,SAAS;IAAM,MAAM,EAAE,YAAY,OAAO;IAAE;;EAMtD,MAAM,kBAAkB,YADA,KAAK,MAAM,MAAM,OAAO,EACK,SAAS;AAE9D,MAAI,gBAAgB,WAAW,EAC9B,QAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;AAIF,QAAM,GACJ,YAAY,uBAAuB,CACnC,IAAI;GACJ,QAAQ;GACR,SAAS;GACT,QAAQ,KAAK,UAAU,gBAAgB;GACvC,CAAC,CACD,MAAM,eAAe,KAAK,MAAM,YAAY,CAC5C,SAAS;AAEX,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,YAAY,MAAM;GAAE;SAC7C;AACP,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;;;;;;;;;AAUH,eAAsB,mBACrB,IACA,OAIoC;AACpC,KAAI;AACH,MAAI,MAAM,eAAe,gBACxB,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAA0B,SAAS;IAAsB;GACxE;AAGF,MAAI,CAAC,MAAM,cAAc,WAAW,eAAe,cAAc,CAChE,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAgC;GACzE;EAGF,MAAM,cAAc,aAAa,MAAM,cAAc;EAErD,MAAM,MAAM,MAAM,GAChB,WAAW,uBAAuB,CAClC,WAAW,CACX,MAAM,cAAc,KAAK,YAAY,CACrC,MAAM,cAAc,KAAK,UAAU,CACnC,kBAAkB;AAEpB,MAAI,CAAC,IACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAyB;GAClE;AAIF,MAAI,IAAI,KAAK,IAAI,WAAW,mBAAG,IAAI,MAAM,EAAE;AAE1C,SAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,cAAc,KAAK,YAAY,CAAC,SAAS;AAC3F,SAAM,GACJ,WAAW,uBAAuB,CAClC,MAAM,sBAAsB,KAAK,YAAY,CAC7C,SAAS;AAEX,UAAO;IACN,SAAS;IACT,OAAO;KAAE,MAAM;KAAiB,SAAS;KAAyB;IAClE;;EAKF,MAAM,WAAW,MAAM,wBAAwB,IAAI,IAAI,QAAQ;AAC/D,MAAI,CAAC,UAAU;AAEd,SAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,WAAW,KAAK,IAAI,QAAQ,CAAC,SAAS;AACxF,UAAO;IACN,SAAS;IACT,OAAO;KAAE,MAAM;KAAiB,SAAS;KAAkB;IAC3D;;AAGF,MAAI,SAAS,UAAU;AAEtB,SAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,WAAW,KAAK,IAAI,QAAQ,CAAC,SAAS;AACxF,UAAO;IACN,SAAS;IACT,OAAO;KAAE,MAAM;KAAiB,SAAS;KAA4B;IACrE;;EAMF,IAAI,SAAS,YADQ,KAAK,MAAM,IAAI,OAAO,EACJ,SAAS,KAAK;AAMrD,MAAI,IAAI,WAAW;GAClB,MAAM,SAAS,MAAM,kBAAkB,IAAI,IAAI,UAAU;AACzD,OAAI,QAAQ,QAAQ,OACnB,UAAS,OAAO,QAAQ,MAAc,OAAO,OAAQ,SAAS,EAAE,CAAC;;AAInE,MAAI,OAAO,WAAW,GAAG;AAExB,SAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,cAAc,KAAK,YAAY,CAAC,SAAS;AAC3F,SAAM,GACJ,WAAW,uBAAuB,CAClC,MAAM,sBAAsB,KAAK,YAAY,CAC7C,SAAS;AACX,UAAO;IACN,SAAS;IACT,OAAO;KACN,MAAM;KACN,SAAS;KACT;IACD;;AAIF,QAAM,GACJ,WAAW,uBAAuB,CAClC,MAAM,sBAAsB,KAAK,YAAY,CAC7C,MAAM,cAAc,KAAK,SAAS,CAClC,SAAS;EAGX,MAAM,cAAc,sBAAsB,eAAe,aAAa;EACtE,MAAM,gBAAgB,UAAU,yBAAyB;AAEzD,QAAM,GACJ,WAAW,uBAAuB,CAClC,OAAO;GACP,YAAY,YAAY;GACxB,YAAY;GACZ,SAAS,IAAI;GACb,QAAQ,KAAK,UAAU,OAAO;GAC9B,aAAa,IAAI;GACjB,YAAY;GACZ,oBAAoB;GACpB,CAAC,CACD,SAAS;AAEX,SAAO;GACN,SAAS;GACT,MAAM;IACL,cAAc,YAAY;IAC1B,eAAe,MAAM;IACrB,YAAY;IACZ,YAAY;IACZ,OAAO,OAAO,KAAK,IAAI;IACvB;GACD;SACM;AACP,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;;;;;;;;;;;AAYH,eAAsB,kBACrB,IACA,OAG2C;AAC3C,KAAI;EACH,MAAM,OAAO,aAAa,MAAM,MAAM;EAGtC,MAAM,MAAM,MAAM,GAChB,WAAW,uBAAuB,CAClC,OAAO;GAAC;GAAc;GAAc;GAAqB,CAAC,CAC1D,MAAM,cAAc,KAAK,KAAK,CAC9B,kBAAkB;AAEpB,MAAI,CAAC,IAEJ,QAAO;GAAE,SAAS;GAAM,MAAM,EAAE,SAAS,MAAM;GAAE;AAGlD,MAAI,IAAI,eAAe,WAAW;AAEjC,SAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,sBAAsB,KAAK,KAAK,CAAC,SAAS;AAC5F,SAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,cAAc,KAAK,KAAK,CAAC,SAAS;QAGpF,OAAM,GAAG,WAAW,uBAAuB,CAAC,MAAM,cAAc,KAAK,KAAK,CAAC,SAAS;AAGrF,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,SAAS,MAAM;GAAE;SAC1C;AACP,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as validateJsonFieldName } from "./validate-
|
|
1
|
+
import { n as validateJsonFieldName, t as validateIdentifier } from "./validate-BpwoDOgt.mjs";
|
|
2
2
|
import { PostgresAdapter, sql } from "kysely";
|
|
3
3
|
|
|
4
4
|
//#region src/database/dialect-helpers.ts
|
|
@@ -58,21 +58,11 @@ function currentTimestampValue(db) {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* Build WHERE clause for status filtering on a content table.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* Visibility is computed, not flipped by cron, so a literal
|
|
65
|
-
* `status = 'published'` comparison undercounts scheduled-and-due entries —
|
|
66
|
-
* every "publicly visible" filter must go through this helper.
|
|
61
|
+
* Scheduled content becomes public only after the publication sweep commits
|
|
62
|
+
* the row with a literal `published` status.
|
|
67
63
|
*/
|
|
68
|
-
function buildStatusCondition(
|
|
64
|
+
function buildStatusCondition(_db, status, tablePrefix) {
|
|
69
65
|
const statusField = tablePrefix ? `${tablePrefix}.status` : "status";
|
|
70
|
-
const scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : "scheduled_at";
|
|
71
|
-
if (status === "published") {
|
|
72
|
-
const scheduledAtExpr = isPostgres(db) ? sql`${sql.ref(scheduledAtField)}::timestamptz` : sql.ref(scheduledAtField);
|
|
73
|
-
const nowExpr = isPostgres(db) ? currentTimestampValue(db) : sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
74
|
-
return sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${nowExpr}))`;
|
|
75
|
-
}
|
|
76
66
|
return sql`${sql.ref(statusField)} = ${status}`;
|
|
77
67
|
}
|
|
78
68
|
/**
|
|
@@ -131,6 +121,24 @@ function binaryType(db) {
|
|
|
131
121
|
return "blob";
|
|
132
122
|
}
|
|
133
123
|
/**
|
|
124
|
+
* SQL expression for extracting a field from a JSON column stored as text.
|
|
125
|
+
*
|
|
126
|
+
* sqlite: json_extract(column, '$.path')
|
|
127
|
+
* postgres: (column)::jsonb->>'path'
|
|
128
|
+
*
|
|
129
|
+
* The Postgres cast is required because JSON columns (e.g.
|
|
130
|
+
* `_plugin_storage.data`) are `text`, and `text ->> unknown` is not an
|
|
131
|
+
* operator. The cast is immutable, so the same expression works in
|
|
132
|
+
* expression indexes — queries and indexes must build it through this
|
|
133
|
+
* helper so the planner can match them.
|
|
134
|
+
*/
|
|
135
|
+
function jsonExtractExpr(db, column, path) {
|
|
136
|
+
validateIdentifier(column, "JSON column name");
|
|
137
|
+
validateJsonFieldName(path, "JSON path");
|
|
138
|
+
if (isPostgres(db)) return `(${column})::jsonb->>'${path}'`;
|
|
139
|
+
return `json_extract(${column}, '$.${path}')`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
134
142
|
* SQL expression for extracting a queryable field from the plugin-storage
|
|
135
143
|
* `data` column.
|
|
136
144
|
*
|
|
@@ -190,5 +198,5 @@ function pluginDataOrderExpr(db, field) {
|
|
|
190
198
|
}
|
|
191
199
|
|
|
192
200
|
//#endregion
|
|
193
|
-
export { currentTimestamp as a, isSqlite as c,
|
|
194
|
-
//# sourceMappingURL=dialect-helpers-
|
|
201
|
+
export { currentTimestamp as a, isSqlite as c, pluginDataExtractExpr as d, pluginDataOrderExpr as f, compoundSelectLimit as i, jsonExtractExpr as l, buildStatusCondition as n, currentTimestampValue as o, tableExists as p, columnExists as r, isPostgres as s, binaryType as t, listTablesLike as u };
|
|
202
|
+
//# sourceMappingURL=dialect-helpers-BSbfaO3F.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialect-helpers-BSbfaO3F.mjs","names":[],"sources":["../src/database/dialect-helpers.ts"],"sourcesContent":["/**\n * Dialect-specific SQL helpers\n *\n * Every function takes a Kysely `db` instance and detects the dialect from\n * the adapter class. No module-level state, no globals, no heuristics —\n * the adapter is the source of truth.\n *\n * This is NOT an ORM abstraction — just targeted helpers for the ~15 places\n * that use raw dialect-specific SQL. Most Kysely schema builder code already\n * works cross-dialect.\n */\n\nimport type { ColumnDataType, Kysely, RawBuilder } from \"kysely\";\nimport { PostgresAdapter, sql } from \"kysely\";\n\nimport type { DatabaseDialectType } from \"../db/adapters.js\";\nimport { validateIdentifier, validateJsonFieldName } from \"./validate.js\";\n\nexport type { DatabaseDialectType };\n\n/**\n * Detect dialect type from a Kysely instance via the adapter class.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function detectDialect(db: Kysely<any>): DatabaseDialectType {\n\tif (db.getExecutor().adapter instanceof PostgresAdapter) return \"postgres\";\n\treturn \"sqlite\";\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function isSqlite(db: Kysely<any>): boolean {\n\treturn detectDialect(db) === \"sqlite\";\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function isPostgres(db: Kysely<any>): boolean {\n\treturn detectDialect(db) === \"postgres\";\n}\n\n/**\n * Declared by an adapter whose backend caps the number of terms in a compound\n * SELECT (`UNION ALL`, `INTERSECT`, `EXCEPT`). SQLite's own\n * SQLITE_LIMIT_COMPOUND_SELECT default is 500 — high enough that no query\n * EmDash builds approaches it — but Cloudflare D1 sets it to 5 and rejects\n * anything larger with \"too many terms in compound SELECT\".\n */\nexport interface CompoundSelectLimitedAdapter {\n\t/** Maximum terms per compound SELECT. Must be a positive integer. */\n\treadonly compoundSelectLimit: number;\n}\n\n/**\n * The backend's compound-SELECT ceiling, or null when it has none worth\n * splitting statements for. Only the adapter knows: the limit is a property of\n * the SQLite build behind the dialect, not of the SQL flavour, so two \"sqlite\"\n * dialects can answer differently.\n *\n * A declared ceiling must be a positive integer — callers batch by it, and\n * every other value silently misbehaves rather than failing: 0 and negatives\n * never advance the batch cursor, fractions overlap batches and double-count,\n * NaN yields an empty batch. A malformed declaration throws here, where the\n * message can name the adapter.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function compoundSelectLimit(db: Kysely<any>): number | null {\n\tconst adapter: object = db.getExecutor().adapter;\n\tif (!(\"compoundSelectLimit\" in adapter)) return null;\n\n\tconst limit: unknown = adapter.compoundSelectLimit;\n\tif (typeof limit !== \"number\" || !Number.isInteger(limit) || limit < 1) {\n\t\tthrow new Error(\n\t\t\t`${adapter.constructor.name} declares compoundSelectLimit ${String(limit)}; it must be a positive integer.`,\n\t\t);\n\t}\n\treturn limit;\n}\n\n/**\n * Default timestamp expression for column defaults.\n * Wrapped in parens for use in CREATE TABLE ... DEFAULT (...).\n *\n * sqlite: (datetime('now'))\n * postgres: CURRENT_TIMESTAMP\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function currentTimestamp(db: Kysely<any>): RawBuilder<string> {\n\tif (isPostgres(db)) {\n\t\treturn sql`CURRENT_TIMESTAMP`;\n\t}\n\treturn sql`(datetime('now'))`;\n}\n\n/**\n * Timestamp expression for use in WHERE clauses and SET expressions.\n * No wrapping parens.\n *\n * sqlite: datetime('now')\n * postgres: CURRENT_TIMESTAMP\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function currentTimestampValue(db: Kysely<any>): RawBuilder<string> {\n\tif (isPostgres(db)) {\n\t\treturn sql`CURRENT_TIMESTAMP`;\n\t}\n\treturn sql`datetime('now')`;\n}\n\n/**\n * Build WHERE clause for status filtering on a content table.\n * Scheduled content becomes public only after the publication sweep commits\n * the row with a literal `published` status.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildStatusCondition(\n\t_db: Kysely<any>,\n\tstatus: string,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst statusField = tablePrefix ? `${tablePrefix}.status` : \"status\";\n\treturn sql`${sql.ref(statusField)} = ${status}`;\n}\n\n/**\n * Check if a table exists in the database.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function tableExists(db: Kysely<any>, tableName: string): Promise<boolean> {\n\tif (isPostgres(db)) {\n\t\t// Scope to the active schema (matches indexExists/columnExists below).\n\t\t// Hardcoding 'public' breaks non-public-schema Postgres deployments.\n\t\tconst result = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM information_schema.tables\n\t\t\t\tWHERE table_schema = current_schema() AND table_name = ${tableName}\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\treturn result.rows[0]?.exists === true;\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table' AND name = ${tableName}\n\t`.execute(db);\n\treturn result.rows.length > 0;\n}\n\n/**\n * Check if an index exists in the database.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function indexExists(db: Kysely<any>, indexName: string): Promise<boolean> {\n\tif (isPostgres(db)) {\n\t\tconst result = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM pg_indexes\n\t\t\t\tWHERE schemaname = current_schema() AND indexname = ${indexName}\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\treturn result.rows[0]?.exists === true;\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'index' AND name = ${indexName}\n\t`.execute(db);\n\treturn result.rows.length > 0;\n}\n\n/**\n * Check if a column exists in the database.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function columnExists(\n\tdb: Kysely<any>,\n\ttableName: string,\n\tcolumnName: string,\n): Promise<boolean> {\n\tif (isPostgres(db)) {\n\t\tconst result = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM information_schema.columns\n\t\t\t\tWHERE table_schema = current_schema()\n\t\t\t\t\tAND table_name = ${tableName}\n\t\t\t\t\tAND column_name = ${columnName}\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\treturn result.rows[0]?.exists === true;\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM pragma_table_info(${tableName})\n\t\tWHERE name = ${columnName}\n\t`.execute(db);\n\treturn result.rows.length > 0;\n}\n\n/**\n * List tables matching a LIKE pattern.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function listTablesLike(db: Kysely<any>, pattern: string): Promise<string[]> {\n\tif (isPostgres(db)) {\n\t\t// Scope to the connection's active schema rather than hardcoding\n\t\t// 'public'. A Postgres deployment using a non-public schema (per-tenant\n\t\t// or shared-cluster setups), or per-test schemas, otherwise sees tables\n\t\t// from the wrong schema — or none at all. Mirrors migration 038.\n\t\tconst result = await sql<{ table_name: string }>`\n\t\t\tSELECT table_name FROM information_schema.tables\n\t\t\tWHERE table_schema = current_schema() AND table_name LIKE ${pattern}\n\t\t`.execute(db);\n\t\treturn result.rows.map((r) => r.table_name);\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table' AND name LIKE ${pattern}\n\t`.execute(db);\n\treturn result.rows.map((r) => r.name);\n}\n\n/**\n * Column type for binary data.\n *\n * sqlite: blob\n * postgres: bytea\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function binaryType(db: Kysely<any>): ColumnDataType {\n\tif (isPostgres(db)) {\n\t\treturn \"bytea\";\n\t}\n\treturn \"blob\";\n}\n\n/**\n * SQL expression for extracting a field from a JSON column stored as text.\n *\n * sqlite: json_extract(column, '$.path')\n * postgres: (column)::jsonb->>'path'\n *\n * The Postgres cast is required because JSON columns (e.g.\n * `_plugin_storage.data`) are `text`, and `text ->> unknown` is not an\n * operator. The cast is immutable, so the same expression works in\n * expression indexes — queries and indexes must build it through this\n * helper so the planner can match them.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function jsonExtractExpr(db: Kysely<any>, column: string, path: string): string {\n\tvalidateIdentifier(column, \"JSON column name\");\n\tvalidateJsonFieldName(path, \"JSON path\");\n\tif (isPostgres(db)) {\n\t\treturn `(${column})::jsonb->>'${path}'`;\n\t}\n\treturn `json_extract(${column}, '$.${path}')`;\n}\n\n/**\n * SQL expression for extracting a queryable field from the plugin-storage\n * `data` column.\n *\n * `_plugin_storage.data` is `text`, so Postgres extraction goes through the\n * `(data)::jsonb->>'field'` cast (#1898) — otherwise `text ->> unknown` is not\n * an operator. But the extracted value is still `text`, so a numeric comparison\n * (`stock >= 10`) compares lexically (`'9' >= '10'` is TRUE) and silently\n * over-counts / oversells; pass `{ numeric: true }` for a numeric comparison.\n *\n * The numeric form is a **type-guarded** cast, not a bare `::numeric`. A bare\n * cast throws `invalid input syntax for type numeric` the moment a single\n * scanned row stores a non-number in that field (documents are schemaless),\n * aborting the whole query — and it would diverge from SQLite, which silently\n * coerces. Guarding with `jsonb_typeof`/`json_type` makes the comparison total\n * and parity-correct on both dialects: a non-number stored value yields `NULL`\n * (no match) instead of an error.\n *\n * The field name is validated before interpolation, so the casts wrap only a\n * safe identifier and add no injection surface.\n *\n * sqlite text: json_extract(data, '$.field')\n * sqlite numeric: CASE WHEN json_type(data, '$.field') IN ('integer', 'real')\n * THEN json_extract(data, '$.field') END\n * postgres text: (data)::jsonb->>'field'\n * postgres numeric: CASE WHEN jsonb_typeof((data)::jsonb->'field') = 'number'\n * THEN ((data)::jsonb->>'field')::numeric END\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function pluginDataExtractExpr(\n\tdb: Kysely<any>,\n\tfield: string,\n\toptions?: { numeric?: boolean },\n): string {\n\tvalidateJsonFieldName(field, \"plugin storage field name\");\n\tif (isPostgres(db)) {\n\t\tconst text = `(data)::jsonb->>'${field}'`;\n\t\tif (!options?.numeric) return text;\n\t\treturn `CASE WHEN jsonb_typeof((data)::jsonb->'${field}') = 'number' THEN (${text})::numeric END`;\n\t}\n\tconst extract = `json_extract(data, '$.${field}')`;\n\tif (!options?.numeric) return extract;\n\treturn `CASE WHEN json_type(data, '$.${field}') IN ('integer', 'real') THEN ${extract} END`;\n}\n\n/**\n * SQL expression for ordering plugin-storage rows by a `data` field.\n *\n * `ORDER BY` has no bound operand to infer numeric-vs-text from, so extracting\n * as text (`->>'field'`) would sort a numeric field lexically on Postgres\n * (`[10, 100, 9]`) while SQLite's `json_extract` sorts it numerically — a\n * cross-dialect divergence. Ordering over the jsonb-native value (`->'field'`,\n * single arrow) fixes this: jsonb btree ordering is numeric among numbers,\n * lexical among strings, and total across heterogeneous values (never throws).\n * SQLite's `json_extract` already orders numerically, so it is unchanged.\n *\n * sqlite: json_extract(data, '$.field')\n * postgres: (data)::jsonb->'field'\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function pluginDataOrderExpr(db: Kysely<any>, field: string): string {\n\tvalidateJsonFieldName(field, \"plugin storage order field name\");\n\tif (isPostgres(db)) {\n\t\treturn `(data)::jsonb->'${field}'`;\n\t}\n\treturn `json_extract(data, '$.${field}')`;\n}\n"],"mappings":"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"}
|
|
@@ -47,4 +47,4 @@ async function devConsoleEmailDeliver(event, _ctx) {
|
|
|
47
47
|
|
|
48
48
|
//#endregion
|
|
49
49
|
export { getDevEmails as i, clearDevEmails as n, devConsoleEmailDeliver as r, DEV_CONSOLE_EMAIL_PLUGIN_ID as t };
|
|
50
|
-
//# sourceMappingURL=email-console-
|
|
50
|
+
//# sourceMappingURL=email-console-C6WdcWbL.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-console-
|
|
1
|
+
{"version":3,"file":"email-console-C6WdcWbL.mjs","names":[],"sources":["../src/plugins/email-console.ts"],"sourcesContent":["/**\n * Dev Console Email Provider\n *\n * Built-in plugin that registers email:deliver as an exclusive hook.\n * Logs emails to console and stores them in memory (capped at 100).\n * Auto-activated when import.meta.env.DEV is true and no other provider is selected.\n *\n */\n\nimport type { EmailDeliverEvent, EmailMessage, PluginContext } from \"./types.js\";\n\n/** Plugin ID for the dev console email provider */\nexport const DEV_CONSOLE_EMAIL_PLUGIN_ID = \"emdash-console-email\";\n\n/** Maximum number of emails to keep in memory */\nconst MAX_STORED_EMAILS = 100;\n\n/**\n * Stored email record (in-memory only)\n */\nexport interface StoredEmail {\n\tmessage: EmailMessage;\n\tsource: string;\n\tsentAt: string;\n}\n\n/**\n * In-memory store for dev emails.\n * Uses globalThis so the same array is shared across Vite SSR module\n * instances (the runtime and the route handler may load separate copies\n * of this module, but globalThis is always the same object).\n */\nconst GLOBAL_KEY = Symbol.for(\"emdash:dev-emails\");\nconst g = globalThis as Record<symbol, unknown>;\nconst storedEmails: StoredEmail[] = (() => {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\tconst existing = g[GLOBAL_KEY] as StoredEmail[] | undefined;\n\tif (existing) return existing;\n\tconst fresh: StoredEmail[] = [];\n\tg[GLOBAL_KEY] = fresh;\n\treturn fresh;\n})();\n\n/**\n * Get all stored dev emails (most recent first).\n */\nexport function getDevEmails(): StoredEmail[] {\n\treturn storedEmails.toReversed();\n}\n\n/**\n * Clear all stored dev emails.\n */\nexport function clearDevEmails(): void {\n\tstoredEmails.length = 0;\n}\n\n/**\n * The email:deliver handler for the dev console provider.\n * Logs to console and stores in memory.\n */\nexport async function devConsoleEmailDeliver(\n\tevent: EmailDeliverEvent,\n\t_ctx: PluginContext,\n): Promise<void> {\n\tconst { message, source } = event;\n\n\tconsole.log(\n\t\t`\\n📧 [dev-email] Email sent\\n` +\n\t\t\t` From: ${source}\\n` +\n\t\t\t` To: ${message.to}\\n` +\n\t\t\t` Subject: ${message.subject}\\n` +\n\t\t\t` Text: ${message.text.slice(0, 200)}${message.text.length > 200 ? \"...\" : \"\"}\\n`,\n\t);\n\n\t// Store the email\n\tstoredEmails.push({\n\t\tmessage,\n\t\tsource,\n\t\tsentAt: new Date().toISOString(),\n\t});\n\n\t// Cap at MAX_STORED_EMAILS\n\twhile (storedEmails.length > MAX_STORED_EMAILS) {\n\t\tstoredEmails.shift();\n\t}\n}\n"],"mappings":";;AAYA,MAAa,8BAA8B;;AAG3C,MAAM,oBAAoB;;;;;;;AAiB1B,MAAM,aAAa,OAAO,IAAI,oBAAoB;AAClD,MAAM,IAAI;AACV,MAAM,sBAAqC;CAE1C,MAAM,WAAW,EAAE;AACnB,KAAI,SAAU,QAAO;CACrB,MAAM,QAAuB,EAAE;AAC/B,GAAE,cAAc;AAChB,QAAO;IACJ;;;;AAKJ,SAAgB,eAA8B;AAC7C,QAAO,aAAa,YAAY;;;;;AAMjC,SAAgB,iBAAuB;AACtC,cAAa,SAAS;;;;;;AAOvB,eAAsB,uBACrB,OACA,MACgB;CAChB,MAAM,EAAE,SAAS,WAAW;AAE5B,SAAQ,IACP,yCACa,OAAO,WACT,QAAQ,GAAG,gBACN,QAAQ,QAAQ,aACnB,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG,QAAQ,KAAK,SAAS,MAAM,QAAQ,GAAG,IACjF;AAGD,cAAa,KAAK;EACjB;EACA;EACA,yBAAQ,IAAI,MAAM,EAAC,aAAa;EAChC,CAAC;AAGF,QAAO,aAAa,SAAS,kBAC5B,cAAa,OAAO"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as mapErrorStatus } from "./errors-
|
|
1
|
+
import { r as InvalidCursorError } from "./types-Cd8iuS1J.mjs";
|
|
2
|
+
import { n as mapErrorStatus } from "./errors-BucQ8VYz.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/api/error.ts
|
|
5
5
|
/**
|
|
@@ -87,4 +87,4 @@ function unwrapResult(result, successStatus = 200) {
|
|
|
87
87
|
|
|
88
88
|
//#endregion
|
|
89
89
|
export { unwrapResult as a, requireDb as i, apiSuccess as n, handleError as r, apiError as t };
|
|
90
|
-
//# sourceMappingURL=error-
|
|
90
|
+
//# sourceMappingURL=error-oh_9i6bR.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-
|
|
1
|
+
{"version":3,"file":"error-oh_9i6bR.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { InvalidCursorError } from \"../database/repositories/types.js\";\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ success: false, error: { code, message } }` with correct\n * Content-Type. Use this for all error responses in API routes.\n */\nexport function apiError(\n\tcode: string,\n\tmessage: string,\n\tstatus: number,\n\tdetails?: Record<string, unknown>,\n): Response {\n\tconst error: { code: string; message: string; details?: Record<string, unknown> } = {\n\t\tcode,\n\t\tmessage,\n\t};\n\tif (details !== undefined) error.details = details;\n\treturn Response.json({ success: false, error }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ success: true, data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ success: true, data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\t// Bubble malformed-cursor errors as a structured 400 instead of a\n\t// generic 500.\n\tif (error instanceof InvalidCursorError) {\n\t\treturn apiError(\"INVALID_CURSOR\", error.message, 400);\n\t}\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(\n\t\t\tresult.error.code,\n\t\t\tresult.error.message,\n\t\t\tmapErrorStatus(result.error.code),\n\t\t\tresult.error.details,\n\t\t);\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SACf,MACA,SACA,QACA,SACW;CACX,MAAM,QAA8E;EACnF;EACA;EACA;AACD,KAAI,YAAY,OAAW,OAAM,UAAU;AAC3C,QAAO,SAAS,KAAK;EAAE,SAAS;EAAO;EAAO,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC;;;;;;;;AASxF,SAAgB,WAAc,MAAS,SAAS,KAAe;AAC9D,QAAO,SAAS,KAAK;EAAE,SAAS;EAAM;EAAM,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC;;;;;;;;;;AAWtF,SAAgB,YACf,OACA,iBACA,cACW;AAGX,KAAI,iBAAiB,mBACpB,QAAO,SAAS,kBAAkB,MAAM,SAAS,IAAI;AAEtD,SAAQ,MAAM,IAAI,aAAa,IAAI,MAAM;AACzC,QAAO,SAAS,cAAc,iBAAiB,IAAI;;;;;;;;AAsBpD,SAAgB,UAAU,IAA8B;AACvD,KAAI,CAAC,GACJ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAEpE,QAAO;;;;;;;;;AAUR,SAAgB,aAAgB,QAAsB,gBAAgB,KAAe;AACpF,KAAI,CAAC,OAAO,QACX,QAAO,SACN,OAAO,MAAM,MACb,OAAO,MAAM,SACb,eAAe,OAAO,MAAM,KAAK,EACjC,OAAO,MAAM,QACb;AAEF,QAAO,WAAW,OAAO,MAAM,cAAc"}
|
|
@@ -217,6 +217,7 @@ const ErrorCode = {
|
|
|
217
217
|
TERM_GET_ERROR: "TERM_GET_ERROR",
|
|
218
218
|
TERM_UPDATE_ERROR: "TERM_UPDATE_ERROR",
|
|
219
219
|
TERM_DELETE_ERROR: "TERM_DELETE_ERROR",
|
|
220
|
+
TERM_REORDER_ERROR: "TERM_REORDER_ERROR",
|
|
220
221
|
TERMS_GET_ERROR: "TERMS_GET_ERROR",
|
|
221
222
|
TERMS_SET_ERROR: "TERMS_SET_ERROR",
|
|
222
223
|
RELATION_LIST_ERROR: "RELATION_LIST_ERROR",
|
|
@@ -389,4 +390,4 @@ function mapErrorStatus(code) {
|
|
|
389
390
|
|
|
390
391
|
//#endregion
|
|
391
392
|
export { mapErrorStatus as n, ErrorCode as t };
|
|
392
|
-
//# sourceMappingURL=errors-
|
|
393
|
+
//# sourceMappingURL=errors-BucQ8VYz.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-BucQ8VYz.mjs","names":[],"sources":["../src/api/errors.ts"],"sourcesContent":["/**\n * Typed error codes and status mapping for the EmDash REST API.\n *\n * All handler-level and route-level error codes are defined here.\n * Routes and handlers should import error codes from this module\n * instead of using ad-hoc strings.\n */\n\nexport const ErrorCode = {\n\t// Shared (used across domains)\n\tNOT_FOUND: \"NOT_FOUND\",\n\tVALIDATION_ERROR: \"VALIDATION_ERROR\",\n\tINVALID_INPUT: \"INVALID_INPUT\",\n\tINVALID_JSON: \"INVALID_JSON\",\n\tINVALID_CURSOR: \"INVALID_CURSOR\",\n\tCONFLICT: \"CONFLICT\",\n\tSLUG_CONFLICT: \"SLUG_CONFLICT\",\n\tNOT_CONFIGURED: \"NOT_CONFIGURED\",\n\tUNAUTHORIZED: \"UNAUTHORIZED\",\n\tFORBIDDEN: \"FORBIDDEN\",\n\tRATE_LIMITED: \"RATE_LIMITED\",\n\tNOT_AUTHENTICATED: \"NOT_AUTHENTICATED\",\n\tNOT_IMPLEMENTED: \"NOT_IMPLEMENTED\",\n\tNOT_SUPPORTED: \"NOT_SUPPORTED\",\n\tMISSING_PARAM: \"MISSING_PARAM\",\n\tCSRF_REJECTED: \"CSRF_REJECTED\",\n\n\t// Content\n\tCONTENT_CREATE_ERROR: \"CONTENT_CREATE_ERROR\",\n\tCONTENT_UPDATE_ERROR: \"CONTENT_UPDATE_ERROR\",\n\tCONTENT_DELETE_ERROR: \"CONTENT_DELETE_ERROR\",\n\tCONTENT_LIST_ERROR: \"CONTENT_LIST_ERROR\",\n\tCONTENT_GET_ERROR: \"CONTENT_GET_ERROR\",\n\tCONTENT_DUPLICATE_ERROR: \"CONTENT_DUPLICATE_ERROR\",\n\tCONTENT_RESTORE_ERROR: \"CONTENT_RESTORE_ERROR\",\n\tCONTENT_PUBLISH_ERROR: \"CONTENT_PUBLISH_ERROR\",\n\tCONTENT_UNPUBLISH_ERROR: \"CONTENT_UNPUBLISH_ERROR\",\n\tCONTENT_SCHEDULE_ERROR: \"CONTENT_SCHEDULE_ERROR\",\n\tCONTENT_UNSCHEDULE_ERROR: \"CONTENT_UNSCHEDULE_ERROR\",\n\tCONTENT_DISCARD_DRAFT_ERROR: \"CONTENT_DISCARD_DRAFT_ERROR\",\n\tCONTENT_COMPARE_ERROR: \"CONTENT_COMPARE_ERROR\",\n\tCONTENT_TRANSLATIONS_ERROR: \"CONTENT_TRANSLATIONS_ERROR\",\n\tCONTENT_COUNT_ERROR: \"CONTENT_COUNT_ERROR\",\n\n\t// Revisions\n\tREVISION_LIST_ERROR: \"REVISION_LIST_ERROR\",\n\tREVISION_GET_ERROR: \"REVISION_GET_ERROR\",\n\tREVISION_RESTORE_ERROR: \"REVISION_RESTORE_ERROR\",\n\tINVALID_REVISION: \"INVALID_REVISION\",\n\n\t// Schema\n\tSCHEMA_LIST_ERROR: \"SCHEMA_LIST_ERROR\",\n\tSCHEMA_GET_ERROR: \"SCHEMA_GET_ERROR\",\n\tSCHEMA_CREATE_ERROR: \"SCHEMA_CREATE_ERROR\",\n\tSCHEMA_UPDATE_ERROR: \"SCHEMA_UPDATE_ERROR\",\n\tSCHEMA_DELETE_ERROR: \"SCHEMA_DELETE_ERROR\",\n\tSCHEMA_EXPORT_ERROR: \"SCHEMA_EXPORT_ERROR\",\n\tSCHEMA_FIELD_LIST_ERROR: \"SCHEMA_FIELD_LIST_ERROR\",\n\tSCHEMA_FIELD_GET_ERROR: \"SCHEMA_FIELD_GET_ERROR\",\n\tSCHEMA_FIELD_CREATE_ERROR: \"SCHEMA_FIELD_CREATE_ERROR\",\n\tSCHEMA_FIELD_UPDATE_ERROR: \"SCHEMA_FIELD_UPDATE_ERROR\",\n\tSCHEMA_FIELD_DELETE_ERROR: \"SCHEMA_FIELD_DELETE_ERROR\",\n\tSCHEMA_FIELD_REORDER_ERROR: \"SCHEMA_FIELD_REORDER_ERROR\",\n\t// Byline schema (Discussion #1174). Reuses RESERVED_SLUG, INVALID_SLUG,\n\t// INVALID_TYPE, FIELD_EXISTS, NOT_FOUND, VALIDATION_ERROR where the\n\t// semantics match; the two below are byline-domain specific:\n\tTRANSLATABLE_LOCKED: \"TRANSLATABLE_LOCKED\",\n\tREORDER_MISMATCH: \"REORDER_MISMATCH\",\n\tORPHAN_LIST_ERROR: \"ORPHAN_LIST_ERROR\",\n\tORPHAN_REGISTER_ERROR: \"ORPHAN_REGISTER_ERROR\",\n\tCOLLECTION_EXISTS: \"COLLECTION_EXISTS\",\n\tCOLLECTION_NOT_FOUND: \"COLLECTION_NOT_FOUND\",\n\tCOLLECTION_SCHEMA_MISMATCH: \"COLLECTION_SCHEMA_MISMATCH\",\n\tTABLE_NOT_FOUND: \"TABLE_NOT_FOUND\",\n\tFIELD_EXISTS: \"FIELD_EXISTS\",\n\tFIELD_TYPE_COLUMN_CHANGE: \"FIELD_TYPE_COLUMN_CHANGE\",\n\tRESERVED_SLUG: \"RESERVED_SLUG\",\n\tINVALID_SLUG: \"INVALID_SLUG\",\n\tCREATE_FAILED: \"CREATE_FAILED\",\n\tUPDATE_FAILED: \"UPDATE_FAILED\",\n\tREGISTER_FAILED: \"REGISTER_FAILED\",\n\n\t// Media\n\tMEDIA_LIST_ERROR: \"MEDIA_LIST_ERROR\",\n\tMEDIA_GET_ERROR: \"MEDIA_GET_ERROR\",\n\tMEDIA_CREATE_ERROR: \"MEDIA_CREATE_ERROR\",\n\tMEDIA_UPDATE_ERROR: \"MEDIA_UPDATE_ERROR\",\n\tMEDIA_DELETE_ERROR: \"MEDIA_DELETE_ERROR\",\n\tMEDIA_USAGE_READ_ERROR: \"MEDIA_USAGE_READ_ERROR\",\n\tMEDIA_USAGE_REPAIR_ERROR: \"MEDIA_USAGE_REPAIR_ERROR\",\n\tNO_STORAGE: \"NO_STORAGE\",\n\tNO_FILE: \"NO_FILE\",\n\tINVALID_TYPE: \"INVALID_TYPE\",\n\tUPLOAD_ERROR: \"UPLOAD_ERROR\",\n\tUPLOAD_URL_ERROR: \"UPLOAD_URL_ERROR\",\n\tCONFIRM_ERROR: \"CONFIRM_ERROR\",\n\tCONFIRM_FAILED: \"CONFIRM_FAILED\",\n\tFILE_NOT_FOUND: \"FILE_NOT_FOUND\",\n\tINVALID_STATE: \"INVALID_STATE\",\n\tFILE_SERVE_ERROR: \"FILE_SERVE_ERROR\",\n\tSTORAGE_NOT_CONFIGURED: \"STORAGE_NOT_CONFIGURED\",\n\tPROVIDER_LIST_ERROR: \"PROVIDER_LIST_ERROR\",\n\tPROVIDER_UPLOAD_ERROR: \"PROVIDER_UPLOAD_ERROR\",\n\tPROVIDER_GET_ERROR: \"PROVIDER_GET_ERROR\",\n\tPROVIDER_DELETE_ERROR: \"PROVIDER_DELETE_ERROR\",\n\n\t// Comments\n\tCOMMENT_LIST_ERROR: \"COMMENT_LIST_ERROR\",\n\tCOMMENT_GET_ERROR: \"COMMENT_GET_ERROR\",\n\tCOMMENT_STATUS_ERROR: \"COMMENT_STATUS_ERROR\",\n\tCOMMENT_DELETE_ERROR: \"COMMENT_DELETE_ERROR\",\n\tCOMMENT_BULK_ERROR: \"COMMENT_BULK_ERROR\",\n\tCOMMENT_INBOX_ERROR: \"COMMENT_INBOX_ERROR\",\n\tCOMMENT_COUNTS_ERROR: \"COMMENT_COUNTS_ERROR\",\n\tCOMMENT_CREATE_ERROR: \"COMMENT_CREATE_ERROR\",\n\tCOMMENTS_DISABLED: \"COMMENTS_DISABLED\",\n\tCOMMENTS_CLOSED: \"COMMENTS_CLOSED\",\n\tCOMMENT_REJECTED: \"COMMENT_REJECTED\",\n\n\t// Auth\n\tACCOUNT_DISABLED: \"ACCOUNT_DISABLED\",\n\tADMIN_EXISTS: \"ADMIN_EXISTS\",\n\tSETUP_COMPLETE: \"SETUP_COMPLETE\",\n\tCREDENTIAL_EXISTS: \"CREDENTIAL_EXISTS\",\n\tCHALLENGE_EXPIRED: \"CHALLENGE_EXPIRED\",\n\tPASSKEY_REGISTER_ERROR: \"PASSKEY_REGISTER_ERROR\",\n\tPASSKEY_REGISTER_OPTIONS_ERROR: \"PASSKEY_REGISTER_OPTIONS_ERROR\",\n\tPASSKEY_OPTIONS_ERROR: \"PASSKEY_OPTIONS_ERROR\",\n\tPASSKEY_VERIFY_ERROR: \"PASSKEY_VERIFY_ERROR\",\n\tPASSKEY_LIST_ERROR: \"PASSKEY_LIST_ERROR\",\n\tPASSKEY_RENAME_ERROR: \"PASSKEY_RENAME_ERROR\",\n\tPASSKEY_DELETE_ERROR: \"PASSKEY_DELETE_ERROR\",\n\tPASSKEY_LIMIT: \"PASSKEY_LIMIT\",\n\tLAST_PASSKEY: \"LAST_PASSKEY\",\n\tLOGOUT_ERROR: \"LOGOUT_ERROR\",\n\tSELF_ROLE_CHANGE: \"SELF_ROLE_CHANGE\",\n\tEMAIL_IN_USE: \"EMAIL_IN_USE\",\n\tEMAIL_NOT_CONFIGURED: \"EMAIL_NOT_CONFIGURED\",\n\tUSER_EXISTS: \"USER_EXISTS\",\n\tINVALID_TOKEN: \"INVALID_TOKEN\",\n\tTOKEN_EXPIRED: \"TOKEN_EXPIRED\",\n\tDOMAIN_NOT_ALLOWED: \"DOMAIN_NOT_ALLOWED\",\n\tINVITE_CREATE_ERROR: \"INVITE_CREATE_ERROR\",\n\tINVITE_VALIDATE_ERROR: \"INVITE_VALIDATE_ERROR\",\n\tINVITE_COMPLETE_ERROR: \"INVITE_COMPLETE_ERROR\",\n\tSIGNUP_VERIFY_ERROR: \"SIGNUP_VERIFY_ERROR\",\n\tSIGNUP_COMPLETE_ERROR: \"SIGNUP_COMPLETE_ERROR\",\n\tRECOVERY_SEND_ERROR: \"RECOVERY_SEND_ERROR\",\n\tUSER_LIST_ERROR: \"USER_LIST_ERROR\",\n\tUSER_DETAIL_ERROR: \"USER_DETAIL_ERROR\",\n\tUSER_UPDATE_ERROR: \"USER_UPDATE_ERROR\",\n\tUSER_DISABLE_ERROR: \"USER_DISABLE_ERROR\",\n\tUSER_ENABLE_ERROR: \"USER_ENABLE_ERROR\",\n\n\t// OAuth (internal codes -- distinct from RFC OAuthErrorCode)\n\tUNSUPPORTED_RESPONSE_TYPE: \"UNSUPPORTED_RESPONSE_TYPE\",\n\tINVALID_REDIRECT_URI: \"INVALID_REDIRECT_URI\",\n\tINVALID_CLIENT: \"INVALID_CLIENT\",\n\tINVALID_SCOPE: \"INVALID_SCOPE\",\n\tAUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n\tINVALID_GRANT: \"INVALID_GRANT\",\n\tUNSUPPORTED_GRANT_TYPE: \"UNSUPPORTED_GRANT_TYPE\",\n\tINVALID_CODE: \"INVALID_CODE\",\n\tEXPIRED_CODE: \"EXPIRED_CODE\",\n\tINSUFFICIENT_ROLE: \"INSUFFICIENT_ROLE\",\n\tINSUFFICIENT_SCOPE: \"INSUFFICIENT_SCOPE\",\n\tINSUFFICIENT_PERMISSIONS: \"INSUFFICIENT_PERMISSIONS\",\n\tTOKEN_EXCHANGE_ERROR: \"TOKEN_EXCHANGE_ERROR\",\n\tTOKEN_REFRESH_ERROR: \"TOKEN_REFRESH_ERROR\",\n\tTOKEN_REVOKE_ERROR: \"TOKEN_REVOKE_ERROR\",\n\tTOKEN_CREATE_ERROR: \"TOKEN_CREATE_ERROR\",\n\tTOKEN_LIST_ERROR: \"TOKEN_LIST_ERROR\",\n\tTOKEN_ERROR: \"TOKEN_ERROR\",\n\tDEVICE_CODE_ERROR: \"DEVICE_CODE_ERROR\",\n\tAUTHORIZE_ERROR: \"AUTHORIZE_ERROR\",\n\tCLIENT_LIST_ERROR: \"CLIENT_LIST_ERROR\",\n\tCLIENT_GET_ERROR: \"CLIENT_GET_ERROR\",\n\tCLIENT_CREATE_ERROR: \"CLIENT_CREATE_ERROR\",\n\tCLIENT_UPDATE_ERROR: \"CLIENT_UPDATE_ERROR\",\n\tCLIENT_DELETE_ERROR: \"CLIENT_DELETE_ERROR\",\n\n\t// Allowed domains\n\tDOMAIN_LIST_ERROR: \"DOMAIN_LIST_ERROR\",\n\tDOMAIN_CREATE_ERROR: \"DOMAIN_CREATE_ERROR\",\n\tDOMAIN_UPDATE_ERROR: \"DOMAIN_UPDATE_ERROR\",\n\tDOMAIN_DELETE_ERROR: \"DOMAIN_DELETE_ERROR\",\n\n\t// Plugins / Marketplace\n\tPLUGIN_LIST_ERROR: \"PLUGIN_LIST_ERROR\",\n\tPLUGIN_GET_ERROR: \"PLUGIN_GET_ERROR\",\n\tPLUGIN_ENABLE_ERROR: \"PLUGIN_ENABLE_ERROR\",\n\tPLUGIN_DISABLE_ERROR: \"PLUGIN_DISABLE_ERROR\",\n\tPLUGIN_ID_CONFLICT: \"PLUGIN_ID_CONFLICT\",\n\tPLUGIN_SETTINGS_READ_ERROR: \"PLUGIN_SETTINGS_READ_ERROR\",\n\tPLUGIN_SETTINGS_UPDATE_ERROR: \"PLUGIN_SETTINGS_UPDATE_ERROR\",\n\tMARKETPLACE_NOT_CONFIGURED: \"MARKETPLACE_NOT_CONFIGURED\",\n\tMARKETPLACE_UNAVAILABLE: \"MARKETPLACE_UNAVAILABLE\",\n\tMARKETPLACE_ERROR: \"MARKETPLACE_ERROR\",\n\tSANDBOX_NOT_AVAILABLE: \"SANDBOX_NOT_AVAILABLE\",\n\tALREADY_INSTALLED: \"ALREADY_INSTALLED\",\n\tALREADY_UP_TO_DATE: \"ALREADY_UP_TO_DATE\",\n\tNO_VERSION: \"NO_VERSION\",\n\tMANIFEST_MISMATCH: \"MANIFEST_MISMATCH\",\n\tMANIFEST_VERSION_MISMATCH: \"MANIFEST_VERSION_MISMATCH\",\n\tAUDIT_FAILED: \"AUDIT_FAILED\",\n\tCHECKSUM_MISMATCH: \"CHECKSUM_MISMATCH\",\n\tINVALID_BUNDLE: \"INVALID_BUNDLE\",\n\tBUNDLE_EXTRACT_FAILED: \"BUNDLE_EXTRACT_FAILED\",\n\tBUNDLE_DOWNLOAD_FAILED: \"BUNDLE_DOWNLOAD_FAILED\",\n\tAGGREGATOR_RESPONSE_INVALID: \"AGGREGATOR_RESPONSE_INVALID\",\n\tAGGREGATOR_HTTP_ERROR: \"AGGREGATOR_HTTP_ERROR\",\n\tAGGREGATOR_NOT_FOUND: \"AGGREGATOR_NOT_FOUND\",\n\tCAPABILITY_ESCALATION: \"CAPABILITY_ESCALATION\",\n\tROUTE_VISIBILITY_ESCALATION: \"ROUTE_VISIBILITY_ESCALATION\",\n\tENV_INCOMPATIBLE: \"ENV_INCOMPATIBLE\",\n\tINSTALL_FAILED: \"INSTALL_FAILED\",\n\tUNINSTALL_FAILED: \"UNINSTALL_FAILED\",\n\tSEARCH_FAILED: \"SEARCH_FAILED\",\n\tGET_PLUGIN_FAILED: \"GET_PLUGIN_FAILED\",\n\tGET_THEME_FAILED: \"GET_THEME_FAILED\",\n\tTHEME_SEARCH_FAILED: \"THEME_SEARCH_FAILED\",\n\tUPDATE_CHECK_FAILED: \"UPDATE_CHECK_FAILED\",\n\tEXCLUSIVE_HOOKS_LIST_ERROR: \"EXCLUSIVE_HOOKS_LIST_ERROR\",\n\tEXCLUSIVE_HOOK_SET_ERROR: \"EXCLUSIVE_HOOK_SET_ERROR\",\n\n\t// Menus\n\tMENU_LIST_ERROR: \"MENU_LIST_ERROR\",\n\tMENU_CREATE_ERROR: \"MENU_CREATE_ERROR\",\n\tMENU_GET_ERROR: \"MENU_GET_ERROR\",\n\tMENU_UPDATE_ERROR: \"MENU_UPDATE_ERROR\",\n\tMENU_DELETE_ERROR: \"MENU_DELETE_ERROR\",\n\tMENU_ITEM_CREATE_ERROR: \"MENU_ITEM_CREATE_ERROR\",\n\tMENU_ITEM_UPDATE_ERROR: \"MENU_ITEM_UPDATE_ERROR\",\n\tMENU_ITEM_DELETE_ERROR: \"MENU_ITEM_DELETE_ERROR\",\n\tMENU_REORDER_ERROR: \"MENU_REORDER_ERROR\",\n\t// Returned when a menu name resolves to multiple locale variants and\n\t// the caller did not pass `locale` to disambiguate. (name, locale) is\n\t// unique, so this only fires for omitted-locale lookups.\n\tAMBIGUOUS_LOCALE: \"AMBIGUOUS_LOCALE\",\n\n\t// Taxonomies\n\tTAXONOMY_LIST_ERROR: \"TAXONOMY_LIST_ERROR\",\n\tTAXONOMY_CREATE_ERROR: \"TAXONOMY_CREATE_ERROR\",\n\tTERM_LIST_ERROR: \"TERM_LIST_ERROR\",\n\tTERM_CREATE_ERROR: \"TERM_CREATE_ERROR\",\n\tTERM_GET_ERROR: \"TERM_GET_ERROR\",\n\tTERM_UPDATE_ERROR: \"TERM_UPDATE_ERROR\",\n\tTERM_DELETE_ERROR: \"TERM_DELETE_ERROR\",\n\tTERM_REORDER_ERROR: \"TERM_REORDER_ERROR\",\n\tTERMS_GET_ERROR: \"TERMS_GET_ERROR\",\n\tTERMS_SET_ERROR: \"TERMS_SET_ERROR\",\n\n\t// Relations (content references)\n\tRELATION_LIST_ERROR: \"RELATION_LIST_ERROR\",\n\tRELATION_CREATE_ERROR: \"RELATION_CREATE_ERROR\",\n\tRELATION_GET_ERROR: \"RELATION_GET_ERROR\",\n\tRELATION_UPDATE_ERROR: \"RELATION_UPDATE_ERROR\",\n\tRELATION_DELETE_ERROR: \"RELATION_DELETE_ERROR\",\n\tRELATION_TRANSLATIONS_ERROR: \"RELATION_TRANSLATIONS_ERROR\",\n\tREFERENCES_GET_ERROR: \"REFERENCES_GET_ERROR\",\n\tREFERENCES_SET_ERROR: \"REFERENCES_SET_ERROR\",\n\n\t// Sections\n\tSECTION_LIST_ERROR: \"SECTION_LIST_ERROR\",\n\tSECTION_CREATE_ERROR: \"SECTION_CREATE_ERROR\",\n\tSECTION_GET_ERROR: \"SECTION_GET_ERROR\",\n\tSECTION_UPDATE_ERROR: \"SECTION_UPDATE_ERROR\",\n\tSECTION_DELETE_ERROR: \"SECTION_DELETE_ERROR\",\n\n\t// Redirects\n\tREDIRECT_LIST_ERROR: \"REDIRECT_LIST_ERROR\",\n\tREDIRECT_CREATE_ERROR: \"REDIRECT_CREATE_ERROR\",\n\tREDIRECT_GET_ERROR: \"REDIRECT_GET_ERROR\",\n\tREDIRECT_UPDATE_ERROR: \"REDIRECT_UPDATE_ERROR\",\n\tREDIRECT_DELETE_ERROR: \"REDIRECT_DELETE_ERROR\",\n\tNOT_FOUND_LIST_ERROR: \"NOT_FOUND_LIST_ERROR\",\n\tNOT_FOUND_SUMMARY_ERROR: \"NOT_FOUND_SUMMARY_ERROR\",\n\tNOT_FOUND_CLEAR_ERROR: \"NOT_FOUND_CLEAR_ERROR\",\n\tNOT_FOUND_PRUNE_ERROR: \"NOT_FOUND_PRUNE_ERROR\",\n\n\t// Widgets\n\tWIDGET_AREA_LIST_ERROR: \"WIDGET_AREA_LIST_ERROR\",\n\tWIDGET_AREA_CREATE_ERROR: \"WIDGET_AREA_CREATE_ERROR\",\n\tWIDGET_AREA_GET_ERROR: \"WIDGET_AREA_GET_ERROR\",\n\tWIDGET_AREA_DELETE_ERROR: \"WIDGET_AREA_DELETE_ERROR\",\n\tWIDGET_CREATE_ERROR: \"WIDGET_CREATE_ERROR\",\n\tWIDGET_UPDATE_ERROR: \"WIDGET_UPDATE_ERROR\",\n\tWIDGET_DELETE_ERROR: \"WIDGET_DELETE_ERROR\",\n\tWIDGET_REORDER_ERROR: \"WIDGET_REORDER_ERROR\",\n\tWIDGET_COMPONENTS_ERROR: \"WIDGET_COMPONENTS_ERROR\",\n\n\t// Setup\n\tALREADY_CONFIGURED: \"ALREADY_CONFIGURED\",\n\tINVALID_SEED: \"INVALID_SEED\",\n\tINVALID_REDIRECT: \"INVALID_REDIRECT\",\n\tSETUP_ERROR: \"SETUP_ERROR\",\n\tSETUP_STATUS_ERROR: \"SETUP_STATUS_ERROR\",\n\tSETUP_ADMIN_ERROR: \"SETUP_ADMIN_ERROR\",\n\tSETUP_VERIFY_ERROR: \"SETUP_VERIFY_ERROR\",\n\tDEV_BYPASS_ERROR: \"DEV_BYPASS_ERROR\",\n\tDEV_RESET_ERROR: \"DEV_RESET_ERROR\",\n\tMIGRATION_ERROR: \"MIGRATION_ERROR\",\n\tSEED_ERROR: \"SEED_ERROR\",\n\n\t// Settings\n\tSETTINGS_READ_ERROR: \"SETTINGS_READ_ERROR\",\n\tSETTINGS_UPDATE_ERROR: \"SETTINGS_UPDATE_ERROR\",\n\tEMAIL_SETTINGS_READ_ERROR: \"EMAIL_SETTINGS_READ_ERROR\",\n\tEMAIL_TEST_ERROR: \"EMAIL_TEST_ERROR\",\n\n\t// Backups\n\tBACKUP_SETTINGS_READ_ERROR: \"BACKUP_SETTINGS_READ_ERROR\",\n\tBACKUP_SETTINGS_ERROR: \"BACKUP_SETTINGS_ERROR\",\n\tBACKUP_EXPORT_ERROR: \"BACKUP_EXPORT_ERROR\",\n\tBACKUP_LIST_ERROR: \"BACKUP_LIST_ERROR\",\n\tBACKUP_CREATE_ERROR: \"BACKUP_CREATE_ERROR\",\n\tBACKUP_DELETE_ERROR: \"BACKUP_DELETE_ERROR\",\n\tBACKUP_DOWNLOAD_ERROR: \"BACKUP_DOWNLOAD_ERROR\",\n\n\t// Search\n\tSEARCH_ERROR: \"SEARCH_ERROR\",\n\tSTATS_ERROR: \"STATS_ERROR\",\n\tSUGGESTION_ERROR: \"SUGGESTION_ERROR\",\n\tREBUILD_ERROR: \"REBUILD_ERROR\",\n\n\t// Import\n\tWXR_ANALYZE_ERROR: \"WXR_ANALYZE_ERROR\",\n\tWXR_PREPARE_ERROR: \"WXR_PREPARE_ERROR\",\n\tWXR_IMPORT_ERROR: \"WXR_IMPORT_ERROR\",\n\tIMPORT_ERROR: \"IMPORT_ERROR\",\n\tREWRITE_ERROR: \"REWRITE_ERROR\",\n\tWP_PLUGIN_ANALYZE_ERROR: \"WP_PLUGIN_ANALYZE_ERROR\",\n\tWP_PLUGIN_IMPORT_ERROR: \"WP_PLUGIN_IMPORT_ERROR\",\n\tSSRF_BLOCKED: \"SSRF_BLOCKED\",\n\tPROBE_ERROR: \"PROBE_ERROR\",\n\n\t// Dashboard\n\tDASHBOARD_ERROR: \"DASHBOARD_ERROR\",\n\tDASHBOARD_STATS_ERROR: \"DASHBOARD_STATS_ERROR\",\n\n\t// Misc\n\tSNAPSHOT_ERROR: \"SNAPSHOT_ERROR\",\n\tTYPEGEN_ERROR: \"TYPEGEN_ERROR\",\n\tSITEMAP_ERROR: \"SITEMAP_ERROR\",\n\tNO_DB: \"NO_DB\",\n\tINVALID_REQUEST: \"INVALID_REQUEST\",\n\tUNKNOWN_ACTION: \"UNKNOWN_ACTION\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n/**\n * OAuth RFC 6749 error codes.\n *\n * These MUST be lowercase per the RFC spec. Used only by OAuth token endpoints.\n * Separate from ErrorCode to prevent mixing conventions.\n */\nexport const OAuthErrorCode = {\n\tINVALID_GRANT: \"invalid_grant\",\n\tUNSUPPORTED_GRANT_TYPE: \"unsupported_grant_type\",\n\tEXPIRED_TOKEN: \"expired_token\",\n\tACCESS_DENIED: \"access_denied\",\n\tAUTHORIZATION_PENDING: \"authorization_pending\",\n} as const;\n\nexport type OAuthErrorCode = (typeof OAuthErrorCode)[keyof typeof OAuthErrorCode];\n\n/**\n * Map a handler error code to an HTTP status code.\n *\n * Shared codes have explicit mappings. Domain-specific `*_ERROR` codes\n * (used in catch blocks via handleError) default to 500. Everything else\n * defaults to 400 (client error).\n */\nexport function mapErrorStatus(code: string | undefined): number {\n\tswitch (code) {\n\t\t// 400 Bad Request\n\t\tcase ErrorCode.VALIDATION_ERROR:\n\t\tcase ErrorCode.INVALID_INPUT:\n\t\tcase ErrorCode.INVALID_JSON:\n\t\tcase ErrorCode.INVALID_CURSOR:\n\t\tcase ErrorCode.MISSING_PARAM:\n\t\tcase ErrorCode.INVALID_REQUEST:\n\t\tcase ErrorCode.NOT_SUPPORTED:\n\t\tcase ErrorCode.INVALID_SLUG:\n\t\tcase ErrorCode.RESERVED_SLUG:\n\t\tcase ErrorCode.INVALID_TYPE:\n\t\tcase ErrorCode.NO_FILE:\n\t\tcase ErrorCode.INVALID_STATE:\n\t\tcase ErrorCode.INVALID_SEED:\n\t\tcase ErrorCode.INVALID_REDIRECT:\n\t\tcase ErrorCode.INVALID_TOKEN:\n\t\tcase ErrorCode.INVALID_REVISION:\n\t\tcase ErrorCode.INVALID_CODE:\n\t\tcase ErrorCode.CHALLENGE_EXPIRED:\n\t\tcase ErrorCode.EXPIRED_CODE:\n\t\tcase ErrorCode.LAST_PASSKEY:\n\t\tcase ErrorCode.PASSKEY_LIMIT:\n\t\tcase ErrorCode.ADMIN_EXISTS:\n\t\tcase ErrorCode.SETUP_COMPLETE:\n\t\tcase ErrorCode.SELF_ROLE_CHANGE:\n\t\tcase ErrorCode.SSRF_BLOCKED:\n\t\tcase ErrorCode.UNKNOWN_ACTION:\n\t\tcase ErrorCode.AMBIGUOUS_LOCALE:\n\t\tcase ErrorCode.REORDER_MISMATCH:\n\t\t\treturn 400;\n\n\t\t// 401 Unauthorized\n\t\tcase ErrorCode.UNAUTHORIZED:\n\t\tcase ErrorCode.NOT_AUTHENTICATED:\n\t\t\treturn 401;\n\n\t\t// 403 Forbidden\n\t\tcase ErrorCode.FORBIDDEN:\n\t\tcase ErrorCode.CSRF_REJECTED:\n\t\tcase ErrorCode.ACCOUNT_DISABLED:\n\t\tcase ErrorCode.COMMENTS_DISABLED:\n\t\tcase ErrorCode.COMMENTS_CLOSED:\n\t\tcase ErrorCode.COMMENT_REJECTED:\n\t\tcase ErrorCode.DOMAIN_NOT_ALLOWED:\n\t\tcase ErrorCode.INSUFFICIENT_ROLE:\n\t\tcase ErrorCode.INSUFFICIENT_SCOPE:\n\t\tcase ErrorCode.INSUFFICIENT_PERMISSIONS:\n\t\tcase ErrorCode.CAPABILITY_ESCALATION:\n\t\tcase ErrorCode.ROUTE_VISIBILITY_ESCALATION:\n\t\tcase ErrorCode.AUDIT_FAILED:\n\t\t\treturn 403;\n\n\t\t// 404 Not Found\n\t\tcase ErrorCode.NOT_FOUND:\n\t\tcase ErrorCode.TABLE_NOT_FOUND:\n\t\tcase ErrorCode.COLLECTION_NOT_FOUND:\n\t\tcase ErrorCode.FILE_NOT_FOUND:\n\t\tcase ErrorCode.NO_VERSION:\n\t\tcase ErrorCode.AGGREGATOR_NOT_FOUND:\n\t\t\treturn 404;\n\n\t\t// 409 Conflict\n\t\tcase ErrorCode.CONFLICT:\n\t\tcase ErrorCode.SLUG_CONFLICT:\n\t\tcase ErrorCode.COLLECTION_EXISTS:\n\t\tcase ErrorCode.FIELD_EXISTS:\n\t\tcase ErrorCode.CREDENTIAL_EXISTS:\n\t\tcase ErrorCode.EMAIL_IN_USE:\n\t\tcase ErrorCode.USER_EXISTS:\n\t\tcase ErrorCode.PLUGIN_ID_CONFLICT:\n\t\tcase ErrorCode.ALREADY_INSTALLED:\n\t\tcase ErrorCode.ALREADY_CONFIGURED:\n\t\tcase ErrorCode.ALREADY_UP_TO_DATE:\n\t\tcase ErrorCode.TRANSLATABLE_LOCKED:\n\t\tcase ErrorCode.ENV_INCOMPATIBLE:\n\t\t\treturn 409;\n\n\t\t// 410 Gone\n\t\tcase ErrorCode.TOKEN_EXPIRED:\n\t\t\treturn 410;\n\n\t\t// 422 Unprocessable Entity\n\t\tcase ErrorCode.CHECKSUM_MISMATCH:\n\t\tcase ErrorCode.INVALID_BUNDLE:\n\t\tcase ErrorCode.BUNDLE_EXTRACT_FAILED:\n\t\t\treturn 422;\n\n\t\t// 429 Too Many Requests\n\t\tcase ErrorCode.RATE_LIMITED:\n\t\t\treturn 429;\n\n\t\t// 500 Internal Server Error\n\t\tcase ErrorCode.NOT_CONFIGURED:\n\t\tcase ErrorCode.NO_STORAGE:\n\t\tcase ErrorCode.NO_DB:\n\t\tcase ErrorCode.STORAGE_NOT_CONFIGURED:\n\t\tcase ErrorCode.EMAIL_NOT_CONFIGURED:\n\t\tcase ErrorCode.COLLECTION_SCHEMA_MISMATCH:\n\t\t\treturn 500;\n\n\t\t// 501 Not Implemented\n\t\tcase ErrorCode.NOT_IMPLEMENTED:\n\t\t\treturn 501;\n\n\t\t// 502 Bad Gateway\n\t\tcase ErrorCode.BUNDLE_DOWNLOAD_FAILED:\n\t\tcase ErrorCode.AGGREGATOR_RESPONSE_INVALID:\n\t\tcase ErrorCode.AGGREGATOR_HTTP_ERROR:\n\t\t\treturn 502;\n\n\t\t// 503 Service Unavailable\n\t\tcase ErrorCode.MARKETPLACE_UNAVAILABLE:\n\t\tcase ErrorCode.MARKETPLACE_NOT_CONFIGURED:\n\t\tcase ErrorCode.SANDBOX_NOT_AVAILABLE:\n\t\t\treturn 503;\n\n\t\t// Domain-specific *_ERROR codes are catch-block codes -- always 500.\n\t\t// WARNING: If adding a new code that ends in _ERROR but represents a\n\t\t// client error (4xx), add it to an explicit case above or it will\n\t\t// be incorrectly mapped to 500.\n\t\tdefault:\n\t\t\treturn code?.endsWith(\"_ERROR\") ? 500 : 400;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape-
|
|
1
|
+
{"version":3,"file":"escape-CXps5F_v.mjs","names":[],"sources":["../src/api/escape.ts"],"sourcesContent":["/** HTML-escape a string to prevent XSS when interpolated into HTML/JS */\nexport function escapeHtml(str: string): string {\n\treturn str\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\")\n\t\t.replaceAll(\"'\", \"'\");\n}\n"],"mappings":";;AACA,SAAgB,WAAW,KAAqB;AAC/C,QAAO,IACL,WAAW,KAAK,QAAQ,CACxB,WAAW,KAAK,OAAO,CACvB,WAAW,KAAK,OAAO,CACvB,WAAW,MAAK,SAAS,CACzB,WAAW,KAAK,SAAS"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { o as __exportAll } from "./runner-
|
|
2
|
-
import { t as after } from "./after-
|
|
1
|
+
import { o as __exportAll } from "./runner-BFTrkYhu.mjs";
|
|
2
|
+
import { t as after } from "./after-CBU2vkoO.mjs";
|
|
3
3
|
import { getRequestContext } from "./request-context.mjs";
|
|
4
4
|
import { r as requestCached } from "./request-cache-BSUptuJR.mjs";
|
|
5
|
-
import { n as BylineSchemaRegistry } from "./byline-registry-
|
|
5
|
+
import { n as BylineSchemaRegistry } from "./byline-registry-ZkPKqEwC.mjs";
|
|
6
6
|
import { n as initWithLock, t as createInitLock } from "./init-lock-DJkX6Hto.mjs";
|
|
7
7
|
|
|
8
8
|
//#region src/bylines/field-defs-cache.ts
|
|
@@ -74,4 +74,4 @@ async function getBylineFieldDefs(db) {
|
|
|
74
74
|
|
|
75
75
|
//#endregion
|
|
76
76
|
export { getBylineFieldDefs as n, field_defs_cache_exports as t };
|
|
77
|
-
//# sourceMappingURL=field-defs-cache-
|
|
77
|
+
//# sourceMappingURL=field-defs-cache-WD82dMQq.mjs.map
|