emdash 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
- package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
- package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
- package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +15 -15
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-Cs7DAACP.mjs → api-DStv36ik.mjs} +123 -20
- package/dist/api-DStv36ik.mjs.map +1 -0
- package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
- package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
- package/dist/{apply-BWMV4Zmw.mjs → apply-Dr7snAMT.mjs} +23 -23
- package/dist/apply-Dr7snAMT.mjs.map +1 -0
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +115 -25
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +6 -6
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs +2 -2
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +26 -4
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +242 -259
- 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 +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +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 +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +33 -33
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +34 -34
- package/dist/astro/routes/api/admin/plugins/updates.mjs +33 -33
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +33 -33
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +33 -33
- 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 +5 -5
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
- package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +6 -6
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
- 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 +3 -3
- 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 +9 -9
- 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.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
- package/dist/astro/routes/api/manifest.mjs +4 -4
- package/dist/astro/routes/api/mcp.mjs +29 -29
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
- package/dist/astro/routes/api/media/_id_.mjs +6 -6
- package/dist/astro/routes/api/media/file/_...key_.mjs +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 +7 -7
- package/dist/astro/routes/api/media.mjs +8 -8
- 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 +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +17 -3
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
- 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 +33 -33
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +33 -33
- package/dist/astro/routes/api/schema/collections/index.mjs +33 -33
- package/dist/astro/routes/api/schema/index.mjs +9 -14
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +33 -33
- package/dist/astro/routes/api/schema/orphans/index.mjs +33 -33
- 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 +11 -11
- package/dist/astro/routes/api/setup/admin.mjs +10 -10
- package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -23
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +23 -23
- package/dist/astro/routes/api/setup/status.mjs +4 -4
- package/dist/astro/routes/api/snapshot.mjs +6 -6
- 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 +6 -6
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +3 -3
- package/dist/astro/routes/robots.txt.mjs +7 -7
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +16 -9
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +8 -8
- package/dist/astro/types.d.mts +19 -12
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-CotM4Yiu.mjs → authorize-DsMSVSaY.mjs} +2 -2
- package/dist/{authorize-CotM4Yiu.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
- package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
- package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
- package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields--WxSNS79.mjs} +2 -2
- package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
- package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-8TMtkBnH.mjs} +3 -3
- package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
- package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DbibsvTl.d.mts} +30 -2
- package/dist/byline-fields-DbibsvTl.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-LJMgENMI.mjs → bylines-BdxWCnPL.mjs} +3 -3
- package/dist/{bylines-LJMgENMI.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
- package/dist/{bylines-BJSva1Un.mjs → bylines-s8c2DXbH.mjs} +50 -6
- package/dist/{bylines-BJSva1Un.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
- package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
- package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
- package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
- package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
- package/dist/cli/index.mjs +46 -32
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
- package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
- package/dist/{comments-BTAbC0Ek.mjs → comments-Vkivawyl.mjs} +3 -3
- package/dist/{comments-BTAbC0Ek.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
- package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
- package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
- package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
- package/dist/content-BIlVx-RX.mjs.map +1 -0
- package/dist/{context-DZ7bEh5-.mjs → context-Y7BRkWes.mjs} +10 -10
- package/dist/{context-DZ7bEh5-.mjs.map → context-Y7BRkWes.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-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
- package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
- package/dist/database/instrumentation.d.mts +10 -1
- package/dist/database/instrumentation.d.mts.map +1 -1
- package/dist/database/instrumentation.mjs +13 -1
- package/dist/database/instrumentation.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
- package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
- package/dist/{device-flow-ptLrVINd.mjs → device-flow-R23SIbQ2.mjs} +5 -5
- package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
- package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
- package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
- package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
- package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
- package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
- package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
- package/dist/{index-CjKdMZ3U.d.mts → index-B1keaX5Y.d.mts} +237 -24
- package/dist/index-B1keaX5Y.d.mts.map +1 -0
- package/dist/{index-D60_SzHG.d.mts → index-DR56od45.d.mts} +3 -3
- package/dist/{index-D60_SzHG.d.mts.map → index-DR56od45.d.mts.map} +1 -1
- package/dist/index.d.mts +17 -17
- package/dist/index.mjs +46 -46
- package/dist/{load-6ZrRhepW.mjs → load-BBetCvLC.mjs} +2 -2
- package/dist/{load-6ZrRhepW.mjs.map → load-BBetCvLC.mjs.map} +1 -1
- package/dist/{loader-Dyx8dhFV.mjs → loader-ZN1ll-d-.mjs} +36 -37
- package/dist/loader-ZN1ll-d-.mjs.map +1 -0
- package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
- package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +11 -11
- package/dist/media/local-runtime.mjs +6 -6
- package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
- package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
- package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
- package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
- package/dist/media-url-VClf8glU.mjs +26 -0
- package/dist/media-url-VClf8glU.mjs.map +1 -0
- package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
- package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
- package/dist/{menus-BKkxXCmd.mjs → menus-DrQLusqj.mjs} +87 -37
- package/dist/menus-DrQLusqj.mjs.map +1 -0
- package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
- package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
- 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-Bw4NdF_S.mjs} +5 -5
- package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.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-DyYIYpPd.d.mts} +3 -3
- package/dist/{options-tb7DJROi.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
- package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.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/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
- package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
- package/dist/{query-Ctlq1aOk.mjs → query-CbUcI4Xk.mjs} +33 -17
- package/dist/query-CbUcI4Xk.mjs.map +1 -0
- package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-C7hjdkS5.mjs} +2 -2
- package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
- package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
- package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
- package/dist/{redirects-C0L9JUk4.mjs → redirects-CCbCqCCd.mjs} +28 -4
- package/dist/redirects-CCbCqCCd.mjs.map +1 -0
- package/dist/{redirects-CacE9eQa.mjs → redirects-DxVoR7PI.mjs} +5 -5
- package/dist/{redirects-CacE9eQa.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
- package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
- package/dist/{registry-CIDxZbhh.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/request-context.d.mts +7 -0
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs +2 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
- package/dist/runner--4wMWwKM.mjs.map +1 -0
- package/dist/{runner-DM1yR5qd.d.mts → runner-DTdhuI9i.d.mts} +2 -2
- package/dist/{runner-DM1yR5qd.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-B4tk0HAG.mjs → schema-C1E70ug_.mjs} +5 -5
- package/dist/{schema-B4tk0HAG.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
- package/dist/{search-f-fNfwab.mjs → search-B3SGZw91.mjs} +4 -4
- package/dist/{search-f-fNfwab.mjs.map → search-B3SGZw91.mjs.map} +1 -1
- package/dist/{secrets-YYbTgB1w.mjs → secrets-ChPTmy9x.mjs} +2 -2
- package/dist/{secrets-YYbTgB1w.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
- package/dist/{sections-biElLfT9.mjs → sections-D_lVzwRZ.mjs} +3 -3
- package/dist/{sections-biElLfT9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +17 -17
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
- package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
- package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
- package/dist/seo-D_LPkOtu.mjs.map +1 -0
- package/dist/{service-BhR2acnc.mjs → service-ChDcsTBs.mjs} +3 -3
- package/dist/{service-BhR2acnc.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
- package/dist/{settings-D_NJvjgN.mjs → settings-Cv47v9u8.mjs} +3 -3
- package/dist/{settings-D_NJvjgN.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
- package/dist/settings-DfxiWY_s.mjs +411 -0
- package/dist/settings-DfxiWY_s.mjs.map +1 -0
- package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-yvPE4OsP.mjs} +2 -2
- package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
- package/dist/{site-url-Cm8-sJy7.mjs → site-url-CnHlmAs9.mjs} +2 -2
- package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-BILwiyGk.mjs} +4 -4
- package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
- package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BdAmbOwx.mjs} +50 -12
- package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
- package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
- package/dist/{taxonomy-DTZrIQpi.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-B7PPP2CC.d.mts} +1 -1
- package/dist/{transport-OnMNbsIA.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
- package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
- package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
- package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
- package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
- package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
- package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
- package/dist/{types-DawhLFwy.d.mts → types-BPzXTV9x.d.mts} +26 -1
- package/dist/{types-DawhLFwy.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
- package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
- package/dist/types-BUUVn1zr.d.mts.map +1 -0
- package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
- package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
- package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
- package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
- package/dist/types-CZI4E3qG.mjs +3 -0
- package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
- package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
- package/dist/{types-i8_uzhMD.d.mts → types-DTniiNto.d.mts} +19 -4
- package/dist/types-DTniiNto.d.mts.map +1 -0
- package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
- package/dist/types-DZk_y-MU.mjs.map +1 -0
- package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
- package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
- package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
- package/dist/user-C0um7wrg.mjs.map +1 -0
- package/dist/{validate-JCXcsqiY.mjs → validate-Bz4vqcX1.mjs} +6 -3
- package/dist/validate-Bz4vqcX1.mjs.map +1 -0
- package/dist/{validate-Dy6nkNls.d.mts → validate-CNwkPWzz.d.mts} +13 -5
- package/dist/validate-CNwkPWzz.d.mts.map +1 -0
- package/dist/{validation-Bq-VyKJg.mjs → validation-DgGTJm3u.mjs} +5 -5
- package/dist/{validation-Bq-VyKJg.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
- package/dist/version-D-5txk2m.mjs +7 -0
- package/dist/{version-CnS-Cr8A.mjs.map → version-D-5txk2m.mjs.map} +1 -1
- package/dist/{widgets-Bap1eS1X.mjs → widgets-DZfmAbE4.mjs} +47 -44
- package/dist/widgets-DZfmAbE4.mjs.map +1 -0
- package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
- package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
- package/package.json +10 -10
- package/src/api/handlers/content.ts +107 -8
- package/src/api/handlers/index.ts +2 -0
- package/src/api/handlers/marketplace.ts +2 -5
- package/src/api/handlers/registry.ts +70 -0
- package/src/api/handlers/seo.ts +9 -1
- package/src/api/openapi/document.ts +25 -0
- package/src/api/schemas/content.ts +33 -0
- package/src/api/schemas/schema.ts +13 -1
- 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.ts +48 -6
- 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/routes/api/schema/index.ts +7 -15
- package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
- package/src/astro/types.ts +8 -1
- package/src/bylines/index.ts +57 -0
- package/src/cli/commands/bundle-utils.ts +2 -0
- package/src/cli/commands/export-seed.ts +28 -12
- package/src/cli/commands/secrets.ts +2 -2
- package/src/components/EmDashImage.astro +22 -4
- package/src/components/Image.astro +20 -3
- package/src/database/instrumentation.ts +13 -0
- package/src/database/migrations/043_content_references.ts +121 -0
- package/src/database/migrations/runner.ts +2 -0
- 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 +172 -67
- package/src/index.ts +8 -1
- package/src/loader.ts +81 -39
- package/src/media/responsive.ts +125 -0
- package/src/plugins/cron.ts +3 -2
- package/src/plugins/index.ts +5 -0
- package/src/plugins/manifest-schema.ts +75 -0
- package/src/plugins/marketplace.ts +2 -5
- package/src/plugins/scheduler/node.ts +9 -2
- package/src/plugins/types.ts +12 -0
- package/src/query.ts +45 -7
- package/src/request-context.ts +8 -0
- package/src/scheduled-publish.ts +153 -0
- package/src/schema/types.ts +11 -1
- package/src/seed/apply.ts +16 -6
- package/src/seed/types.ts +9 -0
- package/src/seed/validate.ts +15 -0
- package/src/seo/index.ts +2 -28
- package/src/seo/media-url.ts +32 -0
- package/src/settings/index.ts +32 -40
- package/src/taxonomies/index.ts +79 -12
- package/src/utils/isolate-cache.ts +189 -0
- package/src/virtual-modules.d.ts +11 -0
- package/src/widgets/index.ts +57 -54
- package/dist/api-Cs7DAACP.mjs.map +0 -1
- package/dist/apply-BWMV4Zmw.mjs.map +0 -1
- package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
- package/dist/content-CyqOmOzm.mjs.map +0 -1
- package/dist/cron-DZovZUnC.mjs.map +0 -1
- package/dist/index-CjKdMZ3U.d.mts.map +0 -1
- package/dist/loader-Dyx8dhFV.mjs.map +0 -1
- package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
- package/dist/menus-BKkxXCmd.mjs.map +0 -1
- package/dist/query-Ctlq1aOk.mjs.map +0 -1
- package/dist/redirects-C0L9JUk4.mjs.map +0 -1
- package/dist/runner-pt6Wl-l-.mjs.map +0 -1
- package/dist/seo-DfjLvu8i.mjs.map +0 -1
- package/dist/settings-b5zW1R1T.mjs +0 -235
- package/dist/settings-b5zW1R1T.mjs.map +0 -1
- package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
- package/dist/types-Cj2S6FuC.mjs +0 -3
- package/dist/types-D8bhH891.mjs.map +0 -1
- package/dist/types-DbCWhHet.d.mts.map +0 -1
- package/dist/types-i8_uzhMD.d.mts.map +0 -1
- package/dist/user-DzEUl5zA.mjs.map +0 -1
- package/dist/validate-Dy6nkNls.d.mts.map +0 -1
- package/dist/validate-JCXcsqiY.mjs.map +0 -1
- package/dist/version-CnS-Cr8A.mjs +0 -7
- package/dist/widgets-Bap1eS1X.mjs.map +0 -1
- package/src/plugins/scheduler/piggyback.ts +0 -71
- /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive image helpers shared by the public Image components.
|
|
3
|
+
*
|
|
4
|
+
* These build a `srcset` for locally-stored / R2-stored media by delegating to
|
|
5
|
+
* Astro's configured image service (`astro:assets`). On Cloudflare that is the
|
|
6
|
+
* Images binding; on Node it is sharp; if neither is available it is a no-op
|
|
7
|
+
* passthrough. The calling `.astro` component passes Astro's `getImage` in so
|
|
8
|
+
* this module stays free of the `astro:assets` virtual import (which only
|
|
9
|
+
* resolves inside an Astro project, not in this precompiled package).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Standard responsive breakpoints. Matches CDN-provider srcset generation. */
|
|
13
|
+
export const RESPONSIVE_BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
|
|
14
|
+
|
|
15
|
+
/** Matches absolute http(s) URLs — the only shape Astro's image services optimize. */
|
|
16
|
+
const ABSOLUTE_HTTP_URL = /^https?:\/\//i;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pick the srcset widths to generate for an image rendered at `maxWidth`.
|
|
20
|
+
* Includes breakpoints up to 2x (retina) plus the rendered width itself, so the
|
|
21
|
+
* browser always has an exact-fit candidate.
|
|
22
|
+
*/
|
|
23
|
+
export function responsiveWidths(maxWidth: number): number[] {
|
|
24
|
+
const cap = maxWidth * 2;
|
|
25
|
+
const widths = new Set(RESPONSIVE_BREAKPOINTS.filter((w) => w <= cap));
|
|
26
|
+
widths.add(maxWidth);
|
|
27
|
+
return [...widths].toSorted((a, b) => a - b);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Build the `sizes` attribute for an image with a known display width. */
|
|
31
|
+
export function responsiveSizes(width: number | undefined): string {
|
|
32
|
+
return width ? `(min-width: ${width}px) ${width}px, 100vw` : "100vw";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make a same-origin media URL absolute so Astro's image service can optimize it.
|
|
37
|
+
*
|
|
38
|
+
* Astro only optimizes absolute http(s) URLs; a same-origin proxy path like
|
|
39
|
+
* `/_emdash/api/media/file/x.jpg` is otherwise treated as an unoptimizable
|
|
40
|
+
* public asset. Resolving it against the site's public origin (and authorizing
|
|
41
|
+
* that origin via `image.remotePatterns`) lets the service transform it.
|
|
42
|
+
*
|
|
43
|
+
* Only **same-origin** root-relative paths are resolved. Protocol-relative
|
|
44
|
+
* URLs (`//evil.com/x`) and backslash tricks (`/\evil.com`) also start with `/`
|
|
45
|
+
* but resolve to a different origin -- a classic SSRF vector once a
|
|
46
|
+
* remotePattern authorizes the media path -- so anything that escapes the
|
|
47
|
+
* origin is returned unchanged (and then skipped by `buildResponsiveImage`,
|
|
48
|
+
* which only accepts absolute http(s) URLs). Already-absolute URLs (CDN/public
|
|
49
|
+
* bucket) and non-path values (`data:`, `blob:`) are returned unchanged too.
|
|
50
|
+
*/
|
|
51
|
+
export function toAbsoluteMediaUrl(src: string, origin: string | undefined): string {
|
|
52
|
+
if (!src || !origin || !src.startsWith("/")) return src;
|
|
53
|
+
try {
|
|
54
|
+
const resolved = new URL(src, origin);
|
|
55
|
+
if (resolved.origin !== new URL(origin).origin) return src;
|
|
56
|
+
return resolved.href;
|
|
57
|
+
} catch {
|
|
58
|
+
return src;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Minimal structural subset of Astro's `getImage`. Astro's `ImageTransform`
|
|
64
|
+
* carries a `[key: string]: any` index signature, so the real `getImage` is
|
|
65
|
+
* assignable to this narrower type.
|
|
66
|
+
*/
|
|
67
|
+
export type GetImage = (options: {
|
|
68
|
+
src: string;
|
|
69
|
+
width?: number;
|
|
70
|
+
height?: number;
|
|
71
|
+
widths?: number[];
|
|
72
|
+
sizes?: string;
|
|
73
|
+
}) => Promise<{ src: string; srcSet?: { attribute?: string } | undefined }>;
|
|
74
|
+
|
|
75
|
+
export interface ResponsiveImage {
|
|
76
|
+
src: string;
|
|
77
|
+
srcset?: string;
|
|
78
|
+
sizes?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate a responsive `src`/`srcset`/`sizes` for a media URL via Astro's
|
|
83
|
+
* configured image service.
|
|
84
|
+
*
|
|
85
|
+
* Astro's image services (sharp, Cloudflare `/cdn-cgi/image`, and the default
|
|
86
|
+
* Cloudflare `cloudflare-binding` service) only optimize **absolute** URLs whose
|
|
87
|
+
* host is authorized via `image.domains` / `image.remotePatterns`. Anything else
|
|
88
|
+
* is passed through unchanged, which would yield a useless srcset (the same URL
|
|
89
|
+
* at every width descriptor). We therefore only attempt optimization for
|
|
90
|
+
* absolute http(s) URLs and verify the service actually rewrote the URL.
|
|
91
|
+
*
|
|
92
|
+
* Returns `null` so callers fall back to a plain `<img>` when:
|
|
93
|
+
* - dimensions are unknown (avoids an inferSize fetch on every render),
|
|
94
|
+
* - the URL is relative (a same-origin proxy/public asset Astro won't optimize),
|
|
95
|
+
* - the host isn't authorized (the service passed the URL through unchanged),
|
|
96
|
+
* - no image service is configured / `getImage` throws.
|
|
97
|
+
*/
|
|
98
|
+
export async function buildResponsiveImage(
|
|
99
|
+
getImage: GetImage,
|
|
100
|
+
opts: { src: string; width?: number; height?: number },
|
|
101
|
+
): Promise<ResponsiveImage | null> {
|
|
102
|
+
const { src, width, height } = opts;
|
|
103
|
+
if (!src || !width || !height) return null;
|
|
104
|
+
if (!ABSOLUTE_HTTP_URL.test(src)) return null;
|
|
105
|
+
try {
|
|
106
|
+
const sizes = responsiveSizes(width);
|
|
107
|
+
const result = await getImage({
|
|
108
|
+
src,
|
|
109
|
+
width,
|
|
110
|
+
height,
|
|
111
|
+
widths: responsiveWidths(width),
|
|
112
|
+
sizes,
|
|
113
|
+
});
|
|
114
|
+
// Passthrough: the service returned the source unchanged (unauthorized
|
|
115
|
+
// host or no optimization available). Don't emit a no-op srcset.
|
|
116
|
+
if (!result.src || result.src === src) return null;
|
|
117
|
+
return {
|
|
118
|
+
src: result.src,
|
|
119
|
+
srcset: result.srcSet?.attribute || undefined,
|
|
120
|
+
sizes,
|
|
121
|
+
};
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/plugins/cron.ts
CHANGED
|
@@ -34,8 +34,9 @@ export type RescheduleFn = () => void;
|
|
|
34
34
|
/**
|
|
35
35
|
* Executes overdue cron tasks.
|
|
36
36
|
*
|
|
37
|
-
* Called by platform
|
|
38
|
-
*
|
|
37
|
+
* Called by the platform driver: the NodeCronScheduler timer on Node, or the
|
|
38
|
+
* Worker's `scheduled()` handler (via runScheduledTasks) on Cloudflare.
|
|
39
|
+
* Stateless — all state lives in the database.
|
|
39
40
|
*/
|
|
40
41
|
export class CronExecutor {
|
|
41
42
|
constructor(
|
package/src/plugins/index.ts
CHANGED
|
@@ -63,6 +63,11 @@ export type { RouteResult, InvokeRouteOptions } from "./routes.js";
|
|
|
63
63
|
export { PluginManager, createPluginManager } from "./manager.js";
|
|
64
64
|
export type { PluginManagerOptions, PluginState } from "./manager.js";
|
|
65
65
|
|
|
66
|
+
// Scheduler (Node timer-based heartbeat; consumed by the generated
|
|
67
|
+
// virtual:emdash/scheduler module on non-serverless adapters)
|
|
68
|
+
export { NodeCronScheduler } from "./scheduler/node.js";
|
|
69
|
+
export type { CronScheduler, SystemCleanupFn } from "./scheduler/types.js";
|
|
70
|
+
|
|
66
71
|
// Sandbox
|
|
67
72
|
export {
|
|
68
73
|
NoopSandboxRunner,
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
* - Marketplace ingest extends this with publishing-specific fields
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import {
|
|
12
|
+
capabilitiesToDeclaredAccess,
|
|
13
|
+
declaredAccessToCapabilities,
|
|
14
|
+
} from "@emdash-cms/plugin-types";
|
|
11
15
|
import { z } from "zod";
|
|
12
16
|
|
|
17
|
+
import type { PluginManifest } from "./types.js";
|
|
18
|
+
|
|
13
19
|
// ── Enum values (must stay in sync with types.ts) ───────────────
|
|
14
20
|
|
|
15
21
|
/**
|
|
@@ -219,16 +225,63 @@ const pluginAdminConfigSchema = z.object({
|
|
|
219
225
|
.optional(),
|
|
220
226
|
});
|
|
221
227
|
|
|
228
|
+
// ── declaredAccess ──────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* An operation's constraint object. Open vocabulary: keys the runtime
|
|
232
|
+
* recognises are enforced, others are advisory. The bundler emits `{}` for a
|
|
233
|
+
* granted operation; presence (not value) signals the grant.
|
|
234
|
+
*/
|
|
235
|
+
const accessConstraints = z.record(z.string(), z.unknown());
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Structured trust contract embedded in the bundle manifest. Mirrors
|
|
239
|
+
* `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host
|
|
240
|
+
* subsystems; operations are modes of participation.
|
|
241
|
+
*/
|
|
242
|
+
const declaredAccessSchema = z.object({
|
|
243
|
+
content: z
|
|
244
|
+
.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })
|
|
245
|
+
.optional(),
|
|
246
|
+
media: z
|
|
247
|
+
.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })
|
|
248
|
+
.optional(),
|
|
249
|
+
network: z
|
|
250
|
+
.object({
|
|
251
|
+
// allowedHosts: absent = unrestricted; present = host-restricted. Reject
|
|
252
|
+
// an empty array (which the decoder would otherwise have to treat as
|
|
253
|
+
// deny-all) to match the record lexicon's `minLength: 1` and keep the
|
|
254
|
+
// "absent vs empty" distinction from ever reaching enforcement ambiguous.
|
|
255
|
+
request: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional(),
|
|
256
|
+
})
|
|
257
|
+
.optional(),
|
|
258
|
+
email: z
|
|
259
|
+
.object({
|
|
260
|
+
send: accessConstraints.optional(),
|
|
261
|
+
events: accessConstraints.optional(),
|
|
262
|
+
transport: accessConstraints.optional(),
|
|
263
|
+
})
|
|
264
|
+
.optional(),
|
|
265
|
+
page: z.object({ fragments: accessConstraints.optional() }).optional(),
|
|
266
|
+
users: z.object({ read: accessConstraints.optional() }).optional(),
|
|
267
|
+
});
|
|
268
|
+
|
|
222
269
|
// ── Main schema ─────────────────────────────────────────────────
|
|
223
270
|
|
|
224
271
|
/**
|
|
225
272
|
* Zod schema matching the PluginManifest interface from types.ts.
|
|
226
273
|
*
|
|
227
274
|
* Every JSON.parse of a manifest.json should validate through this.
|
|
275
|
+
*
|
|
276
|
+
* `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the
|
|
277
|
+
* runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing
|
|
278
|
+
* to make them consistent (declaredAccess authoritative when present). Kept a
|
|
279
|
+
* plain object (no `.transform`) because callers `.pick()`/`.extend()` it.
|
|
228
280
|
*/
|
|
229
281
|
export const pluginManifestSchema = z.object({
|
|
230
282
|
id: z.string().min(1),
|
|
231
283
|
version: z.string().min(1),
|
|
284
|
+
declaredAccess: declaredAccessSchema.optional(),
|
|
232
285
|
capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),
|
|
233
286
|
allowedHosts: z.array(z.string()),
|
|
234
287
|
storage: z.record(z.string(), storageCollectionSchema),
|
|
@@ -254,6 +307,28 @@ export const pluginManifestSchema = z.object({
|
|
|
254
307
|
|
|
255
308
|
export type ValidatedPluginManifest = z.infer<typeof pluginManifestSchema>;
|
|
256
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Reconcile a parsed manifest's trust contract with its enforcement currency.
|
|
312
|
+
* `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`
|
|
313
|
+
* are re-derived from it so what the runtime enforces always matches what was
|
|
314
|
+
* recorded and consented to. A pre-migration bundle without `declaredAccess`
|
|
315
|
+
* has it derived from the legacy capability list instead. The result always
|
|
316
|
+
* carries both, mutually consistent. Apply this at every bundle-parse site.
|
|
317
|
+
*/
|
|
318
|
+
export function reconcileManifestAccess(manifest: ValidatedPluginManifest): PluginManifest {
|
|
319
|
+
const reconciled: ValidatedPluginManifest = manifest.declaredAccess
|
|
320
|
+
? { ...manifest, ...declaredAccessToCapabilities(manifest.declaredAccess) }
|
|
321
|
+
: {
|
|
322
|
+
...manifest,
|
|
323
|
+
declaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts),
|
|
324
|
+
};
|
|
325
|
+
// Block Kit admin elements are typed as `unknown` by the Zod schema (their
|
|
326
|
+
// Element shape is validated at render time), so the validated manifest
|
|
327
|
+
// needs a structural cast up to the runtime PluginManifest.
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- admin elements are unknown[] in Zod; Element type checked at render time
|
|
329
|
+
return reconciled as unknown as PluginManifest;
|
|
330
|
+
}
|
|
331
|
+
|
|
257
332
|
/**
|
|
258
333
|
* Normalize a manifest hook entry — plain strings become `{ name }` objects.
|
|
259
334
|
*/
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createGzipDecoder, unpackTar } from "modern-tar";
|
|
10
10
|
|
|
11
|
-
import { pluginManifestSchema } from "./manifest-schema.js";
|
|
11
|
+
import { pluginManifestSchema, reconcileManifestAccess } from "./manifest-schema.js";
|
|
12
12
|
import type { PluginManifest } from "./types.js";
|
|
13
13
|
|
|
14
14
|
// ── Module-level regex patterns ───────────────────────────────────
|
|
@@ -495,10 +495,7 @@ export async function extractBundle(tarballBytes: Uint8Array): Promise<PluginBun
|
|
|
495
495
|
"INVALID_BUNDLE",
|
|
496
496
|
);
|
|
497
497
|
}
|
|
498
|
-
|
|
499
|
-
// for the Element[] type (Block Kit validation happens at render time).
|
|
500
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
|
|
501
|
-
manifest = result.data as unknown as PluginManifest;
|
|
498
|
+
manifest = reconcileManifestAccess(result.data);
|
|
502
499
|
} catch (err) {
|
|
503
500
|
if (err instanceof MarketplaceError) throw err;
|
|
504
501
|
throw new MarketplaceError(
|
|
@@ -15,8 +15,15 @@ import type { CronScheduler, SystemCleanupFn } from "./types.js";
|
|
|
15
15
|
/** Minimum polling interval (ms) — prevents tight loops if next_run_at is in the past */
|
|
16
16
|
const MIN_INTERVAL_MS = 1000;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Maximum polling interval (ms). Each wake runs the maintenance pass — stale
|
|
20
|
+
* lock recovery *and* the scheduled-publishing sweep + system cleanup. The cap
|
|
21
|
+
* is the worst-case latency for scheduled content when no plugin cron task is
|
|
22
|
+
* due sooner (`getNextDueTime()` only knows about cron tasks, not content
|
|
23
|
+
* `scheduled_at`). Held at 60s so Node publish latency matches the Cloudflare
|
|
24
|
+
* Cron Trigger cadence (`* * * * *`) rather than lagging up to five minutes.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_INTERVAL_MS = 60 * 1000;
|
|
20
27
|
|
|
21
28
|
export class NodeCronScheduler implements CronScheduler {
|
|
22
29
|
private timer: ReturnType<typeof setTimeout> | null = null;
|
package/src/plugins/types.ts
CHANGED
|
@@ -19,10 +19,13 @@ import type { Element } from "@emdash-cms/blocks";
|
|
|
19
19
|
// (e.g. `import { PluginCapability } from "../plugins/types.js"`).
|
|
20
20
|
import {
|
|
21
21
|
CAPABILITY_RENAMES,
|
|
22
|
+
capabilitiesToDeclaredAccess,
|
|
23
|
+
declaredAccessToCapabilities,
|
|
22
24
|
isDeprecatedCapability,
|
|
23
25
|
normalizeCapabilities,
|
|
24
26
|
normalizeCapability,
|
|
25
27
|
type CurrentPluginCapability,
|
|
28
|
+
type DeclaredAccess,
|
|
26
29
|
type DeprecatedPluginCapability,
|
|
27
30
|
type ManifestHookEntry,
|
|
28
31
|
type ManifestRouteEntry,
|
|
@@ -40,10 +43,13 @@ import type { FieldType } from "../schema/types.js";
|
|
|
40
43
|
|
|
41
44
|
export {
|
|
42
45
|
CAPABILITY_RENAMES,
|
|
46
|
+
capabilitiesToDeclaredAccess,
|
|
47
|
+
declaredAccessToCapabilities,
|
|
43
48
|
isDeprecatedCapability,
|
|
44
49
|
normalizeCapabilities,
|
|
45
50
|
normalizeCapability,
|
|
46
51
|
type CurrentPluginCapability,
|
|
52
|
+
type DeclaredAccess,
|
|
47
53
|
type DeprecatedPluginCapability,
|
|
48
54
|
type ManifestHookEntry,
|
|
49
55
|
type ManifestRouteEntry,
|
|
@@ -1336,6 +1342,12 @@ export interface PluginAdminExports {
|
|
|
1336
1342
|
export interface PluginManifest {
|
|
1337
1343
|
id: string;
|
|
1338
1344
|
version: string;
|
|
1345
|
+
/**
|
|
1346
|
+
* The trust contract (see `@emdash-cms/plugin-types`). Authoritative;
|
|
1347
|
+
* `capabilities`/`allowedHosts` are derived from it at the parse boundary
|
|
1348
|
+
* via `reconcileManifestAccess`. Optional during the wire-format migration.
|
|
1349
|
+
*/
|
|
1350
|
+
declaredAccess?: DeclaredAccess;
|
|
1339
1351
|
capabilities: PluginCapability[];
|
|
1340
1352
|
allowedHosts: string[];
|
|
1341
1353
|
storage: PluginStorageConfig;
|
package/src/query.ts
CHANGED
|
@@ -101,13 +101,19 @@ export interface CollectionFilter {
|
|
|
101
101
|
*/
|
|
102
102
|
cursor?: string;
|
|
103
103
|
/**
|
|
104
|
-
* Filter by field values, taxonomy terms, or ranges.
|
|
104
|
+
* Filter by field values, taxonomy terms, byline credits, or ranges.
|
|
105
105
|
*
|
|
106
106
|
* Taxonomy names are detected automatically and filtered via JOIN.
|
|
107
|
+
* The reserved `byline` key filters by byline credit (any credit, not
|
|
108
|
+
* just the primary one) via the `_emdash_content_bylines` junction
|
|
109
|
+
* table; its value is one or more byline translation groups. This
|
|
110
|
+
* matches co-authored entries, which `primary_byline_id` alone misses.
|
|
107
111
|
* Other keys are treated as column filters on the content table.
|
|
108
112
|
*
|
|
109
113
|
* @example { category: 'news' } - Filter by taxonomy term
|
|
110
114
|
* @example { category: ['news', 'featured'] } - Filter by multiple terms (OR)
|
|
115
|
+
* @example { byline: '01HXYZ...' } - Entries credited to a byline (any position)
|
|
116
|
+
* @example { byline: ['01HXYZ...', '01HABC...'] } - Credited to any of these bylines (OR)
|
|
111
117
|
* @example { series: 'main' } - Exact match on a content field
|
|
112
118
|
* @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - Date range
|
|
113
119
|
*/
|
|
@@ -241,6 +247,17 @@ function dataStr(data: Record<string, unknown>, key: string, fallback = ""): str
|
|
|
241
247
|
return typeof val === "string" ? val : fallback;
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
/** Safely read a date-like field from a Record */
|
|
251
|
+
function dataDate(data: Record<string, unknown>, key: string): Date | undefined {
|
|
252
|
+
const val = data[key];
|
|
253
|
+
if (val instanceof Date) {
|
|
254
|
+
return Number.isNaN(val.getTime()) ? undefined : val;
|
|
255
|
+
}
|
|
256
|
+
if (typeof val !== "string" && typeof val !== "number") return undefined;
|
|
257
|
+
const date = new Date(val);
|
|
258
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
259
|
+
}
|
|
260
|
+
|
|
244
261
|
/** Type guard for Record<string, unknown> */
|
|
245
262
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
246
263
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -533,7 +550,9 @@ async function getEmDashCollectionUncached<T extends string, D = InferCollection
|
|
|
533
550
|
// round-trip cost on remote databases (D1 replicas, etc.).
|
|
534
551
|
await Promise.all([
|
|
535
552
|
hydrateEntryBylines(type, entriesWithEdit),
|
|
536
|
-
|
|
553
|
+
// Hydrate terms in the same locale the content rows were resolved to,
|
|
554
|
+
// otherwise localized entries get default-locale taxonomy terms (#1441).
|
|
555
|
+
hydrateEntryTerms(type, entriesWithEdit, resolvedLocale),
|
|
537
556
|
]);
|
|
538
557
|
|
|
539
558
|
return { entries: entriesWithEdit, nextCursor, cacheHint: cacheHint ?? {} };
|
|
@@ -594,10 +613,10 @@ export async function getEmDashEntry<T extends string, D = InferCollectionData<T
|
|
|
594
613
|
function isVisible(entry: ContentEntry<D>): boolean {
|
|
595
614
|
const data = entryData(entry);
|
|
596
615
|
const status = dataStr(data, "status");
|
|
597
|
-
const scheduledAt =
|
|
616
|
+
const scheduledAt = dataDate(data, "scheduledAt");
|
|
598
617
|
const isPublished = status === "published";
|
|
599
618
|
const isScheduledAndReady =
|
|
600
|
-
status === "scheduled" && scheduledAt &&
|
|
619
|
+
status === "scheduled" && scheduledAt !== undefined && scheduledAt.getTime() <= Date.now();
|
|
601
620
|
return isPublished || !!isScheduledAndReady;
|
|
602
621
|
}
|
|
603
622
|
|
|
@@ -611,7 +630,17 @@ export async function getEmDashEntry<T extends string, D = InferCollectionData<T
|
|
|
611
630
|
wrapped: ContentEntry<D>,
|
|
612
631
|
opts: { isPreview: boolean; fallbackLocale?: string; cacheHint: CacheHint },
|
|
613
632
|
): Promise<EntryResult<D>> {
|
|
614
|
-
|
|
633
|
+
// Hydrate terms in the entry's resolved locale (fallback-aware) so a
|
|
634
|
+
// localized entry never picks up default-locale taxonomy terms (#1441).
|
|
635
|
+
// When i18n is disabled we leave the locale unset to preserve the
|
|
636
|
+
// legacy "do not filter by locale" behaviour.
|
|
637
|
+
const termLocale = isI18nEnabled()
|
|
638
|
+
? dataStr(entryData(wrapped), "locale") || undefined
|
|
639
|
+
: undefined;
|
|
640
|
+
await Promise.all([
|
|
641
|
+
hydrateEntryBylines(type, [wrapped]),
|
|
642
|
+
hydrateEntryTerms(type, [wrapped], termLocale),
|
|
643
|
+
]);
|
|
615
644
|
return {
|
|
616
645
|
entry: wrapped,
|
|
617
646
|
isPreview: opts.isPreview,
|
|
@@ -788,9 +817,18 @@ async function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]):
|
|
|
788
817
|
* results and call getEntryTerms() per entry. With hydration, the list page
|
|
789
818
|
* stays at a single round-trip for term data.
|
|
790
819
|
*
|
|
820
|
+
* `locale` must be the locale the entries were resolved to. It is forwarded to
|
|
821
|
+
* `getAllTermsForEntries` so terms are returned in the entry's locale rather
|
|
822
|
+
* than falling back to the request-context / default locale (#1441). Pass
|
|
823
|
+
* `undefined` to keep the legacy "do not filter by locale" behaviour.
|
|
824
|
+
*
|
|
791
825
|
* Fails silently if the taxonomy tables don't exist yet (pre-migration).
|
|
792
826
|
*/
|
|
793
|
-
async function hydrateEntryTerms<D>(
|
|
827
|
+
async function hydrateEntryTerms<D>(
|
|
828
|
+
type: string,
|
|
829
|
+
entries: ContentEntry<D>[],
|
|
830
|
+
locale?: string,
|
|
831
|
+
): Promise<void> {
|
|
794
832
|
if (entries.length === 0) return;
|
|
795
833
|
|
|
796
834
|
try {
|
|
@@ -799,7 +837,7 @@ async function hydrateEntryTerms<D>(type: string, entries: ContentEntry<D>[]): P
|
|
|
799
837
|
const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
|
|
800
838
|
if (ids.length === 0) return;
|
|
801
839
|
|
|
802
|
-
const termsMap = await getAllTermsForEntries(type, ids);
|
|
840
|
+
const termsMap = await getAllTermsForEntries(type, ids, { locale });
|
|
803
841
|
|
|
804
842
|
for (const entry of entries) {
|
|
805
843
|
const data = entryData(entry);
|
package/src/request-context.ts
CHANGED
|
@@ -39,6 +39,13 @@ export interface RequestMetrics {
|
|
|
39
39
|
dbLastOffset: number | null;
|
|
40
40
|
cacheHits: number;
|
|
41
41
|
cacheMisses: number;
|
|
42
|
+
/**
|
|
43
|
+
* Physical database round trips. Differs from `dbCount` (logical queries)
|
|
44
|
+
* when a backend batches: the DO SQL driver coalesces same-turn SELECTs into
|
|
45
|
+
* one RPC, so `rpcCount` can be far lower than `dbCount`. Bumped by the
|
|
46
|
+
* adapter, not the Kysely log hook.
|
|
47
|
+
*/
|
|
48
|
+
rpcCount: number;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export function createRequestMetrics(start: number): RequestMetrics {
|
|
@@ -50,6 +57,7 @@ export function createRequestMetrics(start: number): RequestMetrics {
|
|
|
50
57
|
dbLastOffset: null,
|
|
51
58
|
cacheHits: 0,
|
|
52
59
|
cacheMisses: 0,
|
|
60
|
+
rpcCount: 0,
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
|
|
@@ -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
|
+
}
|
package/src/schema/types.ts
CHANGED
|
@@ -102,7 +102,16 @@ export type CollectionSource =
|
|
|
102
102
|
/** Sub-field definition for repeater fields */
|
|
103
103
|
export interface RepeaterSubField {
|
|
104
104
|
slug: string;
|
|
105
|
-
type:
|
|
105
|
+
type:
|
|
106
|
+
| "string"
|
|
107
|
+
| "text"
|
|
108
|
+
| "url"
|
|
109
|
+
| "number"
|
|
110
|
+
| "integer"
|
|
111
|
+
| "boolean"
|
|
112
|
+
| "datetime"
|
|
113
|
+
| "select"
|
|
114
|
+
| "image";
|
|
106
115
|
label: string;
|
|
107
116
|
required?: boolean;
|
|
108
117
|
options?: string[]; // For select sub-fields
|
|
@@ -118,6 +127,7 @@ export const REPEATER_SUB_FIELD_TYPES = [
|
|
|
118
127
|
"boolean",
|
|
119
128
|
"datetime",
|
|
120
129
|
"select",
|
|
130
|
+
"image",
|
|
121
131
|
] as const;
|
|
122
132
|
|
|
123
133
|
export interface FieldValidation {
|