emdash 0.16.0 → 0.17.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-C4yd_UJR.d.mts → adapters-C5AWLJSD.d.mts} +1 -1
- package/dist/{adapters-C4yd_UJR.d.mts.map → adapters-C5AWLJSD.d.mts.map} +1 -1
- package/dist/{allowed-origins-D0fFk9a6.mjs → allowed-origins-CyYLEJkp.mjs} +2 -2
- package/dist/{allowed-origins-D0fFk9a6.mjs.map → allowed-origins-CyYLEJkp.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +16 -16
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-BNKqxyFX.mjs → api-Dmz40c2V.mjs} +44 -22
- package/dist/api-Dmz40c2V.mjs.map +1 -0
- package/dist/{api-tokens-ucpcNXDt.mjs → api-tokens-VrXNiNvV.mjs} +2 -2
- package/dist/{api-tokens-ucpcNXDt.mjs.map → api-tokens-VrXNiNvV.mjs.map} +1 -1
- package/dist/{apply-BOPaD-s9.mjs → apply-CgamLmed.mjs} +93 -31
- package/dist/apply-CgamLmed.mjs.map +1 -0
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.mjs +19 -3
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +6 -6
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +9 -5
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +2 -2
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +66 -65
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.d.mts +8 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +23 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_.d.mts +10 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +55 -0
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/index.d.mts +9 -0
- package/dist/astro/routes/api/admin/byline-fields/index.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +43 -0
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/reorder.d.mts +8 -0
- package/dist/astro/routes/api/admin/byline-fields/reorder.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +27 -0
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -0
- package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +27 -28
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +13 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +15 -13
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +35 -34
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +35 -34
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -33
- 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 +34 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -33
- 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 +35 -34
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -34
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -33
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -33
- 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 +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -33
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
- package/dist/astro/routes/api/admin/users/index.mjs +5 -5
- package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +9 -9
- package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -8
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/me.mjs +18 -11
- 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 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -8
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -9
- package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -9
- package/dist/astro/routes/api/auth/signup/complete.mjs +9 -9
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +18 -13
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -7
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
- package/dist/astro/routes/api/dashboard.mjs +7 -7
- package/dist/astro/routes/api/dev/emails.mjs +3 -3
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +10 -10
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -10
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -10
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +13 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +4 -4
- package/dist/astro/routes/api/mcp.mjs +34 -30
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
- package/dist/astro/routes/api/media/_id_.mjs +6 -6
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.mjs +8 -8
- package/dist/astro/routes/api/media.d.mts.map +1 -1
- package/dist/astro/routes/api/media.mjs +13 -12
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
- package/dist/astro/routes/api/menus/_name_.mjs +7 -7
- package/dist/astro/routes/api/menus/index.mjs +7 -7
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
- package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
- package/dist/astro/routes/api/oauth/register.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +10 -7
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
- package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
- package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
- package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
- package/dist/astro/routes/api/redirects/index.mjs +9 -9
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -33
- 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 +34 -33
- 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 +34 -33
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -33
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +34 -33
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +6 -6
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -33
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +34 -33
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +9 -9
- package/dist/astro/routes/api/search/index.mjs +8 -8
- package/dist/astro/routes/api/search/rebuild.mjs +9 -9
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +8 -8
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
- package/dist/astro/routes/api/sections/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +11 -11
- package/dist/astro/routes/api/setup/admin-verify.mjs +10 -10
- package/dist/astro/routes/api/setup/admin.mjs +9 -9
- package/dist/astro/routes/api/setup/dev-bypass.mjs +24 -23
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +24 -23
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +4 -4
- package/dist/astro/routes/api/snapshot.mjs +5 -5
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +12 -12
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +12 -12
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +12 -12
- package/dist/astro/routes/api/taxonomies/index.mjs +12 -12
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
- package/dist/astro/routes/api/widget-components.mjs +3 -3
- package/dist/astro/routes/robots.txt.mjs +6 -6
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -8
- package/dist/astro/routes/sitemap.xml.mjs +7 -7
- package/dist/astro/types.d.mts +13 -12
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-Bn4S4DUT.mjs → authorize-_wWM_44T.mjs} +2 -2
- package/dist/{authorize-Bn4S4DUT.mjs.map → authorize-_wWM_44T.mjs.map} +1 -1
- package/dist/byline-BrIVWLm-.mjs +925 -0
- package/dist/byline-BrIVWLm-.mjs.map +1 -0
- package/dist/{bylines-B2_XmnSU.d.mts → byline-fields-BNy7Ng1U.d.mts} +154 -26
- package/dist/byline-fields-BNy7Ng1U.d.mts.map +1 -0
- package/dist/byline-fields-DC3Wkk-U.mjs +123 -0
- package/dist/byline-fields-DC3Wkk-U.mjs.map +1 -0
- package/dist/byline-fields-Dr-xcb6S.mjs +238 -0
- package/dist/byline-fields-Dr-xcb6S.mjs.map +1 -0
- package/dist/byline-registry-CxK5g559.mjs +406 -0
- package/dist/byline-registry-CxK5g559.mjs.map +1 -0
- package/dist/{bylines-n6nykUyI.mjs → bylines-C_POWmGT.mjs} +25 -11
- package/dist/{bylines-n6nykUyI.mjs.map → bylines-C_POWmGT.mjs.map} +1 -1
- package/dist/bylines-sqExMElV.mjs +204 -0
- package/dist/bylines-sqExMElV.mjs.map +1 -0
- package/dist/{cache-BcI1yUjR.mjs → cache-wsDkA8ru.mjs} +2 -2
- package/dist/{cache-BcI1yUjR.mjs.map → cache-wsDkA8ru.mjs.map} +1 -1
- package/dist/{challenge-store-Dng1SxKT.mjs → challenge-store-DGwuCc4R.mjs} +1 -1
- package/dist/{challenge-store-Dng1SxKT.mjs.map → challenge-store-DGwuCc4R.mjs.map} +1 -1
- package/dist/{chunks-cYG4SnIP.mjs → chunks-BAYkM-CF.mjs} +2 -2
- package/dist/{chunks-cYG4SnIP.mjs.map → chunks-BAYkM-CF.mjs.map} +1 -1
- package/dist/cli/index.mjs +29 -23
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +2 -1
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +4 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/{comment-C76G-9tz.mjs → comment-Cd29aktf.mjs} +2 -2
- package/dist/{comment-C76G-9tz.mjs.map → comment-Cd29aktf.mjs.map} +1 -1
- package/dist/{comments-CCxFFGY1.mjs → comments-B7ufhkxN.mjs} +3 -3
- package/dist/{comments-CCxFFGY1.mjs.map → comments-B7ufhkxN.mjs.map} +1 -1
- package/dist/{components-Dx3DM0gg.mjs → components-CTfpu3PZ.mjs} +1 -1
- package/dist/{components-Dx3DM0gg.mjs.map → components-CTfpu3PZ.mjs.map} +1 -1
- package/dist/{content-8voQNTXX.mjs → content-BbqKo3Kc.mjs} +22 -3
- package/dist/content-BbqKo3Kc.mjs.map +1 -0
- package/dist/{context-B7qiYrz2.mjs → context-BsF1rhoI.mjs} +9 -9
- package/dist/{context-B7qiYrz2.mjs.map → context-BsF1rhoI.mjs.map} +1 -1
- package/dist/{cron-Bd3b3iuj.mjs → cron-DZovZUnC.mjs} +1 -1
- package/dist/{cron-Bd3b3iuj.mjs.map → cron-DZovZUnC.mjs.map} +1 -1
- package/dist/{dashboard-BeaFSPpx.mjs → dashboard-BwIX9r-X.mjs} +4 -4
- package/dist/{dashboard-BeaFSPpx.mjs.map → dashboard-BwIX9r-X.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-BiYqoX-n.mjs → db-errors-CtzxKBxe.mjs} +1 -1
- package/dist/{db-errors-BiYqoX-n.mjs.map → db-errors-CtzxKBxe.mjs.map} +1 -1
- package/dist/{default-BvTAYCzx.mjs → default-xLFNSsZ9.mjs} +1 -1
- package/dist/{default-BvTAYCzx.mjs.map → default-xLFNSsZ9.mjs.map} +1 -1
- package/dist/{device-flow-B9oG8PwP.mjs → device-flow-ptLrVINd.mjs} +4 -4
- package/dist/{device-flow-B9oG8PwP.mjs.map → device-flow-ptLrVINd.mjs.map} +1 -1
- package/dist/{email-console-CubRll9q.mjs → email-console-DHT2Fbpj.mjs} +1 -1
- package/dist/{email-console-CubRll9q.mjs.map → email-console-DHT2Fbpj.mjs.map} +1 -1
- package/dist/{error-ChfADBuu.mjs → error-npZWBSb7.mjs} +7 -3
- package/dist/error-npZWBSb7.mjs.map +1 -0
- package/dist/{escape-Cg6kMELH.mjs → escape-bIyGoW5W.mjs} +1 -1
- package/dist/{escape-Cg6kMELH.mjs.map → escape-bIyGoW5W.mjs.map} +1 -1
- package/dist/{fts-manager-C_b-4x8u.mjs → fts-manager-DmUAk-kQ.mjs} +2 -2
- package/dist/{fts-manager-C_b-4x8u.mjs.map → fts-manager-DmUAk-kQ.mjs.map} +1 -1
- package/dist/{hash-DlUxGhQS.mjs → hash-9w3pd3-m.mjs} +1 -1
- package/dist/{hash-DlUxGhQS.mjs.map → hash-9w3pd3-m.mjs.map} +1 -1
- package/dist/{import-DG80rC_I.mjs → import-Dh8bWmyq.mjs} +3 -3
- package/dist/{import-DG80rC_I.mjs.map → import-Dh8bWmyq.mjs.map} +1 -1
- package/dist/{index-BPZFAcgE.d.mts → index-CjKdMZ3U.d.mts} +39 -17
- package/dist/index-CjKdMZ3U.d.mts.map +1 -0
- package/dist/{index-CC42STEm.d.mts → index-D60_SzHG.d.mts} +3 -3
- package/dist/{index-CC42STEm.d.mts.map → index-D60_SzHG.d.mts.map} +1 -1
- package/dist/index.d.mts +17 -17
- package/dist/index.mjs +55 -54
- package/dist/{load-CLFRjk9r.mjs → load-DsoLq7ex.mjs} +2 -2
- package/dist/{load-CLFRjk9r.mjs.map → load-DsoLq7ex.mjs.map} +1 -1
- package/dist/{loader-D-vIJjfY.mjs → loader-CJ6lWO0d.mjs} +75 -19
- package/dist/loader-CJ6lWO0d.mjs.map +1 -0
- package/dist/{manifest-schema-Czqf0TLu.mjs → manifest-schema-Cj-YrzrF.mjs} +1 -1
- package/dist/{manifest-schema-Czqf0TLu.mjs.map → manifest-schema-Cj-YrzrF.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 +5 -5
- package/dist/{media-allowlist-BNloC69x.mjs → media-allowlist-CMcoYIjQ.mjs} +2 -2
- package/dist/{media-allowlist-BNloC69x.mjs.map → media-allowlist-CMcoYIjQ.mjs.map} +1 -1
- package/dist/{media-CKQd8AYU.mjs → media-jk_HzzOl.mjs} +7 -2
- package/dist/media-jk_HzzOl.mjs.map +1 -0
- package/dist/{menus-arUNspyU.mjs → menus-B-5-3aon.mjs} +2 -2
- package/dist/{menus-arUNspyU.mjs.map → menus-B-5-3aon.mjs.map} +1 -1
- package/dist/{menus-C-nWT5Tu.mjs → menus-CyMO6GBx.mjs} +27 -11
- package/dist/menus-CyMO6GBx.mjs.map +1 -0
- package/dist/{mime-KV5TqkMN.mjs → mime-CCEzze7W.mjs} +1 -1
- package/dist/{mime-KV5TqkMN.mjs.map → mime-CCEzze7W.mjs.map} +1 -1
- package/dist/{mode-CaaiebZI.mjs → mode-BjlXswIw.mjs} +1 -1
- package/dist/{mode-CaaiebZI.mjs.map → mode-BjlXswIw.mjs.map} +1 -1
- package/dist/{normalize-CN5kRSMC.mjs → normalize-DVV8nbrL.mjs} +1 -1
- package/dist/{normalize-CN5kRSMC.mjs.map → normalize-DVV8nbrL.mjs.map} +1 -1
- package/dist/{oauth-authorization-CTMeVfvj.mjs → oauth-authorization-DvBAL75d.mjs} +4 -4
- package/dist/{oauth-authorization-CTMeVfvj.mjs.map → oauth-authorization-DvBAL75d.mjs.map} +1 -1
- package/dist/{oauth-clients-eJCbkVSG.mjs → oauth-clients-8mPDStMv.mjs} +1 -1
- package/dist/{oauth-clients-eJCbkVSG.mjs.map → oauth-clients-8mPDStMv.mjs.map} +1 -1
- package/dist/{oauth-state-store-vOSdOeGe.mjs → oauth-state-store-BJ7YtrfD.mjs} +1 -1
- package/dist/{oauth-state-store-vOSdOeGe.mjs.map → oauth-state-store-BJ7YtrfD.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-3JwsVw6N.mjs → oauth-user-lookup-BdDSDvjF.mjs} +1 -1
- package/dist/{oauth-user-lookup-3JwsVw6N.mjs.map → oauth-user-lookup-BdDSDvjF.mjs.map} +1 -1
- package/dist/{options-DhV-gwJb.d.mts → options-tb7DJROi.d.mts} +3 -3
- package/dist/{options-DhV-gwJb.d.mts.map → options-tb7DJROi.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-DHbXfvxO.mjs → parse-4zO5Y2DL.mjs} +2 -2
- package/dist/{parse-DHbXfvxO.mjs.map → parse-4zO5Y2DL.mjs.map} +1 -1
- package/dist/{passkey-config-BloQOT3y.mjs → passkey-config-BDVM86Tj.mjs} +1 -1
- package/dist/{passkey-config-BloQOT3y.mjs.map → passkey-config-BDVM86Tj.mjs.map} +1 -1
- package/dist/{placeholder-KCkkCtgQ.d.mts → placeholder-B9lUUEmj.d.mts} +1 -1
- package/dist/{placeholder-KCkkCtgQ.d.mts.map → placeholder-B9lUUEmj.d.mts.map} +1 -1
- package/dist/{placeholder-LqmHqvBw.mjs → placeholder-BZxr8W1j.mjs} +1 -1
- package/dist/{placeholder-LqmHqvBw.mjs.map → placeholder-BZxr8W1j.mjs.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-D4z0WONU.mjs → preview-BfuRkVKW.mjs} +2 -2
- package/dist/{preview-D4z0WONU.mjs.map → preview-BfuRkVKW.mjs.map} +1 -1
- package/dist/{public-url-CUWWFME2.mjs → public-url-egRHCy1m.mjs} +1 -1
- package/dist/{public-url-CUWWFME2.mjs.map → public-url-egRHCy1m.mjs.map} +1 -1
- package/dist/{query-7m6-l0f_.mjs → query-CuvjwhrE.mjs} +12 -12
- package/dist/{query-7m6-l0f_.mjs.map → query-CuvjwhrE.mjs.map} +1 -1
- package/dist/{rate-limit-D8RAXN8b.mjs → rate-limit-D6VQqBk_.mjs} +2 -2
- package/dist/{rate-limit-D8RAXN8b.mjs.map → rate-limit-D6VQqBk_.mjs.map} +1 -1
- package/dist/{redirect-CjfDGrTd.mjs → redirect-BZUJltlj.mjs} +2 -2
- package/dist/{redirect-CjfDGrTd.mjs.map → redirect-BZUJltlj.mjs.map} +1 -1
- package/dist/{redirect-BINiRYq4.mjs → redirect-Cw3JTlmj.mjs} +1 -1
- package/dist/{redirect-BINiRYq4.mjs.map → redirect-Cw3JTlmj.mjs.map} +1 -1
- package/dist/{redirects-COMLwsV5.mjs → redirects-C0L9JUk4.mjs} +19 -6
- package/dist/redirects-C0L9JUk4.mjs.map +1 -0
- package/dist/{redirects-CowoEHdE.mjs → redirects-DnYuqsEf.mjs} +3 -3
- package/dist/{redirects-CowoEHdE.mjs.map → redirects-DnYuqsEf.mjs.map} +1 -1
- package/dist/{registry-Cyp-dx6J.mjs → registry-Dn6gsx3L.mjs} +13 -5
- package/dist/{registry-Cyp-dx6J.mjs.map → registry-Dn6gsx3L.mjs.map} +1 -1
- package/dist/{request-cache-dzCt8TZB.mjs → request-cache-BYMs-BGX.mjs} +23 -2
- package/dist/{request-cache-dzCt8TZB.mjs.map → request-cache-BYMs-BGX.mjs.map} +1 -1
- package/dist/{request-meta-C_Cjii-T.mjs → request-meta-7ByVLxB-.mjs} +2 -2
- package/dist/{request-meta-C_Cjii-T.mjs.map → request-meta-7ByVLxB-.mjs.map} +1 -1
- package/dist/{resolve-D6sM-SgF.mjs → resolve-BqYMVG0D.mjs} +1 -1
- package/dist/{resolve-D6sM-SgF.mjs.map → resolve-BqYMVG0D.mjs.map} +1 -1
- package/dist/{runner-DSQBurMS.d.mts → runner-DM1yR5qd.d.mts} +2 -2
- package/dist/{runner-DSQBurMS.d.mts.map → runner-DM1yR5qd.d.mts.map} +1 -1
- package/dist/{runner-Drnvs96u.mjs → runner-eAgyIkeg.mjs} +284 -158
- package/dist/runner-eAgyIkeg.mjs.map +1 -0
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-CI9mYPX3.mjs → schema--mYZX4D7.mjs} +5 -5
- package/dist/{schema-CI9mYPX3.mjs.map → schema--mYZX4D7.mjs.map} +1 -1
- package/dist/{search-DKz_mGBP.mjs → search-C6U_NvZI.mjs} +4 -4
- package/dist/{search-DKz_mGBP.mjs.map → search-C6U_NvZI.mjs.map} +1 -1
- package/dist/{secrets-rPdhEBkD.mjs → secrets-YYbTgB1w.mjs} +1 -1
- package/dist/{secrets-rPdhEBkD.mjs.map → secrets-YYbTgB1w.mjs.map} +1 -1
- package/dist/{sections-DBbCDIAT.mjs → sections-Ba-rJLKb.mjs} +3 -3
- package/dist/{sections-DBbCDIAT.mjs.map → sections-Ba-rJLKb.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +18 -17
- package/dist/seo/index.d.mts +1 -1
- package/dist/{seo-BGCyDlkb.mjs → seo-BTzb5ksq.mjs} +2 -2
- package/dist/{seo-BGCyDlkb.mjs.map → seo-BTzb5ksq.mjs.map} +1 -1
- package/dist/{seo-Dq707mNQ.mjs → seo-DfjLvu8i.mjs} +1 -1
- package/dist/{seo-Dq707mNQ.mjs.map → seo-DfjLvu8i.mjs.map} +1 -1
- package/dist/{service-B0H7U1Y9.mjs → service-Cn-kIfZn.mjs} +3 -3
- package/dist/{service-B0H7U1Y9.mjs.map → service-Cn-kIfZn.mjs.map} +1 -1
- package/dist/{settings-DfwNyQkf.mjs → settings-C65OSm41.mjs} +3 -3
- package/dist/{settings-DfwNyQkf.mjs.map → settings-C65OSm41.mjs.map} +1 -1
- package/dist/{settings-BSXRtTzk.mjs → settings-ChlQbwU0.mjs} +4 -4
- package/dist/{settings-BSXRtTzk.mjs.map → settings-ChlQbwU0.mjs.map} +1 -1
- package/dist/{setup-complete-MzzN9u0b.mjs → setup-complete-VoEZfasi.mjs} +1 -1
- package/dist/{setup-complete-MzzN9u0b.mjs.map → setup-complete-VoEZfasi.mjs.map} +1 -1
- package/dist/{setup-nonce-DXuriHsg.mjs → setup-nonce-Bm0uKqmf.mjs} +1 -1
- package/dist/{setup-nonce-DXuriHsg.mjs.map → setup-nonce-Bm0uKqmf.mjs.map} +1 -1
- package/dist/{site-url-xkhw1tcz.mjs → site-url-Cm8-sJy7.mjs} +1 -1
- package/dist/{site-url-xkhw1tcz.mjs.map → site-url-Cm8-sJy7.mjs.map} +1 -1
- package/dist/{ssrf-MZ-zrG6-.mjs → ssrf-BsVGIE0Z.mjs} +1 -1
- package/dist/{ssrf-MZ-zrG6-.mjs.map → ssrf-BsVGIE0Z.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-CcvrMLbR.mjs → taxonomies-CgpzAU6F.mjs} +8 -8
- package/dist/{taxonomies-CcvrMLbR.mjs.map → taxonomies-CgpzAU6F.mjs.map} +1 -1
- package/dist/{taxonomies-4vx0nmMr.mjs → taxonomies-D72gTOg_.mjs} +4 -4
- package/dist/{taxonomies-4vx0nmMr.mjs.map → taxonomies-D72gTOg_.mjs.map} +1 -1
- package/dist/{taxonomy-zqGQUqgu.mjs → taxonomy-BBK-UAEo.mjs} +3 -3
- package/dist/{taxonomy-zqGQUqgu.mjs.map → taxonomy-BBK-UAEo.mjs.map} +1 -1
- package/dist/{tokens-N8otWMmj.mjs → tokens-Bx2afeT-.mjs} +1 -1
- package/dist/{tokens-N8otWMmj.mjs.map → tokens-Bx2afeT-.mjs.map} +1 -1
- package/dist/{transport-B6CHddbu.mjs → transport--Ck3RBin.mjs} +1 -1
- package/dist/{transport-B6CHddbu.mjs.map → transport--Ck3RBin.mjs.map} +1 -1
- package/dist/{transport-C2MGqtL6.d.mts → transport-OnMNbsIA.d.mts} +1 -1
- package/dist/{transport-C2MGqtL6.d.mts.map → transport-OnMNbsIA.d.mts.map} +1 -1
- package/dist/{trusted-proxy-97pajC2f.mjs → trusted-proxy-B4AfnoAp.mjs} +1 -1
- package/dist/{trusted-proxy-97pajC2f.mjs.map → trusted-proxy-B4AfnoAp.mjs.map} +1 -1
- package/dist/types-D8bhH891.mjs +125 -0
- package/dist/{types-DSZl1Dsv.mjs.map → types-D8bhH891.mjs.map} +1 -1
- package/dist/{types-DGHWRQgr.d.mts → types-DMwSpvcw.d.mts} +2 -2
- package/dist/{types-DGHWRQgr.d.mts.map → types-DMwSpvcw.d.mts.map} +1 -1
- package/dist/{types-bYmRn_Uy.d.mts → types-DWnN7weG.d.mts} +1 -1
- package/dist/{types-bYmRn_Uy.d.mts.map → types-DWnN7weG.d.mts.map} +1 -1
- package/dist/{types-Dgo6y-Ut.d.mts → types-DX6v9KzJ.d.mts} +1 -1
- package/dist/{types-Dgo6y-Ut.d.mts.map → types-DX6v9KzJ.d.mts.map} +1 -1
- package/dist/{types-DaqNzqVt.d.mts → types-DawhLFwy.d.mts} +35 -1
- package/dist/{types-DaqNzqVt.d.mts.map → types-DawhLFwy.d.mts.map} +1 -1
- package/dist/{types-CpUuGcd5.d.mts → types-DbCWhHet.d.mts} +8 -2
- package/dist/{types-CpUuGcd5.d.mts.map → types-DbCWhHet.d.mts.map} +1 -1
- package/dist/{types-Cd9UCu3t.mjs → types-DpFmlNyB.mjs} +1 -1
- package/dist/{types-Cd9UCu3t.mjs.map → types-DpFmlNyB.mjs.map} +1 -1
- package/dist/{types-D599-ruj.d.mts → types-Qa7-HJJC.d.mts} +1 -1
- package/dist/{types-D599-ruj.d.mts.map → types-Qa7-HJJC.d.mts.map} +1 -1
- package/dist/{types-B0bmgwMG.mjs → types-SF1DwGf2.mjs} +2 -2
- package/dist/types-SF1DwGf2.mjs.map +1 -0
- package/dist/{types-DaYDYW6g.d.mts → types-i8_uzhMD.d.mts} +40 -2
- package/dist/types-i8_uzhMD.d.mts.map +1 -0
- package/dist/{types-CkDSF81F.d.mts → types-kwqCOUxj.d.mts} +1 -1
- package/dist/{types-CkDSF81F.d.mts.map → types-kwqCOUxj.d.mts.map} +1 -1
- package/dist/{user-hUSOaIJy.mjs → user-X4rtyO4Y.mjs} +2 -2
- package/dist/{user-hUSOaIJy.mjs.map → user-X4rtyO4Y.mjs.map} +1 -1
- package/dist/{utils-C3wTAP-P.mjs → utils-C4Ih4DML.mjs} +1 -1
- package/dist/{utils-C3wTAP-P.mjs.map → utils-C4Ih4DML.mjs.map} +1 -1
- package/dist/{validate-IGltez8n.mjs → validate-DactmcJG.mjs} +23 -3
- package/dist/validate-DactmcJG.mjs.map +1 -0
- package/dist/{validate-DQtHw9NT.d.mts → validate-Dy6nkNls.d.mts} +25 -5
- package/dist/{validate-DQtHw9NT.d.mts.map → validate-Dy6nkNls.d.mts.map} +1 -1
- package/dist/{validation-Bmymau7y.mjs → validation-BYA4i85b.mjs} +6 -6
- package/dist/{validation-Bmymau7y.mjs.map → validation-BYA4i85b.mjs.map} +1 -1
- package/dist/version-FGcv0ooe.mjs +7 -0
- package/dist/{version-BTc87L3L.mjs.map → version-FGcv0ooe.mjs.map} +1 -1
- package/dist/{widgets-yHQa4c6c.mjs → widgets-DG-1jxnz.mjs} +3 -3
- package/dist/{widgets-yHQa4c6c.mjs.map → widgets-DG-1jxnz.mjs.map} +1 -1
- package/dist/{zod-generator-B80aap1J.mjs → zod-generator-BNAObjSt.mjs} +3 -3
- package/dist/{zod-generator-B80aap1J.mjs.map → zod-generator-BNAObjSt.mjs.map} +1 -1
- package/package.json +7 -7
- package/src/api/errors.ts +7 -0
- package/src/api/handlers/byline-fields.ts +212 -0
- package/src/api/handlers/bylines.ts +126 -5
- package/src/api/handlers/content.ts +43 -2
- package/src/api/handlers/media.ts +2 -0
- package/src/api/openapi/document.ts +3 -0
- package/src/api/schemas/byline-fields.ts +188 -0
- package/src/api/schemas/bylines.ts +42 -0
- package/src/api/schemas/content.ts +2 -0
- package/src/api/schemas/index.ts +1 -0
- package/src/api/schemas/media.ts +2 -0
- package/src/astro/integration/routes.ts +27 -0
- package/src/astro/middleware/redirect.ts +5 -1
- package/src/astro/routes/api/admin/byline-fields/[slug]/usage.ts +36 -0
- package/src/astro/routes/api/admin/byline-fields/[slug].ts +92 -0
- package/src/astro/routes/api/admin/byline-fields/index.ts +66 -0
- package/src/astro/routes/api/admin/byline-fields/reorder.ts +39 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +23 -21
- package/src/astro/routes/api/admin/bylines/index.ts +1 -0
- package/src/astro/routes/api/auth/me.ts +21 -10
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +15 -3
- package/src/astro/routes/api/content/[collection]/[id].ts +3 -1
- package/src/astro/routes/api/media.ts +1 -0
- package/src/astro/types.ts +1 -0
- package/src/bylines/field-defs-cache.ts +138 -0
- package/src/bylines/index.ts +37 -4
- package/src/cli/commands/content.ts +4 -2
- package/src/client/index.ts +4 -1
- package/src/components/InlinePortableTextEditor.tsx +69 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +7 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +16 -0
- package/src/content/converters/types.ts +10 -0
- package/src/database/migrations/041_content_locale_list_index.ts +47 -0
- package/src/database/migrations/042_byline_fields.ts +157 -0
- package/src/database/migrations/runner.ts +4 -0
- package/src/database/repositories/byline.ts +758 -50
- package/src/database/repositories/content.ts +43 -3
- package/src/database/repositories/media.ts +14 -0
- package/src/database/repositories/types.ts +38 -0
- package/src/database/types.ts +44 -0
- package/src/emdash-runtime.ts +4 -1
- package/src/index.ts +1 -0
- package/src/loader.ts +98 -10
- package/src/mcp/server.ts +10 -1
- package/src/request-cache.ts +23 -0
- package/src/schema/byline-registry.ts +671 -0
- package/src/schema/registry.ts +14 -0
- package/src/schema/types.ts +133 -0
- package/src/seed/apply.ts +101 -14
- package/src/seed/types.ts +21 -0
- package/src/seed/validate.ts +39 -0
- package/dist/api-BNKqxyFX.mjs.map +0 -1
- package/dist/apply-BOPaD-s9.mjs.map +0 -1
- package/dist/byline-BDylH_m4.mjs +0 -404
- package/dist/byline-BDylH_m4.mjs.map +0 -1
- package/dist/bylines-B2_XmnSU.d.mts.map +0 -1
- package/dist/bylines-B7TFEvFf.mjs +0 -118
- package/dist/bylines-B7TFEvFf.mjs.map +0 -1
- package/dist/content-8voQNTXX.mjs.map +0 -1
- package/dist/error-ChfADBuu.mjs.map +0 -1
- package/dist/index-BPZFAcgE.d.mts.map +0 -1
- package/dist/loader-D-vIJjfY.mjs.map +0 -1
- package/dist/media-CKQd8AYU.mjs.map +0 -1
- package/dist/menus-C-nWT5Tu.mjs.map +0 -1
- package/dist/redirects-COMLwsV5.mjs.map +0 -1
- package/dist/runner-Drnvs96u.mjs.map +0 -1
- package/dist/setup-Cf_TyOv5.mjs +0 -137
- package/dist/setup-Cf_TyOv5.mjs.map +0 -1
- package/dist/types-B0bmgwMG.mjs.map +0 -1
- package/dist/types-DSZl1Dsv.mjs +0 -83
- package/dist/types-DaYDYW6g.d.mts.map +0 -1
- package/dist/validate-IGltez8n.mjs.map +0 -1
- package/dist/version-BTc87L3L.mjs +0 -7
- /package/dist/{api-tokens-iPIHAY8N.mjs → api-tokens-B6VgoE6M.mjs} +0 -0
- /package/dist/{ssrf-BIcd-aXW.mjs → ssrf-BvgVcfNQ.mjs} +0 -0
- /package/dist/{types-1NNkmTIn.mjs → types-Cj2S6FuC.mjs} +0 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
import { ulid } from "ulidx";
|
|
4
|
+
|
|
5
|
+
import { withTransaction } from "../database/transaction.js";
|
|
6
|
+
import type { BylineFieldTable, Database } from "../database/types.js";
|
|
7
|
+
import { validateIdentifier } from "../database/validate.js";
|
|
8
|
+
import {
|
|
9
|
+
BYLINE_FIELD_TYPES,
|
|
10
|
+
RESERVED_BYLINE_FIELD_SLUGS,
|
|
11
|
+
type BylineFieldDefinition,
|
|
12
|
+
type BylineFieldType,
|
|
13
|
+
type BylineFieldValidation,
|
|
14
|
+
type CreateBylineFieldInput,
|
|
15
|
+
type UpdateBylineFieldInput,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
const RESERVED_SET: ReadonlySet<string> = new Set(RESERVED_BYLINE_FIELD_SLUGS);
|
|
19
|
+
const TYPE_SET: ReadonlySet<string> = new Set(BYLINE_FIELD_TYPES);
|
|
20
|
+
|
|
21
|
+
const VERSION_KEY = "byline_fields_version";
|
|
22
|
+
|
|
23
|
+
/** Hard cap on the choices array for a `select`-type field. */
|
|
24
|
+
const MAX_SELECT_OPTIONS = 200;
|
|
25
|
+
/** Hard cap on a slug — mirrors `SchemaRegistry.validateSlug`. */
|
|
26
|
+
const MAX_SLUG_LENGTH = 63;
|
|
27
|
+
/** Hard cap on a label. Bigger than slugs because labels are display strings. */
|
|
28
|
+
const MAX_LABEL_LENGTH = 200;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown for byline-schema validation failures. Mirrors
|
|
32
|
+
* `SchemaError` in `registry.ts` so the admin API layer can map a small
|
|
33
|
+
* set of codes to HTTP statuses without inspecting messages.
|
|
34
|
+
*
|
|
35
|
+
* Codes:
|
|
36
|
+
* - `INVALID_SLUG` — slug fails identifier rules or length cap
|
|
37
|
+
* - `RESERVED_SLUG` — slug collides with a fixed `_emdash_bylines` column
|
|
38
|
+
* - `INVALID_TYPE` — type is not one of the five v1 field types
|
|
39
|
+
* - `INVALID_LABEL` — label missing or exceeds length cap
|
|
40
|
+
* - `INVALID_VALIDATION` — validation payload malformed (e.g. `select` with
|
|
41
|
+
* no `options`, duplicates in `options`)
|
|
42
|
+
* - `FIELD_EXISTS` — slug already registered
|
|
43
|
+
* - `FIELD_NOT_FOUND` — slug not registered
|
|
44
|
+
* - `TRANSLATABLE_LOCKED` — attempt to flip `translatable` while stored
|
|
45
|
+
* values reference the field
|
|
46
|
+
* - `REORDER_MISMATCH` — reorder input doesn't match the registered set
|
|
47
|
+
*/
|
|
48
|
+
export class BylineSchemaError extends Error {
|
|
49
|
+
constructor(
|
|
50
|
+
message: string,
|
|
51
|
+
public code: string,
|
|
52
|
+
public details?: Record<string, unknown>,
|
|
53
|
+
) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = "BylineSchemaError";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Translate a `BylineSchemaError` code to a shared `ErrorCode` for the
|
|
61
|
+
* admin API. HTTP status is then derived by `mapErrorStatus` — this
|
|
62
|
+
* function deliberately doesn't carry one, so the API/handler boundary
|
|
63
|
+
* matches the rest of the codebase (handlers return `ApiResult<T>` with
|
|
64
|
+
* a code, the route layer maps to status via `unwrapResult`).
|
|
65
|
+
*
|
|
66
|
+
* Every code on the right-hand side of `case ... return ...` is defined
|
|
67
|
+
* in `ErrorCode` (`api/errors.ts`). `INVALID_LABEL` and
|
|
68
|
+
* `INVALID_VALIDATION` are intentionally folded into the `default`
|
|
69
|
+
* branch (→ `VALIDATION_ERROR`) so no ad-hoc codes leak out — the
|
|
70
|
+
* registry's domain code names them but the HTTP surface should not.
|
|
71
|
+
*
|
|
72
|
+
* `RESERVED_SLUG` / `INVALID_SLUG` typically don't reach this layer for
|
|
73
|
+
* HTTP callers — the zod schema rejects them first with a clean
|
|
74
|
+
* `VALIDATION_ERROR`. They're still listed so non-HTTP callers (and the
|
|
75
|
+
* test layer) get consistent mapping.
|
|
76
|
+
*
|
|
77
|
+
* `FIELD_NOT_FOUND` is normalised to the shared `NOT_FOUND` code so the
|
|
78
|
+
* admin client can branch on one constant across resource types.
|
|
79
|
+
*/
|
|
80
|
+
export function mapBylineSchemaError(error: BylineSchemaError): {
|
|
81
|
+
code: string;
|
|
82
|
+
message: string;
|
|
83
|
+
details?: Record<string, unknown>;
|
|
84
|
+
} {
|
|
85
|
+
switch (error.code) {
|
|
86
|
+
case "FIELD_NOT_FOUND":
|
|
87
|
+
return { code: "NOT_FOUND", message: error.message, details: error.details };
|
|
88
|
+
case "FIELD_EXISTS":
|
|
89
|
+
case "TRANSLATABLE_LOCKED":
|
|
90
|
+
case "REORDER_MISMATCH":
|
|
91
|
+
case "INVALID_SLUG":
|
|
92
|
+
case "RESERVED_SLUG":
|
|
93
|
+
case "INVALID_TYPE":
|
|
94
|
+
return { code: error.code, message: error.message, details: error.details };
|
|
95
|
+
default:
|
|
96
|
+
// Catches INVALID_LABEL, INVALID_VALIDATION, and any future
|
|
97
|
+
// registry codes we forget to wire up explicitly.
|
|
98
|
+
return { code: "VALIDATION_ERROR", message: error.message, details: error.details };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Registry for byline custom fields (Discussion #1174).
|
|
104
|
+
*
|
|
105
|
+
* Owns CRUD over `_emdash_byline_fields` and the
|
|
106
|
+
* `options.byline_fields_version` counter that drives cache
|
|
107
|
+
* invalidation in `bylines/field-defs-cache.ts`.
|
|
108
|
+
*
|
|
109
|
+
* **Dirty-bit bookend.** Every mutation runs `markVersionDirty` before
|
|
110
|
+
* the schema write and `markVersionClean` after, as standalone writes
|
|
111
|
+
* (not inside `withTransaction`) so concurrent isolates observe the
|
|
112
|
+
* dirty mark *before* the mutation lands. Parity carries meaning:
|
|
113
|
+
* odd = mutation in flight or crashed mid-flight, even = stable.
|
|
114
|
+
* The cache bypasses the global holder while odd.
|
|
115
|
+
*
|
|
116
|
+
* `markVersionDirty` is parity-aware (idempotent on odd) so a
|
|
117
|
+
* crashed prior attempt doesn't invert the bit.
|
|
118
|
+
* `markVersionClean` always advances to a new even value (+2 from
|
|
119
|
+
* even, +1 from odd) so concurrent mutators can't collapse on the
|
|
120
|
+
* same key and pin a stale cache snapshot. Idempotent-retry exits
|
|
121
|
+
* (`FIELD_EXISTS` / `FIELD_NOT_FOUND` / no-op update) call
|
|
122
|
+
* `markVersionClean` too — same code path doubles as crash recovery
|
|
123
|
+
* and false-clean recovery.
|
|
124
|
+
*
|
|
125
|
+
* The residual race: a reader caching between two concurrent
|
|
126
|
+
* `markVersionClean` calls sees a partial-set snapshot until the
|
|
127
|
+
* second clean lands. Bounded by the inter-clean window (~ms).
|
|
128
|
+
* Schema mutations are admin-only and rare; acceptable for now.
|
|
129
|
+
* A CAS-on-bump or dialect-specific lock is tracked as follow-up.
|
|
130
|
+
*
|
|
131
|
+
* **`deleteField` cascade.** Migration 041 already declares
|
|
132
|
+
* `ON DELETE CASCADE` on both value tables. The explicit deletes
|
|
133
|
+
* here are defense-in-depth against FK-pragma misconfig and mirror
|
|
134
|
+
* `BylineRepository.delete`'s app-level cascade for the bylines
|
|
135
|
+
* domain.
|
|
136
|
+
*
|
|
137
|
+
* Reserved-slug rejection runs at the API layer (zod) *and* here so
|
|
138
|
+
* non-HTTP callers (seeds, scripts) can't bypass the check.
|
|
139
|
+
*/
|
|
140
|
+
export class BylineSchemaRegistry {
|
|
141
|
+
constructor(private db: Kysely<Database>) {}
|
|
142
|
+
|
|
143
|
+
async listFields(): Promise<BylineFieldDefinition[]> {
|
|
144
|
+
const rows = await this.db
|
|
145
|
+
.selectFrom("_emdash_byline_fields")
|
|
146
|
+
.selectAll()
|
|
147
|
+
.orderBy("sort_order", "asc")
|
|
148
|
+
.orderBy("created_at", "asc")
|
|
149
|
+
.execute();
|
|
150
|
+
return rows.map((row) => mapFieldRow(row));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getField(slug: string): Promise<BylineFieldDefinition | null> {
|
|
154
|
+
const row = await this.db
|
|
155
|
+
.selectFrom("_emdash_byline_fields")
|
|
156
|
+
.selectAll()
|
|
157
|
+
.where("slug", "=", slug)
|
|
158
|
+
.executeTakeFirst();
|
|
159
|
+
return row ? mapFieldRow(row) : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getFieldById(id: string): Promise<BylineFieldDefinition | null> {
|
|
163
|
+
const row = await this.db
|
|
164
|
+
.selectFrom("_emdash_byline_fields")
|
|
165
|
+
.selectAll()
|
|
166
|
+
.where("id", "=", id)
|
|
167
|
+
.executeTakeFirst();
|
|
168
|
+
return row ? mapFieldRow(row) : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createField(input: CreateBylineFieldInput): Promise<BylineFieldDefinition> {
|
|
172
|
+
this.validateSlug(input.slug);
|
|
173
|
+
this.validateLabel(input.label);
|
|
174
|
+
this.validateType(input.type);
|
|
175
|
+
const validation = this.normaliseValidation(input.type, input.validation ?? null);
|
|
176
|
+
|
|
177
|
+
const existing = await this.getField(input.slug);
|
|
178
|
+
if (existing) {
|
|
179
|
+
// Idempotent retry exit — see class JSDoc.
|
|
180
|
+
await this.markVersionClean();
|
|
181
|
+
throw new BylineSchemaError(`Byline field "${input.slug}" already exists`, "FIELD_EXISTS", {
|
|
182
|
+
slug: input.slug,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const id = ulid();
|
|
187
|
+
const sortOrder = input.sortOrder ?? (await this.nextSortOrder());
|
|
188
|
+
|
|
189
|
+
await this.markVersionDirty();
|
|
190
|
+
await withTransaction(this.db, async (trx) => {
|
|
191
|
+
await trx
|
|
192
|
+
.insertInto("_emdash_byline_fields")
|
|
193
|
+
.values({
|
|
194
|
+
id,
|
|
195
|
+
slug: input.slug,
|
|
196
|
+
label: input.label,
|
|
197
|
+
type: input.type,
|
|
198
|
+
required: input.required ? 1 : 0,
|
|
199
|
+
translatable: input.translatable === false ? 0 : 1,
|
|
200
|
+
validation: validation ? JSON.stringify(validation) : null,
|
|
201
|
+
sort_order: sortOrder,
|
|
202
|
+
})
|
|
203
|
+
.execute();
|
|
204
|
+
});
|
|
205
|
+
await this.markVersionClean();
|
|
206
|
+
|
|
207
|
+
const created = await this.getFieldById(id);
|
|
208
|
+
if (!created) {
|
|
209
|
+
// Should be unreachable on a working DB — but a typed error
|
|
210
|
+
// beats letting the route returning null on a successful path.
|
|
211
|
+
throw new BylineSchemaError("Failed to load created field", "FIELD_NOT_FOUND", {
|
|
212
|
+
id,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return created;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async updateField(slug: string, input: UpdateBylineFieldInput): Promise<BylineFieldDefinition> {
|
|
219
|
+
const field = await this.getField(slug);
|
|
220
|
+
if (!field) {
|
|
221
|
+
// Idempotent retry exit — see class JSDoc.
|
|
222
|
+
await this.markVersionClean();
|
|
223
|
+
throw new BylineSchemaError(`Byline field "${slug}" not found`, "FIELD_NOT_FOUND", {
|
|
224
|
+
slug,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const updates: Partial<{
|
|
229
|
+
label: string;
|
|
230
|
+
required: number;
|
|
231
|
+
translatable: number;
|
|
232
|
+
validation: string | null;
|
|
233
|
+
sort_order: number;
|
|
234
|
+
updated_at: string;
|
|
235
|
+
}> = {};
|
|
236
|
+
|
|
237
|
+
if (input.label !== undefined) {
|
|
238
|
+
this.validateLabel(input.label);
|
|
239
|
+
updates.label = input.label;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (input.required !== undefined) {
|
|
243
|
+
updates.required = input.required ? 1 : 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (input.validation !== undefined) {
|
|
247
|
+
// Validation payload is normalised against the *current* field
|
|
248
|
+
// type — `type` is not updatable, so it's safe to use `field.type`.
|
|
249
|
+
const validation = this.normaliseValidation(field.type, input.validation);
|
|
250
|
+
updates.validation = validation ? JSON.stringify(validation) : null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (input.translatable !== undefined && input.translatable !== field.translatable) {
|
|
254
|
+
// Flipping `translatable` would orphan any values already stored
|
|
255
|
+
// in the table matching the *current* flag. Reject when any
|
|
256
|
+
// value rows reference this field — admins can delete the field
|
|
257
|
+
// (cascading the values) and re-create it with the new flag if
|
|
258
|
+
// they want a clean re-start. Migrating values across tables is
|
|
259
|
+
// out of scope (Discussion #1174 doesn't authorise it).
|
|
260
|
+
const usage = await this.countFieldValues(field.id);
|
|
261
|
+
if (usage > 0) {
|
|
262
|
+
throw new BylineSchemaError(
|
|
263
|
+
`Cannot change "translatable" on field "${slug}" while ${usage} value row(s) exist. ` +
|
|
264
|
+
`Delete the values (or the field) and re-create with the new setting.`,
|
|
265
|
+
"TRANSLATABLE_LOCKED",
|
|
266
|
+
{ slug, valueCount: usage },
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
updates.translatable = input.translatable ? 1 : 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (input.sortOrder !== undefined) {
|
|
273
|
+
updates.sort_order = input.sortOrder;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (Object.keys(updates).length === 0) {
|
|
277
|
+
// No-op update — still advance the clean marker in case
|
|
278
|
+
// we're recovering a crashed prior attempt.
|
|
279
|
+
await this.markVersionClean();
|
|
280
|
+
return field;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
updates.updated_at = new Date().toISOString();
|
|
284
|
+
|
|
285
|
+
await this.markVersionDirty();
|
|
286
|
+
await withTransaction(this.db, async (trx) => {
|
|
287
|
+
await trx
|
|
288
|
+
.updateTable("_emdash_byline_fields")
|
|
289
|
+
.set(updates)
|
|
290
|
+
.where("id", "=", field.id)
|
|
291
|
+
.execute();
|
|
292
|
+
});
|
|
293
|
+
await this.markVersionClean();
|
|
294
|
+
|
|
295
|
+
const updated = await this.getFieldById(field.id);
|
|
296
|
+
if (!updated) {
|
|
297
|
+
throw new BylineSchemaError("Failed to load updated field", "FIELD_NOT_FOUND", {
|
|
298
|
+
slug,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return updated;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async deleteField(slug: string): Promise<void> {
|
|
305
|
+
const field = await this.getField(slug);
|
|
306
|
+
if (!field) {
|
|
307
|
+
// Idempotent retry exit — see class JSDoc.
|
|
308
|
+
await this.markVersionClean();
|
|
309
|
+
throw new BylineSchemaError(`Byline field "${slug}" not found`, "FIELD_NOT_FOUND", {
|
|
310
|
+
slug,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Delete order matters on D1 (no tx): value rows first, definition
|
|
315
|
+
// row last, so a crash leaves the definition recoverable on retry
|
|
316
|
+
// rather than orphan values pointing at a vanished id.
|
|
317
|
+
await this.markVersionDirty();
|
|
318
|
+
await withTransaction(this.db, async (trx) => {
|
|
319
|
+
await trx
|
|
320
|
+
.deleteFrom("_emdash_byline_field_values")
|
|
321
|
+
.where("field_id", "=", field.id)
|
|
322
|
+
.execute();
|
|
323
|
+
await trx
|
|
324
|
+
.deleteFrom("_emdash_byline_field_group_values")
|
|
325
|
+
.where("field_id", "=", field.id)
|
|
326
|
+
.execute();
|
|
327
|
+
await trx.deleteFrom("_emdash_byline_fields").where("id", "=", field.id).execute();
|
|
328
|
+
});
|
|
329
|
+
await this.markVersionClean();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Reorder fields by slug. The input must be the *exact* set of
|
|
334
|
+
* currently registered slugs — no adds, no drops, no duplicates. This
|
|
335
|
+
* keeps the operation invertible (any reorder is followed by a reverse
|
|
336
|
+
* reorder) and removes a class of "did I forget a field?" bugs at the
|
|
337
|
+
* API layer.
|
|
338
|
+
*/
|
|
339
|
+
async reorderFields(slugs: string[]): Promise<void> {
|
|
340
|
+
if (new Set(slugs).size !== slugs.length) {
|
|
341
|
+
throw new BylineSchemaError("Reorder input contains duplicate slugs", "REORDER_MISMATCH", {
|
|
342
|
+
slugs,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const registered = await this.listFields();
|
|
347
|
+
const registeredSlugs = registered.map((f) => f.slug).toSorted();
|
|
348
|
+
const inputSlugs = slugs.toSorted();
|
|
349
|
+
|
|
350
|
+
if (registeredSlugs.length !== inputSlugs.length) {
|
|
351
|
+
throw new BylineSchemaError(
|
|
352
|
+
`Reorder input has ${inputSlugs.length} slug(s); ${registeredSlugs.length} registered`,
|
|
353
|
+
"REORDER_MISMATCH",
|
|
354
|
+
{ registered: registeredSlugs, input: inputSlugs },
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
for (let i = 0; i < registeredSlugs.length; i++) {
|
|
358
|
+
if (registeredSlugs[i] !== inputSlugs[i]) {
|
|
359
|
+
throw new BylineSchemaError(
|
|
360
|
+
"Reorder input does not match the registered field set",
|
|
361
|
+
"REORDER_MISMATCH",
|
|
362
|
+
{ registered: registeredSlugs, input: inputSlugs },
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const now = new Date().toISOString();
|
|
368
|
+
await this.markVersionDirty();
|
|
369
|
+
await withTransaction(this.db, async (trx) => {
|
|
370
|
+
for (let i = 0; i < slugs.length; i++) {
|
|
371
|
+
const slug = slugs[i];
|
|
372
|
+
if (slug === undefined) continue;
|
|
373
|
+
await trx
|
|
374
|
+
.updateTable("_emdash_byline_fields")
|
|
375
|
+
.set({ sort_order: i, updated_at: now })
|
|
376
|
+
.where("slug", "=", slug)
|
|
377
|
+
.execute();
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
await this.markVersionClean();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Per-table usage counts for a field, plus the sum. Backs the
|
|
385
|
+
* destructive-delete confirm dialog in the admin UI (Phase 5).
|
|
386
|
+
*
|
|
387
|
+
* Both counts are surfaced separately for diagnostic value: a
|
|
388
|
+
* non-zero count on the table that doesn't match the field's current
|
|
389
|
+
* `translatable` flag indicates historical drift (e.g. a flip from
|
|
390
|
+
* an older code path). Today the registry rejects such flips with
|
|
391
|
+
* `TRANSLATABLE_LOCKED`, so any drift originates pre-Phase-2.
|
|
392
|
+
*
|
|
393
|
+
* Throws `FIELD_NOT_FOUND` when the slug doesn't resolve — callers
|
|
394
|
+
* shouldn't get back zero counts for a missing field.
|
|
395
|
+
*/
|
|
396
|
+
async getFieldUsage(slug: string): Promise<{
|
|
397
|
+
translatableValueCount: number;
|
|
398
|
+
groupValueCount: number;
|
|
399
|
+
totalAffectedRows: number;
|
|
400
|
+
}> {
|
|
401
|
+
const field = await this.getField(slug);
|
|
402
|
+
if (!field) {
|
|
403
|
+
throw new BylineSchemaError(`Byline field "${slug}" not found`, "FIELD_NOT_FOUND", {
|
|
404
|
+
slug,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
const tr = await this.db
|
|
408
|
+
.selectFrom("_emdash_byline_field_values")
|
|
409
|
+
.select(({ fn }) => [fn.count<number>("field_id").as("count")])
|
|
410
|
+
.where("field_id", "=", field.id)
|
|
411
|
+
.executeTakeFirst();
|
|
412
|
+
const grp = await this.db
|
|
413
|
+
.selectFrom("_emdash_byline_field_group_values")
|
|
414
|
+
.select(({ fn }) => [fn.count<number>("field_id").as("count")])
|
|
415
|
+
.where("field_id", "=", field.id)
|
|
416
|
+
.executeTakeFirst();
|
|
417
|
+
const translatableValueCount = Number(tr?.count ?? 0);
|
|
418
|
+
const groupValueCount = Number(grp?.count ?? 0);
|
|
419
|
+
return {
|
|
420
|
+
translatableValueCount,
|
|
421
|
+
groupValueCount,
|
|
422
|
+
totalAffectedRows: translatableValueCount + groupValueCount,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Read the persisted version counter. Used by the field-defs cache
|
|
428
|
+
* (Phase 3) to detect invalidation. Returns `0` when the row is
|
|
429
|
+
* missing — covers the "tests that didn't run migration 041" case
|
|
430
|
+
* without throwing.
|
|
431
|
+
*/
|
|
432
|
+
async getVersion(): Promise<number> {
|
|
433
|
+
const row = await this.db
|
|
434
|
+
.selectFrom("options")
|
|
435
|
+
.select("value")
|
|
436
|
+
.where("name", "=", VERSION_KEY)
|
|
437
|
+
.executeTakeFirst();
|
|
438
|
+
if (!row) return 0;
|
|
439
|
+
const parsed = Number.parseInt(row.value, 10);
|
|
440
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ============================================
|
|
444
|
+
// Private helpers
|
|
445
|
+
// ============================================
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Force the version counter to an odd integer ("dirty"). Idempotent
|
|
449
|
+
* on odd so a crashed prior attempt can't invert parity. Upsert (not
|
|
450
|
+
* UPDATE) so a missing row still flips parity — `getVersion` returns
|
|
451
|
+
* 0 on missing, which is even, so a bare UPDATE would leave the
|
|
452
|
+
* cache pinned on a stale snapshot. See the class JSDoc.
|
|
453
|
+
*
|
|
454
|
+
* `options.value` qualified: PG's `ON CONFLICT DO UPDATE` puts both
|
|
455
|
+
* the target and `EXCLUDED.value` in scope; bare `value` is ambiguous.
|
|
456
|
+
*/
|
|
457
|
+
private async markVersionDirty(): Promise<void> {
|
|
458
|
+
await sql`
|
|
459
|
+
INSERT INTO options (name, value)
|
|
460
|
+
VALUES (${VERSION_KEY}, '1')
|
|
461
|
+
ON CONFLICT(name) DO UPDATE SET value = CASE
|
|
462
|
+
WHEN CAST(options.value AS INTEGER) % 2 = 0
|
|
463
|
+
THEN CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)
|
|
464
|
+
ELSE options.value
|
|
465
|
+
END
|
|
466
|
+
`.execute(this.db);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Force the version counter to a **new** even integer (+2 from even,
|
|
471
|
+
* +1 from odd). Always-advance — never a no-op — so two concurrent
|
|
472
|
+
* mutators can't collapse on the same even key and pin a stale cache
|
|
473
|
+
* snapshot. See the class JSDoc for the concurrent-collapse rationale.
|
|
474
|
+
*
|
|
475
|
+
* `options.value` qualified — see `markVersionDirty`.
|
|
476
|
+
*/
|
|
477
|
+
private async markVersionClean(): Promise<void> {
|
|
478
|
+
await sql`
|
|
479
|
+
INSERT INTO options (name, value)
|
|
480
|
+
VALUES (${VERSION_KEY}, '2')
|
|
481
|
+
ON CONFLICT(name) DO UPDATE SET value = CASE
|
|
482
|
+
WHEN CAST(options.value AS INTEGER) % 2 = 0
|
|
483
|
+
THEN CAST(CAST(options.value AS INTEGER) + 2 AS TEXT)
|
|
484
|
+
ELSE CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)
|
|
485
|
+
END
|
|
486
|
+
`.execute(this.db);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private async nextSortOrder(): Promise<number> {
|
|
490
|
+
const row = await this.db
|
|
491
|
+
.selectFrom("_emdash_byline_fields")
|
|
492
|
+
.select(({ fn }) => [fn.max<number | null>("sort_order").as("max")])
|
|
493
|
+
.executeTakeFirst();
|
|
494
|
+
const max = row?.max ?? null;
|
|
495
|
+
return max === null ? 0 : max + 1;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private async countFieldValues(fieldId: string): Promise<number> {
|
|
499
|
+
// Count both per-locale and group-shared values. A field can only
|
|
500
|
+
// store in one table at a time (translatable picks), but historic
|
|
501
|
+
// rows might exist in the other if a prior version of this code
|
|
502
|
+
// allowed the flip — count both to be safe.
|
|
503
|
+
const tr = await this.db
|
|
504
|
+
.selectFrom("_emdash_byline_field_values")
|
|
505
|
+
.select(({ fn }) => [fn.count<number>("field_id").as("count")])
|
|
506
|
+
.where("field_id", "=", fieldId)
|
|
507
|
+
.executeTakeFirst();
|
|
508
|
+
const grp = await this.db
|
|
509
|
+
.selectFrom("_emdash_byline_field_group_values")
|
|
510
|
+
.select(({ fn }) => [fn.count<number>("field_id").as("count")])
|
|
511
|
+
.where("field_id", "=", fieldId)
|
|
512
|
+
.executeTakeFirst();
|
|
513
|
+
return Number(tr?.count ?? 0) + Number(grp?.count ?? 0);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private validateSlug(slug: string): void {
|
|
517
|
+
if (!slug || typeof slug !== "string") {
|
|
518
|
+
throw new BylineSchemaError("Byline field slug is required", "INVALID_SLUG", { slug });
|
|
519
|
+
}
|
|
520
|
+
if (slug.length > MAX_SLUG_LENGTH) {
|
|
521
|
+
throw new BylineSchemaError(
|
|
522
|
+
`Byline field slug must be ${MAX_SLUG_LENGTH} characters or less`,
|
|
523
|
+
"INVALID_SLUG",
|
|
524
|
+
{ slug },
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
// `validateIdentifier` enforces /^[a-z][a-z0-9_]*$/ — rejects
|
|
528
|
+
// camelCase, PascalCase, hyphens, leading digits, and identifiers
|
|
529
|
+
// over 128 characters. We hit the 63-char cap above first, which
|
|
530
|
+
// matches the content-collection slug cap.
|
|
531
|
+
try {
|
|
532
|
+
validateIdentifier(slug, "byline field slug");
|
|
533
|
+
} catch (error) {
|
|
534
|
+
throw new BylineSchemaError(
|
|
535
|
+
error instanceof Error ? error.message : "Invalid byline field slug",
|
|
536
|
+
"INVALID_SLUG",
|
|
537
|
+
{ slug },
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
if (RESERVED_SET.has(slug)) {
|
|
541
|
+
throw new BylineSchemaError(`Byline field slug "${slug}" is reserved`, "RESERVED_SLUG", {
|
|
542
|
+
slug,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private validateLabel(label: string): void {
|
|
548
|
+
if (!label || typeof label !== "string") {
|
|
549
|
+
throw new BylineSchemaError("Byline field label is required", "INVALID_LABEL", {
|
|
550
|
+
label,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (label.length > MAX_LABEL_LENGTH) {
|
|
554
|
+
throw new BylineSchemaError(
|
|
555
|
+
`Byline field label must be ${MAX_LABEL_LENGTH} characters or less`,
|
|
556
|
+
"INVALID_LABEL",
|
|
557
|
+
{ length: label.length },
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private validateType(type: BylineFieldType): void {
|
|
563
|
+
if (!TYPE_SET.has(type)) {
|
|
564
|
+
throw new BylineSchemaError(
|
|
565
|
+
`Byline field type "${type}" is not supported. Valid types: ${[...TYPE_SET].join(", ")}`,
|
|
566
|
+
"INVALID_TYPE",
|
|
567
|
+
{ type },
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Normalise + validate a validation payload for a given field type.
|
|
574
|
+
*
|
|
575
|
+
* - `select`: `options` is required, must be a non-empty array of unique
|
|
576
|
+
* non-empty strings, capped at `MAX_SELECT_OPTIONS`.
|
|
577
|
+
* - any other type: `options` is silently dropped if present (a future
|
|
578
|
+
* field type might use it, but v1 doesn't).
|
|
579
|
+
*
|
|
580
|
+
* Returns `null` when the resulting validation object is empty, so the
|
|
581
|
+
* storage column stays NULL rather than carrying `'{}'`.
|
|
582
|
+
*/
|
|
583
|
+
private normaliseValidation(
|
|
584
|
+
type: BylineFieldType,
|
|
585
|
+
validation: BylineFieldValidation | null,
|
|
586
|
+
): BylineFieldValidation | null {
|
|
587
|
+
if (type === "select") {
|
|
588
|
+
const options = validation?.options;
|
|
589
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
590
|
+
throw new BylineSchemaError(
|
|
591
|
+
`Byline field of type "select" requires non-empty "validation.options"`,
|
|
592
|
+
"INVALID_VALIDATION",
|
|
593
|
+
{ type },
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
if (options.length > MAX_SELECT_OPTIONS) {
|
|
597
|
+
throw new BylineSchemaError(
|
|
598
|
+
`Byline field "select" cannot have more than ${MAX_SELECT_OPTIONS} options`,
|
|
599
|
+
"INVALID_VALIDATION",
|
|
600
|
+
{ count: options.length },
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
const seen = new Set<string>();
|
|
604
|
+
for (const option of options) {
|
|
605
|
+
if (typeof option !== "string" || option.length === 0) {
|
|
606
|
+
throw new BylineSchemaError(
|
|
607
|
+
`Byline field "select" options must be non-empty strings`,
|
|
608
|
+
"INVALID_VALIDATION",
|
|
609
|
+
{ option },
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (seen.has(option)) {
|
|
613
|
+
throw new BylineSchemaError(
|
|
614
|
+
`Byline field "select" options must be unique`,
|
|
615
|
+
"INVALID_VALIDATION",
|
|
616
|
+
{ option },
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
seen.add(option);
|
|
620
|
+
}
|
|
621
|
+
return { options };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (validation == null) return null;
|
|
625
|
+
// Non-select: drop `options` if present. Strip nothing else — future
|
|
626
|
+
// field types might extend the shape and we don't want to lose
|
|
627
|
+
// payload silently. Today's `BylineFieldValidation` is `{ options? }`
|
|
628
|
+
// only, so this branch is a pass-through; left explicit for clarity.
|
|
629
|
+
const { options: _drop, ...rest } = validation;
|
|
630
|
+
return Object.keys(rest).length === 0 ? null : (rest as BylineFieldValidation);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function mapFieldRow(row: {
|
|
635
|
+
id: string;
|
|
636
|
+
slug: string;
|
|
637
|
+
label: string;
|
|
638
|
+
type: string;
|
|
639
|
+
required: number;
|
|
640
|
+
translatable: number;
|
|
641
|
+
validation: string | null;
|
|
642
|
+
sort_order: number;
|
|
643
|
+
created_at: string;
|
|
644
|
+
updated_at: string;
|
|
645
|
+
}): BylineFieldDefinition {
|
|
646
|
+
return {
|
|
647
|
+
id: row.id,
|
|
648
|
+
slug: row.slug,
|
|
649
|
+
label: row.label,
|
|
650
|
+
// `type` is stored as TEXT but `createField` rejects anything outside
|
|
651
|
+
// `BYLINE_FIELD_TYPES` before inserting. The assertion narrows on
|
|
652
|
+
// that write-time guarantee.
|
|
653
|
+
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write
|
|
654
|
+
type: row.type as BylineFieldType,
|
|
655
|
+
required: row.required === 1,
|
|
656
|
+
translatable: row.translatable === 1,
|
|
657
|
+
// `validation` is JSON-encoded `BylineFieldValidation | null`, written
|
|
658
|
+
// only through `normaliseValidation`. The cast matches the
|
|
659
|
+
// `JSON.parse(...) as T` pattern in `OptionsRepository`.
|
|
660
|
+
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write
|
|
661
|
+
validation: row.validation ? (JSON.parse(row.validation) as BylineFieldValidation) : null,
|
|
662
|
+
sortOrder: row.sort_order,
|
|
663
|
+
createdAt: row.created_at,
|
|
664
|
+
updatedAt: row.updated_at,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Re-export the table type for callers that want to spell it explicitly.
|
|
669
|
+
// Most callers should rely on the Database interface; this is convenience
|
|
670
|
+
// for tests that hand-roll Kysely queries.
|
|
671
|
+
export type { BylineFieldTable };
|
package/src/schema/registry.ts
CHANGED
|
@@ -772,6 +772,20 @@ export class SchemaRegistry {
|
|
|
772
772
|
CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_published_id`)}
|
|
773
773
|
ON ${sql.ref(tableName)} (deleted_at, published_at DESC, id DESC)
|
|
774
774
|
`.execute(conn);
|
|
775
|
+
|
|
776
|
+
// Locale-aware composite indexes for i18n content lists (see migration 041).
|
|
777
|
+
// Short `loc_upd`/`loc_crt` suffix keeps the updated/created discriminator
|
|
778
|
+
// inside Postgres's 63-byte identifier limit for long slugs; keep these
|
|
779
|
+
// names identical to migration 041.
|
|
780
|
+
await sql`
|
|
781
|
+
CREATE INDEX ${sql.ref(`idx_${tableName}_loc_upd`)}
|
|
782
|
+
ON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)
|
|
783
|
+
`.execute(conn);
|
|
784
|
+
|
|
785
|
+
await sql`
|
|
786
|
+
CREATE INDEX ${sql.ref(`idx_${tableName}_loc_crt`)}
|
|
787
|
+
ON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)
|
|
788
|
+
`.execute(conn);
|
|
775
789
|
}
|
|
776
790
|
|
|
777
791
|
/**
|