emdash 0.20.0 → 0.21.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-BzIHV3sw.d.mts → adapters-BxSmgtbF.d.mts} +1 -1
- package/dist/{adapters-BzIHV3sw.d.mts.map → adapters-BxSmgtbF.d.mts.map} +1 -1
- package/dist/{allowed-origins-B1u7Qnvg.mjs → allowed-origins-BqC8cul8.mjs} +2 -2
- package/dist/{allowed-origins-B1u7Qnvg.mjs.map → allowed-origins-BqC8cul8.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +13 -12
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +3 -2
- package/dist/{api-DStv36ik.mjs → api-DxjIV2o8.mjs} +13 -13
- package/dist/{api-DStv36ik.mjs.map → api-DxjIV2o8.mjs.map} +1 -1
- package/dist/{api-tokens-DPfhPu5V.mjs → api-tokens-BFFkB0jB.mjs} +2 -2
- package/dist/{api-tokens-DPfhPu5V.mjs.map → api-tokens-BFFkB0jB.mjs.map} +1 -1
- package/dist/{apply-Dr7snAMT.mjs → apply-CLjxheyb.mjs} +12 -12
- package/dist/{apply-Dr7snAMT.mjs.map → apply-CLjxheyb.mjs.map} +1 -1
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +50 -15
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +11 -2
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +3 -2
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +1 -1
- package/dist/astro/middleware.mjs +63 -60
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -4
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -4
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -8
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/index.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -26
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -4
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +5 -4
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
- package/dist/astro/routes/api/auth/invite/complete.mjs +9 -8
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +6 -5
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -7
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +5 -4
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -4
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -9
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -7
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +9 -8
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -7
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -9
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +3 -3
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +10 -9
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +8 -7
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -8
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -7
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -9
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +14 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +20 -19
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -5
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +6 -5
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +8 -7
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +10 -9
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +6 -5
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
- package/dist/astro/routes/api/oauth/token.mjs +5 -5
- package/dist/astro/routes/api/openapi.json.mjs +3 -2
- 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 +7 -6
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -6
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +8 -7
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +8 -7
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +5 -5
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -26
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +28 -26
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +9 -8
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +8 -7
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +9 -8
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +5 -5
- package/dist/astro/routes/api/search/suggest.mjs +8 -7
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -7
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +8 -7
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/email.mjs +3 -3
- package/dist/astro/routes/api/settings.mjs +11 -9
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +10 -9
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +9 -8
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +19 -18
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
- package/dist/astro/routes/api/setup/index.mjs +20 -18
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +5 -4
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +5 -4
- package/dist/astro/routes/api/themes/preview.mjs.map +1 -1
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- 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 +5 -5
- 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 +2 -2
- package/dist/astro/routes/robots.txt.mjs +5 -4
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +6 -5
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +12 -12
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-DsMSVSaY.mjs → authorize-D5gfBVU5.mjs} +2 -2
- package/dist/{authorize-DsMSVSaY.mjs.map → authorize-D5gfBVU5.mjs.map} +1 -1
- package/dist/{byline-DUx48sJp.mjs → byline-V_Qp1Ziw.mjs} +27 -14
- package/dist/byline-V_Qp1Ziw.mjs.map +1 -0
- package/dist/{byline-fields-8TMtkBnH.mjs → byline-fields-B0NO1yUB.mjs} +3 -3
- package/dist/{byline-fields-8TMtkBnH.mjs.map → byline-fields-B0NO1yUB.mjs.map} +1 -1
- package/dist/{byline-fields-DbibsvTl.d.mts → byline-fields-CQJRIQkn.d.mts} +32 -32
- package/dist/{byline-fields-DbibsvTl.d.mts.map → byline-fields-CQJRIQkn.d.mts.map} +1 -1
- package/dist/{byline-fields--WxSNS79.mjs → byline-fields-nBVqK_Ff.mjs} +2 -2
- package/dist/{byline-fields--WxSNS79.mjs.map → byline-fields-nBVqK_Ff.mjs.map} +1 -1
- package/dist/{byline-registry-CWP7I71B.mjs → byline-registry-DedidtqC.mjs} +2 -2
- package/dist/{byline-registry-CWP7I71B.mjs.map → byline-registry-DedidtqC.mjs.map} +1 -1
- package/dist/{bylines-BdxWCnPL.mjs → bylines-B2NWnIwS.mjs} +2 -2
- package/dist/{bylines-BdxWCnPL.mjs.map → bylines-B2NWnIwS.mjs.map} +1 -1
- package/dist/{bylines-s8c2DXbH.mjs → bylines-DfGDnred.mjs} +7 -7
- package/dist/{bylines-s8c2DXbH.mjs.map → bylines-DfGDnred.mjs.map} +1 -1
- package/dist/{cache-B_HzASVT.mjs → cache-DTTHWD8n.mjs} +1 -1
- package/dist/{cache-B_HzASVT.mjs.map → cache-DTTHWD8n.mjs.map} +1 -1
- package/dist/{challenge-store-DXX3rfdI.mjs → challenge-store-woE0bbCf.mjs} +1 -1
- package/dist/{challenge-store-DXX3rfdI.mjs.map → challenge-store-woE0bbCf.mjs.map} +1 -1
- package/dist/cli/index.mjs +19 -18
- 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/{comments-Vkivawyl.mjs → comments-D2hNuxNa.mjs} +1 -1
- package/dist/{comments-Vkivawyl.mjs.map → comments-D2hNuxNa.mjs.map} +1 -1
- package/dist/{components-CK0cuUoH.mjs → components-DYKp2gmo.mjs} +1 -1
- package/dist/{components-CK0cuUoH.mjs.map → components-DYKp2gmo.mjs.map} +1 -1
- package/dist/{context-Y7BRkWes.mjs → context-Cm4pt1Ws.mjs} +5 -5
- package/dist/{context-Y7BRkWes.mjs.map → context-Cm4pt1Ws.mjs.map} +1 -1
- package/dist/{cron-BJ2ClIlj.mjs → cron-DdEVrQ2Y.mjs} +1 -1
- package/dist/{cron-BJ2ClIlj.mjs.map → cron-DdEVrQ2Y.mjs.map} +1 -1
- package/dist/{dashboard-2JgAMWxK.mjs → dashboard-C-UYpps0.mjs} +1 -1
- package/dist/{dashboard-2JgAMWxK.mjs.map → dashboard-C-UYpps0.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-CtzxKBxe.mjs → db-errors-BluWkwGI.mjs} +1 -1
- package/dist/{db-errors-CtzxKBxe.mjs.map → db-errors-BluWkwGI.mjs.map} +1 -1
- package/dist/{default-IlBaTFxM.mjs → default-NHGuJzQ3.mjs} +1 -1
- package/dist/{default-IlBaTFxM.mjs.map → default-NHGuJzQ3.mjs.map} +1 -1
- package/dist/{device-flow-R23SIbQ2.mjs → device-flow-BQApWgnW.mjs} +4 -4
- package/dist/{device-flow-R23SIbQ2.mjs.map → device-flow-BQApWgnW.mjs.map} +1 -1
- package/dist/{email-console-DHT2Fbpj.mjs → email-console-BbU3RbWv.mjs} +1 -1
- package/dist/{email-console-DHT2Fbpj.mjs.map → email-console-BbU3RbWv.mjs.map} +1 -1
- package/dist/{error-RwM4dD35.mjs → error-CNn_w7jf.mjs} +1 -1
- package/dist/{error-RwM4dD35.mjs.map → error-CNn_w7jf.mjs.map} +1 -1
- package/dist/{escape-Ds07EEyu.mjs → escape-DPgcxcpL.mjs} +1 -1
- package/dist/{escape-Ds07EEyu.mjs.map → escape-DPgcxcpL.mjs.map} +1 -1
- package/dist/{fts-manager-1RgHmopc.mjs → fts-manager-Cx5z8jdA.mjs} +1 -1
- package/dist/{fts-manager-1RgHmopc.mjs.map → fts-manager-Cx5z8jdA.mjs.map} +1 -1
- package/dist/{hash-9w3pd3-m.mjs → hash-DlvIFn0b.mjs} +1 -1
- package/dist/{hash-9w3pd3-m.mjs.map → hash-DlvIFn0b.mjs.map} +1 -1
- package/dist/{import-Dh8bWmyq.mjs → import-KyxT1Mbs.mjs} +3 -3
- package/dist/{import-Dh8bWmyq.mjs.map → import-KyxT1Mbs.mjs.map} +1 -1
- package/dist/{index-B1keaX5Y.d.mts → index-D2VAiumu.d.mts} +15 -15
- package/dist/{index-B1keaX5Y.d.mts.map → index-D2VAiumu.d.mts.map} +1 -1
- package/dist/{index-DR56od45.d.mts → index-uT2yR66F.d.mts} +3 -3
- package/dist/{index-DR56od45.d.mts.map → index-uT2yR66F.d.mts.map} +1 -1
- package/dist/index.d.mts +16 -16
- package/dist/index.mjs +48 -46
- package/dist/init-lock-DlBHjf9-.mjs +83 -0
- package/dist/init-lock-DlBHjf9-.mjs.map +1 -0
- package/dist/{load-BBetCvLC.mjs → load-Dq91b_DK.mjs} +1 -1
- package/dist/{load-BBetCvLC.mjs.map → load-Dq91b_DK.mjs.map} +1 -1
- package/dist/{loader-ZN1ll-d-.mjs → loader-BqWjcH3h.mjs} +2 -2
- package/dist/{loader-ZN1ll-d-.mjs.map → loader-BqWjcH3h.mjs.map} +1 -1
- package/dist/{manifest-schema-BtwbL_vj.mjs → manifest-schema-DFPeqMAn.mjs} +1 -1
- package/dist/{manifest-schema-BtwbL_vj.mjs.map → manifest-schema-DFPeqMAn.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -2
- package/dist/media/local-runtime.d.mts +11 -11
- package/dist/media/local-runtime.mjs +4 -3
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-allowlist-Dknq-OFY.mjs → media-allowlist-_A0SuDn4.mjs} +2 -2
- package/dist/{media-allowlist-Dknq-OFY.mjs.map → media-allowlist-_A0SuDn4.mjs.map} +1 -1
- package/dist/{media-url-VClf8glU.mjs → media-url-CqLd69IO.mjs} +1 -1
- package/dist/{media-url-VClf8glU.mjs.map → media-url-CqLd69IO.mjs.map} +1 -1
- package/dist/{menus-DrQLusqj.mjs → menus-Ryk9L7fT.mjs} +9 -9
- package/dist/{menus-DrQLusqj.mjs.map → menus-Ryk9L7fT.mjs.map} +1 -1
- package/dist/{mime-CCEzze7W.mjs → mime-YbtlEtvS.mjs} +1 -1
- package/dist/{mime-CCEzze7W.mjs.map → mime-YbtlEtvS.mjs.map} +1 -1
- package/dist/{mode-CO2vQHfq.mjs → mode-CGXzIbD8.mjs} +1 -1
- package/dist/{mode-CO2vQHfq.mjs.map → mode-CGXzIbD8.mjs.map} +1 -1
- package/dist/{normalize-CK5o04zr.mjs → normalize-DKsg36ty.mjs} +1 -1
- package/dist/{normalize-CK5o04zr.mjs.map → normalize-DKsg36ty.mjs.map} +1 -1
- package/dist/{oauth-authorization-Bw4NdF_S.mjs → oauth-authorization-C2kVyjXI.mjs} +4 -4
- package/dist/{oauth-authorization-Bw4NdF_S.mjs.map → oauth-authorization-C2kVyjXI.mjs.map} +1 -1
- package/dist/{oauth-clients-BGGFp57s.mjs → oauth-clients-BC873NCV.mjs} +1 -1
- package/dist/{oauth-clients-BGGFp57s.mjs.map → oauth-clients-BC873NCV.mjs.map} +1 -1
- package/dist/{oauth-state-store-97x0xtN2.mjs → oauth-state-store-Cd--TUaq.mjs} +1 -1
- package/dist/{oauth-state-store-97x0xtN2.mjs.map → oauth-state-store-Cd--TUaq.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-B_vnZHKO.mjs → oauth-user-lookup-e4wOvDud.mjs} +1 -1
- package/dist/{oauth-user-lookup-B_vnZHKO.mjs.map → oauth-user-lookup-e4wOvDud.mjs.map} +1 -1
- package/dist/{options-DyYIYpPd.d.mts → options-9kLgkE8m.d.mts} +3 -3
- package/dist/{options-DyYIYpPd.d.mts.map → options-9kLgkE8m.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-CrGndy1A.mjs → parse-DzSrk1t8.mjs} +2 -2
- package/dist/{parse-CrGndy1A.mjs.map → parse-DzSrk1t8.mjs.map} +1 -1
- package/dist/{passkey-config-C3QgnQnU.mjs → passkey-config-BpjbE_Uv.mjs} +1 -1
- package/dist/{passkey-config-C3QgnQnU.mjs.map → passkey-config-BpjbE_Uv.mjs.map} +1 -1
- package/dist/{placeholder-BZxr8W1j.mjs → placeholder-2xumZh4g.mjs} +1 -1
- package/dist/{placeholder-BZxr8W1j.mjs.map → placeholder-2xumZh4g.mjs.map} +1 -1
- package/dist/{placeholder-CVBv5z8k.d.mts → placeholder-BevVKfay.d.mts} +1 -1
- package/dist/{placeholder-CVBv5z8k.d.mts.map → placeholder-BevVKfay.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/{preview-BfuRkVKW.mjs → preview-Dqv2hwXr.mjs} +2 -2
- package/dist/{preview-BfuRkVKW.mjs.map → preview-Dqv2hwXr.mjs.map} +1 -1
- package/dist/{public-url-BFVC2OTJ.mjs → public-url-D_zARuvZ.mjs} +1 -1
- package/dist/{public-url-BFVC2OTJ.mjs.map → public-url-D_zARuvZ.mjs.map} +1 -1
- package/dist/{query-CbUcI4Xk.mjs → query-Crm038Mc.mjs} +9 -9
- package/dist/{query-CbUcI4Xk.mjs.map → query-Crm038Mc.mjs.map} +1 -1
- package/dist/{rate-limit-C7hjdkS5.mjs → rate-limit-hRTBqmw1.mjs} +2 -2
- package/dist/{rate-limit-C7hjdkS5.mjs.map → rate-limit-hRTBqmw1.mjs.map} +1 -1
- package/dist/{redirect-B_q19j4v.mjs → redirect-C-OOkyku.mjs} +1 -1
- package/dist/{redirect-B_q19j4v.mjs.map → redirect-C-OOkyku.mjs.map} +1 -1
- package/dist/{redirects-CCbCqCCd.mjs → redirects-6Zg2SoYo.mjs} +8 -9
- package/dist/{redirects-CCbCqCCd.mjs.map → redirects-6Zg2SoYo.mjs.map} +1 -1
- package/dist/{redirects-DxVoR7PI.mjs → redirects-CP3TnTLO.mjs} +20 -14
- package/dist/redirects-CP3TnTLO.mjs.map +1 -0
- package/dist/{registry-brYh-rAT.mjs → registry-diMzD1Wf.mjs} +3 -3
- package/dist/{registry-brYh-rAT.mjs.map → registry-diMzD1Wf.mjs.map} +1 -1
- package/dist/{request-cache-D32LpnmI.mjs → request-cache-UwmBAiUK.mjs} +1 -1
- package/dist/{request-cache-D32LpnmI.mjs.map → request-cache-UwmBAiUK.mjs.map} +1 -1
- package/dist/{request-meta-7ByVLxB-.mjs → request-meta-DPechd0W.mjs} +2 -2
- package/dist/{request-meta-7ByVLxB-.mjs.map → request-meta-DPechd0W.mjs.map} +1 -1
- package/dist/{resolve-BqYMVG0D.mjs → resolve-B3NUUtVY.mjs} +1 -1
- package/dist/{resolve-BqYMVG0D.mjs.map → resolve-B3NUUtVY.mjs.map} +1 -1
- package/dist/{runner-DTdhuI9i.d.mts → runner-C8vcbvCe.d.mts} +2 -2
- package/dist/{runner-DTdhuI9i.d.mts.map → runner-C8vcbvCe.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-C1E70ug_.mjs → schema-BDOkd3OU.mjs} +4 -4
- package/dist/{schema-C1E70ug_.mjs.map → schema-BDOkd3OU.mjs.map} +1 -1
- package/dist/{search-B3SGZw91.mjs → search-Bs_J_EW-.mjs} +3 -3
- package/dist/{search-B3SGZw91.mjs.map → search-Bs_J_EW-.mjs.map} +1 -1
- package/dist/{secrets-ChPTmy9x.mjs → secrets-C8xmE6mR.mjs} +21 -11
- package/dist/secrets-C8xmE6mR.mjs.map +1 -0
- package/dist/{sections-D_lVzwRZ.mjs → sections-P0zuBlyz.mjs} +2 -2
- package/dist/{sections-D_lVzwRZ.mjs.map → sections-P0zuBlyz.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +14 -13
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.mjs +1 -1
- package/dist/{seo-D_LPkOtu.mjs → seo-CLhm-Fmb.mjs} +1 -1
- package/dist/{seo-D_LPkOtu.mjs.map → seo-CLhm-Fmb.mjs.map} +1 -1
- package/dist/{seo-B5e6y9Wk.mjs → seo-DpNgGQjF.mjs} +1 -1
- package/dist/{seo-B5e6y9Wk.mjs.map → seo-DpNgGQjF.mjs.map} +1 -1
- package/dist/{service-ChDcsTBs.mjs → service-CDQQnT8W.mjs} +2 -2
- package/dist/{service-ChDcsTBs.mjs.map → service-CDQQnT8W.mjs.map} +1 -1
- package/dist/{settings-DfxiWY_s.mjs → settings-BjBsmVAo.mjs} +10 -184
- package/dist/settings-BjBsmVAo.mjs.map +1 -0
- package/dist/{settings-Cv47v9u8.mjs → settings-sO0Fif4p.mjs} +2 -2
- package/dist/{settings-Cv47v9u8.mjs.map → settings-sO0Fif4p.mjs.map} +1 -1
- package/dist/{setup-complete-yvPE4OsP.mjs → setup-complete-CMMr-oZU.mjs} +1 -1
- package/dist/{setup-complete-yvPE4OsP.mjs.map → setup-complete-CMMr-oZU.mjs.map} +1 -1
- package/dist/{setup-nonce-C9aFzb94.mjs → setup-nonce-169xl4fV.mjs} +1 -1
- package/dist/{setup-nonce-C9aFzb94.mjs.map → setup-nonce-169xl4fV.mjs.map} +1 -1
- package/dist/single-flight-cache-C0UV1Npg.mjs +104 -0
- package/dist/single-flight-cache-C0UV1Npg.mjs.map +1 -0
- package/dist/{site-url-CnHlmAs9.mjs → site-url-vtsuOvSD.mjs} +1 -1
- package/dist/{site-url-CnHlmAs9.mjs.map → site-url-vtsuOvSD.mjs.map} +1 -1
- package/dist/{ssrf-BsVGIE0Z.mjs → ssrf-XO05Voq6.mjs} +1 -1
- package/dist/{ssrf-BsVGIE0Z.mjs.map → ssrf-XO05Voq6.mjs.map} +1 -1
- package/dist/status-2gZklYuj.mjs +30 -0
- package/dist/status-2gZklYuj.mjs.map +1 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +2 -2
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-BdAmbOwx.mjs → taxonomies-BBxYA38v.mjs} +6 -6
- package/dist/{taxonomies-BdAmbOwx.mjs.map → taxonomies-BBxYA38v.mjs.map} +1 -1
- package/dist/{taxonomies-BILwiyGk.mjs → taxonomies-DuESHWKI.mjs} +2 -2
- package/dist/{taxonomies-BILwiyGk.mjs.map → taxonomies-DuESHWKI.mjs.map} +1 -1
- package/dist/{tokens-Bx2afeT-.mjs → tokens-DMkVjxrx.mjs} +1 -1
- package/dist/{tokens-Bx2afeT-.mjs.map → tokens-DMkVjxrx.mjs.map} +1 -1
- package/dist/{transport-CmpLD7W3.mjs → transport-1cIrOb1Y.mjs} +1 -1
- package/dist/{transport-CmpLD7W3.mjs.map → transport-1cIrOb1Y.mjs.map} +1 -1
- package/dist/{transport-B7PPP2CC.d.mts → transport-jdvsZEIt.d.mts} +1 -1
- package/dist/{transport-B7PPP2CC.d.mts.map → transport-jdvsZEIt.d.mts.map} +1 -1
- package/dist/{trusted-proxy-B4AfnoAp.mjs → trusted-proxy-CHp41Fjj.mjs} +1 -1
- package/dist/{trusted-proxy-B4AfnoAp.mjs.map → trusted-proxy-CHp41Fjj.mjs.map} +1 -1
- package/dist/{types-BFgrqwSk.d.mts → types-BFgYtuKd.d.mts} +1 -1
- package/dist/{types-BFgrqwSk.d.mts.map → types-BFgYtuKd.d.mts.map} +1 -1
- package/dist/{types-DZk_y-MU.mjs → types-BIduXPJk.mjs} +1 -1
- package/dist/{types-DZk_y-MU.mjs.map → types-BIduXPJk.mjs.map} +1 -1
- package/dist/{types-DTniiNto.d.mts → types-BTnnBYVX.d.mts} +2 -2
- package/dist/{types-DTniiNto.d.mts.map → types-BTnnBYVX.d.mts.map} +1 -1
- package/dist/{types-BUUVn1zr.d.mts → types-Bzfk2yC8.d.mts} +1 -1
- package/dist/{types-BUUVn1zr.d.mts.map → types-Bzfk2yC8.d.mts.map} +1 -1
- package/dist/{types-BH8-30hc.d.mts → types-CkEuk-Zr.d.mts} +1 -1
- package/dist/{types-BH8-30hc.d.mts.map → types-CkEuk-Zr.d.mts.map} +1 -1
- package/dist/{types-CPAPl93j.d.mts → types-DO7whVYU.d.mts} +2 -2
- package/dist/{types-CPAPl93j.d.mts.map → types-DO7whVYU.d.mts.map} +1 -1
- package/dist/{types-S15DXXNi.d.mts → types-DdkL6fyv.d.mts} +1 -1
- package/dist/{types-S15DXXNi.d.mts.map → types-DdkL6fyv.d.mts.map} +1 -1
- package/dist/{types-DpFmlNyB.mjs → types-DejCHqWT.mjs} +1 -1
- package/dist/{types-DpFmlNyB.mjs.map → types-DejCHqWT.mjs.map} +1 -1
- package/dist/{types-BPzXTV9x.d.mts → types-Del0VMij.d.mts} +1 -1
- package/dist/{types-BPzXTV9x.d.mts.map → types-Del0VMij.d.mts.map} +1 -1
- package/dist/{types-D4kUqbHh.d.mts → types-u_XxjbS8.d.mts} +1 -1
- package/dist/{types-D4kUqbHh.d.mts.map → types-u_XxjbS8.d.mts.map} +1 -1
- package/dist/{utils-C4Ih4DML.mjs → utils-C4M981Br.mjs} +1 -1
- package/dist/{utils-C4Ih4DML.mjs.map → utils-C4M981Br.mjs.map} +1 -1
- package/dist/{validate-Bz4vqcX1.mjs → validate-DGhQPXzI.mjs} +2 -2
- package/dist/{validate-Bz4vqcX1.mjs.map → validate-DGhQPXzI.mjs.map} +1 -1
- package/dist/{validate-CNwkPWzz.d.mts → validate-cJOiOvT2.d.mts} +5 -5
- package/dist/{validate-CNwkPWzz.d.mts.map → validate-cJOiOvT2.d.mts.map} +1 -1
- package/dist/{validation-DgGTJm3u.mjs → validation-DVHjPM1M.mjs} +5 -5
- package/dist/{validation-DgGTJm3u.mjs.map → validation-DVHjPM1M.mjs.map} +1 -1
- package/dist/version-BOjj_cfz.mjs +7 -0
- package/dist/{version-D-5txk2m.mjs.map → version-BOjj_cfz.mjs.map} +1 -1
- package/dist/{widgets-DZfmAbE4.mjs → widgets-Ci6hLwfO.mjs} +4 -4
- package/dist/{widgets-DZfmAbE4.mjs.map → widgets-Ci6hLwfO.mjs.map} +1 -1
- package/dist/{zod-generator-Djo_VHCt.mjs → zod-generator-CarzgPAu.mjs} +2 -2
- package/dist/{zod-generator-Djo_VHCt.mjs.map → zod-generator-CarzgPAu.mjs.map} +1 -1
- package/package.json +5 -5
- package/src/api/handlers/redirects.ts +24 -13
- package/src/api/schemas/redirects.ts +11 -4
- package/src/astro/integration/index.ts +44 -8
- package/src/astro/integration/routes.ts +46 -9
- package/src/astro/middleware/redirect.ts +12 -0
- package/src/bylines/field-defs-cache.ts +70 -20
- package/src/cli/commands/doctor.ts +1 -1
- package/src/config/secrets.ts +28 -14
- package/src/emdash-runtime.ts +5 -5
- package/src/redirects/status.ts +27 -0
- package/src/settings/index.ts +13 -13
- package/src/utils/{isolate-cache.ts → single-flight-cache.ts} +26 -21
- package/dist/byline-DUx48sJp.mjs.map +0 -1
- package/dist/redirects-DxVoR7PI.mjs.map +0 -1
- package/dist/secrets-ChPTmy9x.mjs.map +0 -1
- package/dist/settings-DfxiWY_s.mjs.map +0 -1
- package/dist/version-D-5txk2m.mjs +0 -7
- /package/dist/{api-tokens-Oq39ba-Z.mjs → api-tokens-C7ywRx7l.mjs} +0 -0
- /package/dist/{ssrf-BvgVcfNQ.mjs → ssrf-CRZGzjdL.mjs} +0 -0
- /package/dist/{types-CZI4E3qG.mjs → types-BoRm8-pp.mjs} +0 -0
|
@@ -574,11 +574,11 @@ declare const createCollectionBody: z.ZodObject<{
|
|
|
574
574
|
description: z.ZodOptional<z.ZodString>;
|
|
575
575
|
icon: z.ZodOptional<z.ZodString>;
|
|
576
576
|
supports: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
577
|
+
search: "search";
|
|
577
578
|
drafts: "drafts";
|
|
578
579
|
revisions: "revisions";
|
|
579
580
|
preview: "preview";
|
|
580
581
|
scheduling: "scheduling";
|
|
581
|
-
search: "search";
|
|
582
582
|
}>>>;
|
|
583
583
|
source: z.ZodOptional<z.ZodString>;
|
|
584
584
|
urlPattern: z.ZodOptional<z.ZodString>;
|
|
@@ -590,11 +590,11 @@ declare const updateCollectionBody: z.ZodObject<{
|
|
|
590
590
|
description: z.ZodOptional<z.ZodString>;
|
|
591
591
|
icon: z.ZodOptional<z.ZodString>;
|
|
592
592
|
supports: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
593
|
+
search: "search";
|
|
593
594
|
drafts: "drafts";
|
|
594
595
|
revisions: "revisions";
|
|
595
596
|
preview: "preview";
|
|
596
597
|
scheduling: "scheduling";
|
|
597
|
-
search: "search";
|
|
598
598
|
}>>>;
|
|
599
599
|
urlPattern: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
600
600
|
hasSeo: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -614,14 +614,14 @@ declare const createFieldBody: z.ZodObject<{
|
|
|
614
614
|
string: "string";
|
|
615
615
|
number: "number";
|
|
616
616
|
boolean: "boolean";
|
|
617
|
-
slug: "slug";
|
|
618
|
-
text: "text";
|
|
619
|
-
url: "url";
|
|
620
|
-
select: "select";
|
|
621
617
|
file: "file";
|
|
618
|
+
url: "url";
|
|
622
619
|
image: "image";
|
|
623
620
|
datetime: "datetime";
|
|
621
|
+
slug: "slug";
|
|
622
|
+
text: "text";
|
|
624
623
|
integer: "integer";
|
|
624
|
+
select: "select";
|
|
625
625
|
multiSelect: "multiSelect";
|
|
626
626
|
portableText: "portableText";
|
|
627
627
|
reference: "reference";
|
|
@@ -645,12 +645,12 @@ declare const createFieldBody: z.ZodObject<{
|
|
|
645
645
|
string: "string";
|
|
646
646
|
number: "number";
|
|
647
647
|
boolean: "boolean";
|
|
648
|
-
text: "text";
|
|
649
648
|
url: "url";
|
|
650
|
-
select: "select";
|
|
651
649
|
image: "image";
|
|
652
650
|
datetime: "datetime";
|
|
651
|
+
text: "text";
|
|
653
652
|
integer: "integer";
|
|
653
|
+
select: "select";
|
|
654
654
|
}>;
|
|
655
655
|
label: z.ZodString;
|
|
656
656
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -685,12 +685,12 @@ declare const updateFieldBody: z.ZodObject<{
|
|
|
685
685
|
string: "string";
|
|
686
686
|
number: "number";
|
|
687
687
|
boolean: "boolean";
|
|
688
|
-
text: "text";
|
|
689
688
|
url: "url";
|
|
690
|
-
select: "select";
|
|
691
689
|
image: "image";
|
|
692
690
|
datetime: "datetime";
|
|
691
|
+
text: "text";
|
|
693
692
|
integer: "integer";
|
|
693
|
+
select: "select";
|
|
694
694
|
}>;
|
|
695
695
|
label: z.ZodString;
|
|
696
696
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -743,14 +743,14 @@ declare const fieldSchema: z.ZodObject<{
|
|
|
743
743
|
string: "string";
|
|
744
744
|
number: "number";
|
|
745
745
|
boolean: "boolean";
|
|
746
|
-
slug: "slug";
|
|
747
|
-
text: "text";
|
|
748
|
-
url: "url";
|
|
749
|
-
select: "select";
|
|
750
746
|
file: "file";
|
|
747
|
+
url: "url";
|
|
751
748
|
image: "image";
|
|
752
749
|
datetime: "datetime";
|
|
750
|
+
slug: "slug";
|
|
751
|
+
text: "text";
|
|
753
752
|
integer: "integer";
|
|
753
|
+
select: "select";
|
|
754
754
|
multiSelect: "multiSelect";
|
|
755
755
|
portableText: "portableText";
|
|
756
756
|
reference: "reference";
|
|
@@ -808,14 +808,14 @@ declare const collectionWithFieldsResponseSchema: z.ZodObject<{
|
|
|
808
808
|
string: "string";
|
|
809
809
|
number: "number";
|
|
810
810
|
boolean: "boolean";
|
|
811
|
-
slug: "slug";
|
|
812
|
-
text: "text";
|
|
813
|
-
url: "url";
|
|
814
|
-
select: "select";
|
|
815
811
|
file: "file";
|
|
812
|
+
url: "url";
|
|
816
813
|
image: "image";
|
|
817
814
|
datetime: "datetime";
|
|
815
|
+
slug: "slug";
|
|
816
|
+
text: "text";
|
|
818
817
|
integer: "integer";
|
|
818
|
+
select: "select";
|
|
819
819
|
multiSelect: "multiSelect";
|
|
820
820
|
portableText: "portableText";
|
|
821
821
|
reference: "reference";
|
|
@@ -862,14 +862,14 @@ declare const fieldResponseSchema: z.ZodObject<{
|
|
|
862
862
|
string: "string";
|
|
863
863
|
number: "number";
|
|
864
864
|
boolean: "boolean";
|
|
865
|
-
slug: "slug";
|
|
866
|
-
text: "text";
|
|
867
|
-
url: "url";
|
|
868
|
-
select: "select";
|
|
869
865
|
file: "file";
|
|
866
|
+
url: "url";
|
|
870
867
|
image: "image";
|
|
871
868
|
datetime: "datetime";
|
|
869
|
+
slug: "slug";
|
|
870
|
+
text: "text";
|
|
872
871
|
integer: "integer";
|
|
872
|
+
select: "select";
|
|
873
873
|
multiSelect: "multiSelect";
|
|
874
874
|
portableText: "portableText";
|
|
875
875
|
reference: "reference";
|
|
@@ -899,14 +899,14 @@ declare const fieldListResponseSchema: z.ZodObject<{
|
|
|
899
899
|
string: "string";
|
|
900
900
|
number: "number";
|
|
901
901
|
boolean: "boolean";
|
|
902
|
-
slug: "slug";
|
|
903
|
-
text: "text";
|
|
904
|
-
url: "url";
|
|
905
|
-
select: "select";
|
|
906
902
|
file: "file";
|
|
903
|
+
url: "url";
|
|
907
904
|
image: "image";
|
|
908
905
|
datetime: "datetime";
|
|
906
|
+
slug: "slug";
|
|
907
|
+
text: "text";
|
|
909
908
|
integer: "integer";
|
|
909
|
+
select: "select";
|
|
910
910
|
multiSelect: "multiSelect";
|
|
911
911
|
portableText: "portableText";
|
|
912
912
|
reference: "reference";
|
|
@@ -1846,14 +1846,14 @@ declare const widgetAreaWithWidgetsAndCountSchema: z.ZodObject<{
|
|
|
1846
1846
|
//#region src/api/schemas/redirects.d.ts
|
|
1847
1847
|
declare const createRedirectBody: z.ZodObject<{
|
|
1848
1848
|
source: z.ZodString;
|
|
1849
|
-
destination: z.ZodString
|
|
1849
|
+
destination: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"">]>>;
|
|
1850
1850
|
type: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
1851
1851
|
enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
1852
1852
|
groupName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1853
1853
|
}, z.core.$strip>;
|
|
1854
1854
|
declare const updateRedirectBody: z.ZodObject<{
|
|
1855
1855
|
source: z.ZodOptional<z.ZodString>;
|
|
1856
|
-
destination: z.ZodOptional<z.ZodString
|
|
1856
|
+
destination: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"">]>>;
|
|
1857
1857
|
type: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
1858
1858
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
1859
1859
|
groupName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -2081,8 +2081,8 @@ declare const bylineFieldCreateBody: z.ZodObject<{
|
|
|
2081
2081
|
type: z.ZodEnum<{
|
|
2082
2082
|
string: "string";
|
|
2083
2083
|
boolean: "boolean";
|
|
2084
|
-
text: "text";
|
|
2085
2084
|
url: "url";
|
|
2085
|
+
text: "text";
|
|
2086
2086
|
select: "select";
|
|
2087
2087
|
}>;
|
|
2088
2088
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -2117,8 +2117,8 @@ declare const bylineFieldDefinitionSchema: z.ZodObject<{
|
|
|
2117
2117
|
type: z.ZodEnum<{
|
|
2118
2118
|
string: "string";
|
|
2119
2119
|
boolean: "boolean";
|
|
2120
|
-
text: "text";
|
|
2121
2120
|
url: "url";
|
|
2121
|
+
text: "text";
|
|
2122
2122
|
select: "select";
|
|
2123
2123
|
}>;
|
|
2124
2124
|
required: z.ZodBoolean;
|
|
@@ -2138,8 +2138,8 @@ declare const bylineFieldListResponseSchema: z.ZodObject<{
|
|
|
2138
2138
|
type: z.ZodEnum<{
|
|
2139
2139
|
string: "string";
|
|
2140
2140
|
boolean: "boolean";
|
|
2141
|
-
text: "text";
|
|
2142
2141
|
url: "url";
|
|
2142
|
+
text: "text";
|
|
2143
2143
|
select: "select";
|
|
2144
2144
|
}>;
|
|
2145
2145
|
required: z.ZodBoolean;
|
|
@@ -2169,4 +2169,4 @@ declare const bylineFieldUsageResponseSchema: z.ZodObject<{
|
|
|
2169
2169
|
}, z.core.$strip>;
|
|
2170
2170
|
//#endregion
|
|
2171
2171
|
export { wpRewriteUrlsBody as $, trashedContentListResponseSchema as $n, commentSchema as $t, reorderWidgetsBody as A, mediaResponseSchema as An, menuSchema as At, userSchema as B, contentListResponseSchema as Bn, magicLinkSendBody as Bt, notFoundSummarySchema as C, mediaConfirmBody as Cn, termWithCountSchema as Ct, updateRedirectBody as D, mediaListQuery as Dn, menuItemSchema as Dt, redirectsListQuery as E, mediaItemSchema as En, createMenuItemBody as Et, widgetSchema as F, contentAuthorsResponseSchema as Fn, updateMenuItemBody as Ft, setupAdminVerifyBody as G, contentSeoInput as Gn, passkeyVerifyBody as Gt, usersListQuery as H, contentPublishBody as Hn, passkeyRegisterOptionsBody as Ht, allowedDomainCreateBody as I, contentCompareResponseSchema as In, authMeActionBody as It, importProbeBody as J, contentTranslationSchema as Jn, adminCommentListResponseSchema as Jt, setupAtprotoAdminBody as K, contentSeoSchema as Kn, signupCompleteBody as Kt, allowedDomainUpdateBody as L, contentCreateBody as Ln, inviteCompleteBody as Lt, widgetAreaSchema as M, mediaUploadUrlBody as Mn, menuWithItemsSchema as Mt, widgetAreaWithWidgetsAndCountSchema as N, mediaUploadUrlResponseSchema as Nn, reorderMenuItemsBody as Nt, createWidgetAreaBody as O, mediaListResponseSchema as On, menuItemTypeEnum as Ot, widgetAreaWithWidgetsSchema as P, contentAuthorSchema as Pn, updateMenuBody as Pt, wpPrepareBody as Q, trashedContentItemSchema as Qn, commentListQuery as Qt, userDetailSchema as R, contentItemSchema as Rn, inviteCreateBody as Rt, notFoundSummaryResponseSchema as S, formatFileSize as Sn, termTranslationsSchema as St, redirectSchema as T, mediaExistingResponseSchema as Tn, createMenuBody as Tt, atprotoLoginBody as U, contentResponseSchema as Un, passkeyRegisterVerifyBody as Ut, userUpdateBody as V, contentPreviewUrlBody as Vn, passkeyOptionsBody as Vt, setupAdminBody as W, contentScheduleBody as Wn, passkeyRenameBody as Wt, wpPluginAnalyzeBody as X, contentTrashQuery as Xn, commentBulkResponseSchema as Xt, wpMediaImportBody as Y, contentTranslationsResponseSchema as Yn, commentBulkBody as Yt, wpPluginExecuteBody as Z, contentUpdateBody as Zn, commentCountsResponseSchema as Zt, notFoundEntrySchema as _, orphanedTableSchema as _n, taxonomyListResponseSchema as _t, bylineFieldUpdateBody as a, collectionListResponseSchema as an, httpUrl as ar, searchSuggestQuery as at, notFoundPruneBody as b, updateFieldBody as bn, termResponseSchema as bt, bylineCreditSchema as c, collectionWithFieldsResponseSchema as cn, offsetPaginationQuery as cr, createSectionBody as ct, bylineTranslationCreateBody as d, fieldListResponseSchema as dn, successEnvelope as dr, sectionsListQuery as dt, commentStatusBody as en, VALID_ROLE_LEVELS as er, searchEnableBody as et, bylineTranslationsResponseSchema as f, fieldReorderBody as fn, updateSectionBody as ft, createRedirectBody as g, orphanedTableListResponseSchema as gn, taxonomyDefTranslationsSchema as gt, contentBylineInputSchema as h, orphanRegisterBody as hn, taxonomyDefSchema as ht, bylineFieldReorderBody as i, collectionGetQuery as in, deleteResponseSchema as ir, searchResultSchema as it, updateWidgetBody as j, mediaUpdateBody as jn, menuTranslationsSchema as jt, createWidgetBody as k, mediaProviderListQuery as kn, menuListItemSchema as kt, bylineListResponseSchema as l, createCollectionBody as ln, roleLevel as lr, sectionListResponseSchema as lt, bylinesListQuery as m, fieldSchema as mn, createTermBody as mt, bylineFieldDefinitionSchema as n, publicCommentListResponseSchema as nn, countResponseSchema as nr, searchRebuildBody as nt, bylineFieldUsageResponseSchema as o, collectionResponseSchema as on, localeCode as or, settingsUpdateBody as ot, bylineUpdateBody as p, fieldResponseSchema as pn, createTaxonomyDefBody as pt, setupBody as q, contentTermsBody as qn, signupRequestBody as qt, bylineFieldListResponseSchema as r, publicCommentSchema as rn, cursorPaginationQuery as rr, searchResponseSchema as rt, bylineCreateBody as s, collectionSchema as sn, localeFilterQuery as sr, siteSettingsSchema as st, bylineFieldCreateBody as t, createCommentBody as tn, apiErrorSchema as tr, searchQuery as tt, bylineSummarySchema as u, createFieldBody as un, slugPattern as ur, sectionSchema as ut, notFoundListQuery as v, schemaExportQuery as vn, termGetResponseSchema as vt, redirectListResponseSchema as w, mediaConfirmResponseSchema as wn, updateTermBody as wt, notFoundSummaryQuery as x, DEFAULT_MAX_UPLOAD_SIZE as xn, termSchema as xt, notFoundListResponseSchema as y, updateCollectionBody as yn, termListResponseSchema as yt, userListResponseSchema as z, contentListQuery as zn, inviteRegisterOptionsBody as zt };
|
|
2172
|
-
//# sourceMappingURL=byline-fields-
|
|
2172
|
+
//# sourceMappingURL=byline-fields-CQJRIQkn.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"byline-fields-
|
|
1
|
+
{"version":3,"file":"byline-fields-CQJRIQkn.d.mts","names":[],"sources":["../src/api/schemas/common.ts","../src/api/schemas/content.ts","../src/api/schemas/media.ts","../src/api/schemas/schema.ts","../src/api/schemas/comments.ts","../src/api/schemas/auth.ts","../src/api/schemas/menus.ts","../src/api/schemas/taxonomies.ts","../src/api/schemas/sections.ts","../src/api/schemas/settings.ts","../src/api/schemas/search.ts","../src/api/schemas/import.ts","../src/api/schemas/setup.ts","../src/api/schemas/users.ts","../src/api/schemas/widgets.ts","../src/api/schemas/redirects.ts","../src/api/schemas/bylines.ts","../src/api/schemas/byline-fields.ts"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as BylineSchemaRegistry, r as mapBylineSchemaError, t as BylineSchemaError } from "./byline-registry-
|
|
1
|
+
import { n as BylineSchemaRegistry, r as mapBylineSchemaError, t as BylineSchemaError } from "./byline-registry-DedidtqC.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/api/handlers/byline-fields.ts
|
|
4
4
|
/**
|
|
@@ -120,4 +120,4 @@ async function handleBylineFieldReorder(db, slugs) {
|
|
|
120
120
|
|
|
121
121
|
//#endregion
|
|
122
122
|
export { handleBylineFieldReorder as a, handleBylineFieldList as i, handleBylineFieldDelete as n, handleBylineFieldUpdate as o, handleBylineFieldGet as r, handleBylineFieldUsage as s, handleBylineFieldCreate as t };
|
|
123
|
-
//# sourceMappingURL=byline-fields
|
|
123
|
+
//# sourceMappingURL=byline-fields-nBVqK_Ff.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"byline-fields
|
|
1
|
+
{"version":3,"file":"byline-fields-nBVqK_Ff.mjs","names":[],"sources":["../src/api/handlers/byline-fields.ts"],"sourcesContent":["/**\n * Handler layer for the byline-fields admin API (Phase 4 of Discussion\n * #1174).\n *\n * Each handler:\n * - Takes the `Kysely<Database>` from the route, returns `ApiResult<T>`.\n * - Wraps the registry call in try/catch.\n * - Translates `BylineSchemaError` → shared `ErrorCode` via\n * `mapBylineSchemaError`. HTTP status comes from `mapErrorStatus` at\n * the route's `unwrapResult` site — handlers don't know about\n * statuses.\n * - Catches everything else, logs server-side, returns a 500-class\n * code without leaking `error.message`.\n *\n * Reserved-slug + identifier validation runs at the zod layer (see\n * `schemas/byline-fields.ts`); the registry repeats it for defence in\n * depth (non-HTTP callers). This module assumes inputs have already\n * passed through whichever zod schema the route used.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport {\n\tBylineSchemaError,\n\tBylineSchemaRegistry,\n\tmapBylineSchemaError,\n} from \"../../schema/byline-registry.js\";\nimport type {\n\tBylineFieldDefinition,\n\tCreateBylineFieldInput,\n\tUpdateBylineFieldInput,\n} from \"../../schema/types.js\";\nimport type { ApiResult } from \"../types.js\";\n\n/**\n * Build a structured failure envelope from a `BylineSchemaError`.\n * Centralised so every handler emits the same shape.\n */\nfunction bylineSchemaErrorResult<T>(error: BylineSchemaError): ApiResult<T> {\n\tconst mapped = mapBylineSchemaError(error);\n\treturn {\n\t\tsuccess: false,\n\t\terror: { code: mapped.code, message: mapped.message, details: mapped.details },\n\t};\n}\n\n/**\n * Build a 500-class failure envelope. Logs the underlying error\n * server-side; the message returned to the client is the static\n * fallback to avoid leaking internals.\n */\nfunction internalErrorResult<T>(\n\terror: unknown,\n\tcode: string,\n\tfallbackMessage: string,\n): ApiResult<T> {\n\tconsole.error(`[${code}]`, error);\n\treturn {\n\t\tsuccess: false,\n\t\terror: { code, message: fallbackMessage },\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// List\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldList(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ items: BylineFieldDefinition[] }>> {\n\ttry {\n\t\tconst items = await new BylineSchemaRegistry(db).listFields();\n\t\treturn { success: true, data: { items } };\n\t} catch (error) {\n\t\treturn internalErrorResult(error, \"SCHEMA_FIELD_LIST_ERROR\", \"Failed to list byline fields\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Create\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldCreate(\n\tdb: Kysely<Database>,\n\tinput: CreateBylineFieldInput,\n): Promise<ApiResult<BylineFieldDefinition>> {\n\ttry {\n\t\tconst field = await new BylineSchemaRegistry(db).createField(input);\n\t\treturn { success: true, data: field };\n\t} catch (error) {\n\t\tif (error instanceof BylineSchemaError) {\n\t\t\treturn bylineSchemaErrorResult(error);\n\t\t}\n\t\treturn internalErrorResult(error, \"SCHEMA_FIELD_CREATE_ERROR\", \"Failed to create byline field\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Get one\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldGet(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<ApiResult<BylineFieldDefinition>> {\n\ttry {\n\t\tconst field = await new BylineSchemaRegistry(db).getField(slug);\n\t\tif (!field) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Byline field not found\" },\n\t\t\t};\n\t\t}\n\t\treturn { success: true, data: field };\n\t} catch (error) {\n\t\treturn internalErrorResult(error, \"SCHEMA_FIELD_GET_ERROR\", \"Failed to get byline field\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Update\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldUpdate(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tinput: UpdateBylineFieldInput,\n): Promise<ApiResult<BylineFieldDefinition>> {\n\ttry {\n\t\tconst field = await new BylineSchemaRegistry(db).updateField(slug, input);\n\t\treturn { success: true, data: field };\n\t} catch (error) {\n\t\tif (error instanceof BylineSchemaError) {\n\t\t\treturn bylineSchemaErrorResult(error);\n\t\t}\n\t\treturn internalErrorResult(error, \"SCHEMA_FIELD_UPDATE_ERROR\", \"Failed to update byline field\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Delete\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldDelete(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tawait new BylineSchemaRegistry(db).deleteField(slug);\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch (error) {\n\t\tif (error instanceof BylineSchemaError) {\n\t\t\treturn bylineSchemaErrorResult(error);\n\t\t}\n\t\treturn internalErrorResult(error, \"SCHEMA_FIELD_DELETE_ERROR\", \"Failed to delete byline field\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Usage\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldUsage(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<\n\tApiResult<{\n\t\ttranslatableValueCount: number;\n\t\tgroupValueCount: number;\n\t\ttotalAffectedRows: number;\n\t}>\n> {\n\ttry {\n\t\tconst usage = await new BylineSchemaRegistry(db).getFieldUsage(slug);\n\t\treturn { success: true, data: usage };\n\t} catch (error) {\n\t\tif (error instanceof BylineSchemaError) {\n\t\t\treturn bylineSchemaErrorResult(error);\n\t\t}\n\t\treturn internalErrorResult(\n\t\t\terror,\n\t\t\t\"SCHEMA_FIELD_GET_ERROR\",\n\t\t\t\"Failed to read byline field usage\",\n\t\t);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Reorder\n// ---------------------------------------------------------------------------\n\nexport async function handleBylineFieldReorder(\n\tdb: Kysely<Database>,\n\tslugs: string[],\n): Promise<ApiResult<{ items: BylineFieldDefinition[] }>> {\n\ttry {\n\t\tconst registry = new BylineSchemaRegistry(db);\n\t\tawait registry.reorderFields(slugs);\n\t\tconst items = await registry.listFields();\n\t\treturn { success: true, data: { items } };\n\t} catch (error) {\n\t\tif (error instanceof BylineSchemaError) {\n\t\t\treturn bylineSchemaErrorResult(error);\n\t\t}\n\t\treturn internalErrorResult(\n\t\t\terror,\n\t\t\t\"SCHEMA_FIELD_REORDER_ERROR\",\n\t\t\t\"Failed to reorder byline fields\",\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;AAuCA,SAAS,wBAA2B,OAAwC;CAC3E,MAAM,SAAS,qBAAqB,MAAM;AAC1C,QAAO;EACN,SAAS;EACT,OAAO;GAAE,MAAM,OAAO;GAAM,SAAS,OAAO;GAAS,SAAS,OAAO;GAAS;EAC9E;;;;;;;AAQF,SAAS,oBACR,OACA,MACA,iBACe;AACf,SAAQ,MAAM,IAAI,KAAK,IAAI,MAAM;AACjC,QAAO;EACN,SAAS;EACT,OAAO;GAAE;GAAM,SAAS;GAAiB;EACzC;;AAOF,eAAsB,sBACrB,IACyD;AACzD,KAAI;AAEH,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,OADlB,MAAM,IAAI,qBAAqB,GAAG,CAAC,YAAY,EACtB;GAAE;UACjC,OAAO;AACf,SAAO,oBAAoB,OAAO,2BAA2B,+BAA+B;;;AAQ9F,eAAsB,wBACrB,IACA,OAC4C;AAC5C,KAAI;AAEH,SAAO;GAAE,SAAS;GAAM,MADV,MAAM,IAAI,qBAAqB,GAAG,CAAC,YAAY,MAAM;GAC9B;UAC7B,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO,wBAAwB,MAAM;AAEtC,SAAO,oBAAoB,OAAO,6BAA6B,gCAAgC;;;AAQjG,eAAsB,qBACrB,IACA,MAC4C;AAC5C,KAAI;EACH,MAAM,QAAQ,MAAM,IAAI,qBAAqB,GAAG,CAAC,SAAS,KAAK;AAC/D,MAAI,CAAC,MACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAa,SAAS;IAA0B;GAC/D;AAEF,SAAO;GAAE,SAAS;GAAM,MAAM;GAAO;UAC7B,OAAO;AACf,SAAO,oBAAoB,OAAO,0BAA0B,6BAA6B;;;AAQ3F,eAAsB,wBACrB,IACA,MACA,OAC4C;AAC5C,KAAI;AAEH,SAAO;GAAE,SAAS;GAAM,MADV,MAAM,IAAI,qBAAqB,GAAG,CAAC,YAAY,MAAM,MAAM;GACpC;UAC7B,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO,wBAAwB,MAAM;AAEtC,SAAO,oBAAoB,OAAO,6BAA6B,gCAAgC;;;AAQjG,eAAsB,wBACrB,IACA,MACwC;AACxC,KAAI;AACH,QAAM,IAAI,qBAAqB,GAAG,CAAC,YAAY,KAAK;AACpD,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,SAAS,MAAM;GAAE;UACzC,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO,wBAAwB,MAAM;AAEtC,SAAO,oBAAoB,OAAO,6BAA6B,gCAAgC;;;AAQjG,eAAsB,uBACrB,IACA,MAOC;AACD,KAAI;AAEH,SAAO;GAAE,SAAS;GAAM,MADV,MAAM,IAAI,qBAAqB,GAAG,CAAC,cAAc,KAAK;GAC/B;UAC7B,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO,wBAAwB,MAAM;AAEtC,SAAO,oBACN,OACA,0BACA,oCACA;;;AAQH,eAAsB,yBACrB,IACA,OACyD;AACzD,KAAI;EACH,MAAM,WAAW,IAAI,qBAAqB,GAAG;AAC7C,QAAM,SAAS,cAAc,MAAM;AAEnC,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,OADlB,MAAM,SAAS,YAAY,EACF;GAAE;UACjC,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO,wBAAwB,MAAM;AAEtC,SAAO,oBACN,OACA,8BACA,kCACA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
2
2
|
import { t as withTransaction } from "./transaction-x2tJQ-A1.mjs";
|
|
3
|
-
import { i as RESERVED_BYLINE_FIELD_SLUGS, t as BYLINE_FIELD_TYPES } from "./types-
|
|
3
|
+
import { i as RESERVED_BYLINE_FIELD_SLUGS, t as BYLINE_FIELD_TYPES } from "./types-BIduXPJk.mjs";
|
|
4
4
|
import { sql } from "kysely";
|
|
5
5
|
import { ulid } from "ulidx";
|
|
6
6
|
|
|
@@ -403,4 +403,4 @@ function mapFieldRow(row) {
|
|
|
403
403
|
|
|
404
404
|
//#endregion
|
|
405
405
|
export { BylineSchemaRegistry as n, mapBylineSchemaError as r, BylineSchemaError as t };
|
|
406
|
-
//# sourceMappingURL=byline-registry-
|
|
406
|
+
//# sourceMappingURL=byline-registry-DedidtqC.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"byline-registry-CWP7I71B.mjs","names":[],"sources":["../src/schema/byline-registry.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { BylineFieldTable, Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport {\n\tBYLINE_FIELD_TYPES,\n\tRESERVED_BYLINE_FIELD_SLUGS,\n\ttype BylineFieldDefinition,\n\ttype BylineFieldType,\n\ttype BylineFieldValidation,\n\ttype CreateBylineFieldInput,\n\ttype UpdateBylineFieldInput,\n} from \"./types.js\";\n\nconst RESERVED_SET: ReadonlySet<string> = new Set(RESERVED_BYLINE_FIELD_SLUGS);\nconst TYPE_SET: ReadonlySet<string> = new Set(BYLINE_FIELD_TYPES);\n\nconst VERSION_KEY = \"byline_fields_version\";\n\n/** Hard cap on the choices array for a `select`-type field. */\nconst MAX_SELECT_OPTIONS = 200;\n/** Hard cap on a slug — mirrors `SchemaRegistry.validateSlug`. */\nconst MAX_SLUG_LENGTH = 63;\n/** Hard cap on a label. Bigger than slugs because labels are display strings. */\nconst MAX_LABEL_LENGTH = 200;\n\n/**\n * Error thrown for byline-schema validation failures. Mirrors\n * `SchemaError` in `registry.ts` so the admin API layer can map a small\n * set of codes to HTTP statuses without inspecting messages.\n *\n * Codes:\n * - `INVALID_SLUG` — slug fails identifier rules or length cap\n * - `RESERVED_SLUG` — slug collides with a fixed `_emdash_bylines` column\n * - `INVALID_TYPE` — type is not one of the five v1 field types\n * - `INVALID_LABEL` — label missing or exceeds length cap\n * - `INVALID_VALIDATION` — validation payload malformed (e.g. `select` with\n * no `options`, duplicates in `options`)\n * - `FIELD_EXISTS` — slug already registered\n * - `FIELD_NOT_FOUND` — slug not registered\n * - `TRANSLATABLE_LOCKED` — attempt to flip `translatable` while stored\n * values reference the field\n * - `REORDER_MISMATCH` — reorder input doesn't match the registered set\n */\nexport class BylineSchemaError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: string,\n\t\tpublic details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"BylineSchemaError\";\n\t}\n}\n\n/**\n * Translate a `BylineSchemaError` code to a shared `ErrorCode` for the\n * admin API. HTTP status is then derived by `mapErrorStatus` — this\n * function deliberately doesn't carry one, so the API/handler boundary\n * matches the rest of the codebase (handlers return `ApiResult<T>` with\n * a code, the route layer maps to status via `unwrapResult`).\n *\n * Every code on the right-hand side of `case ... return ...` is defined\n * in `ErrorCode` (`api/errors.ts`). `INVALID_LABEL` and\n * `INVALID_VALIDATION` are intentionally folded into the `default`\n * branch (→ `VALIDATION_ERROR`) so no ad-hoc codes leak out — the\n * registry's domain code names them but the HTTP surface should not.\n *\n * `RESERVED_SLUG` / `INVALID_SLUG` typically don't reach this layer for\n * HTTP callers — the zod schema rejects them first with a clean\n * `VALIDATION_ERROR`. They're still listed so non-HTTP callers (and the\n * test layer) get consistent mapping.\n *\n * `FIELD_NOT_FOUND` is normalised to the shared `NOT_FOUND` code so the\n * admin client can branch on one constant across resource types.\n */\nexport function mapBylineSchemaError(error: BylineSchemaError): {\n\tcode: string;\n\tmessage: string;\n\tdetails?: Record<string, unknown>;\n} {\n\tswitch (error.code) {\n\t\tcase \"FIELD_NOT_FOUND\":\n\t\t\treturn { code: \"NOT_FOUND\", message: error.message, details: error.details };\n\t\tcase \"FIELD_EXISTS\":\n\t\tcase \"TRANSLATABLE_LOCKED\":\n\t\tcase \"REORDER_MISMATCH\":\n\t\tcase \"INVALID_SLUG\":\n\t\tcase \"RESERVED_SLUG\":\n\t\tcase \"INVALID_TYPE\":\n\t\t\treturn { code: error.code, message: error.message, details: error.details };\n\t\tdefault:\n\t\t\t// Catches INVALID_LABEL, INVALID_VALIDATION, and any future\n\t\t\t// registry codes we forget to wire up explicitly.\n\t\t\treturn { code: \"VALIDATION_ERROR\", message: error.message, details: error.details };\n\t}\n}\n\n/**\n * Registry for byline custom fields (Discussion #1174).\n *\n * Owns CRUD over `_emdash_byline_fields` and the\n * `options.byline_fields_version` counter that drives cache\n * invalidation in `bylines/field-defs-cache.ts`.\n *\n * **Dirty-bit bookend.** Every mutation runs `markVersionDirty` before\n * the schema write and `markVersionClean` after, as standalone writes\n * (not inside `withTransaction`) so concurrent isolates observe the\n * dirty mark *before* the mutation lands. Parity carries meaning:\n * odd = mutation in flight or crashed mid-flight, even = stable.\n * The cache bypasses the global holder while odd.\n *\n * `markVersionDirty` is parity-aware (idempotent on odd) so a\n * crashed prior attempt doesn't invert the bit.\n * `markVersionClean` always advances to a new even value (+2 from\n * even, +1 from odd) so concurrent mutators can't collapse on the\n * same key and pin a stale cache snapshot. Idempotent-retry exits\n * (`FIELD_EXISTS` / `FIELD_NOT_FOUND` / no-op update) call\n * `markVersionClean` too — same code path doubles as crash recovery\n * and false-clean recovery.\n *\n * The residual race: a reader caching between two concurrent\n * `markVersionClean` calls sees a partial-set snapshot until the\n * second clean lands. Bounded by the inter-clean window (~ms).\n * Schema mutations are admin-only and rare; acceptable for now.\n * A CAS-on-bump or dialect-specific lock is tracked as follow-up.\n *\n * **`deleteField` cascade.** Migration 041 already declares\n * `ON DELETE CASCADE` on both value tables. The explicit deletes\n * here are defense-in-depth against FK-pragma misconfig and mirror\n * `BylineRepository.delete`'s app-level cascade for the bylines\n * domain.\n *\n * Reserved-slug rejection runs at the API layer (zod) *and* here so\n * non-HTTP callers (seeds, scripts) can't bypass the check.\n */\nexport class BylineSchemaRegistry {\n\tconstructor(private db: Kysely<Database>) {}\n\n\tasync listFields(): Promise<BylineFieldDefinition[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t.execute();\n\t\treturn rows.map((row) => mapFieldRow(row));\n\t}\n\n\tasync getField(slug: string): Promise<BylineFieldDefinition | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.selectAll()\n\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t.executeTakeFirst();\n\t\treturn row ? mapFieldRow(row) : null;\n\t}\n\n\tasync getFieldById(id: string): Promise<BylineFieldDefinition | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\t\treturn row ? mapFieldRow(row) : null;\n\t}\n\n\tasync createField(input: CreateBylineFieldInput): Promise<BylineFieldDefinition> {\n\t\tthis.validateSlug(input.slug);\n\t\tthis.validateLabel(input.label);\n\t\tthis.validateType(input.type);\n\t\tconst validation = this.normaliseValidation(input.type, input.validation ?? null);\n\n\t\tconst existing = await this.getField(input.slug);\n\t\tif (existing) {\n\t\t\t// Idempotent retry exit — see class JSDoc.\n\t\t\tawait this.markVersionClean();\n\t\t\tthrow new BylineSchemaError(`Byline field \"${input.slug}\" already exists`, \"FIELD_EXISTS\", {\n\t\t\t\tslug: input.slug,\n\t\t\t});\n\t\t}\n\n\t\tconst id = ulid();\n\t\tconst sortOrder = input.sortOrder ?? (await this.nextSortOrder());\n\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_byline_fields\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\n\t\t\t\t\tslug: input.slug,\n\t\t\t\t\tlabel: input.label,\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\trequired: input.required ? 1 : 0,\n\t\t\t\t\ttranslatable: input.translatable === false ? 0 : 1,\n\t\t\t\t\tvalidation: validation ? JSON.stringify(validation) : null,\n\t\t\t\t\tsort_order: sortOrder,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t});\n\t\tawait this.markVersionClean();\n\n\t\tconst created = await this.getFieldById(id);\n\t\tif (!created) {\n\t\t\t// Should be unreachable on a working DB — but a typed error\n\t\t\t// beats letting the route returning null on a successful path.\n\t\t\tthrow new BylineSchemaError(\"Failed to load created field\", \"FIELD_NOT_FOUND\", {\n\t\t\t\tid,\n\t\t\t});\n\t\t}\n\t\treturn created;\n\t}\n\n\tasync updateField(slug: string, input: UpdateBylineFieldInput): Promise<BylineFieldDefinition> {\n\t\tconst field = await this.getField(slug);\n\t\tif (!field) {\n\t\t\t// Idempotent retry exit — see class JSDoc.\n\t\t\tawait this.markVersionClean();\n\t\t\tthrow new BylineSchemaError(`Byline field \"${slug}\" not found`, \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\n\t\tconst updates: Partial<{\n\t\t\tlabel: string;\n\t\t\trequired: number;\n\t\t\ttranslatable: number;\n\t\t\tvalidation: string | null;\n\t\t\tsort_order: number;\n\t\t\tupdated_at: string;\n\t\t}> = {};\n\n\t\tif (input.label !== undefined) {\n\t\t\tthis.validateLabel(input.label);\n\t\t\tupdates.label = input.label;\n\t\t}\n\n\t\tif (input.required !== undefined) {\n\t\t\tupdates.required = input.required ? 1 : 0;\n\t\t}\n\n\t\tif (input.validation !== undefined) {\n\t\t\t// Validation payload is normalised against the *current* field\n\t\t\t// type — `type` is not updatable, so it's safe to use `field.type`.\n\t\t\tconst validation = this.normaliseValidation(field.type, input.validation);\n\t\t\tupdates.validation = validation ? JSON.stringify(validation) : null;\n\t\t}\n\n\t\tif (input.translatable !== undefined && input.translatable !== field.translatable) {\n\t\t\t// Flipping `translatable` would orphan any values already stored\n\t\t\t// in the table matching the *current* flag. Reject when any\n\t\t\t// value rows reference this field — admins can delete the field\n\t\t\t// (cascading the values) and re-create it with the new flag if\n\t\t\t// they want a clean re-start. Migrating values across tables is\n\t\t\t// out of scope (Discussion #1174 doesn't authorise it).\n\t\t\tconst usage = await this.countFieldValues(field.id);\n\t\t\tif (usage > 0) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t`Cannot change \"translatable\" on field \"${slug}\" while ${usage} value row(s) exist. ` +\n\t\t\t\t\t\t`Delete the values (or the field) and re-create with the new setting.`,\n\t\t\t\t\t\"TRANSLATABLE_LOCKED\",\n\t\t\t\t\t{ slug, valueCount: usage },\n\t\t\t\t);\n\t\t\t}\n\t\t\tupdates.translatable = input.translatable ? 1 : 0;\n\t\t}\n\n\t\tif (input.sortOrder !== undefined) {\n\t\t\tupdates.sort_order = input.sortOrder;\n\t\t}\n\n\t\tif (Object.keys(updates).length === 0) {\n\t\t\t// No-op update — still advance the clean marker in case\n\t\t\t// we're recovering a crashed prior attempt.\n\t\t\tawait this.markVersionClean();\n\t\t\treturn field;\n\t\t}\n\n\t\tupdates.updated_at = new Date().toISOString();\n\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.updateTable(\"_emdash_byline_fields\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"id\", \"=\", field.id)\n\t\t\t\t.execute();\n\t\t});\n\t\tawait this.markVersionClean();\n\n\t\tconst updated = await this.getFieldById(field.id);\n\t\tif (!updated) {\n\t\t\tthrow new BylineSchemaError(\"Failed to load updated field\", \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\t\treturn updated;\n\t}\n\n\tasync deleteField(slug: string): Promise<void> {\n\t\tconst field = await this.getField(slug);\n\t\tif (!field) {\n\t\t\t// Idempotent retry exit — see class JSDoc.\n\t\t\tawait this.markVersionClean();\n\t\t\tthrow new BylineSchemaError(`Byline field \"${slug}\" not found`, \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\n\t\t// Delete order matters on D1 (no tx): value rows first, definition\n\t\t// row last, so a crash leaves the definition recoverable on retry\n\t\t// rather than orphan values pointing at a vanished id.\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_byline_field_values\")\n\t\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t\t.execute();\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_byline_field_group_values\")\n\t\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t\t.execute();\n\t\t\tawait trx.deleteFrom(\"_emdash_byline_fields\").where(\"id\", \"=\", field.id).execute();\n\t\t});\n\t\tawait this.markVersionClean();\n\t}\n\n\t/**\n\t * Reorder fields by slug. The input must be the *exact* set of\n\t * currently registered slugs — no adds, no drops, no duplicates. This\n\t * keeps the operation invertible (any reorder is followed by a reverse\n\t * reorder) and removes a class of \"did I forget a field?\" bugs at the\n\t * API layer.\n\t */\n\tasync reorderFields(slugs: string[]): Promise<void> {\n\t\tif (new Set(slugs).size !== slugs.length) {\n\t\t\tthrow new BylineSchemaError(\"Reorder input contains duplicate slugs\", \"REORDER_MISMATCH\", {\n\t\t\t\tslugs,\n\t\t\t});\n\t\t}\n\n\t\tconst registered = await this.listFields();\n\t\tconst registeredSlugs = registered.map((f) => f.slug).toSorted();\n\t\tconst inputSlugs = slugs.toSorted();\n\n\t\tif (registeredSlugs.length !== inputSlugs.length) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Reorder input has ${inputSlugs.length} slug(s); ${registeredSlugs.length} registered`,\n\t\t\t\t\"REORDER_MISMATCH\",\n\t\t\t\t{ registered: registeredSlugs, input: inputSlugs },\n\t\t\t);\n\t\t}\n\t\tfor (let i = 0; i < registeredSlugs.length; i++) {\n\t\t\tif (registeredSlugs[i] !== inputSlugs[i]) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t\"Reorder input does not match the registered field set\",\n\t\t\t\t\t\"REORDER_MISMATCH\",\n\t\t\t\t\t{ registered: registeredSlugs, input: inputSlugs },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (let i = 0; i < slugs.length; i++) {\n\t\t\t\tconst slug = slugs[i];\n\t\t\t\tif (slug === undefined) continue;\n\t\t\t\tawait trx\n\t\t\t\t\t.updateTable(\"_emdash_byline_fields\")\n\t\t\t\t\t.set({ sort_order: i, updated_at: now })\n\t\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t\tawait this.markVersionClean();\n\t}\n\n\t/**\n\t * Per-table usage counts for a field, plus the sum. Backs the\n\t * destructive-delete confirm dialog in the admin UI (Phase 5).\n\t *\n\t * Both counts are surfaced separately for diagnostic value: a\n\t * non-zero count on the table that doesn't match the field's current\n\t * `translatable` flag indicates historical drift (e.g. a flip from\n\t * an older code path). Today the registry rejects such flips with\n\t * `TRANSLATABLE_LOCKED`, so any drift originates pre-Phase-2.\n\t *\n\t * Throws `FIELD_NOT_FOUND` when the slug doesn't resolve — callers\n\t * shouldn't get back zero counts for a missing field.\n\t */\n\tasync getFieldUsage(slug: string): Promise<{\n\t\ttranslatableValueCount: number;\n\t\tgroupValueCount: number;\n\t\ttotalAffectedRows: number;\n\t}> {\n\t\tconst field = await this.getField(slug);\n\t\tif (!field) {\n\t\t\tthrow new BylineSchemaError(`Byline field \"${slug}\" not found`, \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\t\tconst tr = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t.executeTakeFirst();\n\t\tconst grp = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_group_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t.executeTakeFirst();\n\t\tconst translatableValueCount = Number(tr?.count ?? 0);\n\t\tconst groupValueCount = Number(grp?.count ?? 0);\n\t\treturn {\n\t\t\ttranslatableValueCount,\n\t\t\tgroupValueCount,\n\t\t\ttotalAffectedRows: translatableValueCount + groupValueCount,\n\t\t};\n\t}\n\n\t/**\n\t * Read the persisted version counter. Used by the field-defs cache\n\t * (Phase 3) to detect invalidation. Returns `0` when the row is\n\t * missing — covers the \"tests that didn't run migration 041\" case\n\t * without throwing.\n\t */\n\tasync getVersion(): Promise<number> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select(\"value\")\n\t\t\t.where(\"name\", \"=\", VERSION_KEY)\n\t\t\t.executeTakeFirst();\n\t\tif (!row) return 0;\n\t\tconst parsed = Number.parseInt(row.value, 10);\n\t\treturn Number.isFinite(parsed) ? parsed : 0;\n\t}\n\n\t// ============================================\n\t// Private helpers\n\t// ============================================\n\n\t/**\n\t * Force the version counter to an odd integer (\"dirty\"). Idempotent\n\t * on odd so a crashed prior attempt can't invert parity. Upsert (not\n\t * UPDATE) so a missing row still flips parity — `getVersion` returns\n\t * 0 on missing, which is even, so a bare UPDATE would leave the\n\t * cache pinned on a stale snapshot. See the class JSDoc.\n\t *\n\t * `options.value` qualified: PG's `ON CONFLICT DO UPDATE` puts both\n\t * the target and `EXCLUDED.value` in scope; bare `value` is ambiguous.\n\t */\n\tprivate async markVersionDirty(): Promise<void> {\n\t\tawait sql`\n\t\t\tINSERT INTO options (name, value)\n\t\t\tVALUES (${VERSION_KEY}, '1')\n\t\t\tON CONFLICT(name) DO UPDATE SET value = CASE\n\t\t\t\tWHEN CAST(options.value AS INTEGER) % 2 = 0\n\t\t\t\t\tTHEN CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)\n\t\t\t\tELSE options.value\n\t\t\tEND\n\t\t`.execute(this.db);\n\t}\n\n\t/**\n\t * Force the version counter to a **new** even integer (+2 from even,\n\t * +1 from odd). Always-advance — never a no-op — so two concurrent\n\t * mutators can't collapse on the same even key and pin a stale cache\n\t * snapshot. See the class JSDoc for the concurrent-collapse rationale.\n\t *\n\t * `options.value` qualified — see `markVersionDirty`.\n\t */\n\tprivate async markVersionClean(): Promise<void> {\n\t\tawait sql`\n\t\t\tINSERT INTO options (name, value)\n\t\t\tVALUES (${VERSION_KEY}, '2')\n\t\t\tON CONFLICT(name) DO UPDATE SET value = CASE\n\t\t\t\tWHEN CAST(options.value AS INTEGER) % 2 = 0\n\t\t\t\t\tTHEN CAST(CAST(options.value AS INTEGER) + 2 AS TEXT)\n\t\t\t\tELSE CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)\n\t\t\tEND\n\t\t`.execute(this.db);\n\t}\n\n\tprivate async nextSortOrder(): Promise<number> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.select(({ fn }) => [fn.max<number | null>(\"sort_order\").as(\"max\")])\n\t\t\t.executeTakeFirst();\n\t\tconst max = row?.max ?? null;\n\t\treturn max === null ? 0 : max + 1;\n\t}\n\n\tprivate async countFieldValues(fieldId: string): Promise<number> {\n\t\t// Count both per-locale and group-shared values. A field can only\n\t\t// store in one table at a time (translatable picks), but historic\n\t\t// rows might exist in the other if a prior version of this code\n\t\t// allowed the flip — count both to be safe.\n\t\tconst tr = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", fieldId)\n\t\t\t.executeTakeFirst();\n\t\tconst grp = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_group_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", fieldId)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(tr?.count ?? 0) + Number(grp?.count ?? 0);\n\t}\n\n\tprivate validateSlug(slug: string): void {\n\t\tif (!slug || typeof slug !== \"string\") {\n\t\t\tthrow new BylineSchemaError(\"Byline field slug is required\", \"INVALID_SLUG\", { slug });\n\t\t}\n\t\tif (slug.length > MAX_SLUG_LENGTH) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Byline field slug must be ${MAX_SLUG_LENGTH} characters or less`,\n\t\t\t\t\"INVALID_SLUG\",\n\t\t\t\t{ slug },\n\t\t\t);\n\t\t}\n\t\t// `validateIdentifier` enforces /^[a-z][a-z0-9_]*$/ — rejects\n\t\t// camelCase, PascalCase, hyphens, leading digits, and identifiers\n\t\t// over 128 characters. We hit the 63-char cap above first, which\n\t\t// matches the content-collection slug cap.\n\t\ttry {\n\t\t\tvalidateIdentifier(slug, \"byline field slug\");\n\t\t} catch (error) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\terror instanceof Error ? error.message : \"Invalid byline field slug\",\n\t\t\t\t\"INVALID_SLUG\",\n\t\t\t\t{ slug },\n\t\t\t);\n\t\t}\n\t\tif (RESERVED_SET.has(slug)) {\n\t\t\tthrow new BylineSchemaError(`Byline field slug \"${slug}\" is reserved`, \"RESERVED_SLUG\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate validateLabel(label: string): void {\n\t\tif (!label || typeof label !== \"string\") {\n\t\t\tthrow new BylineSchemaError(\"Byline field label is required\", \"INVALID_LABEL\", {\n\t\t\t\tlabel,\n\t\t\t});\n\t\t}\n\t\tif (label.length > MAX_LABEL_LENGTH) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Byline field label must be ${MAX_LABEL_LENGTH} characters or less`,\n\t\t\t\t\"INVALID_LABEL\",\n\t\t\t\t{ length: label.length },\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate validateType(type: BylineFieldType): void {\n\t\tif (!TYPE_SET.has(type)) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Byline field type \"${type}\" is not supported. Valid types: ${[...TYPE_SET].join(\", \")}`,\n\t\t\t\t\"INVALID_TYPE\",\n\t\t\t\t{ type },\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Normalise + validate a validation payload for a given field type.\n\t *\n\t * - `select`: `options` is required, must be a non-empty array of unique\n\t * non-empty strings, capped at `MAX_SELECT_OPTIONS`.\n\t * - any other type: `options` is silently dropped if present (a future\n\t * field type might use it, but v1 doesn't).\n\t *\n\t * Returns `null` when the resulting validation object is empty, so the\n\t * storage column stays NULL rather than carrying `'{}'`.\n\t */\n\tprivate normaliseValidation(\n\t\ttype: BylineFieldType,\n\t\tvalidation: BylineFieldValidation | null,\n\t): BylineFieldValidation | null {\n\t\tif (type === \"select\") {\n\t\t\tconst options = validation?.options;\n\t\t\tif (!Array.isArray(options) || options.length === 0) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t`Byline field of type \"select\" requires non-empty \"validation.options\"`,\n\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t{ type },\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (options.length > MAX_SELECT_OPTIONS) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t`Byline field \"select\" cannot have more than ${MAX_SELECT_OPTIONS} options`,\n\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t{ count: options.length },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst seen = new Set<string>();\n\t\t\tfor (const option of options) {\n\t\t\t\tif (typeof option !== \"string\" || option.length === 0) {\n\t\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t\t`Byline field \"select\" options must be non-empty strings`,\n\t\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t\t{ option },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (seen.has(option)) {\n\t\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t\t`Byline field \"select\" options must be unique`,\n\t\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t\t{ option },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tseen.add(option);\n\t\t\t}\n\t\t\treturn { options };\n\t\t}\n\n\t\tif (validation == null) return null;\n\t\t// Non-select: drop `options` if present. Strip nothing else — future\n\t\t// field types might extend the shape and we don't want to lose\n\t\t// payload silently. Today's `BylineFieldValidation` is `{ options? }`\n\t\t// only, so this branch is a pass-through; left explicit for clarity.\n\t\tconst { options: _drop, ...rest } = validation;\n\t\treturn Object.keys(rest).length === 0 ? null : (rest as BylineFieldValidation);\n\t}\n}\n\nfunction mapFieldRow(row: {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\ttype: string;\n\trequired: number;\n\ttranslatable: number;\n\tvalidation: string | null;\n\tsort_order: number;\n\tcreated_at: string;\n\tupdated_at: string;\n}): BylineFieldDefinition {\n\treturn {\n\t\tid: row.id,\n\t\tslug: row.slug,\n\t\tlabel: row.label,\n\t\t// `type` is stored as TEXT but `createField` rejects anything outside\n\t\t// `BYLINE_FIELD_TYPES` before inserting. The assertion narrows on\n\t\t// that write-time guarantee.\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write\n\t\ttype: row.type as BylineFieldType,\n\t\trequired: row.required === 1,\n\t\ttranslatable: row.translatable === 1,\n\t\t// `validation` is JSON-encoded `BylineFieldValidation | null`, written\n\t\t// only through `normaliseValidation`. The cast matches the\n\t\t// `JSON.parse(...) as T` pattern in `OptionsRepository`.\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write\n\t\tvalidation: row.validation ? (JSON.parse(row.validation) as BylineFieldValidation) : null,\n\t\tsortOrder: row.sort_order,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n\n// Re-export the table type for callers that want to spell it explicitly.\n// Most callers should rely on the Database interface; this is convenience\n// for tests that hand-roll Kysely queries.\nexport type { BylineFieldTable };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"byline-registry-DedidtqC.mjs","names":[],"sources":["../src/schema/byline-registry.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { BylineFieldTable, Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport {\n\tBYLINE_FIELD_TYPES,\n\tRESERVED_BYLINE_FIELD_SLUGS,\n\ttype BylineFieldDefinition,\n\ttype BylineFieldType,\n\ttype BylineFieldValidation,\n\ttype CreateBylineFieldInput,\n\ttype UpdateBylineFieldInput,\n} from \"./types.js\";\n\nconst RESERVED_SET: ReadonlySet<string> = new Set(RESERVED_BYLINE_FIELD_SLUGS);\nconst TYPE_SET: ReadonlySet<string> = new Set(BYLINE_FIELD_TYPES);\n\nconst VERSION_KEY = \"byline_fields_version\";\n\n/** Hard cap on the choices array for a `select`-type field. */\nconst MAX_SELECT_OPTIONS = 200;\n/** Hard cap on a slug — mirrors `SchemaRegistry.validateSlug`. */\nconst MAX_SLUG_LENGTH = 63;\n/** Hard cap on a label. Bigger than slugs because labels are display strings. */\nconst MAX_LABEL_LENGTH = 200;\n\n/**\n * Error thrown for byline-schema validation failures. Mirrors\n * `SchemaError` in `registry.ts` so the admin API layer can map a small\n * set of codes to HTTP statuses without inspecting messages.\n *\n * Codes:\n * - `INVALID_SLUG` — slug fails identifier rules or length cap\n * - `RESERVED_SLUG` — slug collides with a fixed `_emdash_bylines` column\n * - `INVALID_TYPE` — type is not one of the five v1 field types\n * - `INVALID_LABEL` — label missing or exceeds length cap\n * - `INVALID_VALIDATION` — validation payload malformed (e.g. `select` with\n * no `options`, duplicates in `options`)\n * - `FIELD_EXISTS` — slug already registered\n * - `FIELD_NOT_FOUND` — slug not registered\n * - `TRANSLATABLE_LOCKED` — attempt to flip `translatable` while stored\n * values reference the field\n * - `REORDER_MISMATCH` — reorder input doesn't match the registered set\n */\nexport class BylineSchemaError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: string,\n\t\tpublic details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"BylineSchemaError\";\n\t}\n}\n\n/**\n * Translate a `BylineSchemaError` code to a shared `ErrorCode` for the\n * admin API. HTTP status is then derived by `mapErrorStatus` — this\n * function deliberately doesn't carry one, so the API/handler boundary\n * matches the rest of the codebase (handlers return `ApiResult<T>` with\n * a code, the route layer maps to status via `unwrapResult`).\n *\n * Every code on the right-hand side of `case ... return ...` is defined\n * in `ErrorCode` (`api/errors.ts`). `INVALID_LABEL` and\n * `INVALID_VALIDATION` are intentionally folded into the `default`\n * branch (→ `VALIDATION_ERROR`) so no ad-hoc codes leak out — the\n * registry's domain code names them but the HTTP surface should not.\n *\n * `RESERVED_SLUG` / `INVALID_SLUG` typically don't reach this layer for\n * HTTP callers — the zod schema rejects them first with a clean\n * `VALIDATION_ERROR`. They're still listed so non-HTTP callers (and the\n * test layer) get consistent mapping.\n *\n * `FIELD_NOT_FOUND` is normalised to the shared `NOT_FOUND` code so the\n * admin client can branch on one constant across resource types.\n */\nexport function mapBylineSchemaError(error: BylineSchemaError): {\n\tcode: string;\n\tmessage: string;\n\tdetails?: Record<string, unknown>;\n} {\n\tswitch (error.code) {\n\t\tcase \"FIELD_NOT_FOUND\":\n\t\t\treturn { code: \"NOT_FOUND\", message: error.message, details: error.details };\n\t\tcase \"FIELD_EXISTS\":\n\t\tcase \"TRANSLATABLE_LOCKED\":\n\t\tcase \"REORDER_MISMATCH\":\n\t\tcase \"INVALID_SLUG\":\n\t\tcase \"RESERVED_SLUG\":\n\t\tcase \"INVALID_TYPE\":\n\t\t\treturn { code: error.code, message: error.message, details: error.details };\n\t\tdefault:\n\t\t\t// Catches INVALID_LABEL, INVALID_VALIDATION, and any future\n\t\t\t// registry codes we forget to wire up explicitly.\n\t\t\treturn { code: \"VALIDATION_ERROR\", message: error.message, details: error.details };\n\t}\n}\n\n/**\n * Registry for byline custom fields (Discussion #1174).\n *\n * Owns CRUD over `_emdash_byline_fields` and the\n * `options.byline_fields_version` counter that drives cache\n * invalidation in `bylines/field-defs-cache.ts`.\n *\n * **Dirty-bit bookend.** Every mutation runs `markVersionDirty` before\n * the schema write and `markVersionClean` after, as standalone writes\n * (not inside `withTransaction`) so concurrent isolates observe the\n * dirty mark *before* the mutation lands. Parity carries meaning:\n * odd = mutation in flight or crashed mid-flight, even = stable.\n * The cache bypasses the global holder while odd.\n *\n * `markVersionDirty` is parity-aware (idempotent on odd) so a\n * crashed prior attempt doesn't invert the bit.\n * `markVersionClean` always advances to a new even value (+2 from\n * even, +1 from odd) so concurrent mutators can't collapse on the\n * same key and pin a stale cache snapshot. Idempotent-retry exits\n * (`FIELD_EXISTS` / `FIELD_NOT_FOUND` / no-op update) call\n * `markVersionClean` too — same code path doubles as crash recovery\n * and false-clean recovery.\n *\n * The residual race: a reader caching between two concurrent\n * `markVersionClean` calls sees a partial-set snapshot until the\n * second clean lands. Bounded by the inter-clean window (~ms).\n * Schema mutations are admin-only and rare; acceptable for now.\n * A CAS-on-bump or dialect-specific lock is tracked as follow-up.\n *\n * **`deleteField` cascade.** Migration 041 already declares\n * `ON DELETE CASCADE` on both value tables. The explicit deletes\n * here are defense-in-depth against FK-pragma misconfig and mirror\n * `BylineRepository.delete`'s app-level cascade for the bylines\n * domain.\n *\n * Reserved-slug rejection runs at the API layer (zod) *and* here so\n * non-HTTP callers (seeds, scripts) can't bypass the check.\n */\nexport class BylineSchemaRegistry {\n\tconstructor(private db: Kysely<Database>) {}\n\n\tasync listFields(): Promise<BylineFieldDefinition[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t.execute();\n\t\treturn rows.map((row) => mapFieldRow(row));\n\t}\n\n\tasync getField(slug: string): Promise<BylineFieldDefinition | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.selectAll()\n\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t.executeTakeFirst();\n\t\treturn row ? mapFieldRow(row) : null;\n\t}\n\n\tasync getFieldById(id: string): Promise<BylineFieldDefinition | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\t\treturn row ? mapFieldRow(row) : null;\n\t}\n\n\tasync createField(input: CreateBylineFieldInput): Promise<BylineFieldDefinition> {\n\t\tthis.validateSlug(input.slug);\n\t\tthis.validateLabel(input.label);\n\t\tthis.validateType(input.type);\n\t\tconst validation = this.normaliseValidation(input.type, input.validation ?? null);\n\n\t\tconst existing = await this.getField(input.slug);\n\t\tif (existing) {\n\t\t\t// Idempotent retry exit — see class JSDoc.\n\t\t\tawait this.markVersionClean();\n\t\t\tthrow new BylineSchemaError(`Byline field \"${input.slug}\" already exists`, \"FIELD_EXISTS\", {\n\t\t\t\tslug: input.slug,\n\t\t\t});\n\t\t}\n\n\t\tconst id = ulid();\n\t\tconst sortOrder = input.sortOrder ?? (await this.nextSortOrder());\n\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_byline_fields\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\n\t\t\t\t\tslug: input.slug,\n\t\t\t\t\tlabel: input.label,\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\trequired: input.required ? 1 : 0,\n\t\t\t\t\ttranslatable: input.translatable === false ? 0 : 1,\n\t\t\t\t\tvalidation: validation ? JSON.stringify(validation) : null,\n\t\t\t\t\tsort_order: sortOrder,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t});\n\t\tawait this.markVersionClean();\n\n\t\tconst created = await this.getFieldById(id);\n\t\tif (!created) {\n\t\t\t// Should be unreachable on a working DB — but a typed error\n\t\t\t// beats letting the route returning null on a successful path.\n\t\t\tthrow new BylineSchemaError(\"Failed to load created field\", \"FIELD_NOT_FOUND\", {\n\t\t\t\tid,\n\t\t\t});\n\t\t}\n\t\treturn created;\n\t}\n\n\tasync updateField(slug: string, input: UpdateBylineFieldInput): Promise<BylineFieldDefinition> {\n\t\tconst field = await this.getField(slug);\n\t\tif (!field) {\n\t\t\t// Idempotent retry exit — see class JSDoc.\n\t\t\tawait this.markVersionClean();\n\t\t\tthrow new BylineSchemaError(`Byline field \"${slug}\" not found`, \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\n\t\tconst updates: Partial<{\n\t\t\tlabel: string;\n\t\t\trequired: number;\n\t\t\ttranslatable: number;\n\t\t\tvalidation: string | null;\n\t\t\tsort_order: number;\n\t\t\tupdated_at: string;\n\t\t}> = {};\n\n\t\tif (input.label !== undefined) {\n\t\t\tthis.validateLabel(input.label);\n\t\t\tupdates.label = input.label;\n\t\t}\n\n\t\tif (input.required !== undefined) {\n\t\t\tupdates.required = input.required ? 1 : 0;\n\t\t}\n\n\t\tif (input.validation !== undefined) {\n\t\t\t// Validation payload is normalised against the *current* field\n\t\t\t// type — `type` is not updatable, so it's safe to use `field.type`.\n\t\t\tconst validation = this.normaliseValidation(field.type, input.validation);\n\t\t\tupdates.validation = validation ? JSON.stringify(validation) : null;\n\t\t}\n\n\t\tif (input.translatable !== undefined && input.translatable !== field.translatable) {\n\t\t\t// Flipping `translatable` would orphan any values already stored\n\t\t\t// in the table matching the *current* flag. Reject when any\n\t\t\t// value rows reference this field — admins can delete the field\n\t\t\t// (cascading the values) and re-create it with the new flag if\n\t\t\t// they want a clean re-start. Migrating values across tables is\n\t\t\t// out of scope (Discussion #1174 doesn't authorise it).\n\t\t\tconst usage = await this.countFieldValues(field.id);\n\t\t\tif (usage > 0) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t`Cannot change \"translatable\" on field \"${slug}\" while ${usage} value row(s) exist. ` +\n\t\t\t\t\t\t`Delete the values (or the field) and re-create with the new setting.`,\n\t\t\t\t\t\"TRANSLATABLE_LOCKED\",\n\t\t\t\t\t{ slug, valueCount: usage },\n\t\t\t\t);\n\t\t\t}\n\t\t\tupdates.translatable = input.translatable ? 1 : 0;\n\t\t}\n\n\t\tif (input.sortOrder !== undefined) {\n\t\t\tupdates.sort_order = input.sortOrder;\n\t\t}\n\n\t\tif (Object.keys(updates).length === 0) {\n\t\t\t// No-op update — still advance the clean marker in case\n\t\t\t// we're recovering a crashed prior attempt.\n\t\t\tawait this.markVersionClean();\n\t\t\treturn field;\n\t\t}\n\n\t\tupdates.updated_at = new Date().toISOString();\n\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.updateTable(\"_emdash_byline_fields\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"id\", \"=\", field.id)\n\t\t\t\t.execute();\n\t\t});\n\t\tawait this.markVersionClean();\n\n\t\tconst updated = await this.getFieldById(field.id);\n\t\tif (!updated) {\n\t\t\tthrow new BylineSchemaError(\"Failed to load updated field\", \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\t\treturn updated;\n\t}\n\n\tasync deleteField(slug: string): Promise<void> {\n\t\tconst field = await this.getField(slug);\n\t\tif (!field) {\n\t\t\t// Idempotent retry exit — see class JSDoc.\n\t\t\tawait this.markVersionClean();\n\t\t\tthrow new BylineSchemaError(`Byline field \"${slug}\" not found`, \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\n\t\t// Delete order matters on D1 (no tx): value rows first, definition\n\t\t// row last, so a crash leaves the definition recoverable on retry\n\t\t// rather than orphan values pointing at a vanished id.\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_byline_field_values\")\n\t\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t\t.execute();\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_byline_field_group_values\")\n\t\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t\t.execute();\n\t\t\tawait trx.deleteFrom(\"_emdash_byline_fields\").where(\"id\", \"=\", field.id).execute();\n\t\t});\n\t\tawait this.markVersionClean();\n\t}\n\n\t/**\n\t * Reorder fields by slug. The input must be the *exact* set of\n\t * currently registered slugs — no adds, no drops, no duplicates. This\n\t * keeps the operation invertible (any reorder is followed by a reverse\n\t * reorder) and removes a class of \"did I forget a field?\" bugs at the\n\t * API layer.\n\t */\n\tasync reorderFields(slugs: string[]): Promise<void> {\n\t\tif (new Set(slugs).size !== slugs.length) {\n\t\t\tthrow new BylineSchemaError(\"Reorder input contains duplicate slugs\", \"REORDER_MISMATCH\", {\n\t\t\t\tslugs,\n\t\t\t});\n\t\t}\n\n\t\tconst registered = await this.listFields();\n\t\tconst registeredSlugs = registered.map((f) => f.slug).toSorted();\n\t\tconst inputSlugs = slugs.toSorted();\n\n\t\tif (registeredSlugs.length !== inputSlugs.length) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Reorder input has ${inputSlugs.length} slug(s); ${registeredSlugs.length} registered`,\n\t\t\t\t\"REORDER_MISMATCH\",\n\t\t\t\t{ registered: registeredSlugs, input: inputSlugs },\n\t\t\t);\n\t\t}\n\t\tfor (let i = 0; i < registeredSlugs.length; i++) {\n\t\t\tif (registeredSlugs[i] !== inputSlugs[i]) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t\"Reorder input does not match the registered field set\",\n\t\t\t\t\t\"REORDER_MISMATCH\",\n\t\t\t\t\t{ registered: registeredSlugs, input: inputSlugs },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\t\tawait this.markVersionDirty();\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (let i = 0; i < slugs.length; i++) {\n\t\t\t\tconst slug = slugs[i];\n\t\t\t\tif (slug === undefined) continue;\n\t\t\t\tawait trx\n\t\t\t\t\t.updateTable(\"_emdash_byline_fields\")\n\t\t\t\t\t.set({ sort_order: i, updated_at: now })\n\t\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t\tawait this.markVersionClean();\n\t}\n\n\t/**\n\t * Per-table usage counts for a field, plus the sum. Backs the\n\t * destructive-delete confirm dialog in the admin UI (Phase 5).\n\t *\n\t * Both counts are surfaced separately for diagnostic value: a\n\t * non-zero count on the table that doesn't match the field's current\n\t * `translatable` flag indicates historical drift (e.g. a flip from\n\t * an older code path). Today the registry rejects such flips with\n\t * `TRANSLATABLE_LOCKED`, so any drift originates pre-Phase-2.\n\t *\n\t * Throws `FIELD_NOT_FOUND` when the slug doesn't resolve — callers\n\t * shouldn't get back zero counts for a missing field.\n\t */\n\tasync getFieldUsage(slug: string): Promise<{\n\t\ttranslatableValueCount: number;\n\t\tgroupValueCount: number;\n\t\ttotalAffectedRows: number;\n\t}> {\n\t\tconst field = await this.getField(slug);\n\t\tif (!field) {\n\t\t\tthrow new BylineSchemaError(`Byline field \"${slug}\" not found`, \"FIELD_NOT_FOUND\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\t\tconst tr = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t.executeTakeFirst();\n\t\tconst grp = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_group_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t.executeTakeFirst();\n\t\tconst translatableValueCount = Number(tr?.count ?? 0);\n\t\tconst groupValueCount = Number(grp?.count ?? 0);\n\t\treturn {\n\t\t\ttranslatableValueCount,\n\t\t\tgroupValueCount,\n\t\t\ttotalAffectedRows: translatableValueCount + groupValueCount,\n\t\t};\n\t}\n\n\t/**\n\t * Read the persisted version counter. Used by the field-defs cache\n\t * (Phase 3) to detect invalidation. Returns `0` when the row is\n\t * missing — covers the \"tests that didn't run migration 041\" case\n\t * without throwing.\n\t */\n\tasync getVersion(): Promise<number> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select(\"value\")\n\t\t\t.where(\"name\", \"=\", VERSION_KEY)\n\t\t\t.executeTakeFirst();\n\t\tif (!row) return 0;\n\t\tconst parsed = Number.parseInt(row.value, 10);\n\t\treturn Number.isFinite(parsed) ? parsed : 0;\n\t}\n\n\t// ============================================\n\t// Private helpers\n\t// ============================================\n\n\t/**\n\t * Force the version counter to an odd integer (\"dirty\"). Idempotent\n\t * on odd so a crashed prior attempt can't invert parity. Upsert (not\n\t * UPDATE) so a missing row still flips parity — `getVersion` returns\n\t * 0 on missing, which is even, so a bare UPDATE would leave the\n\t * cache pinned on a stale snapshot. See the class JSDoc.\n\t *\n\t * `options.value` qualified: PG's `ON CONFLICT DO UPDATE` puts both\n\t * the target and `EXCLUDED.value` in scope; bare `value` is ambiguous.\n\t */\n\tprivate async markVersionDirty(): Promise<void> {\n\t\tawait sql`\n\t\t\tINSERT INTO options (name, value)\n\t\t\tVALUES (${VERSION_KEY}, '1')\n\t\t\tON CONFLICT(name) DO UPDATE SET value = CASE\n\t\t\t\tWHEN CAST(options.value AS INTEGER) % 2 = 0\n\t\t\t\t\tTHEN CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)\n\t\t\t\tELSE options.value\n\t\t\tEND\n\t\t`.execute(this.db);\n\t}\n\n\t/**\n\t * Force the version counter to a **new** even integer (+2 from even,\n\t * +1 from odd). Always-advance — never a no-op — so two concurrent\n\t * mutators can't collapse on the same even key and pin a stale cache\n\t * snapshot. See the class JSDoc for the concurrent-collapse rationale.\n\t *\n\t * `options.value` qualified — see `markVersionDirty`.\n\t */\n\tprivate async markVersionClean(): Promise<void> {\n\t\tawait sql`\n\t\t\tINSERT INTO options (name, value)\n\t\t\tVALUES (${VERSION_KEY}, '2')\n\t\t\tON CONFLICT(name) DO UPDATE SET value = CASE\n\t\t\t\tWHEN CAST(options.value AS INTEGER) % 2 = 0\n\t\t\t\t\tTHEN CAST(CAST(options.value AS INTEGER) + 2 AS TEXT)\n\t\t\t\tELSE CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)\n\t\t\tEND\n\t\t`.execute(this.db);\n\t}\n\n\tprivate async nextSortOrder(): Promise<number> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_fields\")\n\t\t\t.select(({ fn }) => [fn.max<number | null>(\"sort_order\").as(\"max\")])\n\t\t\t.executeTakeFirst();\n\t\tconst max = row?.max ?? null;\n\t\treturn max === null ? 0 : max + 1;\n\t}\n\n\tprivate async countFieldValues(fieldId: string): Promise<number> {\n\t\t// Count both per-locale and group-shared values. A field can only\n\t\t// store in one table at a time (translatable picks), but historic\n\t\t// rows might exist in the other if a prior version of this code\n\t\t// allowed the flip — count both to be safe.\n\t\tconst tr = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", fieldId)\n\t\t\t.executeTakeFirst();\n\t\tconst grp = await this.db\n\t\t\t.selectFrom(\"_emdash_byline_field_group_values\")\n\t\t\t.select(({ fn }) => [fn.count<number>(\"field_id\").as(\"count\")])\n\t\t\t.where(\"field_id\", \"=\", fieldId)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(tr?.count ?? 0) + Number(grp?.count ?? 0);\n\t}\n\n\tprivate validateSlug(slug: string): void {\n\t\tif (!slug || typeof slug !== \"string\") {\n\t\t\tthrow new BylineSchemaError(\"Byline field slug is required\", \"INVALID_SLUG\", { slug });\n\t\t}\n\t\tif (slug.length > MAX_SLUG_LENGTH) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Byline field slug must be ${MAX_SLUG_LENGTH} characters or less`,\n\t\t\t\t\"INVALID_SLUG\",\n\t\t\t\t{ slug },\n\t\t\t);\n\t\t}\n\t\t// `validateIdentifier` enforces /^[a-z][a-z0-9_]*$/ — rejects\n\t\t// camelCase, PascalCase, hyphens, leading digits, and identifiers\n\t\t// over 128 characters. We hit the 63-char cap above first, which\n\t\t// matches the content-collection slug cap.\n\t\ttry {\n\t\t\tvalidateIdentifier(slug, \"byline field slug\");\n\t\t} catch (error) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\terror instanceof Error ? error.message : \"Invalid byline field slug\",\n\t\t\t\t\"INVALID_SLUG\",\n\t\t\t\t{ slug },\n\t\t\t);\n\t\t}\n\t\tif (RESERVED_SET.has(slug)) {\n\t\t\tthrow new BylineSchemaError(`Byline field slug \"${slug}\" is reserved`, \"RESERVED_SLUG\", {\n\t\t\t\tslug,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate validateLabel(label: string): void {\n\t\tif (!label || typeof label !== \"string\") {\n\t\t\tthrow new BylineSchemaError(\"Byline field label is required\", \"INVALID_LABEL\", {\n\t\t\t\tlabel,\n\t\t\t});\n\t\t}\n\t\tif (label.length > MAX_LABEL_LENGTH) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Byline field label must be ${MAX_LABEL_LENGTH} characters or less`,\n\t\t\t\t\"INVALID_LABEL\",\n\t\t\t\t{ length: label.length },\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate validateType(type: BylineFieldType): void {\n\t\tif (!TYPE_SET.has(type)) {\n\t\t\tthrow new BylineSchemaError(\n\t\t\t\t`Byline field type \"${type}\" is not supported. Valid types: ${[...TYPE_SET].join(\", \")}`,\n\t\t\t\t\"INVALID_TYPE\",\n\t\t\t\t{ type },\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Normalise + validate a validation payload for a given field type.\n\t *\n\t * - `select`: `options` is required, must be a non-empty array of unique\n\t * non-empty strings, capped at `MAX_SELECT_OPTIONS`.\n\t * - any other type: `options` is silently dropped if present (a future\n\t * field type might use it, but v1 doesn't).\n\t *\n\t * Returns `null` when the resulting validation object is empty, so the\n\t * storage column stays NULL rather than carrying `'{}'`.\n\t */\n\tprivate normaliseValidation(\n\t\ttype: BylineFieldType,\n\t\tvalidation: BylineFieldValidation | null,\n\t): BylineFieldValidation | null {\n\t\tif (type === \"select\") {\n\t\t\tconst options = validation?.options;\n\t\t\tif (!Array.isArray(options) || options.length === 0) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t`Byline field of type \"select\" requires non-empty \"validation.options\"`,\n\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t{ type },\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (options.length > MAX_SELECT_OPTIONS) {\n\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t`Byline field \"select\" cannot have more than ${MAX_SELECT_OPTIONS} options`,\n\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t{ count: options.length },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst seen = new Set<string>();\n\t\t\tfor (const option of options) {\n\t\t\t\tif (typeof option !== \"string\" || option.length === 0) {\n\t\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t\t`Byline field \"select\" options must be non-empty strings`,\n\t\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t\t{ option },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (seen.has(option)) {\n\t\t\t\t\tthrow new BylineSchemaError(\n\t\t\t\t\t\t`Byline field \"select\" options must be unique`,\n\t\t\t\t\t\t\"INVALID_VALIDATION\",\n\t\t\t\t\t\t{ option },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tseen.add(option);\n\t\t\t}\n\t\t\treturn { options };\n\t\t}\n\n\t\tif (validation == null) return null;\n\t\t// Non-select: drop `options` if present. Strip nothing else — future\n\t\t// field types might extend the shape and we don't want to lose\n\t\t// payload silently. Today's `BylineFieldValidation` is `{ options? }`\n\t\t// only, so this branch is a pass-through; left explicit for clarity.\n\t\tconst { options: _drop, ...rest } = validation;\n\t\treturn Object.keys(rest).length === 0 ? null : (rest as BylineFieldValidation);\n\t}\n}\n\nfunction mapFieldRow(row: {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\ttype: string;\n\trequired: number;\n\ttranslatable: number;\n\tvalidation: string | null;\n\tsort_order: number;\n\tcreated_at: string;\n\tupdated_at: string;\n}): BylineFieldDefinition {\n\treturn {\n\t\tid: row.id,\n\t\tslug: row.slug,\n\t\tlabel: row.label,\n\t\t// `type` is stored as TEXT but `createField` rejects anything outside\n\t\t// `BYLINE_FIELD_TYPES` before inserting. The assertion narrows on\n\t\t// that write-time guarantee.\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write\n\t\ttype: row.type as BylineFieldType,\n\t\trequired: row.required === 1,\n\t\ttranslatable: row.translatable === 1,\n\t\t// `validation` is JSON-encoded `BylineFieldValidation | null`, written\n\t\t// only through `normaliseValidation`. The cast matches the\n\t\t// `JSON.parse(...) as T` pattern in `OptionsRepository`.\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write\n\t\tvalidation: row.validation ? (JSON.parse(row.validation) as BylineFieldValidation) : null,\n\t\tsortOrder: row.sort_order,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n\n// Re-export the table type for callers that want to spell it explicitly.\n// Most callers should rely on the Database interface; this is convenience\n// for tests that hand-roll Kysely queries.\nexport type { BylineFieldTable };\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as getI18nConfig } from "./config-CVssduLe.mjs";
|
|
2
2
|
import { t as EmDashValidationError } from "./types-BXSUSAjt.mjs";
|
|
3
|
-
import { t as BylineRepository } from "./byline-
|
|
3
|
+
import { t as BylineRepository } from "./byline-V_Qp1Ziw.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/api/handlers/bylines.ts
|
|
6
6
|
const norm = (v) => v ?? null;
|
|
@@ -201,4 +201,4 @@ async function handleBylineUpdate(db, id, input) {
|
|
|
201
201
|
|
|
202
202
|
//#endregion
|
|
203
203
|
export { handleBylineTranslations as n, handleBylineUpdate as r, handleBylineCreate as t };
|
|
204
|
-
//# sourceMappingURL=bylines-
|
|
204
|
+
//# sourceMappingURL=bylines-B2NWnIwS.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bylines-BdxWCnPL.mjs","names":[],"sources":["../src/api/handlers/bylines.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport {\n\tBylineRepository,\n\ttype CreateBylineInput,\n\ttype UpdateBylineInput,\n} from \"../../database/repositories/byline.js\";\nimport { EmDashValidationError, type BylineSummary } from \"../../database/repositories/types.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { getI18nConfig } from \"../../i18n/config.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// `undefined → null` so a missing field in the create payload matches the\n// repo's stored `null` (BylineRepository normalises with `?? null` on write).\nconst norm = (v: string | null | undefined): string | null => v ?? null;\n\n/**\n * Whether the existing byline row's fixed columns match a fresh-create\n * payload after null/undefined normalisation. Used by the D1 create-retry\n * recovery branch.\n */\nfunction bylineFixedFieldsMatch(\n\texisting: BylineSummary,\n\tinput: CreateBylineInput,\n\teffectiveLocale: string,\n): boolean {\n\treturn (\n\t\texisting.displayName === input.displayName &&\n\t\tnorm(existing.bio) === norm(input.bio) &&\n\t\tnorm(existing.avatarMediaId) === norm(input.avatarMediaId) &&\n\t\tnorm(existing.websiteUrl) === norm(input.websiteUrl) &&\n\t\tnorm(existing.userId) === norm(input.userId) &&\n\t\texisting.isGuest === (input.isGuest ?? false) &&\n\t\texisting.locale === effectiveLocale\n\t);\n}\n\n/**\n * Whether every key in `existing` appears in `input` with the same value.\n * Allows `input` to contain additional keys (the partial-write recovery\n * case); rejects on a divergent value or a key the input omits.\n */\nfunction existingCustomFieldsAreSubsetOf(\n\texisting: Record<string, unknown>,\n\tinput: Record<string, unknown> | undefined,\n): boolean {\n\tif (!input) return Object.keys(existing).length === 0;\n\tfor (const [slug, value] of Object.entries(existing)) {\n\t\tif (!Object.hasOwn(input, slug)) return false;\n\t\tif (input[slug] !== value) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Reject locales the site doesn't configure. Returns `null` when the locale\n * is fine (omitted, or matches `locales` in the i18n config, or i18n isn't\n * configured at all).\n */\nfunction rejectUnknownLocale(locale: string | undefined): ApiResult<never> | null {\n\tif (!locale) return null;\n\tconst config = getI18nConfig();\n\tif (!config) return null;\n\tif (config.locales.includes(locale)) return null;\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Locale \"${locale}\" is not configured for this site`,\n\t\t},\n\t};\n}\n\n/**\n * Business-logic helpers for the bylines admin API.\n *\n * Mirrors the shape of `packages/core/src/api/handlers/menus.ts`. Route files\n * stay thin: they parse input, call these handlers, and forward the result via\n * `unwrapResult`. The repository (`BylineRepository`) is strict per locale; the\n * handlers add validation and translation-flow guards on top.\n */\n\nexport interface BylineTranslationsResponse {\n\titems: BylineSummary[];\n}\n\n/**\n * List every translation of a byline (by row id). Returns NOT_FOUND when no\n * row with the given id exists.\n */\nexport async function handleBylineTranslations(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<BylineTranslationsResponse>> {\n\ttry {\n\t\tconst repo = new BylineRepository(db);\n\t\tconst anchor = await repo.findById(id);\n\t\tif (!anchor) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Byline not found\" },\n\t\t\t};\n\t\t}\n\t\tconst items = await repo.listTranslations(id);\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"BYLINE_TRANSLATIONS_ERROR\",\n\t\t\t\tmessage: \"Failed to list byline translations\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Create a new byline. When `translationOf` is supplied, the new row joins the\n * source byline's translation_group (a sibling in the same logical identity).\n *\n * Translating from a source row only makes sense when the caller names the\n * target locale, otherwise we'd silently clone into the configured default,\n * which is almost never what's intended (and will collide if the source is\n * already the default-locale row). Mirrors `handleMenuCreate`.\n */\nexport async function handleBylineCreate(\n\tdb: Kysely<Database>,\n\tinput: CreateBylineInput,\n): Promise<ApiResult<BylineSummary>> {\n\ttry {\n\t\tif (input.translationOf && !input.locale) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"`locale` is required when `translationOf` is provided\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst localeErr = rejectUnknownLocale(input.locale);\n\t\tif (localeErr) return localeErr;\n\n\t\tconst repo = new BylineRepository(db);\n\n\t\t// Existence check up front so the repo's \"Source not found\" throw\n\t\t// becomes a clean NOT_FOUND on the API.\n\t\tlet sourceGroup: string | undefined;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await repo.findById(input.translationOf);\n\t\t\tif (!source) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\t\tmessage: \"Source byline for translation not found\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tsourceGroup = source.translationGroup ?? source.id;\n\t\t}\n\n\t\tconst effectiveLocale = input.locale ?? getI18nConfig()?.defaultLocale ?? \"en\";\n\n\t\t// Translation-group guard: the row-per-locale model (PR #916)\n\t\t// allows exactly one row per (translation_group, locale). Reject\n\t\t// here so callers get a clean 409 instead of a UNIQUE constraint\n\t\t// failure from the partial index. The DB constraint is the safety\n\t\t// net; this is the friendly error.\n\t\tif (sourceGroup) {\n\t\t\tconst siblings = await repo.findByTranslationGroup(sourceGroup);\n\t\t\tif (siblings.some((b) => b.locale === effectiveLocale)) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `Translation already exists in locale \"${effectiveLocale}\" for this byline`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Duplicate guard: same (slug, locale) — matches the DB unique key\n\t\t// from migration 040.\n\t\tconst existing = await repo.findBySlug(input.slug, { locale: effectiveLocale });\n\t\tif (existing) {\n\t\t\t// D1 has no transactions, so a crash between the byline insert\n\t\t\t// and the per-field writes leaves a partial row that's\n\t\t\t// otherwise unrecoverable. Treat a same-identity retry that\n\t\t\t// provides customFields as completing the abandoned create.\n\t\t\t// Recovery requires fixed-column + translation-group +\n\t\t\t// subset-customFields match; anything else collapses to a\n\t\t\t// standard duplicate-slug conflict.\n\t\t\tconst expectedTranslationGroup = sourceGroup ?? existing.id;\n\t\t\tconst inputHasFields = !!input.customFields && Object.keys(input.customFields).length > 0;\n\t\t\tif (\n\t\t\t\tinputHasFields &&\n\t\t\t\tbylineFixedFieldsMatch(existing, input, effectiveLocale) &&\n\t\t\t\texisting.translationGroup === expectedTranslationGroup &&\n\t\t\t\texistingCustomFieldsAreSubsetOf(existing.customFields ?? {}, input.customFields)\n\t\t\t) {\n\t\t\t\tconst recovered = await repo.update(existing.id, {\n\t\t\t\t\tcustomFields: input.customFields,\n\t\t\t\t});\n\t\t\t\tif (recovered) return { success: true, data: recovered };\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `Byline \"${input.slug}\" already exists${\n\t\t\t\t\t\tinput.locale ? ` in locale \"${input.locale}\"` : \"\"\n\t\t\t\t\t}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst byline = await repo.create(input);\n\t\treturn { success: true, data: byline };\n\t} catch (error) {\n\t\t// Mirror handleBylineUpdate: surface customFields validation\n\t\t// errors as 400 rather than swallowing them as a generic 500.\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"[BYLINE_CREATE_ERROR]\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"BYLINE_CREATE_ERROR\", message: \"Failed to create byline\" },\n\t\t};\n\t}\n}\n\n/**\n * Update an existing byline. Forwards every field on `UpdateBylineInput`\n * to `BylineRepository.update`, including the Phase 3 `customFields`\n * map; per-field type validation lives in the repo, which throws\n * `EmDashValidationError` on unknown slugs, type mismatches, or\n * `select`-choice misses. This handler translates that into a clean\n * `VALIDATION_ERROR` (400 via `mapErrorStatus`).\n *\n * Returns `NOT_FOUND` when the byline id doesn't resolve. Generic\n * failures surface as `BYLINE_UPDATE_ERROR` (500) without leaking the\n * underlying message.\n */\nexport async function handleBylineUpdate(\n\tdb: Kysely<Database>,\n\tid: string,\n\tinput: UpdateBylineInput,\n): Promise<ApiResult<BylineSummary>> {\n\ttry {\n\t\tconst repo = new BylineRepository(db);\n\t\tconst byline = await repo.update(id, input);\n\t\tif (!byline) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Byline not found\" },\n\t\t\t};\n\t\t}\n\t\treturn { success: true, data: byline };\n\t} catch (error) {\n\t\t// Unknown-key + type-mismatch + select-choice writes throw\n\t\t// EmDashValidationError (Phase 3, see BylineRepository.update).\n\t\t// Map to a clean 400 — the error message names the offending\n\t\t// slug/type, which is safe to surface to the admin client.\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"[BYLINE_UPDATE_ERROR]\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"BYLINE_UPDATE_ERROR\", message: \"Failed to update byline\" },\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bylines-B2NWnIwS.mjs","names":[],"sources":["../src/api/handlers/bylines.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport {\n\tBylineRepository,\n\ttype CreateBylineInput,\n\ttype UpdateBylineInput,\n} from \"../../database/repositories/byline.js\";\nimport { EmDashValidationError, type BylineSummary } from \"../../database/repositories/types.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { getI18nConfig } from \"../../i18n/config.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// `undefined → null` so a missing field in the create payload matches the\n// repo's stored `null` (BylineRepository normalises with `?? null` on write).\nconst norm = (v: string | null | undefined): string | null => v ?? null;\n\n/**\n * Whether the existing byline row's fixed columns match a fresh-create\n * payload after null/undefined normalisation. Used by the D1 create-retry\n * recovery branch.\n */\nfunction bylineFixedFieldsMatch(\n\texisting: BylineSummary,\n\tinput: CreateBylineInput,\n\teffectiveLocale: string,\n): boolean {\n\treturn (\n\t\texisting.displayName === input.displayName &&\n\t\tnorm(existing.bio) === norm(input.bio) &&\n\t\tnorm(existing.avatarMediaId) === norm(input.avatarMediaId) &&\n\t\tnorm(existing.websiteUrl) === norm(input.websiteUrl) &&\n\t\tnorm(existing.userId) === norm(input.userId) &&\n\t\texisting.isGuest === (input.isGuest ?? false) &&\n\t\texisting.locale === effectiveLocale\n\t);\n}\n\n/**\n * Whether every key in `existing` appears in `input` with the same value.\n * Allows `input` to contain additional keys (the partial-write recovery\n * case); rejects on a divergent value or a key the input omits.\n */\nfunction existingCustomFieldsAreSubsetOf(\n\texisting: Record<string, unknown>,\n\tinput: Record<string, unknown> | undefined,\n): boolean {\n\tif (!input) return Object.keys(existing).length === 0;\n\tfor (const [slug, value] of Object.entries(existing)) {\n\t\tif (!Object.hasOwn(input, slug)) return false;\n\t\tif (input[slug] !== value) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Reject locales the site doesn't configure. Returns `null` when the locale\n * is fine (omitted, or matches `locales` in the i18n config, or i18n isn't\n * configured at all).\n */\nfunction rejectUnknownLocale(locale: string | undefined): ApiResult<never> | null {\n\tif (!locale) return null;\n\tconst config = getI18nConfig();\n\tif (!config) return null;\n\tif (config.locales.includes(locale)) return null;\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Locale \"${locale}\" is not configured for this site`,\n\t\t},\n\t};\n}\n\n/**\n * Business-logic helpers for the bylines admin API.\n *\n * Mirrors the shape of `packages/core/src/api/handlers/menus.ts`. Route files\n * stay thin: they parse input, call these handlers, and forward the result via\n * `unwrapResult`. The repository (`BylineRepository`) is strict per locale; the\n * handlers add validation and translation-flow guards on top.\n */\n\nexport interface BylineTranslationsResponse {\n\titems: BylineSummary[];\n}\n\n/**\n * List every translation of a byline (by row id). Returns NOT_FOUND when no\n * row with the given id exists.\n */\nexport async function handleBylineTranslations(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<BylineTranslationsResponse>> {\n\ttry {\n\t\tconst repo = new BylineRepository(db);\n\t\tconst anchor = await repo.findById(id);\n\t\tif (!anchor) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Byline not found\" },\n\t\t\t};\n\t\t}\n\t\tconst items = await repo.listTranslations(id);\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"BYLINE_TRANSLATIONS_ERROR\",\n\t\t\t\tmessage: \"Failed to list byline translations\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Create a new byline. When `translationOf` is supplied, the new row joins the\n * source byline's translation_group (a sibling in the same logical identity).\n *\n * Translating from a source row only makes sense when the caller names the\n * target locale, otherwise we'd silently clone into the configured default,\n * which is almost never what's intended (and will collide if the source is\n * already the default-locale row). Mirrors `handleMenuCreate`.\n */\nexport async function handleBylineCreate(\n\tdb: Kysely<Database>,\n\tinput: CreateBylineInput,\n): Promise<ApiResult<BylineSummary>> {\n\ttry {\n\t\tif (input.translationOf && !input.locale) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"`locale` is required when `translationOf` is provided\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst localeErr = rejectUnknownLocale(input.locale);\n\t\tif (localeErr) return localeErr;\n\n\t\tconst repo = new BylineRepository(db);\n\n\t\t// Existence check up front so the repo's \"Source not found\" throw\n\t\t// becomes a clean NOT_FOUND on the API.\n\t\tlet sourceGroup: string | undefined;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await repo.findById(input.translationOf);\n\t\t\tif (!source) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\t\tmessage: \"Source byline for translation not found\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tsourceGroup = source.translationGroup ?? source.id;\n\t\t}\n\n\t\tconst effectiveLocale = input.locale ?? getI18nConfig()?.defaultLocale ?? \"en\";\n\n\t\t// Translation-group guard: the row-per-locale model (PR #916)\n\t\t// allows exactly one row per (translation_group, locale). Reject\n\t\t// here so callers get a clean 409 instead of a UNIQUE constraint\n\t\t// failure from the partial index. The DB constraint is the safety\n\t\t// net; this is the friendly error.\n\t\tif (sourceGroup) {\n\t\t\tconst siblings = await repo.findByTranslationGroup(sourceGroup);\n\t\t\tif (siblings.some((b) => b.locale === effectiveLocale)) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `Translation already exists in locale \"${effectiveLocale}\" for this byline`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Duplicate guard: same (slug, locale) — matches the DB unique key\n\t\t// from migration 040.\n\t\tconst existing = await repo.findBySlug(input.slug, { locale: effectiveLocale });\n\t\tif (existing) {\n\t\t\t// D1 has no transactions, so a crash between the byline insert\n\t\t\t// and the per-field writes leaves a partial row that's\n\t\t\t// otherwise unrecoverable. Treat a same-identity retry that\n\t\t\t// provides customFields as completing the abandoned create.\n\t\t\t// Recovery requires fixed-column + translation-group +\n\t\t\t// subset-customFields match; anything else collapses to a\n\t\t\t// standard duplicate-slug conflict.\n\t\t\tconst expectedTranslationGroup = sourceGroup ?? existing.id;\n\t\t\tconst inputHasFields = !!input.customFields && Object.keys(input.customFields).length > 0;\n\t\t\tif (\n\t\t\t\tinputHasFields &&\n\t\t\t\tbylineFixedFieldsMatch(existing, input, effectiveLocale) &&\n\t\t\t\texisting.translationGroup === expectedTranslationGroup &&\n\t\t\t\texistingCustomFieldsAreSubsetOf(existing.customFields ?? {}, input.customFields)\n\t\t\t) {\n\t\t\t\tconst recovered = await repo.update(existing.id, {\n\t\t\t\t\tcustomFields: input.customFields,\n\t\t\t\t});\n\t\t\t\tif (recovered) return { success: true, data: recovered };\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `Byline \"${input.slug}\" already exists${\n\t\t\t\t\t\tinput.locale ? ` in locale \"${input.locale}\"` : \"\"\n\t\t\t\t\t}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst byline = await repo.create(input);\n\t\treturn { success: true, data: byline };\n\t} catch (error) {\n\t\t// Mirror handleBylineUpdate: surface customFields validation\n\t\t// errors as 400 rather than swallowing them as a generic 500.\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"[BYLINE_CREATE_ERROR]\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"BYLINE_CREATE_ERROR\", message: \"Failed to create byline\" },\n\t\t};\n\t}\n}\n\n/**\n * Update an existing byline. Forwards every field on `UpdateBylineInput`\n * to `BylineRepository.update`, including the Phase 3 `customFields`\n * map; per-field type validation lives in the repo, which throws\n * `EmDashValidationError` on unknown slugs, type mismatches, or\n * `select`-choice misses. This handler translates that into a clean\n * `VALIDATION_ERROR` (400 via `mapErrorStatus`).\n *\n * Returns `NOT_FOUND` when the byline id doesn't resolve. Generic\n * failures surface as `BYLINE_UPDATE_ERROR` (500) without leaking the\n * underlying message.\n */\nexport async function handleBylineUpdate(\n\tdb: Kysely<Database>,\n\tid: string,\n\tinput: UpdateBylineInput,\n): Promise<ApiResult<BylineSummary>> {\n\ttry {\n\t\tconst repo = new BylineRepository(db);\n\t\tconst byline = await repo.update(id, input);\n\t\tif (!byline) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Byline not found\" },\n\t\t\t};\n\t\t}\n\t\treturn { success: true, data: byline };\n\t} catch (error) {\n\t\t// Unknown-key + type-mismatch + select-choice writes throw\n\t\t// EmDashValidationError (Phase 3, see BylineRepository.update).\n\t\t// Map to a clean 400 — the error message names the offending\n\t\t// slug/type, which is safe to surface to the admin client.\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"[BYLINE_UPDATE_ERROR]\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"BYLINE_UPDATE_ERROR\", message: \"Failed to update byline\" },\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { a as __exportAll } from "./runner--4wMWwKM.mjs";
|
|
2
2
|
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
3
|
-
import { r as requestCached } from "./request-cache-
|
|
4
|
-
import { t as BylineRepository } from "./byline-
|
|
5
|
-
import { n as isMissingTableError } from "./db-errors-
|
|
6
|
-
import { r as getDb } from "./loader-
|
|
7
|
-
import { i as resolveLocaleChain } from "./resolve-
|
|
3
|
+
import { r as requestCached } from "./request-cache-UwmBAiUK.mjs";
|
|
4
|
+
import { t as BylineRepository } from "./byline-V_Qp1Ziw.mjs";
|
|
5
|
+
import { n as isMissingTableError } from "./db-errors-BluWkwGI.mjs";
|
|
6
|
+
import { r as getDb } from "./loader-BqWjcH3h.mjs";
|
|
7
|
+
import { i as resolveLocaleChain } from "./resolve-B3NUUtVY.mjs";
|
|
8
8
|
import { sql } from "kysely";
|
|
9
9
|
|
|
10
10
|
//#region src/bylines/index.ts
|
|
@@ -217,7 +217,7 @@ async function getBylinesForEntries(collection, entries) {
|
|
|
217
217
|
* @param options - Optional locale, ordering, status, and limit
|
|
218
218
|
*/
|
|
219
219
|
async function getEntriesByByline(collection, byline, options = {}) {
|
|
220
|
-
const { getEmDashCollection } = await import("./query-
|
|
220
|
+
const { getEmDashCollection } = await import("./query-Crm038Mc.mjs").then((n) => n.o);
|
|
221
221
|
const queryOptions = { where: { byline } };
|
|
222
222
|
if (options.locale !== void 0) queryOptions.locale = options.locale;
|
|
223
223
|
if (options.orderBy !== void 0) queryOptions.orderBy = options.orderBy;
|
|
@@ -229,4 +229,4 @@ async function getEntriesByByline(collection, byline, options = {}) {
|
|
|
229
229
|
|
|
230
230
|
//#endregion
|
|
231
231
|
export { invalidateBylineCache as a, getEntriesByByline as i, getByline as n, getBylineBySlug as r, bylines_exports as t };
|
|
232
|
-
//# sourceMappingURL=bylines-
|
|
232
|
+
//# sourceMappingURL=bylines-DfGDnred.mjs.map
|