emdash 0.22.0 → 0.23.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-hUy8vOB-.d.mts → adapters-DAAjOID4.d.mts} +1 -1
- package/dist/{adapters-hUy8vOB-.d.mts.map → adapters-DAAjOID4.d.mts.map} +1 -1
- package/dist/{allowed-origins-DjlhyfNN.mjs → allowed-origins-D5FxMUo8.mjs} +2 -2
- package/dist/{allowed-origins-DjlhyfNN.mjs.map → allowed-origins-D5FxMUo8.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +11 -11
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-BDuM4bEy.mjs → api-BQ_yKRdG.mjs} +12 -12
- package/dist/{api-BDuM4bEy.mjs.map → api-BQ_yKRdG.mjs.map} +1 -1
- package/dist/{api-tokens-ViYKeIp9.mjs → api-tokens-D6ppjIVi.mjs} +2 -2
- package/dist/{api-tokens-ViYKeIp9.mjs.map → api-tokens-D6ppjIVi.mjs.map} +1 -1
- package/dist/{apply-wDqQoxX4.mjs → apply-CWRyxIoO.mjs} +12 -12
- package/dist/{apply-wDqQoxX4.mjs.map → apply-CWRyxIoO.mjs.map} +1 -1
- package/dist/astro/index.d.mts +11 -11
- package/dist/astro/index.mjs +12 -6
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +6 -6
- package/dist/astro/middleware/redirect.mjs +3 -3
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +5 -3
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +349 -128
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +7 -7
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +7 -7
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +7 -7
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -11
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -11
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +12 -11
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -9
- package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +27 -26
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +27 -26
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +26 -25
- 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 +26 -25
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +26 -25
- 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 +27 -26
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +27 -26
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +26 -25
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +26 -25
- 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 +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +26 -25
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +5 -5
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
- package/dist/astro/routes/api/auth/invite/complete.mjs +9 -9
- package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -8
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +5 -5
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -8
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -9
- package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -9
- package/dist/astro/routes/api/auth/signup/complete.mjs +9 -9
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
- package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +10 -10
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
- package/dist/astro/routes/api/dashboard.mjs +3 -3
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +10 -10
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +8 -7
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -10
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +12 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +29 -29
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
- package/dist/astro/routes/api/media/_id_.mjs +6 -6
- package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +8 -8
- package/dist/astro/routes/api/media.mjs +10 -10
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_.mjs +6 -6
- package/dist/astro/routes/api/menus/index.mjs +6 -6
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
- package/dist/astro/routes/api/oauth/token.mjs +5 -5
- package/dist/astro/routes/api/openapi.json.mjs +3 -3
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +7 -7
- package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -7
- package/dist/astro/routes/api/redirects/_id_.mjs +8 -8
- package/dist/astro/routes/api/redirects/index.mjs +8 -8
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +26 -25
- 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 +26 -25
- 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 +26 -25
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +26 -25
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +26 -25
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +7 -6
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +26 -25
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +26 -25
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +9 -9
- package/dist/astro/routes/api/search/index.mjs +8 -8
- package/dist/astro/routes/api/search/rebuild.mjs +9 -9
- package/dist/astro/routes/api/search/stats.mjs +5 -5
- package/dist/astro/routes/api/search/suggest.mjs +8 -8
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
- package/dist/astro/routes/api/sections/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +3 -3
- package/dist/astro/routes/api/settings.mjs +9 -9
- package/dist/astro/routes/api/setup/admin-verify.mjs +10 -10
- package/dist/astro/routes/api/setup/admin.mjs +9 -9
- package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -16
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
- package/dist/astro/routes/api/setup/index.mjs +18 -17
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +5 -5
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +10 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +10 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +10 -10
- package/dist/astro/routes/api/taxonomies/index.mjs +10 -10
- package/dist/astro/routes/api/themes/preview.mjs +4 -4
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_.mjs +4 -4
- package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +3 -3
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +6 -6
- package/dist/astro/routes/sitemap.xml.mjs +4 -4
- package/dist/astro/types.d.mts +12 -12
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-C0EYoUeV.mjs → authorize-Cse0k7B0.mjs} +2 -2
- package/dist/{authorize-C0EYoUeV.mjs.map → authorize-Cse0k7B0.mjs.map} +1 -1
- package/dist/{byline-CC0WS4Gu.mjs → byline-DVo-liaP.mjs} +15 -73
- package/dist/byline-DVo-liaP.mjs.map +1 -0
- package/dist/{byline-fields-53i9Et0x.mjs → byline-fields-B-KXkg5R.mjs} +2 -2
- package/dist/{byline-fields-53i9Et0x.mjs.map → byline-fields-B-KXkg5R.mjs.map} +1 -1
- package/dist/{byline-fields-DNp0HNKc.mjs → byline-fields-DaMKzkhO.mjs} +1 -1
- package/dist/{byline-fields-DNp0HNKc.mjs.map → byline-fields-DaMKzkhO.mjs.map} +1 -1
- package/dist/{byline-fields-jy07PZJM.d.mts → byline-fields-RFPkDLRw.d.mts} +21 -1
- package/dist/byline-fields-RFPkDLRw.d.mts.map +1 -0
- package/dist/{bylines-CCO1CYnH.mjs → bylines-f5ppBpD5.mjs} +6 -6
- package/dist/{bylines-CCO1CYnH.mjs.map → bylines-f5ppBpD5.mjs.map} +1 -1
- package/dist/{bylines-DGekMGGm.mjs → bylines-pIKgc_TL.mjs} +2 -2
- package/dist/{bylines-DGekMGGm.mjs.map → bylines-pIKgc_TL.mjs.map} +1 -1
- package/dist/{cache-pXTpun6s.mjs → cache-Q6V4U-Xz.mjs} +1 -1
- package/dist/{cache-pXTpun6s.mjs.map → cache-Q6V4U-Xz.mjs.map} +1 -1
- package/dist/{challenge-store-C6E_kWTs.mjs → challenge-store-LhiqMccz.mjs} +1 -1
- package/dist/{challenge-store-C6E_kWTs.mjs.map → challenge-store-LhiqMccz.mjs.map} +1 -1
- package/dist/cli/index.mjs +16 -15
- 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-reaction-B6AdcpXw.mjs → comment-reaction-Dyb-AutY.mjs} +1 -1
- package/dist/{comment-reaction-B6AdcpXw.mjs.map → comment-reaction-Dyb-AutY.mjs.map} +1 -1
- package/dist/{comments-CtPkT2tp.mjs → comments-Cczx7AvD.mjs} +1 -1
- package/dist/{comments-CtPkT2tp.mjs.map → comments-Cczx7AvD.mjs.map} +1 -1
- package/dist/{components-D6ZnrzbB.mjs → components-CYt4uVK9.mjs} +1 -1
- package/dist/{components-D6ZnrzbB.mjs.map → components-CYt4uVK9.mjs.map} +1 -1
- package/dist/{context-f1__jpzF.mjs → context-B1W8soLv.mjs} +58 -20
- package/dist/context-B1W8soLv.mjs.map +1 -0
- package/dist/{cron-CQPxBjYs.mjs → cron-C5LVoNmP.mjs} +14 -2
- package/dist/cron-C5LVoNmP.mjs.map +1 -0
- package/dist/{dashboard-BOESNcwE.mjs → dashboard-CYjpQZ6-.mjs} +1 -1
- package/dist/{dashboard-BOESNcwE.mjs.map → dashboard-CYjpQZ6-.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- 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/{db-errors-CrC5w3Jz.mjs → db-errors-UXB1CK42.mjs} +1 -1
- package/dist/{db-errors-CrC5w3Jz.mjs.map → db-errors-UXB1CK42.mjs.map} +1 -1
- package/dist/{default-CpBEPn6a.mjs → default-CeRG-Ot4.mjs} +1 -1
- package/dist/{default-CpBEPn6a.mjs.map → default-CeRG-Ot4.mjs.map} +1 -1
- package/dist/{device-flow-Bn-qiDrw.mjs → device-flow-DENDCQ9F.mjs} +4 -4
- package/dist/{device-flow-Bn-qiDrw.mjs.map → device-flow-DENDCQ9F.mjs.map} +1 -1
- package/dist/{email-console-B9VS6eN4.mjs → email-console-DJP32ucW.mjs} +1 -1
- package/dist/{email-console-B9VS6eN4.mjs.map → email-console-DJP32ucW.mjs.map} +1 -1
- package/dist/{error-BQ1pxs0L.mjs → error-Bl6kZgtt.mjs} +1 -1
- package/dist/{error-BQ1pxs0L.mjs.map → error-Bl6kZgtt.mjs.map} +1 -1
- package/dist/{escape-BtbJWyaW.mjs → escape-Bjio4ZsM.mjs} +1 -1
- package/dist/{escape-BtbJWyaW.mjs.map → escape-Bjio4ZsM.mjs.map} +1 -1
- package/dist/field-defs-cache-ztrdzUGb.mjs +77 -0
- package/dist/field-defs-cache-ztrdzUGb.mjs.map +1 -0
- package/dist/{fts-manager-DtiWqwNJ.mjs → fts-manager-CQuHt-vS.mjs} +1 -1
- package/dist/{fts-manager-DtiWqwNJ.mjs.map → fts-manager-CQuHt-vS.mjs.map} +1 -1
- package/dist/{hash-BowcuPwv.mjs → hash-DrvzIXcz.mjs} +1 -1
- package/dist/{hash-BowcuPwv.mjs.map → hash-DrvzIXcz.mjs.map} +1 -1
- package/dist/{import-DqSGOlmR.mjs → import-Bb1T9WJS.mjs} +3 -3
- package/dist/{import-DqSGOlmR.mjs.map → import-Bb1T9WJS.mjs.map} +1 -1
- package/dist/{index-C2dLNT6S.d.mts → index-BaIj5hIq.d.mts} +39 -17
- package/dist/index-BaIj5hIq.d.mts.map +1 -0
- package/dist/{index-ZDEwR7qW.d.mts → index-qbL02uhs.d.mts} +3 -3
- package/dist/{index-ZDEwR7qW.d.mts.map → index-qbL02uhs.d.mts.map} +1 -1
- package/dist/index.d.mts +17 -17
- package/dist/index.mjs +46 -45
- package/dist/{load-Dtoo7KcJ.mjs → load-BNo-ER8C.mjs} +1 -1
- package/dist/{load-Dtoo7KcJ.mjs.map → load-BNo-ER8C.mjs.map} +1 -1
- package/dist/{loader-C8Z48Uof.mjs → loader-Dn74CG1b.mjs} +68 -9
- package/dist/loader-Dn74CG1b.mjs.map +1 -0
- package/dist/{manifest-schema-DDSbwkAL.mjs → manifest-schema-BWKnQYQF.mjs} +1 -1
- package/dist/{manifest-schema-DDSbwkAL.mjs.map → manifest-schema-BWKnQYQF.mjs.map} +1 -1
- package/dist/media/image-endpoint.mjs +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -2
- package/dist/media/local-runtime.d.mts +19 -11
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +9 -7
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-allowlist-DunzrKFM.mjs → media-allowlist-B77I5_bw.mjs} +2 -2
- package/dist/{media-allowlist-DunzrKFM.mjs.map → media-allowlist-B77I5_bw.mjs.map} +1 -1
- package/dist/{media-url-S22B6aPr.mjs → media-url-O4rm9-SQ.mjs} +1 -1
- package/dist/{media-url-S22B6aPr.mjs.map → media-url-O4rm9-SQ.mjs.map} +1 -1
- package/dist/{menus-DOs1MQJg.mjs → menus-DS8VTZgo.mjs} +56 -13
- package/dist/menus-DS8VTZgo.mjs.map +1 -0
- package/dist/{mime-DYpsOAny.mjs → mime-BTzvzJ8M.mjs} +1 -1
- package/dist/{mime-DYpsOAny.mjs.map → mime-BTzvzJ8M.mjs.map} +1 -1
- package/dist/{mode-CFh8Dw8G.mjs → mode-BB0F8xTC.mjs} +1 -1
- package/dist/{mode-CFh8Dw8G.mjs.map → mode-BB0F8xTC.mjs.map} +1 -1
- package/dist/{normalize-Uam4_Vkr.mjs → normalize-ClbNIenm.mjs} +1 -1
- package/dist/{normalize-Uam4_Vkr.mjs.map → normalize-ClbNIenm.mjs.map} +1 -1
- package/dist/{oauth-authorization-BN-t83Uy.mjs → oauth-authorization-CsvzIp_F.mjs} +4 -4
- package/dist/{oauth-authorization-BN-t83Uy.mjs.map → oauth-authorization-CsvzIp_F.mjs.map} +1 -1
- package/dist/{oauth-clients-R6I_V0qz.mjs → oauth-clients-C9SYwEbZ.mjs} +1 -1
- package/dist/{oauth-clients-R6I_V0qz.mjs.map → oauth-clients-C9SYwEbZ.mjs.map} +1 -1
- package/dist/{oauth-state-store-Csoc6kkR.mjs → oauth-state-store---zrApfB.mjs} +1 -1
- package/dist/{oauth-state-store-Csoc6kkR.mjs.map → oauth-state-store---zrApfB.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-d7VYjTpx.mjs → oauth-user-lookup-SHsWRlG9.mjs} +1 -1
- package/dist/{oauth-user-lookup-d7VYjTpx.mjs.map → oauth-user-lookup-SHsWRlG9.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{options-DFFpvNJU.d.mts → options-CqECG0gM.d.mts} +3 -3
- package/dist/{options-DFFpvNJU.d.mts.map → options-CqECG0gM.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-qrHlnzTy.mjs → parse-C9hpjUyY.mjs} +2 -2
- package/dist/{parse-qrHlnzTy.mjs.map → parse-C9hpjUyY.mjs.map} +1 -1
- package/dist/{passkey-config-BJTbcnI5.mjs → passkey-config-C0YfSBko.mjs} +1 -1
- package/dist/{passkey-config-BJTbcnI5.mjs.map → passkey-config-C0YfSBko.mjs.map} +1 -1
- package/dist/{placeholder-CIDCpyEY.mjs → placeholder-B0s2mWu5.mjs} +1 -1
- package/dist/{placeholder-CIDCpyEY.mjs.map → placeholder-B0s2mWu5.mjs.map} +1 -1
- package/dist/{placeholder-CirPauNG.d.mts → placeholder-QoI4gEzP.d.mts} +1 -1
- package/dist/{placeholder-CirPauNG.d.mts.map → placeholder-QoI4gEzP.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{portable-text-BIFhrU6T.mjs → portable-text-BICg8bTk.mjs} +1 -1
- package/dist/{portable-text-BIFhrU6T.mjs.map → portable-text-BICg8bTk.mjs.map} +1 -1
- package/dist/{preview-IDye9SPQ.mjs → preview-DKGCt2_p.mjs} +2 -2
- package/dist/{preview-IDye9SPQ.mjs.map → preview-DKGCt2_p.mjs.map} +1 -1
- package/dist/{public-url-8lFyQ8ZF.mjs → public-url-CTVqgMmg.mjs} +1 -1
- package/dist/{public-url-8lFyQ8ZF.mjs.map → public-url-CTVqgMmg.mjs.map} +1 -1
- package/dist/{query-B1RVFUNM.mjs → query-DgUHZLWB.mjs} +79 -8
- package/dist/query-DgUHZLWB.mjs.map +1 -0
- package/dist/{rate-limit-DfNAqIWW.mjs → rate-limit-BuQ5d4X_.mjs} +2 -2
- package/dist/{rate-limit-DfNAqIWW.mjs.map → rate-limit-BuQ5d4X_.mjs.map} +1 -1
- package/dist/{redirect-B4wuX2A5.mjs → redirect-CS-PHtNh.mjs} +1 -1
- package/dist/{redirect-B4wuX2A5.mjs.map → redirect-CS-PHtNh.mjs.map} +1 -1
- package/dist/{redirects-CT_vb3NV.mjs → redirects-BZ8Mxn8H.mjs} +2 -2
- package/dist/{redirects-CT_vb3NV.mjs.map → redirects-BZ8Mxn8H.mjs.map} +1 -1
- package/dist/{redirects-DUvXgzCX.mjs → redirects-x9eFPeyi.mjs} +4 -2
- package/dist/redirects-x9eFPeyi.mjs.map +1 -0
- package/dist/{registry-CjK96fHD.mjs → registry-CK1SS6pi.mjs} +2 -2
- package/dist/{registry-CjK96fHD.mjs.map → registry-CK1SS6pi.mjs.map} +1 -1
- package/dist/{request-meta-ei3ATilC.mjs → request-meta-CmS1tDFf.mjs} +2 -2
- package/dist/{request-meta-ei3ATilC.mjs.map → request-meta-CmS1tDFf.mjs.map} +1 -1
- package/dist/{resolve-CpbBglV0.mjs → resolve-C7I0qiR0.mjs} +1 -1
- package/dist/{resolve-CpbBglV0.mjs.map → resolve-C7I0qiR0.mjs.map} +1 -1
- package/dist/{runner-jdXtFEd3.d.mts → runner-CEtq-6Sm.d.mts} +2 -2
- package/dist/{runner-jdXtFEd3.d.mts.map → runner-CEtq-6Sm.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-5z2Jfnbm.mjs → schema-CbFRFoM7.mjs} +3 -3
- package/dist/{schema-5z2Jfnbm.mjs.map → schema-CbFRFoM7.mjs.map} +1 -1
- package/dist/{search-D9FkoM-k.mjs → search-C-waE_YW.mjs} +3 -3
- package/dist/{search-D9FkoM-k.mjs.map → search-C-waE_YW.mjs.map} +1 -1
- package/dist/{secrets-BUE5UQys.mjs → secrets-BSf9pRRY.mjs} +2 -2
- package/dist/{secrets-BUE5UQys.mjs.map → secrets-BSf9pRRY.mjs.map} +1 -1
- package/dist/{sections-DWYZo6HK.mjs → sections-osX9U-YJ.mjs} +2 -2
- package/dist/{sections-DWYZo6HK.mjs.map → sections-osX9U-YJ.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +12 -11
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.mjs +1 -1
- package/dist/{seo-Cu47j3mJ.mjs → seo-4FovMwNS.mjs} +1 -1
- package/dist/{seo-Cu47j3mJ.mjs.map → seo-4FovMwNS.mjs.map} +1 -1
- package/dist/{seo-DRnqnstF.mjs → seo-DCEF-AVr.mjs} +1 -1
- package/dist/{seo-DRnqnstF.mjs.map → seo-DCEF-AVr.mjs.map} +1 -1
- package/dist/{service-DTmtqCnw.mjs → service-jby1RRmU.mjs} +2 -2
- package/dist/{service-DTmtqCnw.mjs.map → service-jby1RRmU.mjs.map} +1 -1
- package/dist/{session-user-DmtPMNa1.mjs → session-user-B8aLtKAH.mjs} +1 -1
- package/dist/{session-user-DmtPMNa1.mjs.map → session-user-B8aLtKAH.mjs.map} +1 -1
- package/dist/{settings-DUrVDN0c.mjs → settings-4bRT2NIf.mjs} +2 -2
- package/dist/{settings-DUrVDN0c.mjs.map → settings-4bRT2NIf.mjs.map} +1 -1
- package/dist/{settings-yaxu9mU-.mjs → settings-t-H2jPuJ.mjs} +3 -3
- package/dist/{settings-yaxu9mU-.mjs.map → settings-t-H2jPuJ.mjs.map} +1 -1
- package/dist/{setup-complete-BWFHzgvZ.mjs → setup-complete-gEiySUc-.mjs} +1 -1
- package/dist/{setup-complete-BWFHzgvZ.mjs.map → setup-complete-gEiySUc-.mjs.map} +1 -1
- package/dist/{setup-nonce-DFWG-J5c.mjs → setup-nonce-DzS50zme.mjs} +1 -1
- package/dist/{setup-nonce-DFWG-J5c.mjs.map → setup-nonce-DzS50zme.mjs.map} +1 -1
- package/dist/{single-flight-cache-B5p5cNOL.mjs → single-flight-cache-Cdfkic3t.mjs} +1 -1
- package/dist/{single-flight-cache-B5p5cNOL.mjs.map → single-flight-cache-Cdfkic3t.mjs.map} +1 -1
- package/dist/{site-url-BOTPgmeM.mjs → site-url-BLebyON8.mjs} +1 -1
- package/dist/{site-url-BOTPgmeM.mjs.map → site-url-BLebyON8.mjs.map} +1 -1
- package/dist/{ssrf-B0VeRLrp.mjs → ssrf-BRKb343l.mjs} +1 -1
- package/dist/{ssrf-B0VeRLrp.mjs.map → ssrf-BRKb343l.mjs.map} +1 -1
- package/dist/{status-CB2basWJ.mjs → status-vUK0SA17.mjs} +1 -1
- package/dist/{status-CB2basWJ.mjs.map → status-vUK0SA17.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-CJmkEpdS.mjs → taxonomies-BBB8O-CU.mjs} +2 -2
- package/dist/{taxonomies-CJmkEpdS.mjs.map → taxonomies-BBB8O-CU.mjs.map} +1 -1
- package/dist/{taxonomies-Be9B1jEj.mjs → taxonomies-Dxrc_szG.mjs} +26 -6
- package/dist/{taxonomies-Be9B1jEj.mjs.map → taxonomies-Dxrc_szG.mjs.map} +1 -1
- package/dist/{tokens-BvrJvIB6.mjs → tokens-LeuXF9gG.mjs} +1 -1
- package/dist/{tokens-BvrJvIB6.mjs.map → tokens-LeuXF9gG.mjs.map} +1 -1
- package/dist/{transport-k7YjfmEn.d.mts → transport-BZ-03aFT.d.mts} +1 -1
- package/dist/{transport-k7YjfmEn.d.mts.map → transport-BZ-03aFT.d.mts.map} +1 -1
- package/dist/{transport-B4rXnSTf.mjs → transport-_2nBz7e9.mjs} +1 -1
- package/dist/{transport-B4rXnSTf.mjs.map → transport-_2nBz7e9.mjs.map} +1 -1
- package/dist/{trusted-proxy-DY1_UqHr.mjs → trusted-proxy-DZY5WCn2.mjs} +1 -1
- package/dist/{trusted-proxy-DY1_UqHr.mjs.map → trusted-proxy-DZY5WCn2.mjs.map} +1 -1
- package/dist/{types-Czg1S3gB.d.mts → types-B-gIxFCE.d.mts} +1 -1
- package/dist/{types-Czg1S3gB.d.mts.map → types-B-gIxFCE.d.mts.map} +1 -1
- package/dist/{types-DXLVzV2w.d.mts → types-BG8_XTLj.d.mts} +1 -1
- package/dist/{types-DXLVzV2w.d.mts.map → types-BG8_XTLj.d.mts.map} +1 -1
- package/dist/{types-CWGYDYqF.d.mts → types-BWh2MjmA.d.mts} +15 -2
- package/dist/types-BWh2MjmA.d.mts.map +1 -0
- package/dist/{types-CgvhU-hF.d.mts → types-BWqGVhFn.d.mts} +1 -1
- package/dist/{types-CgvhU-hF.d.mts.map → types-BWqGVhFn.d.mts.map} +1 -1
- package/dist/{types-DYF2_Vf4.mjs → types-CQAugunJ.mjs} +1 -1
- package/dist/{types-DYF2_Vf4.mjs.map → types-CQAugunJ.mjs.map} +1 -1
- package/dist/{types-DPCpkzvS.d.mts → types-CTLJYMhN.d.mts} +1 -1
- package/dist/{types-DPCpkzvS.d.mts.map → types-CTLJYMhN.d.mts.map} +1 -1
- package/dist/{types-CmivgZbp.d.mts → types-CUsXGRVj.d.mts} +1 -1
- package/dist/{types-CmivgZbp.d.mts.map → types-CUsXGRVj.d.mts.map} +1 -1
- package/dist/types-DXOIbece.mjs.map +1 -1
- package/dist/{types-CKNXt63n.d.mts → types-DrYMm9s5.d.mts} +1 -1
- package/dist/{types-CKNXt63n.d.mts.map → types-DrYMm9s5.d.mts.map} +1 -1
- package/dist/{types-CIKX2HO5.d.mts → types-TTx8uP7V.d.mts} +2 -2
- package/dist/{types-CIKX2HO5.d.mts.map → types-TTx8uP7V.d.mts.map} +1 -1
- package/dist/{types-DCNNfeCv.d.mts → types-fS7-VTdR.d.mts} +1 -1
- package/dist/{types-DCNNfeCv.d.mts.map → types-fS7-VTdR.d.mts.map} +1 -1
- package/dist/{utils-LbHcSRTj.mjs → utils-B7A57fm9.mjs} +1 -1
- package/dist/{utils-LbHcSRTj.mjs.map → utils-B7A57fm9.mjs.map} +1 -1
- package/dist/{validate-Bwl64sxv.mjs → validate-0Utjspbs.mjs} +1 -1
- package/dist/{validate-Bwl64sxv.mjs.map → validate-0Utjspbs.mjs.map} +1 -1
- package/dist/{validate-CE2M0nvx.d.mts → validate-DOFrIb-c.d.mts} +5 -5
- package/dist/{validate-CE2M0nvx.d.mts.map → validate-DOFrIb-c.d.mts.map} +1 -1
- package/dist/{validation-C_yOvFaO.mjs → validation-D9EnzH1T.mjs} +5 -5
- package/dist/{validation-C_yOvFaO.mjs.map → validation-D9EnzH1T.mjs.map} +1 -1
- package/dist/version-BgVM_1MU.mjs +7 -0
- package/dist/{version-RnYXYjzD.mjs.map → version-BgVM_1MU.mjs.map} +1 -1
- package/dist/{widgets-8GONuRHo.mjs → widgets-BSTzPUYa.mjs} +3 -3
- package/dist/{widgets-8GONuRHo.mjs.map → widgets-BSTzPUYa.mjs.map} +1 -1
- package/dist/{zod-generator-D481JjzD.mjs → zod-generator-ElVQOav7.mjs} +50 -14
- package/dist/zod-generator-ElVQOav7.mjs.map +1 -0
- package/package.json +8 -7
- package/src/api/schemas/bylines.ts +8 -0
- package/src/astro/integration/virtual-modules.ts +8 -0
- package/src/astro/integration/vite-config.ts +9 -1
- package/src/astro/middleware/request-context.ts +6 -1
- package/src/astro/middleware/scoped-db.ts +131 -0
- package/src/astro/middleware/stream-end-metrics.ts +4 -8
- package/src/astro/middleware.ts +90 -28
- package/src/astro/routes/admin.astro +5 -1
- package/src/astro/routes/api/schema/index.ts +9 -3
- package/src/cli/commands/export-seed.ts +10 -7
- package/src/components/Block.astro +71 -0
- package/src/components/EmDashMedia.astro +1 -2
- package/src/components/Image.astro +36 -2
- package/src/components/InlinePortableTextEditor.tsx +10 -2
- package/src/components/index.ts +5 -0
- package/src/components/portable-text-text-align.ts +38 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +5 -1
- package/src/content/converters/prosemirror-to-portable-text.ts +8 -0
- package/src/content/converters/types.ts +1 -0
- package/src/database/repositories/byline.ts +14 -0
- package/src/database/repositories/types.ts +13 -0
- package/src/emdash-runtime.ts +226 -71
- package/src/loader.ts +97 -5
- package/src/media/local-runtime.ts +18 -5
- package/src/plugins/context.ts +94 -22
- package/src/plugins/cron.ts +18 -2
- package/src/plugins/routes.ts +40 -1
- package/src/query.ts +108 -1
- package/src/schema/zod-generator.ts +53 -8
- package/src/taxonomies/index.ts +26 -0
- package/src/ui.ts +5 -0
- package/src/virtual-modules.d.ts +13 -0
- package/dist/byline-CC0WS4Gu.mjs.map +0 -1
- package/dist/byline-fields-jy07PZJM.d.mts.map +0 -1
- package/dist/context-f1__jpzF.mjs.map +0 -1
- package/dist/cron-CQPxBjYs.mjs.map +0 -1
- package/dist/index-C2dLNT6S.d.mts.map +0 -1
- package/dist/loader-C8Z48Uof.mjs.map +0 -1
- package/dist/menus-DOs1MQJg.mjs.map +0 -1
- package/dist/query-B1RVFUNM.mjs.map +0 -1
- package/dist/redirects-DUvXgzCX.mjs.map +0 -1
- package/dist/types-CWGYDYqF.d.mts.map +0 -1
- package/dist/version-RnYXYjzD.mjs +0 -7
- package/dist/zod-generator-D481JjzD.mjs.map +0 -1
- /package/dist/{api-tokens-BVH-H8Z9.mjs → api-tokens-B4BQybOp.mjs} +0 -0
- /package/dist/{ssrf-Cz1e2QPn.mjs → ssrf-CcX9zvMK.mjs} +0 -0
- /package/dist/{types-Bfr2Pj6z.mjs → types-BvbeGEtr.mjs} +0 -0
package/src/loader.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { LiveLoader } from "astro/loaders";
|
|
15
|
-
import { Kysely, sql, type Dialect } from "kysely";
|
|
15
|
+
import { Kysely, type RawBuilder, sql, type Dialect } from "kysely";
|
|
16
16
|
|
|
17
17
|
import { currentTimestampValue, isPostgres } from "./database/dialect-helpers.js";
|
|
18
18
|
import { kyselyLogOption } from "./database/instrumentation.js";
|
|
@@ -72,8 +72,83 @@ const SYSTEM_COLUMNS = new Set([
|
|
|
72
72
|
// fields. The aliases are _emdash_-prefixed so they can't shadow a user
|
|
73
73
|
// field named e.g. `seo_title`.
|
|
74
74
|
...SEO_ALIAS_COLUMNS,
|
|
75
|
+
// Folded hydration JSON columns (see foldedHydrationSelects) — surfaced via
|
|
76
|
+
// the FOLDED_* markers, never as flat fields.
|
|
77
|
+
"_emdash_terms",
|
|
78
|
+
"_emdash_bylines",
|
|
75
79
|
]);
|
|
76
80
|
|
|
81
|
+
/** Markers for byline/taxonomy hydration folded into the content query. */
|
|
82
|
+
export const FOLDED_TERMS = Symbol.for("emdash:foldedTerms");
|
|
83
|
+
export const FOLDED_BYLINES = Symbol.for("emdash:foldedBylines");
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Correlated JSON-array subqueries that fold taxonomy-term and byline hydration
|
|
87
|
+
* into the content query, removing the two separate hydration round trips per
|
|
88
|
+
* fetch. `outer` is the content table's alias/name; each subquery correlates on
|
|
89
|
+
* `<outer>.id`, so the base query stays one row per entry (no join fan-out, no
|
|
90
|
+
* duplicated content payload). Order is NOT applied in the aggregate (it differs
|
|
91
|
+
* across dialects) — the consumer sorts terms by label and credits by sortOrder.
|
|
92
|
+
*
|
|
93
|
+
* Dialect-specific aggregation: SQLite `json_group_array`/`json_object` returns
|
|
94
|
+
* a JSON *string*; Postgres `json_agg`/`json_build_object` (coalesced to `[]`)
|
|
95
|
+
* returns parsed JSON. {@link stashFolded} handles both.
|
|
96
|
+
*/
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance
|
|
98
|
+
function foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {
|
|
99
|
+
const o = sql.ref(outer);
|
|
100
|
+
const pg = isPostgres(db);
|
|
101
|
+
const obj = (pairs: string) =>
|
|
102
|
+
pg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);
|
|
103
|
+
const agg = (inner: RawBuilder<unknown>) =>
|
|
104
|
+
pg ? sql`coalesce(json_agg(${inner}), '[]'::json)` : sql`json_group_array(${inner})`;
|
|
105
|
+
|
|
106
|
+
const termObj = obj(
|
|
107
|
+
"'id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group",
|
|
108
|
+
);
|
|
109
|
+
// Filter terms to the entry's own locale (matches #1441: terms render in the
|
|
110
|
+
// entry's resolved locale, not all locale variants of the attached group).
|
|
111
|
+
const terms = sql`(SELECT ${agg(termObj)} FROM ${sql.ref("content_taxonomies")} AS ct JOIN ${sql.ref("taxonomies")} AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ${type} AND ct.entry_id = ${o}.id AND t.locale = ${o}.locale) AS ${sql.ref("_emdash_terms")}`;
|
|
112
|
+
|
|
113
|
+
const bylineInner = obj(
|
|
114
|
+
"'id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group",
|
|
115
|
+
);
|
|
116
|
+
const creditObj = pg
|
|
117
|
+
? sql.raw(
|
|
118
|
+
"json_build_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', ",
|
|
119
|
+
)
|
|
120
|
+
: sql.raw("json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', ");
|
|
121
|
+
const credit = sql`${creditObj}${bylineInner})`;
|
|
122
|
+
const bylines = sql`(SELECT ${agg(credit)} FROM ${sql.ref("_emdash_content_bylines")} AS cb JOIN ${sql.ref("_emdash_bylines")} AS b ON b.translation_group = cb.byline_id LEFT JOIN ${sql.ref("media")} AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ${type} AND cb.content_id = ${o}.id AND b.locale = ${o}.locale) AS ${sql.ref("_emdash_bylines")}`;
|
|
123
|
+
return { terms, bylines };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.
|
|
128
|
+
* SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.
|
|
129
|
+
*/
|
|
130
|
+
function stashFolded(data: Record<string, unknown>, row: Record<string, unknown>): void {
|
|
131
|
+
for (const [col, sym] of [
|
|
132
|
+
["_emdash_terms", FOLDED_TERMS],
|
|
133
|
+
["_emdash_bylines", FOLDED_BYLINES],
|
|
134
|
+
] as const) {
|
|
135
|
+
const raw = row[col];
|
|
136
|
+
let value: unknown;
|
|
137
|
+
if (typeof raw === "string") {
|
|
138
|
+
try {
|
|
139
|
+
value = JSON.parse(raw);
|
|
140
|
+
} catch {
|
|
141
|
+
continue; // malformed: fall back to the query path
|
|
142
|
+
}
|
|
143
|
+
} else if (Array.isArray(raw)) {
|
|
144
|
+
value = raw; // Postgres json/jsonb already parsed by the driver
|
|
145
|
+
} else {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
Object.defineProperty(data, sym, { value, enumerable: false, configurable: true });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
77
152
|
/** Resolved SEO shape attached to `entry.data.seo`. Mirrors `ContentSeo`. */
|
|
78
153
|
interface EntrySeo {
|
|
79
154
|
title: string | null;
|
|
@@ -785,8 +860,14 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
785
860
|
)`
|
|
786
861
|
: sql``;
|
|
787
862
|
|
|
863
|
+
// Fold byline + taxonomy hydration into the list query.
|
|
864
|
+
const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
|
|
865
|
+
db,
|
|
866
|
+
type,
|
|
867
|
+
tableName,
|
|
868
|
+
);
|
|
788
869
|
result = await sql<Record<string, unknown>>`
|
|
789
|
-
SELECT
|
|
870
|
+
SELECT *, ${termsSelect}, ${bylinesSelect} FROM ${sql.ref(tableName)}
|
|
790
871
|
WHERE deleted_at IS NULL
|
|
791
872
|
AND ${statusCondition}
|
|
792
873
|
${localeFilter}
|
|
@@ -814,11 +895,13 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
814
895
|
rowLocale !== "" &&
|
|
815
896
|
(rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
|
|
816
897
|
const id = shouldPrefix ? `${rowLocale}/${slug}` : slug;
|
|
898
|
+
const data = mapRowToData(row);
|
|
899
|
+
stashFolded(data, row);
|
|
817
900
|
return {
|
|
818
901
|
id,
|
|
819
902
|
slug: rowStr(row, "slug"),
|
|
820
903
|
status: rowStr(row, "status", "draft"),
|
|
821
|
-
data
|
|
904
|
+
data,
|
|
822
905
|
cacheHint: {
|
|
823
906
|
tags: [rowStr(row, "id")],
|
|
824
907
|
lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
|
|
@@ -922,9 +1005,17 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
922
1005
|
([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,
|
|
923
1006
|
),
|
|
924
1007
|
);
|
|
1008
|
+
// Fold byline + taxonomy hydration into the content query (see
|
|
1009
|
+
// foldedHydrationSelects), removing the two separate hydration
|
|
1010
|
+
// round trips per fetch.
|
|
1011
|
+
const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
|
|
1012
|
+
db,
|
|
1013
|
+
type,
|
|
1014
|
+
"c",
|
|
1015
|
+
);
|
|
925
1016
|
const result = locale
|
|
926
1017
|
? await sql<Record<string, unknown>>`
|
|
927
|
-
SELECT c.*, ${seoSelect}
|
|
1018
|
+
SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
|
|
928
1019
|
FROM ${sql.ref(tableName)} AS c
|
|
929
1020
|
LEFT JOIN ${sql.ref("_emdash_seo")} AS s
|
|
930
1021
|
ON s.collection = ${type} AND s.content_id = c.id
|
|
@@ -933,7 +1024,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
933
1024
|
LIMIT 1
|
|
934
1025
|
`.execute(db)
|
|
935
1026
|
: await sql<Record<string, unknown>>`
|
|
936
|
-
SELECT c.*, ${seoSelect}
|
|
1027
|
+
SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
|
|
937
1028
|
FROM ${sql.ref(tableName)} AS c
|
|
938
1029
|
LEFT JOIN ${sql.ref("_emdash_seo")} AS s
|
|
939
1030
|
ON s.collection = ${type} AND s.content_id = c.id
|
|
@@ -1015,6 +1106,7 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
1015
1106
|
const entryData = mapRowToData(row);
|
|
1016
1107
|
const entrySeo = extractSeo(row);
|
|
1017
1108
|
if (entrySeo) entryData.seo = entrySeo;
|
|
1109
|
+
stashFolded(entryData, row);
|
|
1018
1110
|
return {
|
|
1019
1111
|
id: entryId,
|
|
1020
1112
|
slug: rowStr(row, "slug"),
|
|
@@ -29,6 +29,14 @@ export interface LocalMediaRuntimeConfig {
|
|
|
29
29
|
enabled?: boolean;
|
|
30
30
|
// These are injected by the runtime, not from user config
|
|
31
31
|
db?: Kysely<Database>;
|
|
32
|
+
/**
|
|
33
|
+
* Resolver for the live connection, preferred over `db`. The runtime
|
|
34
|
+
* injects it so a connection-backed adapter (Postgres over Hyperdrive)
|
|
35
|
+
* serves provider queries from the current request-scoped connection in ALS
|
|
36
|
+
* rather than a snapshot of the per-isolate singleton. Omitted for stateless
|
|
37
|
+
* adapters (D1, Node SQLite), where `db` is used directly.
|
|
38
|
+
*/
|
|
39
|
+
getDb?: () => Kysely<Database>;
|
|
32
40
|
storage?: Storage;
|
|
33
41
|
}
|
|
34
42
|
|
|
@@ -42,11 +50,15 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
42
50
|
throw new Error("Local media provider requires database connection");
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
// Resolve the connection per operation (not captured once) so a
|
|
54
|
+
// connection-backed adapter uses the current event-scoped connection; falls
|
|
55
|
+
// back to the injected `db` for stateless adapters.
|
|
56
|
+
const resolveDb = config.getDb ?? (() => db);
|
|
57
|
+
const repo = () => new MediaRepository(resolveDb());
|
|
46
58
|
|
|
47
59
|
const provider: MediaProvider = {
|
|
48
60
|
async list(options: MediaListOptions) {
|
|
49
|
-
const result = await repo.findMany({
|
|
61
|
+
const result = await repo().findMany({
|
|
50
62
|
cursor: options.cursor,
|
|
51
63
|
limit: options.limit,
|
|
52
64
|
mimeType: options.mimeType,
|
|
@@ -75,7 +87,7 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
75
87
|
},
|
|
76
88
|
|
|
77
89
|
async get(id: string) {
|
|
78
|
-
const item = await repo.findById(id);
|
|
90
|
+
const item = await repo().findById(id);
|
|
79
91
|
if (!item) return null;
|
|
80
92
|
|
|
81
93
|
return {
|
|
@@ -108,7 +120,8 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
108
120
|
},
|
|
109
121
|
|
|
110
122
|
async delete(id: string) {
|
|
111
|
-
const
|
|
123
|
+
const repoInstance = repo();
|
|
124
|
+
const item = await repoInstance.findById(id);
|
|
112
125
|
if (!item) return;
|
|
113
126
|
|
|
114
127
|
// Delete from storage if available
|
|
@@ -120,7 +133,7 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
await
|
|
136
|
+
await repoInstance.delete(id);
|
|
124
137
|
|
|
125
138
|
// If this row was referenced by `logo`, `favicon`, or
|
|
126
139
|
// `seo.defaultOgImage`, the worker-scoped settings cache now
|
package/src/plugins/context.ts
CHANGED
|
@@ -432,23 +432,55 @@ export function createMediaAccess(db: Kysely<Database>): MediaAccess {
|
|
|
432
432
|
|
|
433
433
|
/**
|
|
434
434
|
* Create full media access with write operations.
|
|
435
|
-
*
|
|
435
|
+
*
|
|
436
|
+
* `getUploadUrlFn` is optional: when omitted, `getUploadUrl()` is derived from
|
|
437
|
+
* `storage` (create a pending record + a signed PUT URL), mirroring the REST
|
|
438
|
+
* `/_emdash/api/media/upload-url` endpoint. `upload()` only needs `storage`.
|
|
439
|
+
* If storage is not provided, both throw at call time.
|
|
436
440
|
*/
|
|
437
441
|
export function createMediaAccessWithWrite(
|
|
438
442
|
db: Kysely<Database>,
|
|
439
|
-
getUploadUrlFn:
|
|
440
|
-
filename: string,
|
|
441
|
-
|
|
442
|
-
) => Promise<{ uploadUrl: string; mediaId: string }>,
|
|
443
|
+
getUploadUrlFn:
|
|
444
|
+
| ((filename: string, contentType: string) => Promise<{ uploadUrl: string; mediaId: string }>)
|
|
445
|
+
| undefined,
|
|
443
446
|
storage?: Storage,
|
|
444
447
|
): MediaAccessWithWrite {
|
|
445
448
|
const mediaRepo = new MediaRepository(db);
|
|
446
449
|
const readAccess = createMediaAccess(db);
|
|
447
450
|
|
|
451
|
+
const getUploadUrl =
|
|
452
|
+
getUploadUrlFn ??
|
|
453
|
+
(async (filename: string, contentType: string) => {
|
|
454
|
+
if (!storage) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
"Media getUploadUrl() requires a storage backend. Configure storage in PluginContextFactoryOptions.",
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const basename = filename.split("/").pop() ?? filename;
|
|
461
|
+
const dotIdx = basename.lastIndexOf(".");
|
|
462
|
+
const ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : "";
|
|
463
|
+
const storageKey = `${ulid()}${ext}`;
|
|
464
|
+
|
|
465
|
+
const media = await mediaRepo.createPending({
|
|
466
|
+
filename: basename,
|
|
467
|
+
mimeType: contentType,
|
|
468
|
+
storageKey,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const signed = await storage.getSignedUploadUrl({
|
|
472
|
+
key: storageKey,
|
|
473
|
+
contentType,
|
|
474
|
+
expiresIn: 3600,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return { uploadUrl: signed.url, mediaId: media.id };
|
|
478
|
+
});
|
|
479
|
+
|
|
448
480
|
return {
|
|
449
481
|
...readAccess,
|
|
450
482
|
|
|
451
|
-
getUploadUrl
|
|
483
|
+
getUploadUrl,
|
|
452
484
|
|
|
453
485
|
async upload(
|
|
454
486
|
filename: string,
|
|
@@ -836,14 +868,26 @@ export function createUserAccess(db: Kysely<Database>): UserAccess {
|
|
|
836
868
|
|
|
837
869
|
export interface PluginContextFactoryOptions {
|
|
838
870
|
db: Kysely<Database>;
|
|
871
|
+
/**
|
|
872
|
+
* Resolver for the database connection, preferred over `db` when present.
|
|
873
|
+
* Called per `createContext()` so connection-backed adapters (e.g. Postgres
|
|
874
|
+
* over Hyperdrive) get the current request/event-scoped connection from ALS
|
|
875
|
+
* rather than a snapshot of the per-isolate singleton — reusing the
|
|
876
|
+
* singleton's socket from a later event trips workerd's cross-request I/O
|
|
877
|
+
* guard. When omitted, `db` is used directly (correct for stateless
|
|
878
|
+
* adapters like D1 and Node SQLite). `db` remains required as the fallback.
|
|
879
|
+
*/
|
|
880
|
+
getDb?: () => Kysely<Database>;
|
|
839
881
|
/**
|
|
840
882
|
* Storage backend for direct media uploads.
|
|
841
883
|
* If not provided, upload() will throw.
|
|
842
884
|
*/
|
|
843
885
|
storage?: Storage;
|
|
844
886
|
/**
|
|
845
|
-
*
|
|
846
|
-
*
|
|
887
|
+
* Explicit provider for `ctx.media.getUploadUrl()`. Optional: when omitted
|
|
888
|
+
* but `storage` is configured, the factory derives a working `getUploadUrl()`
|
|
889
|
+
* (and `upload()`) from storage. Only when neither `getUploadUrl` nor
|
|
890
|
+
* `storage` is present do media write operations become unavailable.
|
|
847
891
|
*/
|
|
848
892
|
getUploadUrl?: (
|
|
849
893
|
filename: string,
|
|
@@ -877,8 +921,7 @@ export interface PluginContextFactoryOptions {
|
|
|
877
921
|
* Factory for creating plugin contexts
|
|
878
922
|
*/
|
|
879
923
|
export class PluginContextFactory {
|
|
880
|
-
private
|
|
881
|
-
private db: Kysely<Database>;
|
|
924
|
+
private resolveDb: () => Kysely<Database>;
|
|
882
925
|
private storage?: Storage;
|
|
883
926
|
private getUploadUrl?: (
|
|
884
927
|
filename: string,
|
|
@@ -888,10 +931,16 @@ export class PluginContextFactory {
|
|
|
888
931
|
private urlHelper: (path: string) => string;
|
|
889
932
|
private cronReschedule?: () => void;
|
|
890
933
|
private emailPipeline?: EmailPipeline;
|
|
934
|
+
/**
|
|
935
|
+
* Plugin IDs already warned about a missing media-write backend, so the
|
|
936
|
+
* warning fires once per factory instead of on every hook/route context
|
|
937
|
+
* creation (which would spam logs for hook-participating plugins).
|
|
938
|
+
*/
|
|
939
|
+
private warnedMissingMediaBackend = new Set<string>();
|
|
891
940
|
|
|
892
941
|
constructor(options: PluginContextFactoryOptions) {
|
|
893
|
-
|
|
894
|
-
this.
|
|
942
|
+
const fixedDb = options.db;
|
|
943
|
+
this.resolveDb = options.getDb ?? (() => fixedDb);
|
|
895
944
|
this.storage = options.storage;
|
|
896
945
|
this.getUploadUrl = options.getUploadUrl;
|
|
897
946
|
this.site = createSiteInfo(options.siteInfo ?? {});
|
|
@@ -906,10 +955,17 @@ export class PluginContextFactory {
|
|
|
906
955
|
createContext(plugin: ResolvedPlugin): PluginContext {
|
|
907
956
|
const capabilities = new Set(plugin.capabilities);
|
|
908
957
|
|
|
958
|
+
// Resolve the connection once per context. For stateless adapters this
|
|
959
|
+
// is the singleton; for connection-backed adapters it's the current
|
|
960
|
+
// request/event-scoped connection from ALS. All repos below are built
|
|
961
|
+
// from this local `db` so a hook never queries a stale singleton socket.
|
|
962
|
+
const db = this.resolveDb();
|
|
963
|
+
const optionsRepo = new OptionsRepository(db);
|
|
964
|
+
|
|
909
965
|
// Always available
|
|
910
|
-
const kv = createKVAccess(
|
|
966
|
+
const kv = createKVAccess(optionsRepo, plugin.id);
|
|
911
967
|
const log = createLogAccess(plugin.id);
|
|
912
|
-
const storage = createStorageAccess(
|
|
968
|
+
const storage = createStorageAccess(db, plugin.id, plugin.storage);
|
|
913
969
|
|
|
914
970
|
// Capability-gated: content
|
|
915
971
|
// Note: capabilities reach this point already normalized to the
|
|
@@ -917,17 +973,33 @@ export class PluginContextFactory {
|
|
|
917
973
|
// names ("read:content", "write:content") never appear here.
|
|
918
974
|
let content: ContentAccess | ContentAccessWithWrite | undefined;
|
|
919
975
|
if (capabilities.has("content:write")) {
|
|
920
|
-
content = createContentAccessWithWrite(
|
|
976
|
+
content = createContentAccessWithWrite(db);
|
|
921
977
|
} else if (capabilities.has("content:read")) {
|
|
922
|
-
content = createContentAccess(
|
|
978
|
+
content = createContentAccess(db);
|
|
923
979
|
}
|
|
924
980
|
|
|
925
981
|
// Capability-gated: media
|
|
982
|
+
// `upload()` only needs `storage`; `getUploadUrl()` is derived from
|
|
983
|
+
// storage when no explicit provider is wired. Granting write access on
|
|
984
|
+
// either avoids silently degrading media:write to read-only — the bug
|
|
985
|
+
// where the runtime threads `storage` but not `getUploadUrl`.
|
|
926
986
|
let media: MediaAccess | MediaAccessWithWrite | undefined;
|
|
927
|
-
if (capabilities.has("media:write")
|
|
928
|
-
|
|
987
|
+
if (capabilities.has("media:write")) {
|
|
988
|
+
if (this.getUploadUrl || this.storage) {
|
|
989
|
+
media = createMediaAccessWithWrite(db, this.getUploadUrl, this.storage);
|
|
990
|
+
} else {
|
|
991
|
+
if (!this.warnedMissingMediaBackend.has(plugin.id)) {
|
|
992
|
+
this.warnedMissingMediaBackend.add(plugin.id);
|
|
993
|
+
log.warn(
|
|
994
|
+
"declares the media:write capability but no storage backend is configured; upload() is unavailable.",
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
if (capabilities.has("media:read")) {
|
|
998
|
+
media = createMediaAccess(db);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
929
1001
|
} else if (capabilities.has("media:read")) {
|
|
930
|
-
media = createMediaAccess(
|
|
1002
|
+
media = createMediaAccess(db);
|
|
931
1003
|
}
|
|
932
1004
|
|
|
933
1005
|
// Capability-gated: http
|
|
@@ -941,14 +1013,14 @@ export class PluginContextFactory {
|
|
|
941
1013
|
// Capability-gated: users
|
|
942
1014
|
let users: UserAccess | undefined;
|
|
943
1015
|
if (capabilities.has("users:read")) {
|
|
944
|
-
users = createUserAccess(
|
|
1016
|
+
users = createUserAccess(db);
|
|
945
1017
|
}
|
|
946
1018
|
|
|
947
|
-
// Cron access
|
|
1019
|
+
// Cron access — always available (scoped to plugin), but only if
|
|
948
1020
|
// the runtime provided a reschedule callback (i.e. cron is wired up).
|
|
949
1021
|
let cron: CronAccess | undefined;
|
|
950
1022
|
if (this.cronReschedule) {
|
|
951
|
-
cron = new CronAccessImpl(
|
|
1023
|
+
cron = new CronAccessImpl(db, plugin.id, this.cronReschedule);
|
|
952
1024
|
}
|
|
953
1025
|
|
|
954
1026
|
// Email access — requires email:send capability AND a configured provider
|
package/src/plugins/cron.ts
CHANGED
|
@@ -39,10 +39,26 @@ export type RescheduleFn = () => void;
|
|
|
39
39
|
* Stateless — all state lives in the database.
|
|
40
40
|
*/
|
|
41
41
|
export class CronExecutor {
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the database connection to use for this tick. A resolver (not a
|
|
44
|
+
* captured instance) so connection-backed adapters work across events: on
|
|
45
|
+
* Cloudflare the `scheduled()` handler installs an event-scoped connection
|
|
46
|
+
* in ALS, and this resolves to it instead of the per-isolate singleton
|
|
47
|
+
* whose socket belongs to an earlier request. Accepts a plain `Kysely` too
|
|
48
|
+
* (wrapped in a constant resolver) for callers/tests that don't need ALS.
|
|
49
|
+
*/
|
|
50
|
+
private readonly resolveDb: () => Kysely<Database>;
|
|
51
|
+
|
|
42
52
|
constructor(
|
|
43
|
-
|
|
53
|
+
db: Kysely<Database> | (() => Kysely<Database>),
|
|
44
54
|
private invokeCronHook: InvokeCronHookFn,
|
|
45
|
-
) {
|
|
55
|
+
) {
|
|
56
|
+
this.resolveDb = typeof db === "function" ? db : () => db;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private get db(): Kysely<Database> {
|
|
60
|
+
return this.resolveDb();
|
|
61
|
+
}
|
|
46
62
|
|
|
47
63
|
/**
|
|
48
64
|
* Process all overdue tasks.
|
package/src/plugins/routes.ts
CHANGED
|
@@ -12,6 +12,42 @@ import { PluginContextFactory, type PluginContextFactoryOptions } from "./contex
|
|
|
12
12
|
import { extractRequestMeta } from "./request-meta.js";
|
|
13
13
|
import type { ResolvedPlugin, RouteContext, PluginRoute } from "./types.js";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Body-reading methods on `Request`. EmDash parses the request body once before
|
|
17
|
+
* the handler runs and exposes the result as `ctx.input`, leaving the underlying
|
|
18
|
+
* stream consumed. Calling any of these on `ctx.request` would re-read a spent
|
|
19
|
+
* stream and throw an opaque platform error ("Body is unusable: Body has already
|
|
20
|
+
* been read") with no hint about `ctx.input` — so the guard replaces them with an
|
|
21
|
+
* actionable message instead (#1293).
|
|
22
|
+
*/
|
|
23
|
+
const CONSUMED_BODY_METHODS = new Set(["json", "text", "arrayBuffer", "blob", "formData", "bytes"]);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wrap the request handed to a plugin route handler so an accidental
|
|
27
|
+
* `ctx.request.json()` (or `.text()`, `.formData()`, …) fails with a message
|
|
28
|
+
* pointing at `ctx.input` rather than the runtime's cryptic "body already read"
|
|
29
|
+
* error. Every non-body member passes through unchanged; function members are
|
|
30
|
+
* bound to the underlying request so methods like `clone()` don't throw an
|
|
31
|
+
* "Illegal invocation" when called on the proxy.
|
|
32
|
+
*/
|
|
33
|
+
function guardConsumedRequestBody(request: Request): Request {
|
|
34
|
+
return new Proxy(request, {
|
|
35
|
+
get(target, prop) {
|
|
36
|
+
if (typeof prop === "string" && CONSUMED_BODY_METHODS.has(prop)) {
|
|
37
|
+
return () => {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`[emdash] ctx.request.${prop}() is not available inside a plugin route handler: ` +
|
|
40
|
+
`EmDash has already parsed the request body and exposes it as ctx.input. ` +
|
|
41
|
+
`Read ctx.input instead of ctx.request.${prop}().`,
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const value = Reflect.get(target, prop, target);
|
|
46
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
15
51
|
/**
|
|
16
52
|
* Route metadata (public flag) without the handler.
|
|
17
53
|
* Used by the catch-all route to decide auth before dispatch.
|
|
@@ -100,7 +136,10 @@ export class PluginRouteHandler {
|
|
|
100
136
|
const routeContext: RouteContext = {
|
|
101
137
|
...baseContext,
|
|
102
138
|
input: validatedInput,
|
|
103
|
-
|
|
139
|
+
// The body is already parsed into `input`; guard `ctx.request`'s
|
|
140
|
+
// body-reading methods so a re-read fails with an actionable message
|
|
141
|
+
// (#1293). Metadata extraction uses the original request (headers only).
|
|
142
|
+
request: guardConsumedRequestBody(options.request),
|
|
104
143
|
requestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders),
|
|
105
144
|
};
|
|
106
145
|
|
package/src/query.ts
CHANGED
|
@@ -26,7 +26,13 @@
|
|
|
26
26
|
|
|
27
27
|
import { encodeCursor } from "./database/repositories/types.js";
|
|
28
28
|
import { getFallbackChain, getI18nConfig, isI18nEnabled } from "./i18n/config.js";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
CURSOR_RAW_VALUES,
|
|
31
|
+
FOLDED_BYLINES,
|
|
32
|
+
FOLDED_TERMS,
|
|
33
|
+
type WhereRange,
|
|
34
|
+
type WhereValue,
|
|
35
|
+
} from "./loader.js";
|
|
30
36
|
import {
|
|
31
37
|
cachedQuery,
|
|
32
38
|
contentNamespaces,
|
|
@@ -34,6 +40,7 @@ import {
|
|
|
34
40
|
} from "./object-cache/index.js";
|
|
35
41
|
import { requestCached } from "./request-cache.js";
|
|
36
42
|
import { getRequestContext } from "./request-context.js";
|
|
43
|
+
import type { TaxonomyTerm } from "./taxonomies/types.js";
|
|
37
44
|
import { isMissingTableError } from "./utils/db-errors.js";
|
|
38
45
|
import {
|
|
39
46
|
createEditable,
|
|
@@ -930,6 +937,67 @@ interface CachedEntryValue {
|
|
|
930
937
|
async function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]): Promise<void> {
|
|
931
938
|
if (entries.length === 0) return;
|
|
932
939
|
|
|
940
|
+
// Fast path: bylines were folded into the content query. Parse the JSON
|
|
941
|
+
// (no extra round trip) for the common case — explicit credits, no byline
|
|
942
|
+
// custom fields, no author fallback. The query path below handles the rest:
|
|
943
|
+
// - author fallback (entry has authorId but no explicit credit), and
|
|
944
|
+
// - custom byline fields (can't be expressed in the folded subquery).
|
|
945
|
+
if (entries.every((e) => FOLDED_BYLINES in entryData(e))) {
|
|
946
|
+
const parsed = entries.map((entry) => {
|
|
947
|
+
const data = entryData(entry);
|
|
948
|
+
const folded = Reflect.get(data, FOLDED_BYLINES);
|
|
949
|
+
const rows = Array.isArray(folded) ? folded : [];
|
|
950
|
+
const credits = rows
|
|
951
|
+
.map((raw) => {
|
|
952
|
+
const b = raw?.byline ?? {};
|
|
953
|
+
return {
|
|
954
|
+
roleLabel: raw?.roleLabel ?? null,
|
|
955
|
+
sortOrder: Number(raw?.sortOrder ?? 0),
|
|
956
|
+
source: "explicit" as const,
|
|
957
|
+
byline: { ...b, isGuest: Boolean(b.isGuest), customFields: {} },
|
|
958
|
+
};
|
|
959
|
+
})
|
|
960
|
+
.toSorted((a, b) => a.sortOrder - b.sortOrder);
|
|
961
|
+
return { data, credits };
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// Fall back to the full query path when the fold can't be trusted to be
|
|
965
|
+
// complete: an entry with a byline reference (explicit primary, or an
|
|
966
|
+
// author for the author-fallback) but no folded credits — e.g. a credit
|
|
967
|
+
// in a different locale than the row, which the locale-correlated subquery
|
|
968
|
+
// skips, or the author-fallback path which the fold doesn't express.
|
|
969
|
+
let needsQueryPath = parsed.some(
|
|
970
|
+
(p) =>
|
|
971
|
+
p.credits.length === 0 &&
|
|
972
|
+
(dataStr(p.data, "authorId") !== "" || dataStr(p.data, "primaryBylineId") !== ""),
|
|
973
|
+
);
|
|
974
|
+
let hasCustomFields = false;
|
|
975
|
+
if (!needsQueryPath) {
|
|
976
|
+
try {
|
|
977
|
+
const { getDb } = await import("./loader.js");
|
|
978
|
+
const db = await getDb();
|
|
979
|
+
const { getBylineFieldDefs } = await import("./bylines/field-defs-cache.js");
|
|
980
|
+
hasCustomFields = (await getBylineFieldDefs(db)).length > 0;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
// A missing table is expected pre-migration and means there are no
|
|
983
|
+
// custom fields — the fold's values are complete. Any other error
|
|
984
|
+
// (lock-init failure, dialect error) means the probe can't be
|
|
985
|
+
// trusted, so fall back to the query path rather than risk serving
|
|
986
|
+
// folded bylines with empty customFields.
|
|
987
|
+
if (!isMissingTableError(error)) needsQueryPath = true;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (!needsQueryPath && !hasCustomFields) {
|
|
992
|
+
for (const p of parsed) {
|
|
993
|
+
p.data.bylines = p.credits;
|
|
994
|
+
p.data.byline = p.credits[0]?.byline ?? null;
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
// Fall through to the full query path for fallback / custom-field cases.
|
|
999
|
+
}
|
|
1000
|
+
|
|
933
1001
|
try {
|
|
934
1002
|
const { getBylinesForEntries } = await import("./bylines/index.js");
|
|
935
1003
|
|
|
@@ -1005,6 +1073,45 @@ async function hydrateEntryTerms<D>(
|
|
|
1005
1073
|
): Promise<void> {
|
|
1006
1074
|
if (entries.length === 0) return;
|
|
1007
1075
|
|
|
1076
|
+
// Fast path: terms were folded into the content query. Group the JSON and
|
|
1077
|
+
// skip the separate content_taxonomies query.
|
|
1078
|
+
if (entries.every((e) => FOLDED_TERMS in entryData(e))) {
|
|
1079
|
+
const perEntry: Array<{ entryId: string; byTaxonomy: Record<string, TaxonomyTerm[]> }> = [];
|
|
1080
|
+
for (const entry of entries) {
|
|
1081
|
+
const data = entryData(entry);
|
|
1082
|
+
const folded = Reflect.get(data, FOLDED_TERMS);
|
|
1083
|
+
const rows = Array.isArray(folded) ? folded : [];
|
|
1084
|
+
const grouped: Record<string, TaxonomyTerm[]> = {};
|
|
1085
|
+
for (const r of rows) {
|
|
1086
|
+
const name = String(r?.name);
|
|
1087
|
+
(grouped[name] ??= []).push({
|
|
1088
|
+
id: r?.id,
|
|
1089
|
+
name,
|
|
1090
|
+
slug: r?.slug,
|
|
1091
|
+
label: r?.label,
|
|
1092
|
+
parentId: r?.parent_id ?? undefined,
|
|
1093
|
+
children: [],
|
|
1094
|
+
locale: r?.locale,
|
|
1095
|
+
translationGroup: r?.translation_group,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
// Match getAllTermsForEntries' ORDER BY label (dropped from the
|
|
1099
|
+
// aggregate since SQLite and Postgres order it differently).
|
|
1100
|
+
for (const [name, arr] of Object.entries(grouped)) {
|
|
1101
|
+
grouped[name] = arr.toSorted((a, b) => String(a.label).localeCompare(String(b.label)));
|
|
1102
|
+
}
|
|
1103
|
+
data.terms = grouped;
|
|
1104
|
+
const entryId = dataStr(data, "id");
|
|
1105
|
+
if (entryId) perEntry.push({ entryId, byTaxonomy: grouped });
|
|
1106
|
+
}
|
|
1107
|
+
// Prime the per-entry request cache (wildcard + present taxonomies) so
|
|
1108
|
+
// subsequent getEntryTerms(...) calls in this render hit the cache instead
|
|
1109
|
+
// of issuing an N+1 query. No DB lookup — purely from the folded data.
|
|
1110
|
+
const { primeFoldedEntryTerms } = await import("./taxonomies/index.js");
|
|
1111
|
+
primeFoldedEntryTerms(type, perEntry, { locale });
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1008
1115
|
try {
|
|
1009
1116
|
const { getAllTermsForEntries } = await import("./taxonomies/index.js");
|
|
1010
1117
|
|