emdash 0.16.1 → 0.17.1
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-CuuZG6op.mjs} +93 -31
- package/dist/apply-CuuZG6op.mjs.map +1 -0
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.mjs +28 -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-DWLnr6-k.d.mts → byline-fields-BNy7Ng1U.d.mts} +151 -23
- 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 +140 -32
- 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-D_p_jIP1.d.mts → index-CjKdMZ3U.d.mts} +38 -16
- 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-Bt52mHXp.mjs} +19 -18
- package/dist/query-Bt52mHXp.mjs.map +1 -0
- 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-ByLlXrv5.mjs} +8 -8
- package/dist/{taxonomies-CcvrMLbR.mjs.map → taxonomies-ByLlXrv5.mjs.map} +1 -1
- package/dist/{taxonomies-4vx0nmMr.mjs → taxonomies-CbO6v7EE.mjs} +4 -4
- package/dist/{taxonomies-4vx0nmMr.mjs.map → taxonomies-CbO6v7EE.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-CWbvq9LG.mjs +7 -0
- package/dist/{version-ITD3PlQd.mjs.map → version-CWbvq9LG.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/integration/vite-config.ts +16 -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/cli/commands/export-seed.ts +174 -12
- 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/query.ts +7 -7
- 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-B7TFEvFf.mjs +0 -118
- package/dist/bylines-B7TFEvFf.mjs.map +0 -1
- package/dist/bylines-DWLnr6-k.d.mts.map +0 -1
- package/dist/content-8voQNTXX.mjs.map +0 -1
- package/dist/error-ChfADBuu.mjs.map +0 -1
- package/dist/index-D_p_jIP1.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/query-7m6-l0f_.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-ITD3PlQd.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,925 @@
|
|
|
1
|
+
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
2
|
+
import { c as listTablesLike } from "./dialect-helpers-BKCvISIQ.mjs";
|
|
3
|
+
import { i as encodeCursor, r as decodeCursor, t as EmDashValidationError } from "./types-SF1DwGf2.mjs";
|
|
4
|
+
import { t as withTransaction } from "./transaction-NQj4VJ7Z.mjs";
|
|
5
|
+
import { getRequestContext } from "./request-context.mjs";
|
|
6
|
+
import { i as setRequestCacheEntry, n as peekRequestCache, r as requestCached, t as clearRequestCacheEntry } from "./request-cache-BYMs-BGX.mjs";
|
|
7
|
+
import { n as BylineSchemaRegistry } from "./byline-registry-CxK5g559.mjs";
|
|
8
|
+
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-BAYkM-CF.mjs";
|
|
9
|
+
import { sql } from "kysely";
|
|
10
|
+
import { ulid } from "ulidx";
|
|
11
|
+
|
|
12
|
+
//#region src/bylines/field-defs-cache.ts
|
|
13
|
+
const HOLDER_KEY = Symbol.for("emdash:byline-field-defs");
|
|
14
|
+
const g = globalThis;
|
|
15
|
+
const holder = g[HOLDER_KEY] ?? (() => {
|
|
16
|
+
const h = {
|
|
17
|
+
cached: null,
|
|
18
|
+
cachedVersion: -1
|
|
19
|
+
};
|
|
20
|
+
g[HOLDER_KEY] = h;
|
|
21
|
+
return h;
|
|
22
|
+
})();
|
|
23
|
+
const REQUEST_CACHE_KEY_VERSION = "byline-fields-version";
|
|
24
|
+
const REQUEST_CACHE_KEY_DEFS_PREFIX = "byline-field-defs:";
|
|
25
|
+
/**
|
|
26
|
+
* Read the persisted `options.byline_fields_version` counter. Cached for
|
|
27
|
+
* the duration of the current request via `requestCached`. Returns `0`
|
|
28
|
+
* when the row is missing (matches `BylineSchemaRegistry.getVersion`).
|
|
29
|
+
*/
|
|
30
|
+
async function getBylineFieldsVersion(db) {
|
|
31
|
+
return requestCached(REQUEST_CACHE_KEY_VERSION, () => new BylineSchemaRegistry(db).getVersion());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve registered byline custom-field definitions. Two-tier cache:
|
|
35
|
+
* per-request via `requestCached`, then per-isolate via the global
|
|
36
|
+
* holder.
|
|
37
|
+
*
|
|
38
|
+
* The global holder is bypassed for isolated requests (playground / DO
|
|
39
|
+
* preview, which point at a divergent schema) and for dirty versions
|
|
40
|
+
* (odd counter — see `BylineSchemaRegistry`'s class JSDoc — indicates
|
|
41
|
+
* an in-flight or crashed mutation). Both bypass paths still hit the
|
|
42
|
+
* per-request cache, so a single render dedupes within itself.
|
|
43
|
+
*
|
|
44
|
+
* Always returns an array. Empty = no custom fields registered.
|
|
45
|
+
*/
|
|
46
|
+
async function getBylineFieldDefs(db) {
|
|
47
|
+
const isolated = getRequestContext()?.dbIsIsolated === true;
|
|
48
|
+
const version = await getBylineFieldsVersion(db);
|
|
49
|
+
const dirty = version % 2 !== 0;
|
|
50
|
+
return requestCached(`${REQUEST_CACHE_KEY_DEFS_PREFIX}${version}`, async () => {
|
|
51
|
+
if (isolated || dirty) return new BylineSchemaRegistry(db).listFields();
|
|
52
|
+
if (holder.cached !== null && holder.cachedVersion === version) return holder.cached;
|
|
53
|
+
const defs = new BylineSchemaRegistry(db).listFields().catch((error) => {
|
|
54
|
+
if (holder.cached === defs) {
|
|
55
|
+
holder.cached = null;
|
|
56
|
+
holder.cachedVersion = -1;
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
});
|
|
60
|
+
holder.cached = defs;
|
|
61
|
+
holder.cachedVersion = version;
|
|
62
|
+
return defs;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/database/repositories/byline.ts
|
|
68
|
+
function rowToByline(row) {
|
|
69
|
+
return {
|
|
70
|
+
id: row.id,
|
|
71
|
+
slug: row.slug,
|
|
72
|
+
displayName: row.display_name,
|
|
73
|
+
bio: row.bio,
|
|
74
|
+
avatarMediaId: row.avatar_media_id,
|
|
75
|
+
avatarStorageKey: row.avatar_storage_key ?? null,
|
|
76
|
+
avatarAlt: row.avatar_alt ?? null,
|
|
77
|
+
websiteUrl: row.website_url,
|
|
78
|
+
userId: row.user_id,
|
|
79
|
+
isGuest: row.is_guest === 1,
|
|
80
|
+
createdAt: row.created_at,
|
|
81
|
+
updatedAt: row.updated_at,
|
|
82
|
+
locale: row.locale,
|
|
83
|
+
translationGroup: row.translation_group
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Merge a single decoded value into a `BylineSummary.customFields` map.
|
|
88
|
+
* Centralised so the merge semantics (null storage, JSON.parse failure
|
|
89
|
+
* handling) live in one place across both translatable and group-shared
|
|
90
|
+
* paths.
|
|
91
|
+
*
|
|
92
|
+
* A stored row with `value = NULL` (representing an explicit null) is
|
|
93
|
+
* surfaced as `null` in `customFields`. A row with a malformed JSON
|
|
94
|
+
* payload is dropped silently with a `console.warn` — a corrupted
|
|
95
|
+
* payload shouldn't break the entire byline hydration; the field-defs
|
|
96
|
+
* cache will let admins replace the value, and the warning makes the
|
|
97
|
+
* issue debuggable. (Storage path uses `JSON.stringify`, so the only
|
|
98
|
+
* way to get malformed JSON is direct DB tampering or a future
|
|
99
|
+
* migration bug.)
|
|
100
|
+
*/
|
|
101
|
+
function assignCustomFieldValue(summary, field, stored) {
|
|
102
|
+
const target = summary.customFields ?? {};
|
|
103
|
+
if (stored === null) target[field.slug] = null;
|
|
104
|
+
else try {
|
|
105
|
+
target[field.slug] = JSON.parse(stored);
|
|
106
|
+
} catch {
|
|
107
|
+
console.warn(`[BylineRepository] dropping malformed JSON for byline=${summary.id} field=${field.slug}: ${stored.slice(0, 60)}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
summary.customFields = target;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Coerce a raw write-path value to `CustomFieldValue`, throwing
|
|
114
|
+
* `EmDashValidationError` on type mismatch. `null` clears the field
|
|
115
|
+
* (DELETE in the write path).
|
|
116
|
+
*
|
|
117
|
+
* TODO: `field.required` is not enforced. The admin UI exposes the
|
|
118
|
+
* toggle but the backend accepts missing values; design pass needed
|
|
119
|
+
* on the enforcement model.
|
|
120
|
+
*/
|
|
121
|
+
function coerceFieldValue(field, raw) {
|
|
122
|
+
if (raw === null) return null;
|
|
123
|
+
switch (field.type) {
|
|
124
|
+
case "string":
|
|
125
|
+
case "text":
|
|
126
|
+
if (typeof raw !== "string") throw new EmDashValidationError(`Byline field "${field.slug}" expects a string value (received ${typeof raw})`, {
|
|
127
|
+
slug: field.slug,
|
|
128
|
+
type: field.type,
|
|
129
|
+
received: typeof raw
|
|
130
|
+
});
|
|
131
|
+
return raw;
|
|
132
|
+
case "url": {
|
|
133
|
+
if (typeof raw !== "string") throw new EmDashValidationError(`Byline field "${field.slug}" expects a string value (received ${typeof raw})`, {
|
|
134
|
+
slug: field.slug,
|
|
135
|
+
type: field.type,
|
|
136
|
+
received: typeof raw
|
|
137
|
+
});
|
|
138
|
+
if (raw === "") return raw;
|
|
139
|
+
let parsed;
|
|
140
|
+
try {
|
|
141
|
+
parsed = new URL(raw);
|
|
142
|
+
} catch {
|
|
143
|
+
throw new EmDashValidationError(`Byline field "${field.slug}" expects a valid URL (received "${raw}")`, {
|
|
144
|
+
slug: field.slug,
|
|
145
|
+
type: field.type,
|
|
146
|
+
received: raw
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new EmDashValidationError(`Byline field "${field.slug}" must use http or https scheme (received "${parsed.protocol}")`, {
|
|
150
|
+
slug: field.slug,
|
|
151
|
+
type: field.type,
|
|
152
|
+
received: raw,
|
|
153
|
+
protocol: parsed.protocol
|
|
154
|
+
});
|
|
155
|
+
return raw;
|
|
156
|
+
}
|
|
157
|
+
case "boolean":
|
|
158
|
+
if (typeof raw !== "boolean") throw new EmDashValidationError(`Byline field "${field.slug}" expects a boolean value (received ${typeof raw})`, {
|
|
159
|
+
slug: field.slug,
|
|
160
|
+
type: field.type,
|
|
161
|
+
received: typeof raw
|
|
162
|
+
});
|
|
163
|
+
return raw;
|
|
164
|
+
case "select": {
|
|
165
|
+
if (typeof raw !== "string") throw new EmDashValidationError(`Byline field "${field.slug}" expects a string value (received ${typeof raw})`, {
|
|
166
|
+
slug: field.slug,
|
|
167
|
+
type: field.type,
|
|
168
|
+
received: typeof raw
|
|
169
|
+
});
|
|
170
|
+
const options = field.validation?.options ?? [];
|
|
171
|
+
if (!options.includes(raw)) throw new EmDashValidationError(`Byline field "${field.slug}" value "${raw}" is not one of the registered choices`, {
|
|
172
|
+
slug: field.slug,
|
|
173
|
+
value: raw,
|
|
174
|
+
options
|
|
175
|
+
});
|
|
176
|
+
return raw;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Byline repository for content credits.
|
|
182
|
+
*
|
|
183
|
+
* Bylines are per-locale (migration 040). Translations of the same byline
|
|
184
|
+
* share a `translation_group` ULID. `_emdash_content_bylines.byline_id` and
|
|
185
|
+
* `ec_*.primary_byline_id` store the translation_group (not a row id) so a
|
|
186
|
+
* single credit spans every locale variant of a byline.
|
|
187
|
+
*
|
|
188
|
+
* The repository does not resolve locale fallbacks on its own — callers
|
|
189
|
+
* supply the locale they want. Hydration is strict per locale: a credit at
|
|
190
|
+
* locale X renders iff a byline row exists at locale X within the credited
|
|
191
|
+
* translation group. This mirrors `TaxonomyRepository.getTermsForEntry` and
|
|
192
|
+
* the convention established by PR #916.
|
|
193
|
+
*
|
|
194
|
+
* Runtime helpers in `packages/core/src/bylines/index.ts` may layer fallback
|
|
195
|
+
* resolution on top for the "look up one byline by slug" path, but the
|
|
196
|
+
* relation-hydration methods on this class are always strict.
|
|
197
|
+
*/
|
|
198
|
+
var BylineRepository = class {
|
|
199
|
+
constructor(db) {
|
|
200
|
+
this.db = db;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Merge `customFields` onto each `BylineSummary` produced from the
|
|
204
|
+
* given rows. Two batched queries total — one against
|
|
205
|
+
* `_emdash_byline_field_values` (keyed by `byline_id`), one against
|
|
206
|
+
* `_emdash_byline_field_group_values` (keyed by `translation_group`)
|
|
207
|
+
* — both chunked at `SQL_BATCH_SIZE` for D1's bound-parameter cap.
|
|
208
|
+
*
|
|
209
|
+
* When zero fields are registered, every row gets `customFields = {}`
|
|
210
|
+
* with no value-table reads (the field-defs cache returns `[]`).
|
|
211
|
+
* Group-shared values are looked up via the row's `translation_group`,
|
|
212
|
+
* so every locale sibling of the same byline identity sees the same
|
|
213
|
+
* non-translatable value without re-reading per row.
|
|
214
|
+
*
|
|
215
|
+
* **Duplicate-row handling.** Callers (notably `getContentBylinesMany`
|
|
216
|
+
* for list views with repeated authors) can pass the same byline row
|
|
217
|
+
* multiple times. We assign values by *iterating both `rows` and
|
|
218
|
+
* `summaries` in lockstep by index*, not by deduping into a Map keyed
|
|
219
|
+
* on byline id. A Map approach silently drops earlier duplicates' merge
|
|
220
|
+
* step (last writer wins, earlier instances keep their initial `{}`).
|
|
221
|
+
* Iterating by index gives every duplicate its own merged copy.
|
|
222
|
+
*
|
|
223
|
+
* Hydration is *strict per row* — values are merged onto whichever
|
|
224
|
+
* `BylineRow` produced them. Fallback semantics (e.g. "if no value
|
|
225
|
+
* for this locale, show the default-locale value") are not the
|
|
226
|
+
* repository's concern; consumers layer them on top if wanted, the
|
|
227
|
+
* same way `BylineRepository` doesn't resolve locale fallback for
|
|
228
|
+
* the base byline lookup.
|
|
229
|
+
*/
|
|
230
|
+
async withCustomFields(rows) {
|
|
231
|
+
const summaries = rows.map(rowToByline);
|
|
232
|
+
for (const summary of summaries) summary.customFields = {};
|
|
233
|
+
await this.applyCustomFieldsTo(summaries);
|
|
234
|
+
return summaries;
|
|
235
|
+
}
|
|
236
|
+
async withCustomFieldsOne(row) {
|
|
237
|
+
if (!row) return null;
|
|
238
|
+
const [result] = await this.withCustomFields([row]);
|
|
239
|
+
return result ?? null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Hydrate `customFields` on each `BylineSummary`, mutating in place.
|
|
243
|
+
*
|
|
244
|
+
* The public entry point for callers that fetch byline rows in
|
|
245
|
+
* multiple passes (e.g. `getBylinesForEntries`, which buckets by
|
|
246
|
+
* locale and calls `getContentBylinesMany` per bucket) and want a
|
|
247
|
+
* single batched hydration over the union of bylines, not one per
|
|
248
|
+
* pass. Use with the `skipHydration` option on the read methods to
|
|
249
|
+
* defer customFields work to a single call here.
|
|
250
|
+
*
|
|
251
|
+
* Two batched queries total (translatable + group-shared) regardless
|
|
252
|
+
* of how many bylines, locales, or translation_groups are in the
|
|
253
|
+
* input — meets the Phase 3 query-count envelope for mixed-locale
|
|
254
|
+
* list views even when sibling locales reference disjoint
|
|
255
|
+
* translation_groups.
|
|
256
|
+
*
|
|
257
|
+
* Replaces any existing `customFields` on each summary with a freshly
|
|
258
|
+
* fetched map. Callers that want to merge rather than replace should
|
|
259
|
+
* not use this entry point.
|
|
260
|
+
*/
|
|
261
|
+
async hydrateBylineCustomFields(summaries) {
|
|
262
|
+
for (const summary of summaries) summary.customFields = {};
|
|
263
|
+
await this.applyCustomFieldsTo(summaries);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Shared merge engine for `withCustomFields` and
|
|
267
|
+
* `hydrateBylineCustomFields`. Reads field defs (cached), batches the
|
|
268
|
+
* translatable + group-shared fetches, and walks `summaries` directly
|
|
269
|
+
* to apply values.
|
|
270
|
+
*
|
|
271
|
+
* Iterates `summaries` (not a `summaryById` map) so duplicate
|
|
272
|
+
* `BylineSummary` objects sharing the same `id` — e.g. the same
|
|
273
|
+
* author credited to multiple entries — each get their own merged
|
|
274
|
+
* values. The previous Map-based dedup silently dropped earlier
|
|
275
|
+
* duplicates' merge step.
|
|
276
|
+
*/
|
|
277
|
+
async applyCustomFieldsTo(summaries) {
|
|
278
|
+
if (summaries.length === 0) return;
|
|
279
|
+
const defs = await getBylineFieldDefs(this.db);
|
|
280
|
+
if (defs.length === 0) return;
|
|
281
|
+
const fieldById = new Map(defs.map((d) => [d.id, d]));
|
|
282
|
+
const translatableByByline = /* @__PURE__ */ new Map();
|
|
283
|
+
const bylineIds = [...new Set(summaries.map((s) => s.id))];
|
|
284
|
+
for (const chunk of chunks(bylineIds, SQL_BATCH_SIZE)) {
|
|
285
|
+
const trRows = await this.db.selectFrom("_emdash_byline_field_values").select([
|
|
286
|
+
"byline_id",
|
|
287
|
+
"field_id",
|
|
288
|
+
"value"
|
|
289
|
+
]).where("byline_id", "in", chunk).execute();
|
|
290
|
+
for (const trRow of trRows) {
|
|
291
|
+
let fieldMap = translatableByByline.get(trRow.byline_id);
|
|
292
|
+
if (!fieldMap) {
|
|
293
|
+
fieldMap = /* @__PURE__ */ new Map();
|
|
294
|
+
translatableByByline.set(trRow.byline_id, fieldMap);
|
|
295
|
+
}
|
|
296
|
+
fieldMap.set(trRow.field_id, trRow.value);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const groups = [...new Set(summaries.map((s) => s.translationGroup).filter((g) => typeof g === "string" && g.length > 0))];
|
|
300
|
+
const groupByGroup = await this.loadGroupValuesByIds(groups);
|
|
301
|
+
for (const summary of summaries) {
|
|
302
|
+
const trValues = translatableByByline.get(summary.id);
|
|
303
|
+
if (trValues) for (const [fieldId, value] of trValues) {
|
|
304
|
+
const field = fieldById.get(fieldId);
|
|
305
|
+
if (!field || !field.translatable) continue;
|
|
306
|
+
assignCustomFieldValue(summary, field, value);
|
|
307
|
+
}
|
|
308
|
+
if (summary.translationGroup) {
|
|
309
|
+
const grpValues = groupByGroup.get(summary.translationGroup);
|
|
310
|
+
if (grpValues) for (const [fieldId, value] of grpValues) {
|
|
311
|
+
const field = fieldById.get(fieldId);
|
|
312
|
+
if (!field || field.translatable) continue;
|
|
313
|
+
assignCustomFieldValue(summary, field, value);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Resolve the group-shared custom-field values for a set of
|
|
320
|
+
* translation_groups, sharing work across hydration calls within the
|
|
321
|
+
* same request via per-group `requestCached` entries.
|
|
322
|
+
*
|
|
323
|
+
* The non-translatable storage table (`_emdash_byline_field_group_values`)
|
|
324
|
+
* is keyed by `translation_group`, which is locale-agnostic. Combining
|
|
325
|
+
* this method with `skipHydration` on `getContentBylinesMany` and a
|
|
326
|
+
* single `hydrateBylineCustomFields` call (see
|
|
327
|
+
* `getBylinesForEntries`) keeps mixed-locale list hydration to **one**
|
|
328
|
+
* batched group-shared SQL per request — even with disjoint
|
|
329
|
+
* translation_groups across locale buckets. Solo callers (`findById`,
|
|
330
|
+
* `findMany`, etc.) still get the same per-call batching they had
|
|
331
|
+
* before; the cache simply means a second call in the same request
|
|
332
|
+
* for an overlapping group is free.
|
|
333
|
+
*
|
|
334
|
+
* Cache key: `byline-field-group-values:${groupId}` — one entry per
|
|
335
|
+
* group. Writes use `setRequestCacheEntry` (idempotent, doesn't
|
|
336
|
+
* overwrite); `BylineRepository.update` calls `clearRequestCacheEntry`
|
|
337
|
+
* after a group-shared write to keep the cache fresh within the same
|
|
338
|
+
* request.
|
|
339
|
+
*/
|
|
340
|
+
async loadGroupValuesByIds(groups) {
|
|
341
|
+
const result = /* @__PURE__ */ new Map();
|
|
342
|
+
if (groups.length === 0) return result;
|
|
343
|
+
const missing = [];
|
|
344
|
+
for (const g of groups) {
|
|
345
|
+
const cached = peekRequestCache(`byline-field-group-values:${g}`);
|
|
346
|
+
if (cached) result.set(g, await cached);
|
|
347
|
+
else missing.push(g);
|
|
348
|
+
}
|
|
349
|
+
if (missing.length === 0) return result;
|
|
350
|
+
const fetched = /* @__PURE__ */ new Map();
|
|
351
|
+
for (const g of missing) fetched.set(g, /* @__PURE__ */ new Map());
|
|
352
|
+
for (const chunk of chunks(missing, SQL_BATCH_SIZE)) {
|
|
353
|
+
const grpRows = await this.db.selectFrom("_emdash_byline_field_group_values").select([
|
|
354
|
+
"translation_group",
|
|
355
|
+
"field_id",
|
|
356
|
+
"value"
|
|
357
|
+
]).where("translation_group", "in", chunk).execute();
|
|
358
|
+
for (const grpRow of grpRows) {
|
|
359
|
+
const fieldMap = fetched.get(grpRow.translation_group);
|
|
360
|
+
if (!fieldMap) continue;
|
|
361
|
+
fieldMap.set(grpRow.field_id, grpRow.value);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const g of missing) {
|
|
365
|
+
const m = fetched.get(g);
|
|
366
|
+
if (!m) continue;
|
|
367
|
+
setRequestCacheEntry(`byline-field-group-values:${g}`, m);
|
|
368
|
+
result.set(g, m);
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
async findById(id) {
|
|
373
|
+
const row = await this.db.selectFrom("_emdash_bylines").selectAll().where("id", "=", id).executeTakeFirst();
|
|
374
|
+
return this.withCustomFieldsOne(row);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Find a byline by slug. When `locale` is provided, filter by it strictly.
|
|
378
|
+
* When omitted, returns the lowest-locale-code match (deterministic across
|
|
379
|
+
* calls). Mirrors `TaxonomyRepository.findBySlug`.
|
|
380
|
+
*/
|
|
381
|
+
async findBySlug(slug, options) {
|
|
382
|
+
let query = this.db.selectFrom("_emdash_bylines").selectAll().where("slug", "=", slug);
|
|
383
|
+
if (options?.locale !== void 0) query = query.where("locale", "=", options.locale);
|
|
384
|
+
const row = await query.orderBy("locale", "asc").executeTakeFirst();
|
|
385
|
+
return this.withCustomFieldsOne(row);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Find the byline linked to a CMS user. Post-migration 040 the partial
|
|
389
|
+
* unique on user_id is `(user_id, locale)`, so `locale` is required to
|
|
390
|
+
* disambiguate when multiple locale variants exist. When omitted, returns
|
|
391
|
+
* the lowest-locale-code match.
|
|
392
|
+
*/
|
|
393
|
+
async findByUserId(userId, options) {
|
|
394
|
+
let query = this.db.selectFrom("_emdash_bylines").selectAll().where("user_id", "=", userId);
|
|
395
|
+
if (options?.locale !== void 0) query = query.where("locale", "=", options.locale);
|
|
396
|
+
const row = await query.orderBy("locale", "asc").executeTakeFirst();
|
|
397
|
+
return this.withCustomFieldsOne(row);
|
|
398
|
+
}
|
|
399
|
+
async findMany(options) {
|
|
400
|
+
const limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);
|
|
401
|
+
let query = this.db.selectFrom("_emdash_bylines").selectAll().orderBy("created_at", "desc").orderBy("id", "desc").limit(limit + 1);
|
|
402
|
+
if (options?.search) {
|
|
403
|
+
const term = `%${options.search.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
|
|
404
|
+
query = query.where((eb) => eb.or([eb("display_name", "like", term), eb("slug", "like", term)]));
|
|
405
|
+
}
|
|
406
|
+
if (options?.isGuest !== void 0) query = query.where("is_guest", "=", options.isGuest ? 1 : 0);
|
|
407
|
+
if (options?.userId !== void 0) query = query.where("user_id", "=", options.userId);
|
|
408
|
+
if (options?.locale !== void 0) query = query.where("locale", "=", options.locale);
|
|
409
|
+
if (options?.cursor) {
|
|
410
|
+
const decoded = decodeCursor(options.cursor);
|
|
411
|
+
query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
|
|
412
|
+
}
|
|
413
|
+
const rows = await query.execute();
|
|
414
|
+
const pageRows = rows.slice(0, limit);
|
|
415
|
+
const items = await this.withCustomFields(pageRows);
|
|
416
|
+
const result = { items };
|
|
417
|
+
if (rows.length > limit) {
|
|
418
|
+
const last = items.at(-1);
|
|
419
|
+
if (last) result.nextCursor = encodeCursor(last.createdAt, last.id);
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* List every sibling row in `translation_group`. Used by the admin
|
|
425
|
+
* `TranslationsPanel` to render one entry per configured locale.
|
|
426
|
+
*/
|
|
427
|
+
async listTranslations(id) {
|
|
428
|
+
const anchor = await this.findById(id);
|
|
429
|
+
if (!anchor) return [];
|
|
430
|
+
const group = anchor.translationGroup ?? anchor.id;
|
|
431
|
+
return this.findByTranslationGroup(group);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Direct lookup by `translation_group`. Returns every locale variant of a
|
|
435
|
+
* byline, ordered by locale code (deterministic).
|
|
436
|
+
*/
|
|
437
|
+
async findByTranslationGroup(translationGroup) {
|
|
438
|
+
const rows = await this.db.selectFrom("_emdash_bylines").selectAll().where("translation_group", "=", translationGroup).orderBy("locale", "asc").execute();
|
|
439
|
+
return this.withCustomFields(rows);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Validate a `customFields` input map into a write list before any row
|
|
443
|
+
* write — throws `EmDashValidationError` on unknown slugs, type
|
|
444
|
+
* mismatches, or select-choice misses.
|
|
445
|
+
*/
|
|
446
|
+
async resolveCustomFieldWrites(customFields) {
|
|
447
|
+
if (!customFields || Object.keys(customFields).length === 0) return [];
|
|
448
|
+
const defs = await getBylineFieldDefs(this.db);
|
|
449
|
+
const bySlug = new Map(defs.map((d) => [d.slug, d]));
|
|
450
|
+
const writes = [];
|
|
451
|
+
for (const [slug, raw] of Object.entries(customFields)) {
|
|
452
|
+
const field = bySlug.get(slug);
|
|
453
|
+
if (!field) throw new EmDashValidationError(`Unknown byline custom field "${slug}"`, {
|
|
454
|
+
slug,
|
|
455
|
+
registered: defs.map((d) => d.slug)
|
|
456
|
+
});
|
|
457
|
+
writes.push({
|
|
458
|
+
field,
|
|
459
|
+
value: coerceFieldValue(field, raw)
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return writes;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Write a validated custom-field list against a byline row inside the
|
|
466
|
+
* caller's transaction. Per-field writes route to
|
|
467
|
+
* `_emdash_byline_field_values` (translatable) or
|
|
468
|
+
* `_emdash_byline_field_group_values` (group-shared); `null` clears.
|
|
469
|
+
* Returns `true` when any group-shared row was touched so the caller
|
|
470
|
+
* can invalidate the per-request cache post-commit.
|
|
471
|
+
*/
|
|
472
|
+
async applyCustomFieldWritesInTrx(trx, bylineId, translationGroup, writes, now) {
|
|
473
|
+
if (writes.length === 0) return false;
|
|
474
|
+
let touchedGroupShared = false;
|
|
475
|
+
for (const { field, value } of writes) {
|
|
476
|
+
if (!field.translatable) touchedGroupShared = true;
|
|
477
|
+
if (field.translatable) if (value === null) await trx.deleteFrom("_emdash_byline_field_values").where("byline_id", "=", bylineId).where("field_id", "=", field.id).execute();
|
|
478
|
+
else {
|
|
479
|
+
const encoded = JSON.stringify(value);
|
|
480
|
+
await trx.insertInto("_emdash_byline_field_values").values({
|
|
481
|
+
byline_id: bylineId,
|
|
482
|
+
field_id: field.id,
|
|
483
|
+
value: encoded,
|
|
484
|
+
created_at: now,
|
|
485
|
+
updated_at: now
|
|
486
|
+
}).onConflict((oc) => oc.columns(["byline_id", "field_id"]).doUpdateSet({
|
|
487
|
+
value: encoded,
|
|
488
|
+
updated_at: now
|
|
489
|
+
})).execute();
|
|
490
|
+
}
|
|
491
|
+
else if (value === null) await trx.deleteFrom("_emdash_byline_field_group_values").where("translation_group", "=", translationGroup).where("field_id", "=", field.id).execute();
|
|
492
|
+
else {
|
|
493
|
+
const encoded = JSON.stringify(value);
|
|
494
|
+
await trx.insertInto("_emdash_byline_field_group_values").values({
|
|
495
|
+
translation_group: translationGroup,
|
|
496
|
+
field_id: field.id,
|
|
497
|
+
value: encoded,
|
|
498
|
+
created_at: now,
|
|
499
|
+
updated_at: now
|
|
500
|
+
}).onConflict((oc) => oc.columns(["translation_group", "field_id"]).doUpdateSet({
|
|
501
|
+
value: encoded,
|
|
502
|
+
updated_at: now
|
|
503
|
+
})).execute();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return touchedGroupShared;
|
|
507
|
+
}
|
|
508
|
+
async create(input) {
|
|
509
|
+
const id = ulid();
|
|
510
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
511
|
+
const customFieldWrites = await this.resolveCustomFieldWrites(input.customFields);
|
|
512
|
+
let translationGroup = id;
|
|
513
|
+
if (input.translationOf) {
|
|
514
|
+
const source = await this.findById(input.translationOf);
|
|
515
|
+
if (!source) throw new Error("Source byline for translation not found");
|
|
516
|
+
translationGroup = source.translationGroup ?? source.id;
|
|
517
|
+
}
|
|
518
|
+
let touchedGroupShared = false;
|
|
519
|
+
await withTransaction(this.db, async (trx) => {
|
|
520
|
+
await trx.insertInto("_emdash_bylines").values({
|
|
521
|
+
id,
|
|
522
|
+
slug: input.slug,
|
|
523
|
+
display_name: input.displayName,
|
|
524
|
+
bio: input.bio ?? null,
|
|
525
|
+
avatar_media_id: input.avatarMediaId ?? null,
|
|
526
|
+
website_url: input.websiteUrl ?? null,
|
|
527
|
+
user_id: input.userId ?? null,
|
|
528
|
+
is_guest: input.isGuest ? 1 : 0,
|
|
529
|
+
created_at: now,
|
|
530
|
+
updated_at: now,
|
|
531
|
+
...input.locale !== void 0 ? { locale: input.locale } : {},
|
|
532
|
+
translation_group: translationGroup
|
|
533
|
+
}).execute();
|
|
534
|
+
touchedGroupShared = await this.applyCustomFieldWritesInTrx(trx, id, translationGroup, customFieldWrites, now);
|
|
535
|
+
});
|
|
536
|
+
if (touchedGroupShared) clearRequestCacheEntry(`byline-field-group-values:${translationGroup}`);
|
|
537
|
+
const byline = await this.findById(id);
|
|
538
|
+
if (!byline) throw new Error("Failed to create byline");
|
|
539
|
+
return byline;
|
|
540
|
+
}
|
|
541
|
+
async update(id, input) {
|
|
542
|
+
const existing = await this.findById(id);
|
|
543
|
+
if (!existing) return null;
|
|
544
|
+
const customFieldWrites = await this.resolveCustomFieldWrites(input.customFields);
|
|
545
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
546
|
+
const updates = { updated_at: now };
|
|
547
|
+
if (input.slug !== void 0) updates.slug = input.slug;
|
|
548
|
+
if (input.displayName !== void 0) updates.display_name = input.displayName;
|
|
549
|
+
if (input.bio !== void 0) updates.bio = input.bio;
|
|
550
|
+
if (input.avatarMediaId !== void 0) updates.avatar_media_id = input.avatarMediaId;
|
|
551
|
+
if (input.websiteUrl !== void 0) updates.website_url = input.websiteUrl;
|
|
552
|
+
if (input.userId !== void 0) updates.user_id = input.userId;
|
|
553
|
+
if (input.isGuest !== void 0) updates.is_guest = input.isGuest ? 1 : 0;
|
|
554
|
+
const group = existing.translationGroup ?? existing.id;
|
|
555
|
+
let touchedGroupShared = false;
|
|
556
|
+
await withTransaction(this.db, async (trx) => {
|
|
557
|
+
await trx.updateTable("_emdash_bylines").set(updates).where("id", "=", id).execute();
|
|
558
|
+
touchedGroupShared = await this.applyCustomFieldWritesInTrx(trx, id, group, customFieldWrites, now);
|
|
559
|
+
});
|
|
560
|
+
if (touchedGroupShared) clearRequestCacheEntry(`byline-field-group-values:${group}`);
|
|
561
|
+
return await this.findById(id);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Delete a byline row. When this row is the last sibling in its
|
|
565
|
+
* translation group, also drops every junction row pointing at the group,
|
|
566
|
+
* clears `primary_byline_id` references, and removes the byline's
|
|
567
|
+
* non-translatable custom-field values. When other siblings remain in
|
|
568
|
+
* the group, junctions, `primary_byline_id` pointers, and group-shared
|
|
569
|
+
* custom-field values stay intact — the credit (and its shared metadata)
|
|
570
|
+
* lives on at other locales.
|
|
571
|
+
*
|
|
572
|
+
* **Application-level cascade.** The byline domain has standardised on
|
|
573
|
+
* app-level cascade rather than trusting FK ON DELETE CASCADE, partly
|
|
574
|
+
* because migration 040 had to strip its own FK to support the
|
|
575
|
+
* translation_group remap (#1021), and partly so cleanup doesn't
|
|
576
|
+
* depend on `PRAGMA foreign_keys = ON` (set in production via
|
|
577
|
+
* `connection.ts:60`, but easy to bypass in tests, scripts, and
|
|
578
|
+
* one-off tools). Every byline-related deletion table is cleared
|
|
579
|
+
* explicitly here:
|
|
580
|
+
*
|
|
581
|
+
* - `_emdash_byline_field_values` (per-byline translatable values) —
|
|
582
|
+
* migration 041 declares FK ON DELETE CASCADE on `byline_id`; the
|
|
583
|
+
* explicit DELETE removes the dependency on that pragma.
|
|
584
|
+
* - `_emdash_content_bylines` — migration 040 dropped its FK.
|
|
585
|
+
* - `ec_*.primary_byline_id` — never had an FK.
|
|
586
|
+
* - `_emdash_byline_field_group_values` (translation-group-keyed) —
|
|
587
|
+
* keyed by a text column with no FK to bylines, so app-level cleanup
|
|
588
|
+
* is the only path.
|
|
589
|
+
*
|
|
590
|
+
* The FKs that remain (migration 041) serve as defense-in-depth.
|
|
591
|
+
*/
|
|
592
|
+
async delete(id) {
|
|
593
|
+
const existing = await this.findById(id);
|
|
594
|
+
if (!existing) return false;
|
|
595
|
+
const group = existing.translationGroup ?? existing.id;
|
|
596
|
+
await withTransaction(this.db, async (trx) => {
|
|
597
|
+
await trx.deleteFrom("_emdash_byline_field_values").where("byline_id", "=", id).execute();
|
|
598
|
+
await trx.deleteFrom("_emdash_bylines").where("id", "=", id).execute();
|
|
599
|
+
const remaining = await trx.selectFrom("_emdash_bylines").select(({ fn }) => [fn.count("id").as("count")]).where("translation_group", "=", group).executeTakeFirst();
|
|
600
|
+
if (Number(remaining?.count ?? 0) > 0) return;
|
|
601
|
+
await trx.deleteFrom("_emdash_content_bylines").where("byline_id", "=", group).execute();
|
|
602
|
+
await trx.deleteFrom("_emdash_byline_field_group_values").where("translation_group", "=", group).execute();
|
|
603
|
+
const tableNames = await listTablesLike(trx, "ec_%");
|
|
604
|
+
for (const tableName of tableNames) {
|
|
605
|
+
validateIdentifier(tableName, "content table");
|
|
606
|
+
await sql`
|
|
607
|
+
UPDATE ${sql.ref(tableName)}
|
|
608
|
+
SET primary_byline_id = NULL
|
|
609
|
+
WHERE primary_byline_id = ${group}
|
|
610
|
+
`.execute(trx);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Strict per-locale credit hydration. Joins `_emdash_content_bylines` to
|
|
617
|
+
* `_emdash_bylines` on `translation_group = byline_id`, then filters to
|
|
618
|
+
* the requested locale. Credits whose translation group lacks a row at
|
|
619
|
+
* the requested locale are omitted — callers wanting fallback behaviour
|
|
620
|
+
* apply it themselves. Mirrors `TaxonomyRepository.getTermsForEntry`.
|
|
621
|
+
*/
|
|
622
|
+
async getContentBylines(collectionSlug, contentId, options) {
|
|
623
|
+
let query = this.db.selectFrom("_emdash_content_bylines as cb").innerJoin("_emdash_bylines as b", "b.translation_group", "cb.byline_id").leftJoin("media as m", "m.id", "b.avatar_media_id").select([
|
|
624
|
+
"cb.sort_order as sort_order",
|
|
625
|
+
"cb.role_label as role_label",
|
|
626
|
+
"b.id as id",
|
|
627
|
+
"b.slug as slug",
|
|
628
|
+
"b.display_name as display_name",
|
|
629
|
+
"b.bio as bio",
|
|
630
|
+
"b.avatar_media_id as avatar_media_id",
|
|
631
|
+
"m.storage_key as avatar_storage_key",
|
|
632
|
+
"m.alt as avatar_alt",
|
|
633
|
+
"b.website_url as website_url",
|
|
634
|
+
"b.user_id as user_id",
|
|
635
|
+
"b.is_guest as is_guest",
|
|
636
|
+
"b.created_at as created_at",
|
|
637
|
+
"b.updated_at as updated_at",
|
|
638
|
+
"b.locale as locale",
|
|
639
|
+
"b.translation_group as translation_group"
|
|
640
|
+
]).where("cb.collection_slug", "=", collectionSlug).where("cb.content_id", "=", contentId).orderBy("cb.sort_order", "asc");
|
|
641
|
+
if (options?.locale !== void 0) query = query.where("b.locale", "=", options.locale);
|
|
642
|
+
const rows = await query.execute();
|
|
643
|
+
const bylineRows = rows.map((row) => ({
|
|
644
|
+
id: row.id,
|
|
645
|
+
slug: row.slug,
|
|
646
|
+
display_name: row.display_name,
|
|
647
|
+
bio: row.bio,
|
|
648
|
+
avatar_media_id: row.avatar_media_id,
|
|
649
|
+
avatar_storage_key: row.avatar_storage_key,
|
|
650
|
+
avatar_alt: row.avatar_alt,
|
|
651
|
+
website_url: row.website_url,
|
|
652
|
+
user_id: row.user_id,
|
|
653
|
+
is_guest: row.is_guest,
|
|
654
|
+
created_at: row.created_at,
|
|
655
|
+
updated_at: row.updated_at,
|
|
656
|
+
locale: row.locale,
|
|
657
|
+
translation_group: row.translation_group
|
|
658
|
+
}));
|
|
659
|
+
const hydrated = await this.withCustomFields(bylineRows);
|
|
660
|
+
return rows.map((row, i) => {
|
|
661
|
+
const byline = hydrated[i];
|
|
662
|
+
if (!byline) throw new Error("getContentBylines: hydration row count mismatch");
|
|
663
|
+
return {
|
|
664
|
+
byline,
|
|
665
|
+
sortOrder: row.sort_order,
|
|
666
|
+
roleLabel: row.role_label
|
|
667
|
+
};
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Does this entry have any explicit byline credits — at any locale?
|
|
672
|
+
*
|
|
673
|
+
* Used to disambiguate "no credits exist" (fall back to author-linked
|
|
674
|
+
* byline) from "credits exist but don't resolve at the requested locale"
|
|
675
|
+
* (strict per-locale model: render no byline). Without this check the
|
|
676
|
+
* locale-strict hydration would silently turn a missing translation into
|
|
677
|
+
* an author-inferred byline, contradicting editorial intent.
|
|
678
|
+
*/
|
|
679
|
+
async hasContentBylines(collectionSlug, contentId) {
|
|
680
|
+
return await this.db.selectFrom("_emdash_content_bylines").select("id").where("collection_slug", "=", collectionSlug).where("content_id", "=", contentId).limit(1).executeTakeFirst() !== void 0;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Batch variant of `hasContentBylines`. Returns the set of content IDs
|
|
684
|
+
* that have at least one junction row (locale-agnostic).
|
|
685
|
+
*/
|
|
686
|
+
async hasContentBylinesMany(collectionSlug, contentIds) {
|
|
687
|
+
const result = /* @__PURE__ */ new Set();
|
|
688
|
+
if (contentIds.length === 0) return result;
|
|
689
|
+
const uniqueContentIds = [...new Set(contentIds)];
|
|
690
|
+
for (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {
|
|
691
|
+
const rows = await this.db.selectFrom("_emdash_content_bylines").select("content_id").distinct().where("collection_slug", "=", collectionSlug).where("content_id", "in", chunk).execute();
|
|
692
|
+
for (const row of rows) result.add(row.content_id);
|
|
693
|
+
}
|
|
694
|
+
return result;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Batch variant of `getContentBylines`. Same strict-per-locale semantics
|
|
698
|
+
* applied to the requested locale (single value, not per-entry).
|
|
699
|
+
*
|
|
700
|
+
* When callers need per-entry-locale filtering (e.g. a list endpoint
|
|
701
|
+
* returning entries at mixed locales), they should group the input ids by
|
|
702
|
+
* the entry's locale and call this method once per group.
|
|
703
|
+
*
|
|
704
|
+
* When the caller will issue multiple `getContentBylinesMany` calls in
|
|
705
|
+
* one request (e.g. per locale bucket) and wants a *single* batched
|
|
706
|
+
* customFields hydration over the union of returned bylines, pass
|
|
707
|
+
* `skipHydration: true` on each call and finish with
|
|
708
|
+
* `hydrateBylineCustomFields(allBylines)`. The returned bylines carry
|
|
709
|
+
* `customFields = {}` until that hydration call runs — matching the
|
|
710
|
+
* "always populated" invariant from AC #6 — so callers that forget to
|
|
711
|
+
* hydrate get an empty map rather than `undefined`.
|
|
712
|
+
*/
|
|
713
|
+
async getContentBylinesMany(collectionSlug, contentIds, options) {
|
|
714
|
+
const result = /* @__PURE__ */ new Map();
|
|
715
|
+
if (contentIds.length === 0) return result;
|
|
716
|
+
const uniqueContentIds = [...new Set(contentIds)];
|
|
717
|
+
for (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {
|
|
718
|
+
let query = this.db.selectFrom("_emdash_content_bylines as cb").innerJoin("_emdash_bylines as b", "b.translation_group", "cb.byline_id").leftJoin("media as m", "m.id", "b.avatar_media_id").select([
|
|
719
|
+
"cb.content_id as content_id",
|
|
720
|
+
"cb.sort_order as sort_order",
|
|
721
|
+
"cb.role_label as role_label",
|
|
722
|
+
"b.id as id",
|
|
723
|
+
"b.slug as slug",
|
|
724
|
+
"b.display_name as display_name",
|
|
725
|
+
"b.bio as bio",
|
|
726
|
+
"b.avatar_media_id as avatar_media_id",
|
|
727
|
+
"m.storage_key as avatar_storage_key",
|
|
728
|
+
"m.alt as avatar_alt",
|
|
729
|
+
"b.website_url as website_url",
|
|
730
|
+
"b.user_id as user_id",
|
|
731
|
+
"b.is_guest as is_guest",
|
|
732
|
+
"b.created_at as created_at",
|
|
733
|
+
"b.updated_at as updated_at",
|
|
734
|
+
"b.locale as locale",
|
|
735
|
+
"b.translation_group as translation_group"
|
|
736
|
+
]).where("cb.collection_slug", "=", collectionSlug).where("cb.content_id", "in", chunk).orderBy("cb.sort_order", "asc");
|
|
737
|
+
if (options?.locale !== void 0) query = query.where("b.locale", "=", options.locale);
|
|
738
|
+
const rows = await query.execute();
|
|
739
|
+
const bylineRows = rows.map((row) => ({
|
|
740
|
+
id: row.id,
|
|
741
|
+
slug: row.slug,
|
|
742
|
+
display_name: row.display_name,
|
|
743
|
+
bio: row.bio,
|
|
744
|
+
avatar_media_id: row.avatar_media_id,
|
|
745
|
+
avatar_storage_key: row.avatar_storage_key,
|
|
746
|
+
avatar_alt: row.avatar_alt,
|
|
747
|
+
website_url: row.website_url,
|
|
748
|
+
user_id: row.user_id,
|
|
749
|
+
is_guest: row.is_guest,
|
|
750
|
+
created_at: row.created_at,
|
|
751
|
+
updated_at: row.updated_at,
|
|
752
|
+
locale: row.locale,
|
|
753
|
+
translation_group: row.translation_group
|
|
754
|
+
}));
|
|
755
|
+
let bylines;
|
|
756
|
+
if (options?.skipHydration === true) {
|
|
757
|
+
bylines = bylineRows.map(rowToByline);
|
|
758
|
+
for (const b of bylines) b.customFields = {};
|
|
759
|
+
} else bylines = await this.withCustomFields(bylineRows);
|
|
760
|
+
for (let i = 0; i < rows.length; i++) {
|
|
761
|
+
const row = rows[i];
|
|
762
|
+
const byline = bylines[i];
|
|
763
|
+
if (!row || !byline) continue;
|
|
764
|
+
const contentId = row.content_id;
|
|
765
|
+
const credit = {
|
|
766
|
+
byline,
|
|
767
|
+
sortOrder: row.sort_order,
|
|
768
|
+
roleLabel: row.role_label
|
|
769
|
+
};
|
|
770
|
+
const existing = result.get(contentId);
|
|
771
|
+
if (existing) existing.push(credit);
|
|
772
|
+
else result.set(contentId, [credit]);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Batch-fetch byline profiles linked to user IDs in a single query.
|
|
779
|
+
* Strict-locale variant of `findByUserId`.
|
|
780
|
+
*
|
|
781
|
+
* `skipHydration: true` returns bylines with `customFields = {}` so
|
|
782
|
+
* callers issuing multiple `findByUserIds` calls in one request (e.g.
|
|
783
|
+
* the per-locale-bucket author-fallback path in `getBylinesForEntries`)
|
|
784
|
+
* can defer customFields hydration to a single batched
|
|
785
|
+
* `hydrateBylineCustomFields` call across the union — keeping the
|
|
786
|
+
* Phase 3 query-count envelope at "+1 group-shared query per
|
|
787
|
+
* hydration pass" even when buckets fetch disjoint author bylines.
|
|
788
|
+
*/
|
|
789
|
+
async findByUserIds(userIds, options) {
|
|
790
|
+
const result = /* @__PURE__ */ new Map();
|
|
791
|
+
if (userIds.length === 0) return result;
|
|
792
|
+
for (const chunk of chunks(userIds, SQL_BATCH_SIZE)) {
|
|
793
|
+
let query = this.db.selectFrom("_emdash_bylines as b").leftJoin("media as m", "m.id", "b.avatar_media_id").select([
|
|
794
|
+
"b.id as id",
|
|
795
|
+
"b.slug as slug",
|
|
796
|
+
"b.display_name as display_name",
|
|
797
|
+
"b.bio as bio",
|
|
798
|
+
"b.avatar_media_id as avatar_media_id",
|
|
799
|
+
"m.storage_key as avatar_storage_key",
|
|
800
|
+
"m.alt as avatar_alt",
|
|
801
|
+
"b.website_url as website_url",
|
|
802
|
+
"b.user_id as user_id",
|
|
803
|
+
"b.is_guest as is_guest",
|
|
804
|
+
"b.created_at as created_at",
|
|
805
|
+
"b.updated_at as updated_at",
|
|
806
|
+
"b.locale as locale",
|
|
807
|
+
"b.translation_group as translation_group"
|
|
808
|
+
]).where("b.user_id", "in", chunk);
|
|
809
|
+
if (options?.locale !== void 0) query = query.where("b.locale", "=", options.locale);
|
|
810
|
+
const rows = await query.execute();
|
|
811
|
+
let bylines;
|
|
812
|
+
if (options?.skipHydration === true) {
|
|
813
|
+
bylines = rows.map(rowToByline);
|
|
814
|
+
for (const b of bylines) b.customFields = {};
|
|
815
|
+
} else bylines = await this.withCustomFields(rows);
|
|
816
|
+
for (let i = 0; i < rows.length; i++) {
|
|
817
|
+
const row = rows[i];
|
|
818
|
+
const summary = bylines[i];
|
|
819
|
+
if (!row || !summary || !row.user_id) continue;
|
|
820
|
+
result.set(row.user_id, summary);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Clone every junction row from `sourceContentId` to `targetContentId`,
|
|
827
|
+
* preserving `sort_order` and `role_label`. Used by the content
|
|
828
|
+
* translation flow: a newly created translation inherits the source's
|
|
829
|
+
* byline credits at the storage level. Because the junction stores
|
|
830
|
+
* `translation_group` (not a row id), the copy is locale-agnostic — the
|
|
831
|
+
* credits resolve to whichever locale variants of each byline exist when
|
|
832
|
+
* the translated entry is hydrated.
|
|
833
|
+
*
|
|
834
|
+
* No-op when the source has no credits. Skips when the target already
|
|
835
|
+
* has credits (idempotent for re-runs).
|
|
836
|
+
*/
|
|
837
|
+
async copyContentBylines(collection, sourceContentId, targetContentId) {
|
|
838
|
+
validateIdentifier(collection, "collection slug");
|
|
839
|
+
const tableName = `ec_${collection}`;
|
|
840
|
+
validateIdentifier(tableName, "content table");
|
|
841
|
+
if (await this.db.selectFrom("_emdash_content_bylines").select("id").where("collection_slug", "=", collection).where("content_id", "=", targetContentId).executeTakeFirst()) return;
|
|
842
|
+
const sourceRows = await this.db.selectFrom("_emdash_content_bylines").select([
|
|
843
|
+
"byline_id",
|
|
844
|
+
"sort_order",
|
|
845
|
+
"role_label"
|
|
846
|
+
]).where("collection_slug", "=", collection).where("content_id", "=", sourceContentId).orderBy("sort_order", "asc").execute();
|
|
847
|
+
if (sourceRows.length === 0) return;
|
|
848
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
849
|
+
await this.db.insertInto("_emdash_content_bylines").values(sourceRows.map((row) => ({
|
|
850
|
+
id: ulid(),
|
|
851
|
+
collection_slug: collection,
|
|
852
|
+
content_id: targetContentId,
|
|
853
|
+
byline_id: row.byline_id,
|
|
854
|
+
sort_order: row.sort_order,
|
|
855
|
+
role_label: row.role_label,
|
|
856
|
+
created_at: now
|
|
857
|
+
}))).execute();
|
|
858
|
+
const firstByline = sourceRows[0]?.byline_id ?? null;
|
|
859
|
+
await sql`
|
|
860
|
+
UPDATE ${sql.ref(tableName)}
|
|
861
|
+
SET primary_byline_id = ${firstByline}
|
|
862
|
+
WHERE id = ${targetContentId}
|
|
863
|
+
`.execute(this.db);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Replace the set of byline credits on a content entry. Accepts row ids
|
|
867
|
+
* at the wire (consistent with how the admin sends them), translates
|
|
868
|
+
* each to its `translation_group` on write, and stores the group in
|
|
869
|
+
* `_emdash_content_bylines.byline_id` and `ec_*.primary_byline_id`.
|
|
870
|
+
*
|
|
871
|
+
* The returned credits are hydrated with strict-locale matching at the
|
|
872
|
+
* locale of the rows the caller supplied (i.e. the locale of the byline
|
|
873
|
+
* each `bylineId` resolves to) — adequate for the autosave round-trip,
|
|
874
|
+
* which then re-hydrates the entry against its own locale separately.
|
|
875
|
+
*/
|
|
876
|
+
async setContentBylines(collectionSlug, contentId, inputBylines) {
|
|
877
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
878
|
+
const tableName = `ec_${collectionSlug}`;
|
|
879
|
+
validateIdentifier(tableName, "content table");
|
|
880
|
+
const idToGroup = /* @__PURE__ */ new Map();
|
|
881
|
+
if (inputBylines.length > 0) {
|
|
882
|
+
const wireIds = [...new Set(inputBylines.map((item) => item.bylineId))];
|
|
883
|
+
const rows = await this.db.selectFrom("_emdash_bylines").select(["id", "translation_group"]).where("id", "in", wireIds).execute();
|
|
884
|
+
if (rows.length !== wireIds.length) throw new Error("One or more byline IDs do not exist");
|
|
885
|
+
for (const row of rows) idToGroup.set(row.id, row.translation_group ?? row.id);
|
|
886
|
+
}
|
|
887
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
888
|
+
const bylines = [];
|
|
889
|
+
for (const item of inputBylines) {
|
|
890
|
+
const group = idToGroup.get(item.bylineId);
|
|
891
|
+
if (!group) throw new Error(`Missing translation_group for byline ${item.bylineId}`);
|
|
892
|
+
if (seenGroups.has(group)) continue;
|
|
893
|
+
seenGroups.add(group);
|
|
894
|
+
bylines.push({
|
|
895
|
+
...item,
|
|
896
|
+
group
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
await this.db.deleteFrom("_emdash_content_bylines").where("collection_slug", "=", collectionSlug).where("content_id", "=", contentId).execute();
|
|
900
|
+
for (let i = 0; i < bylines.length; i++) {
|
|
901
|
+
const item = bylines[i];
|
|
902
|
+
if (!item) continue;
|
|
903
|
+
await this.db.insertInto("_emdash_content_bylines").values({
|
|
904
|
+
id: ulid(),
|
|
905
|
+
collection_slug: collectionSlug,
|
|
906
|
+
content_id: contentId,
|
|
907
|
+
byline_id: item.group,
|
|
908
|
+
sort_order: i,
|
|
909
|
+
role_label: item.roleLabel ?? null,
|
|
910
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
911
|
+
}).execute();
|
|
912
|
+
}
|
|
913
|
+
const primaryGroup = bylines[0]?.group ?? null;
|
|
914
|
+
await sql`
|
|
915
|
+
UPDATE ${sql.ref(tableName)}
|
|
916
|
+
SET primary_byline_id = ${primaryGroup}
|
|
917
|
+
WHERE id = ${contentId}
|
|
918
|
+
`.execute(this.db);
|
|
919
|
+
return await this.getContentBylines(collectionSlug, contentId);
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
//#endregion
|
|
924
|
+
export { BylineRepository as t };
|
|
925
|
+
//# sourceMappingURL=byline-BrIVWLm-.mjs.map
|