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,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled publishing sweep
|
|
3
|
+
*
|
|
4
|
+
* Promotes content whose scheduled publish time has passed. Driven by the
|
|
5
|
+
* platform scheduler alongside cron ticks and system cleanup — never by a
|
|
6
|
+
* request. On Node the cron scheduler's maintenance pass calls it; on
|
|
7
|
+
* Cloudflare the Worker's `scheduled()` handler does.
|
|
8
|
+
*
|
|
9
|
+
* Like `runSystemCleanup`, each collection sweep is independent and non-fatal:
|
|
10
|
+
* one collection failing must not stop the rest.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Kysely } from "kysely";
|
|
14
|
+
|
|
15
|
+
import { handleContentPublish } from "./api/handlers/content.js";
|
|
16
|
+
import { ContentRepository } from "./database/repositories/content.js";
|
|
17
|
+
import type { Database } from "./database/types.js";
|
|
18
|
+
import { SchemaRegistry } from "./schema/registry.js";
|
|
19
|
+
|
|
20
|
+
/** A content item that was promoted to published by a sweep. */
|
|
21
|
+
export interface PublishedRef {
|
|
22
|
+
collection: string;
|
|
23
|
+
id: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default cap on items promoted per collection in a single sweep. Bounds the
|
|
28
|
+
* publish/webhook fan-out of one tick so a large backlog can't exhaust a Worker
|
|
29
|
+
* invocation's CPU/subrequest budget; the remainder drains on later ticks.
|
|
30
|
+
*/
|
|
31
|
+
export const SCHEDULED_PUBLISH_BATCH_LIMIT = 100;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Publishes a single content item. Mirrors the relevant subset of
|
|
35
|
+
* `handleContentPublish`'s return shape. Production callers pass
|
|
36
|
+
* `EmDashRuntime.handleContentPublish` so `content:afterPublish` hooks fire
|
|
37
|
+
* (search indexing, webhooks, syndication); the default falls back to the raw
|
|
38
|
+
* handler (no hooks) for callers that have only a `db`.
|
|
39
|
+
*/
|
|
40
|
+
export type ScheduledPublishFn = (
|
|
41
|
+
collection: string,
|
|
42
|
+
id: string,
|
|
43
|
+
options: { publishedAt?: string; requireScheduledDue?: boolean },
|
|
44
|
+
) => Promise<{ success: boolean; error?: { code?: string } }>;
|
|
45
|
+
|
|
46
|
+
export interface PublishDueContentOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Publish callback. Production callers pass the runtime's
|
|
49
|
+
* `handleContentPublish` so `content:afterPublish` hooks fire (search
|
|
50
|
+
* indexing, webhooks, syndication). Defaults to the raw DB handler (no hooks).
|
|
51
|
+
*/
|
|
52
|
+
publish?: ScheduledPublishFn;
|
|
53
|
+
/**
|
|
54
|
+
* Invoked after each collection's batch with the items promoted in that
|
|
55
|
+
* batch. Lets request-less callers (the Cloudflare `scheduled()` handler)
|
|
56
|
+
* purge edge-cache tags incrementally instead of only after the whole sweep,
|
|
57
|
+
* so a runtime killed mid-sweep strands at most one batch behind stale cache
|
|
58
|
+
* rather than everything published so far. Failures are logged, never fatal.
|
|
59
|
+
*/
|
|
60
|
+
onPublished?: (refs: PublishedRef[]) => Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Maximum items promoted per collection per sweep. Defaults to
|
|
63
|
+
* `SCHEDULED_PUBLISH_BATCH_LIMIT`. Pass `0` (or a negative) for unbounded.
|
|
64
|
+
*/
|
|
65
|
+
limit?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Publish every content item whose `scheduled_at` is in the past.
|
|
70
|
+
*
|
|
71
|
+
* Iterates all collections, finds due items (`findReadyToPublish` returns both
|
|
72
|
+
* scheduled drafts and published entries with pending scheduled changes), and
|
|
73
|
+
* publishes each. `publish()` clears `scheduled_at`, so a second sweep is a
|
|
74
|
+
* no-op — safe to run on every tick.
|
|
75
|
+
*
|
|
76
|
+
* Bounded per collection by `limit` (default `SCHEDULED_PUBLISH_BATCH_LIMIT`):
|
|
77
|
+
* a large backlog drains across successive ticks rather than in one unbounded
|
|
78
|
+
* pass. After each collection's batch, `onPublished` (if given) is awaited so
|
|
79
|
+
* cache-tag invalidation happens incrementally, not just at the very end.
|
|
80
|
+
*
|
|
81
|
+
* Returns every item it promoted so request-less callers (the Cloudflare
|
|
82
|
+
* `scheduled()` handler) can also act on the full set.
|
|
83
|
+
*/
|
|
84
|
+
export async function publishDueContent(
|
|
85
|
+
db: Kysely<Database>,
|
|
86
|
+
options: PublishDueContentOptions = {},
|
|
87
|
+
): Promise<PublishedRef[]> {
|
|
88
|
+
const { publish, onPublished, limit = SCHEDULED_PUBLISH_BATCH_LIMIT } = options;
|
|
89
|
+
const published: PublishedRef[] = [];
|
|
90
|
+
|
|
91
|
+
let collections;
|
|
92
|
+
try {
|
|
93
|
+
collections = await new SchemaRegistry(db).listCollections();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error("[scheduled-publish] Failed to list collections:", error);
|
|
96
|
+
return published;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const repo = new ContentRepository(db);
|
|
100
|
+
const doPublish: ScheduledPublishFn =
|
|
101
|
+
publish ?? ((collection, id, opts) => handleContentPublish(db, collection, id, opts));
|
|
102
|
+
// 0 / negative means unbounded; findReadyToPublish treats that as "no LIMIT".
|
|
103
|
+
const batchLimit = limit > 0 ? limit : undefined;
|
|
104
|
+
|
|
105
|
+
for (const collection of collections) {
|
|
106
|
+
try {
|
|
107
|
+
const due = await repo.findReadyToPublish(collection.slug, batchLimit);
|
|
108
|
+
const batch: PublishedRef[] = [];
|
|
109
|
+
for (const item of due) {
|
|
110
|
+
// First publication of a scheduled draft should record the intended
|
|
111
|
+
// scheduled time, not the (later) sweep time. Items already published
|
|
112
|
+
// with pending draft changes keep their original published_at.
|
|
113
|
+
const publishedAt = item.publishedAt == null ? (item.scheduledAt ?? undefined) : undefined;
|
|
114
|
+
const result = await doPublish(collection.slug, item.id, {
|
|
115
|
+
publishedAt,
|
|
116
|
+
requireScheduledDue: true,
|
|
117
|
+
});
|
|
118
|
+
if (result.success) {
|
|
119
|
+
batch.push({ collection: collection.slug, id: item.id });
|
|
120
|
+
} else if (result.error?.code === "NOT_DUE") {
|
|
121
|
+
// Unscheduled or rescheduled between selection and publish — the
|
|
122
|
+
// editor changed their mind; skip quietly, not a failure.
|
|
123
|
+
} else {
|
|
124
|
+
console.error(
|
|
125
|
+
`[scheduled-publish] Failed to publish ${collection.slug}/${item.id}:`,
|
|
126
|
+
result.error,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (batch.length > 0) {
|
|
132
|
+
published.push(...batch);
|
|
133
|
+
if (onPublished) {
|
|
134
|
+
// Purge this batch's cache tags before moving to the next
|
|
135
|
+
// collection, so a mid-sweep kill can't strand already-published
|
|
136
|
+
// content behind stale cache.
|
|
137
|
+
try {
|
|
138
|
+
await onPublished(batch);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(
|
|
141
|
+
`[scheduled-publish] onPublished failed after "${collection.slug}" batch:`,
|
|
142
|
+
error,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(`[scheduled-publish] Sweep failed for "${collection.slug}":`, error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return published;
|
|
153
|
+
}
|
|
@@ -288,6 +288,9 @@ export function generateTypeScript(collection: CollectionWithFields): string {
|
|
|
288
288
|
lines.push(` publishedAt: Date | null;`);
|
|
289
289
|
// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry
|
|
290
290
|
lines.push(` bylines?: ContentBylineCredit[];`);
|
|
291
|
+
// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,
|
|
292
|
+
// keyed by taxonomy name (e.g. data.terms?.tag)
|
|
293
|
+
lines.push(` terms?: Record<string, TaxonomyTerm[]>;`);
|
|
291
294
|
lines.push(`}`);
|
|
292
295
|
|
|
293
296
|
return lines.join("\n");
|
|
@@ -312,8 +315,9 @@ export function generateTypesFile(collections: CollectionWithFields[]): string {
|
|
|
312
315
|
c.fields.some((f) => f.type === "portableText"),
|
|
313
316
|
);
|
|
314
317
|
|
|
315
|
-
// Build imports - ContentBylineCredit
|
|
316
|
-
|
|
318
|
+
// Build imports - ContentBylineCredit and TaxonomyTerm are always needed
|
|
319
|
+
// for the hydrated bylines/terms fields
|
|
320
|
+
const imports = ["ContentBylineCredit", "TaxonomyTerm"];
|
|
317
321
|
if (needsPortableText) {
|
|
318
322
|
imports.push("PortableTextBlock");
|
|
319
323
|
}
|
package/src/seed/apply.ts
CHANGED
|
@@ -115,6 +115,13 @@ export async function applySeed(
|
|
|
115
115
|
const seedIdMap = new Map<string, string>(); // seed id -> real entry id
|
|
116
116
|
const seedBylineIdMap = new Map<string, string>(); // seed byline id -> real byline id
|
|
117
117
|
|
|
118
|
+
// Fallback locale for rows that omit an explicit `locale`. Prefer the runtime
|
|
119
|
+
// config (runtime-driven seeds), then the seed's self-described `defaultLocale`
|
|
120
|
+
// (CLI exports run outside the runtime), and only then `en`. Without the
|
|
121
|
+
// seed-carried default, a non-`en` single-locale project would be rewritten to
|
|
122
|
+
// `en` on apply (#1421).
|
|
123
|
+
const defaultLocale = getI18nConfig()?.defaultLocale ?? seed.defaultLocale ?? "en";
|
|
124
|
+
|
|
118
125
|
// 1. Site settings
|
|
119
126
|
if (seed.settings) {
|
|
120
127
|
await setSiteSettings(seed.settings, db);
|
|
@@ -224,10 +231,9 @@ export async function applySeed(
|
|
|
224
231
|
// seed-local id -> resolved info, used to wire `translationOf` refs.
|
|
225
232
|
const defSeedIdMap = new Map<string, { id: string; translationGroup: string }>();
|
|
226
233
|
const termSeedIdMap = new Map<string, string>();
|
|
227
|
-
const fallbackLocale = getI18nConfig()?.defaultLocale ?? "en";
|
|
228
234
|
|
|
229
235
|
for (const taxonomy of seed.taxonomies) {
|
|
230
|
-
const defLocale = taxonomy.locale ??
|
|
236
|
+
const defLocale = taxonomy.locale ?? defaultLocale;
|
|
231
237
|
|
|
232
238
|
// (name, locale) is the UNIQUE key after migration 036.
|
|
233
239
|
const existingDef = await db
|
|
@@ -418,8 +424,13 @@ export async function applySeed(
|
|
|
418
424
|
// Create content entries
|
|
419
425
|
for (const [collectionSlug, entries] of Object.entries(seed.content)) {
|
|
420
426
|
for (const entry of entries) {
|
|
427
|
+
// Resolve the entry's locale up front so a non-`en` single-locale
|
|
428
|
+
// export (which omits `locale`) is filed under the project default
|
|
429
|
+
// rather than `en` (#1421).
|
|
430
|
+
const entryLocale = entry.locale ?? defaultLocale;
|
|
431
|
+
|
|
421
432
|
// Check if entry exists (by slug + locale for locale-aware lookup)
|
|
422
|
-
const existing = await contentRepo.findBySlug(collectionSlug, entry.slug,
|
|
433
|
+
const existing = await contentRepo.findBySlug(collectionSlug, entry.slug, entryLocale);
|
|
423
434
|
|
|
424
435
|
if (existing) {
|
|
425
436
|
if (onConflict === "error") {
|
|
@@ -515,7 +526,7 @@ export async function applySeed(
|
|
|
515
526
|
slug: entry.slug,
|
|
516
527
|
status,
|
|
517
528
|
data: resolvedData,
|
|
518
|
-
locale:
|
|
529
|
+
locale: entryLocale,
|
|
519
530
|
translationOf,
|
|
520
531
|
publishedAt: status === "published" ? new Date().toISOString() : null,
|
|
521
532
|
});
|
|
@@ -545,10 +556,9 @@ export async function applySeed(
|
|
|
545
556
|
const menuSeedIdMap = new Map<string, { id: string; translationGroup: string }>();
|
|
546
557
|
// Shared across menus: translated items reference anchor items in sibling menus.
|
|
547
558
|
const itemSeedIdMap = new Map<string, { id: string; translationGroup: string }>();
|
|
548
|
-
const fallbackLocale = getI18nConfig()?.defaultLocale ?? "en";
|
|
549
559
|
|
|
550
560
|
for (const menu of seed.menus) {
|
|
551
|
-
const locale = menu.locale ??
|
|
561
|
+
const locale = menu.locale ?? defaultLocale;
|
|
552
562
|
let lookup = db
|
|
553
563
|
.selectFrom("_emdash_menus")
|
|
554
564
|
.selectAll()
|
package/src/seed/types.ts
CHANGED
|
@@ -19,6 +19,15 @@ export interface SeedFile {
|
|
|
19
19
|
/** Seed format version */
|
|
20
20
|
version: "1";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Default locale for locale-bearing rows (menus, taxonomies, content) that
|
|
24
|
+
* omit an explicit `locale`. Lets a non-`en` single-locale project survive an
|
|
25
|
+
* `export-seed` → `seed` round-trip: `apply` runs outside the Astro runtime
|
|
26
|
+
* (no i18n config), so without this it would backfill the omitted locale as
|
|
27
|
+
* `en`. See #1421.
|
|
28
|
+
*/
|
|
29
|
+
defaultLocale?: string;
|
|
30
|
+
|
|
22
31
|
/** Metadata about the seed */
|
|
23
32
|
meta?: {
|
|
24
33
|
name?: string;
|
package/src/seed/validate.ts
CHANGED
|
@@ -57,6 +57,21 @@ export function validateSeed(data: unknown): ValidationResult {
|
|
|
57
57
|
errors.push(`Unsupported seed version: ${String(seed.version)}`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// defaultLocale backfills the locale of rows that omit one, so a blank or
|
|
61
|
+
// whitespace-padded value would silently write empty/invalid locales to the DB.
|
|
62
|
+
// Exported seeds never hit this, but it's part of the public schema now.
|
|
63
|
+
if (seed.defaultLocale !== undefined) {
|
|
64
|
+
if (
|
|
65
|
+
typeof seed.defaultLocale !== "string" ||
|
|
66
|
+
seed.defaultLocale.length === 0 ||
|
|
67
|
+
seed.defaultLocale !== seed.defaultLocale.trim()
|
|
68
|
+
) {
|
|
69
|
+
errors.push(
|
|
70
|
+
"defaultLocale: must be a non-empty string with no leading or trailing whitespace",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
// Validate collections
|
|
61
76
|
if (seed.collections) {
|
|
62
77
|
if (!Array.isArray(seed.collections)) {
|
package/src/taxonomies/index.ts
CHANGED
|
@@ -148,6 +148,7 @@ export async function getTaxonomyTerms(
|
|
|
148
148
|
name: term.name,
|
|
149
149
|
slug: term.slug,
|
|
150
150
|
label: term.label,
|
|
151
|
+
description: term.data ? JSON.parse(term.data).description : undefined,
|
|
151
152
|
children: [],
|
|
152
153
|
count: counts.get(term.translation_group ?? term.id) ?? 0,
|
|
153
154
|
locale: term.locale,
|
|
@@ -189,13 +190,6 @@ export async function getTerm(
|
|
|
189
190
|
|
|
190
191
|
if (!row) return null;
|
|
191
192
|
|
|
192
|
-
const countResult = await db
|
|
193
|
-
.selectFrom("content_taxonomies")
|
|
194
|
-
.select((eb) => eb.fn.count<number>("entry_id").as("count"))
|
|
195
|
-
.where("taxonomy_id", "=", row.translation_group ?? row.id)
|
|
196
|
-
.executeTakeFirst();
|
|
197
|
-
const count = countResult?.count ?? 0;
|
|
198
|
-
|
|
199
193
|
let childrenQuery = db
|
|
200
194
|
.selectFrom("taxonomies")
|
|
201
195
|
.selectAll()
|
|
@@ -203,7 +197,18 @@ export async function getTerm(
|
|
|
203
197
|
.orderBy("label", "asc");
|
|
204
198
|
const termLocale = row.locale;
|
|
205
199
|
if (termLocale) childrenQuery = childrenQuery.where("locale", "=", termLocale);
|
|
206
|
-
|
|
200
|
+
|
|
201
|
+
// The usage-count and children queries both depend only on the term row,
|
|
202
|
+
// so run them concurrently to save a round trip on remote databases.
|
|
203
|
+
const [countResult, childRows] = await Promise.all([
|
|
204
|
+
db
|
|
205
|
+
.selectFrom("content_taxonomies")
|
|
206
|
+
.select((eb) => eb.fn.count<number>("entry_id").as("count"))
|
|
207
|
+
.where("taxonomy_id", "=", row.translation_group ?? row.id)
|
|
208
|
+
.executeTakeFirst(),
|
|
209
|
+
childrenQuery.execute(),
|
|
210
|
+
]);
|
|
211
|
+
const count = countResult?.count ?? 0;
|
|
207
212
|
|
|
208
213
|
const children = childRows.map<TaxonomyTerm>((child) => ({
|
|
209
214
|
id: child.id,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reclaimable initialization lock for isolate-lifetime singletons.
|
|
3
|
+
*
|
|
4
|
+
* Guards "first request initializes, everyone else waits" sections
|
|
5
|
+
* (runtime creation, database init) against a workerd failure mode: if the
|
|
6
|
+
* request that owns the initialization is cancelled mid-await (client
|
|
7
|
+
* disconnect, context teardown), its continuation — including any `finally`
|
|
8
|
+
* that would release the lock — never runs. A plain boolean or shared
|
|
9
|
+
* promise then stays stuck forever and every subsequent request in the
|
|
10
|
+
* isolate hangs until the platform kills it (observed as 524s at the
|
|
11
|
+
* 100-second wall limit, with the isolate poisoned until eviction).
|
|
12
|
+
*
|
|
13
|
+
* This lock instead records *when* the owner started. Waiters poll — we
|
|
14
|
+
* deliberately never await a promise created by another request, which
|
|
15
|
+
* workerd flags — and if the owner has held the lock past `deadlineMs`,
|
|
16
|
+
* the next waiter assumes the owner is dead, reclaims the lock, and runs
|
|
17
|
+
* the initialization itself. Waiters also give up after `maxWaitMs` so a
|
|
18
|
+
* request degrades to an error response rather than hanging.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface InitLock {
|
|
22
|
+
/** Epoch ms when the current owner claimed the lock, or null when free. */
|
|
23
|
+
ownerStartedAt: number | null;
|
|
24
|
+
/**
|
|
25
|
+
* Monotonic claim counter identifying the current owner. Release is
|
|
26
|
+
* gated on it: a slow owner that finishes after a waiter has reclaimed
|
|
27
|
+
* the lock must not clear the reclaimer's claim — that would let yet
|
|
28
|
+
* another caller claim the lock and start a third concurrent init.
|
|
29
|
+
*/
|
|
30
|
+
generation: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createInitLock(): InitLock {
|
|
34
|
+
return { ownerStartedAt: null, generation: 0 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface InitLockOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Reclaim the lock if the owner has held it longer than this. Must be
|
|
40
|
+
* comfortably above the slowest legitimate init (cold migrations on a
|
|
41
|
+
* contended D1, including the concurrent-migrator wait) — a too-short
|
|
42
|
+
* deadline risks two concurrent inits, a too-long one delays recovery
|
|
43
|
+
* of a poisoned isolate. Nested locks must compose: an outer lock's
|
|
44
|
+
* deadline must exceed the deadline of any lock its init acquires.
|
|
45
|
+
*/
|
|
46
|
+
deadlineMs?: number;
|
|
47
|
+
/** Waiter poll interval. */
|
|
48
|
+
pollMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Give up waiting after this long and throw instead of hanging.
|
|
51
|
+
* Defaults to `deadlineMs` plus headroom so a waiter always survives
|
|
52
|
+
* long enough to reclaim a dead owner before giving up.
|
|
53
|
+
*/
|
|
54
|
+
maxWaitMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Called with the in-flight init promise (errors pre-swallowed) so the
|
|
57
|
+
* caller can hand it to the host's lifetime extender (waitUntil via
|
|
58
|
+
* `after()`). If the owning request is cancelled mid-init, the anchored
|
|
59
|
+
* promise keeps the context alive: init completes, populates the cache,
|
|
60
|
+
* and the `finally` below releases the lock — preventing the poisoning
|
|
61
|
+
* instead of merely recovering from it via reclaim.
|
|
62
|
+
*/
|
|
63
|
+
anchor?: (promise: Promise<void>) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const DEFAULT_DEADLINE_MS = 15_000;
|
|
67
|
+
const DEFAULT_POLL_MS = 50;
|
|
68
|
+
const MAX_WAIT_HEADROOM_MS = 15_000;
|
|
69
|
+
|
|
70
|
+
function sleep(ms: number): Promise<void> {
|
|
71
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Return the cached value if present, otherwise initialize it under the
|
|
76
|
+
* lock. `init` is responsible for storing the value so that `getCached`
|
|
77
|
+
* returns it on subsequent calls — waiters re-check `getCached` after the
|
|
78
|
+
* owner finishes rather than sharing the owner's promise.
|
|
79
|
+
*
|
|
80
|
+
* `init` receives an `isCurrentClaim` predicate and must gate its cache
|
|
81
|
+
* publication on it: a slow init that was reclaimed past the deadline
|
|
82
|
+
* must not overwrite the value published by the reclaimer (for the
|
|
83
|
+
* runtime singleton that would orphan the reclaimer's active cron
|
|
84
|
+
* scheduler). A losing init should also tear down any side resources it
|
|
85
|
+
* started, since its result will never be published.
|
|
86
|
+
*/
|
|
87
|
+
export async function initWithLock<T>(
|
|
88
|
+
lock: InitLock,
|
|
89
|
+
getCached: () => T | null | undefined,
|
|
90
|
+
init: (isCurrentClaim: () => boolean) => Promise<T>,
|
|
91
|
+
options?: InitLockOptions,
|
|
92
|
+
): Promise<T> {
|
|
93
|
+
const deadlineMs = options?.deadlineMs ?? DEFAULT_DEADLINE_MS;
|
|
94
|
+
const pollMs = options?.pollMs ?? DEFAULT_POLL_MS;
|
|
95
|
+
const maxWaitMs = options?.maxWaitMs ?? deadlineMs + MAX_WAIT_HEADROOM_MS;
|
|
96
|
+
// Date.now() is deliberate and only works because every loop iteration
|
|
97
|
+
// awaits: in workerd the clock only advances across I/O, so a sync spin
|
|
98
|
+
// would never observe the deadline. Don't "optimize" away the sleep.
|
|
99
|
+
const waitStart = Date.now();
|
|
100
|
+
|
|
101
|
+
for (;;) {
|
|
102
|
+
const cached = getCached();
|
|
103
|
+
if (cached !== null && cached !== undefined) {
|
|
104
|
+
return cached;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const ownerStartedAt = lock.ownerStartedAt;
|
|
108
|
+
if (ownerStartedAt === null || Date.now() - ownerStartedAt > deadlineMs) {
|
|
109
|
+
// Free, or the owner has been gone past the deadline — claim it.
|
|
110
|
+
// Synchronous between awaits, so two waiters can't both claim.
|
|
111
|
+
lock.generation += 1;
|
|
112
|
+
const claim = lock.generation;
|
|
113
|
+
lock.ownerStartedAt = Date.now();
|
|
114
|
+
try {
|
|
115
|
+
// Promise.resolve().then(...) so a synchronous throw from
|
|
116
|
+
// init still becomes a rejection after the anchor attaches.
|
|
117
|
+
const isCurrentClaim = () => lock.generation === claim;
|
|
118
|
+
const initPromise = Promise.resolve().then(() => init(isCurrentClaim));
|
|
119
|
+
options?.anchor?.(
|
|
120
|
+
initPromise.then(
|
|
121
|
+
() => undefined,
|
|
122
|
+
() => undefined,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
return await initPromise;
|
|
126
|
+
} finally {
|
|
127
|
+
// If this request dies mid-init unanchored this never runs;
|
|
128
|
+
// the next waiter reclaims after deadlineMs instead. Release
|
|
129
|
+
// only while still the current owner: a reclaimer may have
|
|
130
|
+
// taken the lock while this (slow) init was running, and
|
|
131
|
+
// clearing its claim would admit a third concurrent init.
|
|
132
|
+
if (lock.generation === claim) {
|
|
133
|
+
lock.ownerStartedAt = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Date.now() - waitStart > maxWaitMs) {
|
|
139
|
+
throw new Error(`initWithLock: timed out after ${maxWaitMs}ms waiting for initialization`);
|
|
140
|
+
}
|
|
141
|
+
await sleep(pollMs);
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/virtual-modules.d.ts
CHANGED
|
@@ -132,6 +132,17 @@ declare module "virtual:emdash/wait-until" {
|
|
|
132
132
|
export const waitUntil: ((promise: Promise<unknown>) => void) | undefined;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
declare module "virtual:emdash/scheduler" {
|
|
136
|
+
import type { CreateSchedulerFn } from "./emdash-runtime.js";
|
|
137
|
+
/**
|
|
138
|
+
* Factory for the timer-based cron/maintenance heartbeat. A
|
|
139
|
+
* `NodeCronScheduler` factory on long-lived runtimes (Node/Bun); `null`
|
|
140
|
+
* under serverless adapters (e.g. Cloudflare) where an external Cron
|
|
141
|
+
* Trigger drives scheduled work instead.
|
|
142
|
+
*/
|
|
143
|
+
export const createScheduler: CreateSchedulerFn | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
135
146
|
declare module "virtual:emdash/admin-registry" {
|
|
136
147
|
/**
|
|
137
148
|
* Plugin admin module registry.
|