emdash 0.17.2 → 0.19.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/api/route-utils.d.mts +2 -2
- package/dist/api/route-utils.mjs +14 -14
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-B7GATEYo.mjs → api-BZ6bhjYs.mjs} +88 -16
- package/dist/api-BZ6bhjYs.mjs.map +1 -0
- package/dist/{apply-BrVqULFe.mjs → apply-hQkKKBCf.mjs} +23 -23
- package/dist/apply-hQkKKBCf.mjs.map +1 -0
- package/dist/astro/index.d.mts +8 -8
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +113 -23
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +7 -7
- package/dist/astro/middleware/auth.mjs +2 -2
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs +2 -2
- package/dist/astro/middleware.d.mts +26 -4
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +414 -215
- 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 +2 -2
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
- 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/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 +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +30 -30
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/updates.mjs +30 -30
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +30 -30
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +30 -30
- 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 +6 -6
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- 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 +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
- 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 +6 -6
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
- package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
- 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 +7 -7
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +2 -2
- package/dist/astro/routes/api/import/probe.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
- package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +28 -28
- 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 +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 +6 -6
- package/dist/astro/routes/api/media.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_.mjs +7 -7
- package/dist/astro/routes/api/menus/index.mjs +7 -7
- package/dist/astro/routes/api/oauth/authorize.mjs +1 -1
- package/dist/astro/routes/api/oauth/device/authorize.mjs +4 -4
- package/dist/astro/routes/api/oauth/device/code.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/token.mjs +5 -5
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/revoke.mjs +4 -4
- package/dist/astro/routes/api/oauth/token.mjs +4 -4
- package/dist/astro/routes/api/openapi.json.mjs +17 -3
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
- package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
- package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
- package/dist/astro/routes/api/redirects/index.mjs +10 -10
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +30 -30
- package/dist/astro/routes/api/schema/collections/index.mjs +30 -30
- package/dist/astro/routes/api/schema/index.mjs +6 -6
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +30 -30
- package/dist/astro/routes/api/schema/orphans/index.mjs +30 -30
- 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 +6 -6
- 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 +5 -5
- package/dist/astro/routes/api/settings.mjs +12 -12
- package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
- package/dist/astro/routes/api/setup/admin.mjs +6 -6
- package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +21 -21
- 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 +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- 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 +5 -5
- 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 +6 -6
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +6 -6
- package/dist/astro/routes/sitemap.xml.mjs +6 -6
- package/dist/astro/types.d.mts +15 -8
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{authorize-CLTmOUyx.mjs → authorize-C_8t2KGa.mjs} +2 -2
- package/dist/{authorize-CLTmOUyx.mjs.map → authorize-C_8t2KGa.mjs.map} +1 -1
- package/dist/{byline-CAhk4FrG.mjs → byline-DUx48sJp.mjs} +6 -6
- package/dist/{byline-CAhk4FrG.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
- package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-51kg6Vuv.mjs} +3 -3
- package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-51kg6Vuv.mjs.map} +1 -1
- package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields-C_OsR-KF.mjs} +2 -2
- package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields-C_OsR-KF.mjs.map} +1 -1
- package/dist/{byline-fields-CR5hGLMw.d.mts → byline-fields-DYXKDuNX.d.mts} +53 -29
- package/dist/byline-fields-DYXKDuNX.d.mts.map +1 -0
- package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
- package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
- package/dist/{bylines-CbrD7STW.mjs → bylines-Cx5n-WqP.mjs} +3 -3
- package/dist/{bylines-CbrD7STW.mjs.map → bylines-Cx5n-WqP.mjs.map} +1 -1
- package/dist/{bylines-DCczH3AV.mjs → bylines-wurS258E.mjs} +50 -6
- package/dist/{bylines-DCczH3AV.mjs.map → bylines-wurS258E.mjs.map} +1 -1
- package/dist/{cache-DIHHyPkt.mjs → cache-B_HzASVT.mjs} +3 -3
- package/dist/{cache-DIHHyPkt.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
- package/dist/{chunks-DnnHlRG3.mjs → chunks-BerYVuve.mjs} +2 -2
- package/dist/{chunks-DnnHlRG3.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
- package/dist/cli/index.mjs +40 -27
- 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/{comment-DkAfGX9E.mjs → comment-sqQxNpN3.mjs} +2 -2
- package/dist/{comment-DkAfGX9E.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
- package/dist/{comments-DLFnXs7J.mjs → comments-CJ0RZsYR.mjs} +3 -3
- package/dist/{comments-DLFnXs7J.mjs.map → comments-CJ0RZsYR.mjs.map} +1 -1
- package/dist/{content-C7aJ7keg.mjs → content-BIlVx-RX.mjs} +132 -43
- package/dist/content-BIlVx-RX.mjs.map +1 -0
- package/dist/{context-Ca0HkaIh.mjs → context-GG52SPgh.mjs} +10 -10
- package/dist/{context-Ca0HkaIh.mjs.map → context-GG52SPgh.mjs.map} +1 -1
- package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
- package/dist/cron-BJ2ClIlj.mjs.map +1 -0
- package/dist/{dashboard-BrfLIsX1.mjs → dashboard-2JgAMWxK.mjs} +4 -4
- package/dist/{dashboard-BrfLIsX1.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +1 -1
- package/dist/{device-flow-ptLrVINd.mjs → device-flow-s6_q3T7A.mjs} +2 -2
- package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-s6_q3T7A.mjs.map} +1 -1
- package/dist/{error-Bk9s3Ism.mjs → error-RwM4dD35.mjs} +2 -2
- package/dist/{error-Bk9s3Ism.mjs.map → error-RwM4dD35.mjs.map} +1 -1
- package/dist/{fts-manager-XpDfbIKo.mjs → fts-manager-1RgHmopc.mjs} +2 -2
- package/dist/{fts-manager-XpDfbIKo.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
- package/dist/{index-D60_SzHG.d.mts → index-BpYeJO1E.d.mts} +2 -2
- package/dist/{index-D60_SzHG.d.mts.map → index-BpYeJO1E.d.mts.map} +1 -1
- package/dist/{index-C8ciqSMJ.d.mts → index-FfiTQJq2.d.mts} +202 -20
- package/dist/index-FfiTQJq2.d.mts.map +1 -0
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +43 -43
- package/dist/{load-CF5oETkh.mjs → load-B84ohfBk.mjs} +2 -2
- package/dist/{load-CF5oETkh.mjs.map → load-B84ohfBk.mjs.map} +1 -1
- package/dist/{loader-BxyvbrZP.mjs → loader-CpZKpFz0.mjs} +32 -30
- package/dist/loader-CpZKpFz0.mjs.map +1 -0
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +6 -6
- package/dist/{media-Cyz5BhSN.mjs → media-JOf3pNkw.mjs} +2 -2
- package/dist/{media-Cyz5BhSN.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
- package/dist/{menus-PFp8FDuO.mjs → menus-DX4_E01q.mjs} +3 -3
- package/dist/{menus-PFp8FDuO.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
- package/dist/{menus-CIdZ_Q6U.mjs → menus-Dp9xporj.mjs} +112 -16
- package/dist/menus-Dp9xporj.mjs.map +1 -0
- package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
- package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
- package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-1aPAYjiC.mjs} +2 -2
- package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-1aPAYjiC.mjs.map} +1 -1
- package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
- package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
- package/dist/{options-tb7DJROi.d.mts → options-D4MnavW_.d.mts} +3 -3
- package/dist/{options-tb7DJROi.d.mts.map → options-D4MnavW_.d.mts.map} +1 -1
- package/dist/{parse-B-K21lvm.mjs → parse-CrGndy1A.mjs} +2 -2
- package/dist/{parse-B-K21lvm.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
- package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
- package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
- package/dist/plugin-utils.d.mts +7 -7
- package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
- package/dist/{query-Cc649nDl.mjs → query-BFQ029Ts.mjs} +21 -15
- package/dist/query-BFQ029Ts.mjs.map +1 -0
- package/dist/{rate-limit-BI1OdpQH.mjs → rate-limit-ClFFUga6.mjs} +2 -2
- package/dist/{rate-limit-BI1OdpQH.mjs.map → rate-limit-ClFFUga6.mjs.map} +1 -1
- package/dist/{redirect-C-FeA4j9.mjs → redirect-CRWIt8Zj.mjs} +3 -3
- package/dist/{redirect-C-FeA4j9.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
- package/dist/{redirects-C0L9JUk4.mjs → redirects-DEygMrRO.mjs} +25 -3
- package/dist/redirects-DEygMrRO.mjs.map +1 -0
- package/dist/{redirects-C1UgU9E0.mjs → redirects-OIu6vQ2i.mjs} +5 -5
- package/dist/{redirects-C1UgU9E0.mjs.map → redirects-OIu6vQ2i.mjs.map} +1 -1
- package/dist/{registry-C-T_PWgp.mjs → registry-brYh-rAT.mjs} +6 -6
- package/dist/{registry-C-T_PWgp.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
- package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
- package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
- package/dist/{runner-BiuUfx-V.mjs → runner--4wMWwKM.mjs} +224 -168
- package/dist/runner--4wMWwKM.mjs.map +1 -0
- package/dist/{runner-DM1yR5qd.d.mts → runner-BcRuXq_h.d.mts} +2 -2
- package/dist/{runner-DM1yR5qd.d.mts.map → runner-BcRuXq_h.d.mts.map} +1 -1
- package/dist/runtime.d.mts +7 -7
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-BpCJh2lU.mjs → schema-CS7Eg5gh.mjs} +5 -5
- package/dist/{schema-BpCJh2lU.mjs.map → schema-CS7Eg5gh.mjs.map} +1 -1
- package/dist/{search-BrF7k0Ho.mjs → search-o-aQzHI1.mjs} +4 -4
- package/dist/{search-BrF7k0Ho.mjs.map → search-o-aQzHI1.mjs.map} +1 -1
- package/dist/{secrets-YYbTgB1w.mjs → secrets-C_ZtRos3.mjs} +2 -2
- package/dist/{secrets-YYbTgB1w.mjs.map → secrets-C_ZtRos3.mjs.map} +1 -1
- package/dist/{sections-8DEa-dWt.mjs → sections-DhsZ0ns9.mjs} +3 -3
- package/dist/{sections-8DEa-dWt.mjs.map → sections-DhsZ0ns9.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +16 -16
- package/dist/seo/index.d.mts +1 -1
- package/dist/{seo-CKr7pLfA.mjs → seo-B5e6y9Wk.mjs} +2 -2
- package/dist/{seo-CKr7pLfA.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
- package/dist/{service-9P2cdyR_.mjs → service-DAxg8RPR.mjs} +2 -2
- package/dist/{service-9P2cdyR_.mjs.map → service-DAxg8RPR.mjs.map} +1 -1
- package/dist/{settings-Jro4YcUb.mjs → settings-B1p-gPUK.mjs} +5 -5
- package/dist/{settings-Jro4YcUb.mjs.map → settings-B1p-gPUK.mjs.map} +1 -1
- package/dist/{settings-DYVzINdn.mjs → settings-DIsbHTRE.mjs} +3 -3
- package/dist/{settings-DYVzINdn.mjs.map → settings-DIsbHTRE.mjs.map} +1 -1
- package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-Yuv78yua.mjs} +2 -2
- package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-Yuv78yua.mjs.map} +1 -1
- package/dist/{site-url-Cm8-sJy7.mjs → site-url-mEVmwIFi.mjs} +2 -2
- package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-mEVmwIFi.mjs.map} +1 -1
- package/dist/{taxonomies-CGD6y79Q.mjs → taxonomies-BEW7S5AI.mjs} +10 -8
- package/dist/taxonomies-BEW7S5AI.mjs.map +1 -0
- package/dist/{taxonomies-C0bVme_m.mjs → taxonomies-UusDXv3C.mjs} +4 -4
- package/dist/{taxonomies-C0bVme_m.mjs.map → taxonomies-UusDXv3C.mjs.map} +1 -1
- package/dist/{taxonomy-Db5xwphL.mjs → taxonomy-CdllE4oq.mjs} +3 -3
- package/dist/{taxonomy-Db5xwphL.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts → transport-BwQeeY2p.d.mts} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts.map → transport-BwQeeY2p.d.mts.map} +1 -1
- package/dist/{types-CfyYQ7eY.mjs → types-BXSUSAjt.mjs} +16 -3
- package/dist/{types-CfyYQ7eY.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
- package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
- package/dist/{types-D8bhH891.mjs.map → types-DZk_y-MU.mjs.map} +1 -1
- package/dist/{types-DawhLFwy.d.mts → types-OT_Es5mp.d.mts} +26 -1
- package/dist/{types-DawhLFwy.d.mts.map → types-OT_Es5mp.d.mts.map} +1 -1
- package/dist/{types-i8_uzhMD.d.mts → types-WVmpZBJV.d.mts} +18 -3
- package/dist/types-WVmpZBJV.d.mts.map +1 -0
- package/dist/{user-tLdHUEXV.mjs → user-C0um7wrg.mjs} +18 -2
- package/dist/user-C0um7wrg.mjs.map +1 -0
- package/dist/{validate-Dy6nkNls.d.mts → validate-BPAHUSge.d.mts} +10 -2
- package/dist/validate-BPAHUSge.d.mts.map +1 -0
- package/dist/{validate-DWmnRg6E.mjs → validate-ZP9Dvg0P.mjs} +6 -3
- package/dist/validate-ZP9Dvg0P.mjs.map +1 -0
- package/dist/{validation-BQ_TP-On.mjs → validation-CE5i4q0c.mjs} +5 -5
- package/dist/{validation-BQ_TP-On.mjs.map → validation-CE5i4q0c.mjs.map} +1 -1
- package/dist/version-Dw0JXu45.mjs +7 -0
- package/dist/{version-CgcnMvqS.mjs.map → version-Dw0JXu45.mjs.map} +1 -1
- package/dist/{widgets-DzlINGI6.mjs → widgets-ClEnYQCH.mjs} +2 -2
- package/dist/{widgets-DzlINGI6.mjs.map → widgets-ClEnYQCH.mjs.map} +1 -1
- package/dist/{zod-generator-MMm56Prt.mjs → zod-generator-Djo_VHCt.mjs} +4 -3
- package/dist/zod-generator-Djo_VHCt.mjs.map +1 -0
- package/package.json +7 -7
- package/src/api/handlers/content.ts +107 -8
- package/src/api/handlers/index.ts +2 -0
- package/src/api/openapi/document.ts +25 -0
- package/src/api/schemas/content.ts +33 -0
- package/src/astro/integration/index.ts +98 -0
- package/src/astro/integration/routes.ts +6 -0
- package/src/astro/integration/virtual-modules.ts +39 -0
- package/src/astro/integration/vite-config.ts +12 -0
- package/src/astro/middleware/stream-end-metrics.ts +96 -0
- package/src/astro/middleware.ts +107 -31
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
- package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
- package/src/astro/types.ts +8 -1
- package/src/bylines/index.ts +57 -0
- package/src/cli/commands/export-seed.ts +28 -12
- package/src/components/EmDashImage.astro +23 -4
- package/src/components/Image.astro +20 -3
- package/src/database/migrations/043_content_references.ts +121 -0
- package/src/database/migrations/runner.ts +9 -2
- package/src/database/repositories/content.ts +225 -67
- package/src/database/repositories/index.ts +7 -0
- package/src/database/repositories/relation.ts +467 -0
- package/src/database/repositories/types.ts +31 -0
- package/src/database/repositories/user.ts +18 -0
- package/src/database/types.ts +34 -0
- package/src/emdash-runtime.ts +318 -168
- package/src/index.ts +8 -1
- package/src/loader.ts +67 -34
- package/src/media/responsive.ts +125 -0
- package/src/menus/index.ts +27 -9
- package/src/plugins/cron.ts +3 -2
- package/src/plugins/hooks.ts +35 -6
- package/src/plugins/index.ts +5 -0
- package/src/plugins/manager.ts +1 -0
- package/src/plugins/scheduler/node.ts +9 -2
- package/src/query.ts +32 -5
- package/src/scheduled-publish.ts +153 -0
- package/src/schema/zod-generator.ts +6 -2
- package/src/seed/apply.ts +16 -6
- package/src/seed/types.ts +9 -0
- package/src/seed/validate.ts +15 -0
- package/src/taxonomies/index.ts +13 -8
- package/src/utils/init-lock.ts +143 -0
- package/src/virtual-modules.d.ts +11 -0
- package/dist/api-B7GATEYo.mjs.map +0 -1
- package/dist/apply-BrVqULFe.mjs.map +0 -1
- package/dist/byline-fields-CR5hGLMw.d.mts.map +0 -1
- package/dist/content-C7aJ7keg.mjs.map +0 -1
- package/dist/cron-DZovZUnC.mjs.map +0 -1
- package/dist/index-C8ciqSMJ.d.mts.map +0 -1
- package/dist/loader-BxyvbrZP.mjs.map +0 -1
- package/dist/menus-CIdZ_Q6U.mjs.map +0 -1
- package/dist/query-Cc649nDl.mjs.map +0 -1
- package/dist/redirects-C0L9JUk4.mjs.map +0 -1
- package/dist/runner-BiuUfx-V.mjs.map +0 -1
- package/dist/taxonomies-CGD6y79Q.mjs.map +0 -1
- package/dist/types-i8_uzhMD.d.mts.map +0 -1
- package/dist/user-tLdHUEXV.mjs.map +0 -1
- package/dist/validate-DWmnRg6E.mjs.map +0 -1
- package/dist/validate-Dy6nkNls.d.mts.map +0 -1
- package/dist/version-CgcnMvqS.mjs +0 -7
- package/dist/zod-generator-MMm56Prt.mjs.map +0 -1
- package/src/plugins/scheduler/piggyback.ts +0 -71
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
|
|
3
|
+
import { getI18nConfig } from "../../i18n/config.js";
|
|
4
|
+
import { currentTimestamp } from "../dialect-helpers.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Content references.
|
|
8
|
+
*
|
|
9
|
+
* `_emdash_relations` defines relationship types (row-per-locale, mirroring
|
|
10
|
+
* `_emdash_taxonomy_defs`): which collection is the parent, which is the child
|
|
11
|
+
* (the side that may multiply), and localized labels for each role.
|
|
12
|
+
*
|
|
13
|
+
* `_emdash_content_references` holds directed `parent → child` edges between
|
|
14
|
+
* content entries. Both endpoints and the relation are referenced by
|
|
15
|
+
* `translation_group`, so edges are locale-agnostic. As with
|
|
16
|
+
* `content_taxonomies`, group-linking precludes SQL foreign keys; referential
|
|
17
|
+
* cleanup is an application-layer concern.
|
|
18
|
+
*
|
|
19
|
+
* Idempotency: every `CREATE TABLE` and `CREATE INDEX` uses `.ifNotExists()`,
|
|
20
|
+
* so a partial prior run (a crash mid-migration, or a retry after the runner's
|
|
21
|
+
* race-recovery path) re-applies cleanly — including any indexes that landed in
|
|
22
|
+
* the failed pass after their table.
|
|
23
|
+
*/
|
|
24
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
25
|
+
const defaultLocale = getI18nConfig()?.defaultLocale ?? "en";
|
|
26
|
+
|
|
27
|
+
await db.schema
|
|
28
|
+
.createTable("_emdash_relations")
|
|
29
|
+
.ifNotExists()
|
|
30
|
+
.addColumn("id", "text", (c) => c.primaryKey())
|
|
31
|
+
.addColumn("name", "text", (c) => c.notNull())
|
|
32
|
+
.addColumn("parent_collection", "text", (c) => c.notNull())
|
|
33
|
+
.addColumn("child_collection", "text", (c) => c.notNull())
|
|
34
|
+
.addColumn("parent_label", "text", (c) => c.notNull())
|
|
35
|
+
.addColumn("child_label", "text", (c) => c.notNull())
|
|
36
|
+
.addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale))
|
|
37
|
+
.addColumn("translation_group", "text", (c) => c.notNull())
|
|
38
|
+
.addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
|
|
39
|
+
.addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
|
|
40
|
+
.addUniqueConstraint("_emdash_relations_name_locale_unique", ["name", "locale"])
|
|
41
|
+
.execute();
|
|
42
|
+
|
|
43
|
+
await db.schema
|
|
44
|
+
.createIndex("idx__emdash_relations_locale")
|
|
45
|
+
.ifNotExists()
|
|
46
|
+
.on("_emdash_relations")
|
|
47
|
+
.column("locale")
|
|
48
|
+
.execute();
|
|
49
|
+
await db.schema
|
|
50
|
+
.createIndex("idx__emdash_relations_translation_group")
|
|
51
|
+
.ifNotExists()
|
|
52
|
+
.on("_emdash_relations")
|
|
53
|
+
.column("translation_group")
|
|
54
|
+
.execute();
|
|
55
|
+
await db.schema
|
|
56
|
+
.createIndex("idx__emdash_relations_parent_collection")
|
|
57
|
+
.ifNotExists()
|
|
58
|
+
.on("_emdash_relations")
|
|
59
|
+
.column("parent_collection")
|
|
60
|
+
.execute();
|
|
61
|
+
await db.schema
|
|
62
|
+
.createIndex("idx__emdash_relations_child_collection")
|
|
63
|
+
.ifNotExists()
|
|
64
|
+
.on("_emdash_relations")
|
|
65
|
+
.column("child_collection")
|
|
66
|
+
.execute();
|
|
67
|
+
|
|
68
|
+
// One row per (translation_group, locale): the row-per-locale model wants a
|
|
69
|
+
// relation to have at most one variant per locale. Migration 040 enforces
|
|
70
|
+
// the same invariant for `_emdash_bylines` with a *partial* unique
|
|
71
|
+
// (`WHERE translation_group IS NOT NULL`) only because it back-fills an
|
|
72
|
+
// existing table; this table is new and `translation_group` is `NOT NULL`,
|
|
73
|
+
// so a plain unique index suffices.
|
|
74
|
+
await db.schema
|
|
75
|
+
.createIndex("idx__emdash_relations_group_locale_unique")
|
|
76
|
+
.ifNotExists()
|
|
77
|
+
.unique()
|
|
78
|
+
.on("_emdash_relations")
|
|
79
|
+
.columns(["translation_group", "locale"])
|
|
80
|
+
.execute();
|
|
81
|
+
|
|
82
|
+
await db.schema
|
|
83
|
+
.createTable("_emdash_content_references")
|
|
84
|
+
.ifNotExists()
|
|
85
|
+
.addColumn("id", "text", (c) => c.primaryKey())
|
|
86
|
+
.addColumn("relation_group", "text", (c) => c.notNull())
|
|
87
|
+
.addColumn("parent_group", "text", (c) => c.notNull())
|
|
88
|
+
.addColumn("child_group", "text", (c) => c.notNull())
|
|
89
|
+
.addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0))
|
|
90
|
+
.addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
|
|
91
|
+
.addUniqueConstraint("content_references_unique", [
|
|
92
|
+
"relation_group",
|
|
93
|
+
"parent_group",
|
|
94
|
+
"child_group",
|
|
95
|
+
])
|
|
96
|
+
.execute();
|
|
97
|
+
|
|
98
|
+
await db.schema
|
|
99
|
+
.createIndex("idx__emdash_content_references_parent")
|
|
100
|
+
.ifNotExists()
|
|
101
|
+
.on("_emdash_content_references")
|
|
102
|
+
.columns(["parent_group", "relation_group", "sort_order"])
|
|
103
|
+
.execute();
|
|
104
|
+
await db.schema
|
|
105
|
+
.createIndex("idx__emdash_content_references_child")
|
|
106
|
+
.ifNotExists()
|
|
107
|
+
.on("_emdash_content_references")
|
|
108
|
+
.columns(["child_group", "relation_group"])
|
|
109
|
+
.execute();
|
|
110
|
+
await db.schema
|
|
111
|
+
.createIndex("idx__emdash_content_references_relation")
|
|
112
|
+
.ifNotExists()
|
|
113
|
+
.on("_emdash_content_references")
|
|
114
|
+
.column("relation_group")
|
|
115
|
+
.execute();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
119
|
+
await db.schema.dropTable("_emdash_content_references").ifExists().execute();
|
|
120
|
+
await db.schema.dropTable("_emdash_relations").ifExists().execute();
|
|
121
|
+
}
|
|
@@ -44,6 +44,7 @@ import * as m039 from "./039_fix_fts5_triggers.js";
|
|
|
44
44
|
import * as m040 from "./040_byline_i18n.js";
|
|
45
45
|
import * as m041 from "./041_content_locale_list_index.js";
|
|
46
46
|
import * as m042 from "./042_byline_fields.js";
|
|
47
|
+
import * as m043 from "./043_content_references.js";
|
|
47
48
|
|
|
48
49
|
const MIGRATIONS: Readonly<Record<string, Migration>> = Object.freeze({
|
|
49
50
|
"001_initial": m001,
|
|
@@ -87,6 +88,7 @@ const MIGRATIONS: Readonly<Record<string, Migration>> = Object.freeze({
|
|
|
87
88
|
"040_byline_i18n": m040,
|
|
88
89
|
"041_content_locale_list_index": m041,
|
|
89
90
|
"042_byline_fields": m042,
|
|
91
|
+
"043_content_references": m043,
|
|
90
92
|
});
|
|
91
93
|
|
|
92
94
|
/** Total number of registered migrations. Exported for use in tests. */
|
|
@@ -178,8 +180,13 @@ const MIGRATION_RACE_PATTERN = new RegExp(
|
|
|
178
180
|
"i",
|
|
179
181
|
);
|
|
180
182
|
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
+
/**
|
|
184
|
+
* How long to wait for a concurrent migrator to finish before giving up.
|
|
185
|
+
* Exported because the db init lock's reclaim deadline must comfortably
|
|
186
|
+
* exceed it (see DB_INIT_DEADLINE_MS in emdash-runtime.ts) — a healthy
|
|
187
|
+
* init can legitimately block this long inside waitForConcurrentMigrator.
|
|
188
|
+
*/
|
|
189
|
+
export const MIGRATION_RACE_WAIT_MS = 10_000;
|
|
183
190
|
/** Polling interval while waiting for a concurrent migrator. */
|
|
184
191
|
const MIGRATION_RACE_POLL_MS = 100;
|
|
185
192
|
|
|
@@ -11,8 +11,14 @@ import type {
|
|
|
11
11
|
FindManyOptions,
|
|
12
12
|
FindManyResult,
|
|
13
13
|
ContentItem,
|
|
14
|
+
ContentDateField,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
import {
|
|
17
|
+
EmDashValidationError,
|
|
18
|
+
ScheduledNotDueError,
|
|
19
|
+
encodeCursor,
|
|
20
|
+
decodeCursor,
|
|
14
21
|
} from "./types.js";
|
|
15
|
-
import { EmDashValidationError, encodeCursor, decodeCursor } from "./types.js";
|
|
16
22
|
|
|
17
23
|
// Regex pattern for ULID validation
|
|
18
24
|
const ULID_PATTERN = /^[0-9A-Z]{26}$/;
|
|
@@ -20,6 +26,18 @@ const ULID_PATTERN = /^[0-9A-Z]{26}$/;
|
|
|
20
26
|
// LIKE wildcards that must be escaped so user search input is matched literally.
|
|
21
27
|
const LIKE_WILDCARD_RE = /[\\%_]/g;
|
|
22
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Whitelist mapping a public date-filter field to its physical column. Keeping
|
|
31
|
+
* this separate from `mapOrderField` makes the filterable set explicit and
|
|
32
|
+
* prevents filtering on arbitrary columns.
|
|
33
|
+
*/
|
|
34
|
+
const DATE_FILTER_COLUMNS: Record<ContentDateField, "created_at" | "updated_at" | "published_at"> =
|
|
35
|
+
{
|
|
36
|
+
createdAt: "created_at",
|
|
37
|
+
updatedAt: "updated_at",
|
|
38
|
+
publishedAt: "published_at",
|
|
39
|
+
};
|
|
40
|
+
|
|
23
41
|
/**
|
|
24
42
|
* System columns that exist in every ec_* table
|
|
25
43
|
*/
|
|
@@ -493,6 +511,7 @@ export class ContentRepository {
|
|
|
493
511
|
}
|
|
494
512
|
|
|
495
513
|
query = this.applySearchFilter(query, options.where);
|
|
514
|
+
query = this.applyDateFilter(query, options.where);
|
|
496
515
|
|
|
497
516
|
// Handle cursor pagination — decodeCursor throws InvalidCursorError
|
|
498
517
|
// on malformed input; let it propagate so handlers surface a
|
|
@@ -785,19 +804,35 @@ export class ContentRepository {
|
|
|
785
804
|
);
|
|
786
805
|
}
|
|
787
806
|
|
|
807
|
+
/**
|
|
808
|
+
* Apply the optional inclusive date-range filter. The field is mapped
|
|
809
|
+
* through `DATE_FILTER_COLUMNS` (a closed whitelist), and bounds compare
|
|
810
|
+
* lexicographically against the stored ISO 8601 timestamps. A `publishedAt`
|
|
811
|
+
* range naturally excludes never-published rows (their column is NULL).
|
|
812
|
+
*/
|
|
813
|
+
private applyDateFilter<QB extends { where: (cb: (eb: any) => unknown) => QB }>(
|
|
814
|
+
query: QB,
|
|
815
|
+
where?: { dateFilter?: { field: string; from?: string; to?: string } },
|
|
816
|
+
): QB {
|
|
817
|
+
const filter = where?.dateFilter;
|
|
818
|
+
if (!filter) return query;
|
|
819
|
+
const column = DATE_FILTER_COLUMNS[filter.field as ContentDateField];
|
|
820
|
+
if (!column) {
|
|
821
|
+
throw new EmDashValidationError(`Invalid date filter field: ${filter.field}`);
|
|
822
|
+
}
|
|
823
|
+
const { from, to } = filter;
|
|
824
|
+
if (!from && !to) return query;
|
|
825
|
+
|
|
826
|
+
let next = query;
|
|
827
|
+
if (from) next = next.where((eb) => eb(column as any, ">=", from));
|
|
828
|
+
if (to) next = next.where((eb) => eb(column as any, "<=", to));
|
|
829
|
+
return next;
|
|
830
|
+
}
|
|
831
|
+
|
|
788
832
|
/**
|
|
789
833
|
* Count content items
|
|
790
834
|
*/
|
|
791
|
-
async count(
|
|
792
|
-
type: string,
|
|
793
|
-
where?: {
|
|
794
|
-
status?: string;
|
|
795
|
-
authorId?: string;
|
|
796
|
-
locale?: string;
|
|
797
|
-
q?: string;
|
|
798
|
-
searchColumns?: string[];
|
|
799
|
-
},
|
|
800
|
-
): Promise<number> {
|
|
835
|
+
async count(type: string, where?: FindManyOptions["where"]): Promise<number> {
|
|
801
836
|
const tableName = getTableName(type);
|
|
802
837
|
|
|
803
838
|
let query = this.db
|
|
@@ -818,11 +853,34 @@ export class ContentRepository {
|
|
|
818
853
|
}
|
|
819
854
|
|
|
820
855
|
query = this.applySearchFilter(query, where);
|
|
856
|
+
query = this.applyDateFilter(query, where);
|
|
821
857
|
|
|
822
858
|
const result = await query.executeTakeFirst();
|
|
823
859
|
return Number(result?.count || 0);
|
|
824
860
|
}
|
|
825
861
|
|
|
862
|
+
/**
|
|
863
|
+
* Distinct, non-null `author_id` values across the collection's live
|
|
864
|
+
* (non-trashed) content. Used to populate the admin author filter with
|
|
865
|
+
* only the users who have actually authored entries, rather than the
|
|
866
|
+
* full user directory (which requires admin privileges to read).
|
|
867
|
+
*/
|
|
868
|
+
async findDistinctAuthorIds(type: string): Promise<string[]> {
|
|
869
|
+
const tableName = getTableName(type);
|
|
870
|
+
|
|
871
|
+
const rows = await this.db
|
|
872
|
+
.selectFrom(tableName as keyof Database)
|
|
873
|
+
.select("author_id")
|
|
874
|
+
.distinct()
|
|
875
|
+
.where("deleted_at" as never, "is", null)
|
|
876
|
+
.where("author_id" as never, "is not", null)
|
|
877
|
+
.execute();
|
|
878
|
+
|
|
879
|
+
return rows
|
|
880
|
+
.map((row) => (row as { author_id: string | null }).author_id)
|
|
881
|
+
.filter((id): id is string => id !== null);
|
|
882
|
+
}
|
|
883
|
+
|
|
826
884
|
// get overall statistics (total, published, draft) for a content type in a single query
|
|
827
885
|
async getStats(type: string): Promise<{ total: number; published: number; draft: number }> {
|
|
828
886
|
const tableName = getTableName(type);
|
|
@@ -933,17 +991,30 @@ export class ContentRepository {
|
|
|
933
991
|
* Returns all content where scheduled_at <= now, regardless of status.
|
|
934
992
|
* This covers both draft-scheduled posts (status='scheduled') and
|
|
935
993
|
* published posts with scheduled draft changes (status='published').
|
|
994
|
+
*
|
|
995
|
+
* `limit` (optional) caps how many due rows are returned, oldest-due first.
|
|
996
|
+
* The scheduled-publishing sweep passes a limit so a large backlog can't
|
|
997
|
+
* fan out unbounded publish/webhook work in a single tick (and blow a Worker
|
|
998
|
+
* invocation's CPU/subrequest budget); the remainder drains on later ticks.
|
|
936
999
|
*/
|
|
937
|
-
async findReadyToPublish(type: string): Promise<ContentItem[]> {
|
|
1000
|
+
async findReadyToPublish(type: string, limit?: number): Promise<ContentItem[]> {
|
|
938
1001
|
const tableName = getTableName(type);
|
|
939
1002
|
const now = new Date().toISOString();
|
|
940
1003
|
|
|
1004
|
+
// Embed an empty fragment when unbounded so callers that want every due
|
|
1005
|
+
// row (manual flows, tests) keep the original behaviour.
|
|
1006
|
+
const limitClause =
|
|
1007
|
+
typeof limit === "number" && Number.isInteger(limit) && limit > 0
|
|
1008
|
+
? sql`LIMIT ${limit}`
|
|
1009
|
+
: sql``;
|
|
1010
|
+
|
|
941
1011
|
const result = await sql<Record<string, unknown>>`
|
|
942
1012
|
SELECT * FROM ${sql.ref(tableName)}
|
|
943
1013
|
WHERE scheduled_at IS NOT NULL
|
|
944
1014
|
AND scheduled_at <= ${now}
|
|
945
1015
|
AND deleted_at IS NULL
|
|
946
1016
|
ORDER BY scheduled_at ASC
|
|
1017
|
+
${limitClause}
|
|
947
1018
|
`.execute(this.db);
|
|
948
1019
|
|
|
949
1020
|
return result.rows.map((row) => this.mapRow(type, row));
|
|
@@ -978,8 +1049,21 @@ export class ContentRepository {
|
|
|
978
1049
|
* original date) and falls back to the current time on first publish. Pass
|
|
979
1050
|
* an explicit value to backdate a publish (e.g. when migrating content from
|
|
980
1051
|
* another CMS).
|
|
1052
|
+
*
|
|
1053
|
+
* `requireDue` (optional) gates the publish on the row still being due:
|
|
1054
|
+
* `scheduled_at` non-null and in the past. Used by the scheduled-publishing
|
|
1055
|
+
* sweep to avoid publishing content an editor unscheduled or rescheduled
|
|
1056
|
+
* between selection and publish. It claims the row with a single conditional
|
|
1057
|
+
* UPDATE (clearing `scheduled_at`) before any other write, so it is atomic
|
|
1058
|
+
* even on D1 (no multi-statement transactions) and serialises against
|
|
1059
|
+
* `unschedule()` and concurrent sweeps — no TOCTOU and no double publish.
|
|
981
1060
|
*/
|
|
982
|
-
async publish(
|
|
1061
|
+
async publish(
|
|
1062
|
+
type: string,
|
|
1063
|
+
id: string,
|
|
1064
|
+
publishedAt?: string,
|
|
1065
|
+
requireDue = false,
|
|
1066
|
+
): Promise<ContentItem> {
|
|
983
1067
|
const tableName = getTableName(type);
|
|
984
1068
|
const now = new Date().toISOString();
|
|
985
1069
|
|
|
@@ -988,71 +1072,145 @@ export class ContentRepository {
|
|
|
988
1072
|
throw new EmDashValidationError("Content item not found");
|
|
989
1073
|
}
|
|
990
1074
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1075
|
+
// Scheduled sweep: atomically claim the row before any other write. A
|
|
1076
|
+
// single conditional UPDATE is atomic per-statement on every dialect
|
|
1077
|
+
// (it doesn't depend on a wrapping transaction, which D1 lacks). If the
|
|
1078
|
+
// schedule was cleared or pushed to the future (unschedule/reschedule)
|
|
1079
|
+
// or another sweep already claimed it, this affects 0 rows and we bail
|
|
1080
|
+
// before promoting any revision — so the row can't be double-published.
|
|
1081
|
+
let claimedScheduledAt: string | null = null;
|
|
1082
|
+
let claimedUpdatedAt: string | null = null;
|
|
1083
|
+
if (requireDue) {
|
|
1084
|
+
const claim = await sql`
|
|
1085
|
+
UPDATE ${sql.ref(tableName)}
|
|
1086
|
+
SET scheduled_at = NULL,
|
|
1087
|
+
updated_at = ${now}
|
|
1088
|
+
WHERE id = ${id}
|
|
1089
|
+
AND scheduled_at IS NOT NULL
|
|
1090
|
+
AND scheduled_at <= ${now}
|
|
1091
|
+
AND deleted_at IS NULL
|
|
1092
|
+
`.execute(this.db);
|
|
1093
|
+
if ((claim.numAffectedRows ?? 0n) === 0n) {
|
|
1094
|
+
throw new ScheduledNotDueError();
|
|
1095
|
+
}
|
|
1096
|
+
// Remember what we cleared so we can put it back if the publish work
|
|
1097
|
+
// below fails on a driver without transactions (see catch). Both
|
|
1098
|
+
// values come from the pre-claim snapshot: if a concurrent
|
|
1099
|
+
// reschedule-to-a-different-past-time landed between findById and the
|
|
1100
|
+
// claim, the restore writes the snapshot value rather than the one the
|
|
1101
|
+
// claim actually cleared. That window is tiny and the restore is
|
|
1102
|
+
// best-effort retry bookkeeping, so the imprecision is acceptable.
|
|
1103
|
+
claimedScheduledAt = existing.scheduledAt;
|
|
1104
|
+
claimedUpdatedAt = existing.updatedAt;
|
|
1002
1105
|
}
|
|
1003
1106
|
|
|
1004
|
-
//
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1107
|
+
// Track whether the final publish write committed. On D1 the claim above
|
|
1108
|
+
// is already durable (withTransaction is a no-op there), so if a later
|
|
1109
|
+
// step throws we must restore the schedule — otherwise the row is left
|
|
1110
|
+
// `scheduled` with `scheduled_at = NULL` and no sweep ever retries it.
|
|
1111
|
+
let publishCommitted = false;
|
|
1112
|
+
try {
|
|
1113
|
+
const revisionRepo = new RevisionRepository(this.db);
|
|
1114
|
+
let revisionToPublish = existing.draftRevisionId || existing.liveRevisionId;
|
|
1009
1115
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1116
|
+
if (!revisionToPublish) {
|
|
1117
|
+
// No revision exists - create one from current data
|
|
1118
|
+
const revision = await revisionRepo.create({
|
|
1119
|
+
collection: type,
|
|
1120
|
+
entryId: id,
|
|
1121
|
+
data: existing.data,
|
|
1122
|
+
});
|
|
1123
|
+
revisionToPublish = revision.id;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// Sync the revision's data into the content table columns
|
|
1127
|
+
// so the content table always holds the published version
|
|
1128
|
+
const revision = await revisionRepo.findById(revisionToPublish);
|
|
1129
|
+
if (revision) {
|
|
1130
|
+
await this.syncDataColumns(type, id, revision.data);
|
|
1131
|
+
|
|
1132
|
+
// Sync slug from revision if stored there
|
|
1133
|
+
if (typeof revision.data._slug === "string") {
|
|
1134
|
+
await sql`
|
|
1135
|
+
UPDATE ${sql.ref(tableName)}
|
|
1136
|
+
SET slug = ${revision.data._slug}
|
|
1137
|
+
WHERE id = ${id}
|
|
1138
|
+
`.execute(this.db);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (publishedAt !== undefined) {
|
|
1143
|
+
// Caller supplied an explicit timestamp, so we overwrite published_at
|
|
1144
|
+
// directly (used to backdate a publish, e.g. for content migrations).
|
|
1145
|
+
await sql`
|
|
1146
|
+
UPDATE ${sql.ref(tableName)}
|
|
1147
|
+
SET live_revision_id = ${revisionToPublish},
|
|
1148
|
+
draft_revision_id = NULL,
|
|
1149
|
+
status = 'published',
|
|
1150
|
+
scheduled_at = NULL,
|
|
1151
|
+
published_at = ${publishedAt},
|
|
1152
|
+
updated_at = ${now}
|
|
1153
|
+
WHERE id = ${id}
|
|
1154
|
+
AND deleted_at IS NULL
|
|
1155
|
+
`.execute(this.db);
|
|
1156
|
+
} else {
|
|
1157
|
+
// No timestamp supplied — preserve existing published_at on
|
|
1158
|
+
// idempotent re-publish, fall back to `now` on first publish.
|
|
1012
1159
|
await sql`
|
|
1013
1160
|
UPDATE ${sql.ref(tableName)}
|
|
1014
|
-
SET
|
|
1161
|
+
SET live_revision_id = ${revisionToPublish},
|
|
1162
|
+
draft_revision_id = NULL,
|
|
1163
|
+
status = 'published',
|
|
1164
|
+
scheduled_at = NULL,
|
|
1165
|
+
published_at = COALESCE(published_at, ${now}),
|
|
1166
|
+
updated_at = ${now}
|
|
1015
1167
|
WHERE id = ${id}
|
|
1168
|
+
AND deleted_at IS NULL
|
|
1016
1169
|
`.execute(this.db);
|
|
1017
1170
|
}
|
|
1018
|
-
|
|
1171
|
+
publishCommitted = true;
|
|
1019
1172
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
UPDATE ${sql.ref(tableName)}
|
|
1025
|
-
SET live_revision_id = ${revisionToPublish},
|
|
1026
|
-
draft_revision_id = NULL,
|
|
1027
|
-
status = 'published',
|
|
1028
|
-
scheduled_at = NULL,
|
|
1029
|
-
published_at = ${publishedAt},
|
|
1030
|
-
updated_at = ${now}
|
|
1031
|
-
WHERE id = ${id}
|
|
1032
|
-
AND deleted_at IS NULL
|
|
1033
|
-
`.execute(this.db);
|
|
1034
|
-
} else {
|
|
1035
|
-
// No timestamp supplied — preserve existing published_at on
|
|
1036
|
-
// idempotent re-publish, fall back to `now` on first publish.
|
|
1037
|
-
await sql`
|
|
1038
|
-
UPDATE ${sql.ref(tableName)}
|
|
1039
|
-
SET live_revision_id = ${revisionToPublish},
|
|
1040
|
-
draft_revision_id = NULL,
|
|
1041
|
-
status = 'published',
|
|
1042
|
-
scheduled_at = NULL,
|
|
1043
|
-
published_at = COALESCE(published_at, ${now}),
|
|
1044
|
-
updated_at = ${now}
|
|
1045
|
-
WHERE id = ${id}
|
|
1046
|
-
AND deleted_at IS NULL
|
|
1047
|
-
`.execute(this.db);
|
|
1048
|
-
}
|
|
1173
|
+
const updated = await this.findById(type, id);
|
|
1174
|
+
if (!updated) {
|
|
1175
|
+
throw new Error("Content not found");
|
|
1176
|
+
}
|
|
1049
1177
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1178
|
+
return updated;
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
// Best-effort schedule restore for the no-transaction (D1) case so a
|
|
1181
|
+
// failed publish stays retryable. Skipped when the publish actually
|
|
1182
|
+
// committed (the failure was afterwards). On SQLite/Postgres the
|
|
1183
|
+
// enclosing transaction rolls the claim back, so this restore also
|
|
1184
|
+
// rolls back — a harmless no-op. Never mask the original error.
|
|
1185
|
+
if (requireDue && claimedScheduledAt && !publishCommitted) {
|
|
1186
|
+
try {
|
|
1187
|
+
// Only restore if the row still has pending work: either it's not
|
|
1188
|
+
// published, or it's a published row that still has a draft change
|
|
1189
|
+
// queued. This avoids re-adding a stale schedule (and triggering a
|
|
1190
|
+
// redundant republish) when another actor fully published the row
|
|
1191
|
+
// in the failure window — that publish clears draft_revision_id.
|
|
1192
|
+
// Restore updated_at to its pre-claim value too — the claim bumped
|
|
1193
|
+
// it to `now`, and a failed publish made no real change, so leaving
|
|
1194
|
+
// it advanced would be a phantom modification for "changed since"
|
|
1195
|
+
// consumers (sync, ETags, incremental indexers).
|
|
1196
|
+
await sql`
|
|
1197
|
+
UPDATE ${sql.ref(tableName)}
|
|
1198
|
+
SET scheduled_at = ${claimedScheduledAt},
|
|
1199
|
+
updated_at = ${claimedUpdatedAt ?? now}
|
|
1200
|
+
WHERE id = ${id}
|
|
1201
|
+
AND scheduled_at IS NULL
|
|
1202
|
+
AND deleted_at IS NULL
|
|
1203
|
+
AND (status != 'published' OR draft_revision_id IS NOT NULL)
|
|
1204
|
+
`.execute(this.db);
|
|
1205
|
+
} catch (restoreError) {
|
|
1206
|
+
console.error(
|
|
1207
|
+
`[content] Failed to restore schedule for ${type}/${id} after publish failure:`,
|
|
1208
|
+
restoreError,
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
throw error;
|
|
1053
1213
|
}
|
|
1054
|
-
|
|
1055
|
-
return updated;
|
|
1056
1214
|
}
|
|
1057
1215
|
|
|
1058
1216
|
/**
|
|
@@ -42,3 +42,10 @@ export { BylineRepository } from "./byline.js";
|
|
|
42
42
|
export type { CreateBylineInput, UpdateBylineInput, ContentBylineInput } from "./byline.js";
|
|
43
43
|
export type * from "./types.js";
|
|
44
44
|
export { EmDashValidationError, InvalidCursorError, encodeCursor, decodeCursor } from "./types.js";
|
|
45
|
+
export { RelationRepository } from "./relation.js";
|
|
46
|
+
export type {
|
|
47
|
+
Relation,
|
|
48
|
+
CreateRelationInput,
|
|
49
|
+
UpdateRelationInput,
|
|
50
|
+
ContentReference,
|
|
51
|
+
} from "./relation.js";
|