emdash 0.33.0 → 0.35.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-BMyNSSZi.d.mts → adapters-Bb1vG41D.d.mts} +33 -2
- package/dist/adapters-Bb1vG41D.d.mts.map +1 -0
- package/dist/{allowed-origins-CG1pACtS.mjs → allowed-origins-DW7sYyqg.mjs} +2 -2
- package/dist/{allowed-origins-CG1pACtS.mjs.map → allowed-origins-DW7sYyqg.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +22 -21
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +6 -5
- package/dist/{api-D2hunekQ.mjs → api-BeBglVxU.mjs} +98 -407
- package/dist/api-BeBglVxU.mjs.map +1 -0
- package/dist/{api-tokens-CSND0i1z.mjs → api-tokens-KO24G1Fd.mjs} +2 -2
- package/dist/{api-tokens-CSND0i1z.mjs.map → api-tokens-KO24G1Fd.mjs.map} +1 -1
- package/dist/{apply-kwNEJW2l.mjs → apply-kUDt80-G.mjs} +131 -107
- package/dist/apply-kUDt80-G.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +163 -49
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.mjs +8 -8
- package/dist/astro/middleware/media-usage-write-fence.d.mts +7 -0
- package/dist/astro/middleware/media-usage-write-fence.d.mts.map +1 -0
- package/dist/astro/middleware/media-usage-write-fence.mjs +35 -0
- package/dist/astro/middleware/media-usage-write-fence.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +10 -7
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +3 -3
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +12 -11
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +1763 -173
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +12 -11
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +12 -11
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +12 -11
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
- package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -13
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -11
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/index.mjs +12 -11
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +36 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +36 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +15 -14
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/work/index.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +37 -0
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +37 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +51 -43
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +51 -43
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -6
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +50 -42
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +50 -42
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +50 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +50 -42
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +51 -43
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +51 -43
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +47 -42
- 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 +4 -4
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +47 -42
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -9
- package/dist/astro/routes/api/auth/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
- package/dist/astro/routes/api/auth/me.mjs +10 -9
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +4 -4
- 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 +9 -8
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/options.mjs +14 -13
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +15 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +14 -13
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -13
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +18 -17
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -17
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- 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 +44 -31
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/index.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +10 -8
- package/dist/astro/routes/api/dashboard.mjs.map +1 -1
- package/dist/astro/routes/api/dev/emails.mjs +4 -4
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +18 -16
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +15 -15
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +14 -12
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +13 -12
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +14 -14
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -16
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +28 -27
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +5 -5
- package/dist/astro/routes/api/mcp.d.mts.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +174 -72
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +13 -11
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
- package/dist/astro/routes/api/media/_id_/usage.mjs +17 -16
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +16 -15
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
- package/dist/astro/routes/api/media/providers/index.mjs +4 -4
- package/dist/astro/routes/api/media/upload-url.mjs +11 -10
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +18 -15
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/items.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/translations.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_.mjs +11 -11
- package/dist/astro/routes/api/menus/index.mjs +11 -11
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
- package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
- package/dist/astro/routes/api/oauth/register.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
- package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
- package/dist/astro/routes/api/oauth/token.mjs +7 -7
- package/dist/astro/routes/api/openapi.json.mjs +152 -6
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +8 -7
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +13 -12
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +18 -17
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +13 -12
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +18 -17
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +47 -42
- 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 +47 -42
- 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 +47 -42
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +47 -42
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +47 -42
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +47 -42
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +10 -9
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +47 -42
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +47 -42
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +13 -13
- package/dist/astro/routes/api/search/index.mjs +12 -12
- package/dist/astro/routes/api/search/rebuild.mjs +13 -13
- package/dist/astro/routes/api/search/stats.mjs +8 -8
- package/dist/astro/routes/api/search/suggest.mjs +12 -12
- package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +12 -11
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +14 -13
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +14 -13
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +13 -12
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +30 -31
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/index.mjs +31 -35
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +5 -5
- package/dist/astro/routes/api/snapshot.mjs +8 -8
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +18 -16
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts +8 -0
- package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +46 -0
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_.d.mts +19 -0
- package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_.mjs +88 -0
- package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -0
- package/dist/astro/routes/api/taxonomies/index.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +6 -6
- 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 +10 -9
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -6
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +4 -4
- package/dist/astro/routes/robots.txt.mjs +7 -6
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
- package/dist/astro/routes/sitemap.xml.mjs +8 -7
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +35 -17
- 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-qRhWQnel.mjs → authorize-DUElxgOQ.mjs} +2 -2
- package/dist/{authorize-qRhWQnel.mjs.map → authorize-DUElxgOQ.mjs.map} +1 -1
- package/dist/{backup-CtA6LDNw.mjs → backup-DWYpJlLj.mjs} +4 -4
- package/dist/{backup-CtA6LDNw.mjs.map → backup-DWYpJlLj.mjs.map} +1 -1
- package/dist/{base64-B-PsqheR.mjs → base64-CvLhUKQv.mjs} +1 -1
- package/dist/{base64-B-PsqheR.mjs.map → base64-CvLhUKQv.mjs.map} +1 -1
- package/dist/bundle-vn56sLos.mjs +663 -0
- package/dist/bundle-vn56sLos.mjs.map +1 -0
- package/dist/{byline-fields-CNc0Yq5i.mjs → byline-fields-txaLGoME.mjs} +2 -2
- package/dist/{byline-fields-CNc0Yq5i.mjs.map → byline-fields-txaLGoME.mjs.map} +1 -1
- package/dist/{byline-registry-ZkPKqEwC.mjs → byline-registry-DDun8sxh.mjs} +2 -2
- package/dist/{byline-registry-ZkPKqEwC.mjs.map → byline-registry-DDun8sxh.mjs.map} +1 -1
- package/dist/{byline-Cp6csL84.mjs → byline-yjvWq4ip.mjs} +5 -5
- package/dist/{byline-Cp6csL84.mjs.map → byline-yjvWq4ip.mjs.map} +1 -1
- package/dist/{bylines-cf-C9lmF.mjs → bylines-Bu7pchQ4.mjs} +5 -5
- package/dist/{bylines-cf-C9lmF.mjs.map → bylines-Bu7pchQ4.mjs.map} +1 -1
- package/dist/{bylines-CEj5CdT4.mjs → bylines-DjXS5p0P.mjs} +7 -7
- package/dist/{bylines-CEj5CdT4.mjs.map → bylines-DjXS5p0P.mjs.map} +1 -1
- package/dist/{cache-C9FptTFR.mjs → cache-C46-3Irl.mjs} +3 -3
- package/dist/{cache-C9FptTFR.mjs.map → cache-C46-3Irl.mjs.map} +1 -1
- package/dist/{challenge-store-D9VNKELo.mjs → challenge-store-SIL5DsF2.mjs} +1 -1
- package/dist/{challenge-store-D9VNKELo.mjs.map → challenge-store-SIL5DsF2.mjs.map} +1 -1
- package/dist/{chunks-DbYzib_k.mjs → chunks-fiZ8G9RJ.mjs} +2 -2
- package/dist/{chunks-DbYzib_k.mjs.map → chunks-fiZ8G9RJ.mjs.map} +1 -1
- package/dist/cli/index.mjs +655 -677
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +74 -4
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +25 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/collection-deletion-DQPvMD8R.mjs +779 -0
- package/dist/collection-deletion-DQPvMD8R.mjs.map +1 -0
- package/dist/{comment-BIkJ6Fil.mjs → comment-Dxlep6cE.mjs} +2 -2
- package/dist/{comment-BIkJ6Fil.mjs.map → comment-Dxlep6cE.mjs.map} +1 -1
- package/dist/{comment-reaction-D-laEceb.mjs → comment-reaction-D-2J84UE.mjs} +2 -2
- package/dist/{comment-reaction-D-laEceb.mjs.map → comment-reaction-D-2J84UE.mjs.map} +1 -1
- package/dist/{comments-0_w6KChA.mjs → comments-CTfZtLmM.mjs} +3 -3
- package/dist/{comments-0_w6KChA.mjs.map → comments-CTfZtLmM.mjs.map} +1 -1
- package/dist/{components-BPxrRjpE.mjs → components-CQy3lh9x.mjs} +1 -1
- package/dist/{components-BPxrRjpE.mjs.map → components-CQy3lh9x.mjs.map} +1 -1
- package/dist/{config-DyF3OKg_.mjs → config-C3EBstHi.mjs} +18 -2
- package/dist/config-C3EBstHi.mjs.map +1 -0
- package/dist/config-COQzTe5Q.d.mts +34 -0
- package/dist/config-COQzTe5Q.d.mts.map +1 -0
- package/dist/{content-CJ94NEx5.mjs → content-ZHCc1ngN.mjs} +509 -78
- package/dist/content-ZHCc1ngN.mjs.map +1 -0
- package/dist/content-list-query-wJ2WN8Vp.d.mts +26 -0
- package/dist/content-list-query-wJ2WN8Vp.d.mts.map +1 -0
- package/dist/content-refresh-B_8SJzuU.mjs +2560 -0
- package/dist/content-refresh-B_8SJzuU.mjs.map +1 -0
- package/dist/{context-C_1nrqnW.mjs → context-ZldbZcle.mjs} +25 -17
- package/dist/context-ZldbZcle.mjs.map +1 -0
- package/dist/{cron-DRV-UC4K.mjs → cron-Y9aLtgrA.mjs} +1 -1
- package/dist/{cron-DRV-UC4K.mjs.map → cron-Y9aLtgrA.mjs.map} +1 -1
- package/dist/{dashboard-DINnNVVU.mjs → dashboard-DvHbk_cv.mjs} +44 -10
- package/dist/dashboard-DvHbk_cv.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -4
- package/dist/db/index.mjs +31 -8
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql-migrations.d.mts +11 -0
- package/dist/db/libsql-migrations.d.mts.map +1 -0
- package/dist/db/libsql-migrations.mjs +57 -0
- package/dist/db/libsql-migrations.mjs.map +1 -0
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +10 -0
- package/dist/db/postgres-migrations.d.mts.map +1 -0
- package/dist/db/postgres-migrations.mjs +47 -0
- package/dist/db/postgres-migrations.mjs.map +1 -0
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite-migrations.d.mts +10 -0
- package/dist/db/sqlite-migrations.d.mts.map +1 -0
- package/dist/db/sqlite-migrations.mjs +28 -0
- package/dist/db/sqlite-migrations.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-CcWLaRiR.mjs → db-errors-Cl-sKYTm.mjs} +1 -1
- package/dist/{db-errors-CcWLaRiR.mjs.map → db-errors-Cl-sKYTm.mjs.map} +1 -1
- package/dist/{default-BD2FG0xV.mjs → default-DdDl3fxv.mjs} +1 -1
- package/dist/{default-BD2FG0xV.mjs.map → default-DdDl3fxv.mjs.map} +1 -1
- package/dist/{deferred-tasks-C-WRCcKo.d.mts → deferred-tasks-SDWxrgzJ.d.mts} +1 -1
- package/dist/{deferred-tasks-C-WRCcKo.d.mts.map → deferred-tasks-SDWxrgzJ.d.mts.map} +1 -1
- package/dist/{device-flow-CTqd8mud.mjs → device-flow-C0GdD_z8.mjs} +4 -4
- package/dist/{device-flow-CTqd8mud.mjs.map → device-flow-C0GdD_z8.mjs.map} +1 -1
- package/dist/direct-executor-CDXeJCSs.mjs +85 -0
- package/dist/direct-executor-CDXeJCSs.mjs.map +1 -0
- package/dist/dist-D_6CKR8z.mjs +810 -0
- package/dist/dist-D_6CKR8z.mjs.map +1 -0
- package/dist/{email-console-C6WdcWbL.mjs → email-console-BHNjIwiB.mjs} +1 -1
- package/dist/{email-console-C6WdcWbL.mjs.map → email-console-BHNjIwiB.mjs.map} +1 -1
- package/dist/{enrich-CFJJgxs_.mjs → enrich-_ySMEvEE.mjs} +2 -2
- package/dist/{enrich-CFJJgxs_.mjs.map → enrich-_ySMEvEE.mjs.map} +1 -1
- package/dist/{error-oh_9i6bR.mjs → error-DupyJZE2.mjs} +3 -3
- package/dist/{error-oh_9i6bR.mjs.map → error-DupyJZE2.mjs.map} +1 -1
- package/dist/{errors-BucQ8VYz.mjs → errors-CLUPm7aD.mjs} +14 -2
- package/dist/errors-CLUPm7aD.mjs.map +1 -0
- package/dist/{escape-CXps5F_v.mjs → escape-DUY2r2Zs.mjs} +1 -1
- package/dist/{escape-CXps5F_v.mjs.map → escape-DUY2r2Zs.mjs.map} +1 -1
- package/dist/{field-defs-cache-WD82dMQq.mjs → field-defs-cache-BzwBlJrR.mjs} +4 -4
- package/dist/{field-defs-cache-WD82dMQq.mjs.map → field-defs-cache-BzwBlJrR.mjs.map} +1 -1
- package/dist/{fts-manager-BITakW_R.mjs → fts-manager-DyZEohZC.mjs} +76 -50
- package/dist/fts-manager-DyZEohZC.mjs.map +1 -0
- package/dist/{hreflang-BdZuVBhL.mjs → hreflang-BBe83kKa.mjs} +7 -7
- package/dist/{hreflang-BdZuVBhL.mjs.map → hreflang-BBe83kKa.mjs.map} +1 -1
- package/dist/{hreflang-BaLCNcDx.d.mts → hreflang-DwT2E0Sa.d.mts} +2 -2
- package/dist/{hreflang-BaLCNcDx.d.mts.map → hreflang-DwT2E0Sa.d.mts.map} +1 -1
- package/dist/identity-Ys6Dlc9C.mjs +35 -0
- package/dist/identity-Ys6Dlc9C.mjs.map +1 -0
- package/dist/{import-C-9uqyT3.mjs → import-DPkLmeXO.mjs} +5 -5
- package/dist/{import-C-9uqyT3.mjs.map → import-DPkLmeXO.mjs.map} +1 -1
- package/dist/{index-M37iz29I.d.mts → index-BVBZj4-7.d.mts} +236 -104
- package/dist/index-BVBZj4-7.d.mts.map +1 -0
- package/dist/{index-MGw47uWq.d.mts → index-C9KuQyO-.d.mts} +5 -10
- package/dist/index-C9KuQyO-.d.mts.map +1 -0
- package/dist/index.d.mts +21 -19
- package/dist/index.mjs +73 -67
- package/dist/{init-lock-DJkX6Hto.mjs → init-lock-6b309ZrF.mjs} +1 -1
- package/dist/{init-lock-DJkX6Hto.mjs.map → init-lock-6b309ZrF.mjs.map} +1 -1
- package/dist/{load-DV1DZuUJ.mjs → load-YJ2xl3ra.mjs} +2 -2
- package/dist/{load-DV1DZuUJ.mjs.map → load-YJ2xl3ra.mjs.map} +1 -1
- package/dist/{loader-CGugY7Zl.mjs → loader-CdG_Ci3H.mjs} +95 -53
- package/dist/loader-CdG_Ci3H.mjs.map +1 -0
- package/dist/manifest-builder-B252yQUf.mjs +189 -0
- package/dist/manifest-builder-B252yQUf.mjs.map +1 -0
- package/dist/{manifest-schema-Da7kj23y.mjs → manifest-schema-BXbUAHod.mjs} +1 -1
- package/dist/{manifest-schema-Da7kj23y.mjs.map → manifest-schema-BXbUAHod.mjs.map} +1 -1
- package/dist/media/image-endpoint.mjs +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +3 -2
- package/dist/media/index.mjs.map +1 -1
- package/dist/media/local-runtime.d.mts +12 -12
- package/dist/media/local-runtime.mjs +6 -5
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-OhYL-7Io.mjs → media-BWWWb8kg.mjs} +92 -3
- package/dist/media-BWWWb8kg.mjs.map +1 -0
- package/dist/{media-BP7S-nHT.mjs → media-CzhAn2--.mjs} +2 -2
- package/dist/{media-BP7S-nHT.mjs.map → media-CzhAn2--.mjs.map} +1 -1
- package/dist/{media-allowlist-D-peH1H5.mjs → media-allowlist-D6x-Us02.mjs} +14 -3
- package/dist/media-allowlist-D6x-Us02.mjs.map +1 -0
- package/dist/{media-upload-DtrwU2B0.mjs → media-upload-CHBVn6m-.mjs} +10 -8
- package/dist/{media-upload-DtrwU2B0.mjs.map → media-upload-CHBVn6m-.mjs.map} +1 -1
- package/dist/{media-url-DYs_YLOx.mjs → media-url-BvoX88FA.mjs} +1 -1
- package/dist/{media-url-DYs_YLOx.mjs.map → media-url-BvoX88FA.mjs.map} +1 -1
- package/dist/{media-usage-BlxfTkN0.mjs → media-usage-BZCydfvu.mjs} +137 -60
- package/dist/media-usage-BZCydfvu.mjs.map +1 -0
- package/dist/media-usage-work-CLo_6tct.mjs +152 -0
- package/dist/media-usage-work-CLo_6tct.mjs.map +1 -0
- package/dist/media-usage-work-DrCbqBcl.mjs +424 -0
- package/dist/media-usage-work-DrCbqBcl.mjs.map +1 -0
- package/dist/media-usage-write-fence-aHEgN1zB.mjs +42 -0
- package/dist/media-usage-write-fence-aHEgN1zB.mjs.map +1 -0
- package/dist/{menus-Y54RDPIN.mjs → menus-DSo5QeW0.mjs} +351 -95
- package/dist/menus-DSo5QeW0.mjs.map +1 -0
- package/dist/{menus-BcQWxeXu.mjs → menus-TCNSd2vp.mjs} +3 -3
- package/dist/{menus-BcQWxeXu.mjs.map → menus-TCNSd2vp.mjs.map} +1 -1
- package/dist/migrations/index.d.mts +22 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +7 -0
- package/dist/{mode-BWyTBe3I.mjs → mode-BDoH_WcF.mjs} +1 -1
- package/dist/{mode-BWyTBe3I.mjs.map → mode-BDoH_WcF.mjs.map} +1 -1
- package/dist/{normalize-C-SHXmra.mjs → normalize-Bc-s_r-a.mjs} +1 -1
- package/dist/{normalize-C-SHXmra.mjs.map → normalize-Bc-s_r-a.mjs.map} +1 -1
- package/dist/{oauth-authorization-ClvktmrL.mjs → oauth-authorization-iOikmrJd.mjs} +4 -4
- package/dist/{oauth-authorization-ClvktmrL.mjs.map → oauth-authorization-iOikmrJd.mjs.map} +1 -1
- package/dist/{oauth-clients-8ao-U66l.mjs → oauth-clients-CvI0nHMJ.mjs} +1 -1
- package/dist/{oauth-clients-8ao-U66l.mjs.map → oauth-clients-CvI0nHMJ.mjs.map} +1 -1
- package/dist/{oauth-state-store-B1ofdTiQ.mjs → oauth-state-store-Ba8KZlwW.mjs} +1 -1
- package/dist/{oauth-state-store-B1ofdTiQ.mjs.map → oauth-state-store-Ba8KZlwW.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-CscEuIiR.mjs → oauth-user-lookup-C5SQwGYC.mjs} +1 -1
- package/dist/{oauth-user-lookup-CscEuIiR.mjs.map → oauth-user-lookup-C5SQwGYC.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{options-CA4eVX6P.d.mts → options--hhz-jZ1.d.mts} +4 -3
- package/dist/options--hhz-jZ1.d.mts.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-CJKNuewM.mjs → parse-DFbuc-Fh.mjs} +2 -2
- package/dist/{parse-CJKNuewM.mjs.map → parse-DFbuc-Fh.mjs.map} +1 -1
- package/dist/{passkey-config-D-zrzMJI.mjs → passkey-config-DFAmUyuE.mjs} +1 -1
- package/dist/{passkey-config-D-zrzMJI.mjs.map → passkey-config-DFAmUyuE.mjs.map} +1 -1
- package/dist/{placeholder-DaeNS-VG.d.mts → placeholder-Dp0K3RkD.d.mts} +1 -1
- package/dist/{placeholder-DaeNS-VG.d.mts.map → placeholder-Dp0K3RkD.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +8 -1
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-utils.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.mjs +7 -4
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/policy-TluUIxuu.mjs +45 -0
- package/dist/policy-TluUIxuu.mjs.map +1 -0
- package/dist/{portable-text-B_kqBmXB.mjs → portable-text-imBW5cHK.mjs} +1 -1
- package/dist/{portable-text-B_kqBmXB.mjs.map → portable-text-imBW5cHK.mjs.map} +1 -1
- package/dist/{preview-BU6PaLpM.mjs → preview-PW-nOnq4.mjs} +2 -2
- package/dist/{preview-BU6PaLpM.mjs.map → preview-PW-nOnq4.mjs.map} +1 -1
- package/dist/protocol-DjsJHTlK.d.mts +52 -0
- package/dist/protocol-DjsJHTlK.d.mts.map +1 -0
- package/dist/{public-url-Ox9oOlgp.mjs → public-url-CWoFhQT8.mjs} +1 -1
- package/dist/{public-url-Ox9oOlgp.mjs.map → public-url-CWoFhQT8.mjs.map} +1 -1
- package/dist/{query-rC9HKv1N.mjs → query-BWs7O5Il.mjs} +3 -3
- package/dist/{query-rC9HKv1N.mjs.map → query-BWs7O5Il.mjs.map} +1 -1
- package/dist/{query-C5w7PsRQ.mjs → query-zTVAR1Sl.mjs} +31 -49
- package/dist/query-zTVAR1Sl.mjs.map +1 -0
- package/dist/{rate-limit-kug6q99V.mjs → rate-limit-Bx4g9m3p.mjs} +2 -2
- package/dist/{rate-limit-kug6q99V.mjs.map → rate-limit-Bx4g9m3p.mjs.map} +1 -1
- package/dist/{redirect-Egf2R9yP.mjs → redirect-BgAOGHDq.mjs} +1 -1
- package/dist/{redirect-Egf2R9yP.mjs.map → redirect-BgAOGHDq.mjs.map} +1 -1
- package/dist/{redirect-DIJxXKOG.mjs → redirect-COsS6SWJ.mjs} +2 -2
- package/dist/{redirect-DIJxXKOG.mjs.map → redirect-COsS6SWJ.mjs.map} +1 -1
- package/dist/{redirects-G8wAhWAf.mjs → redirects-DTDHWH31.mjs} +133 -14
- package/dist/redirects-DTDHWH31.mjs.map +1 -0
- package/dist/{redirects-Bzm812bY.mjs → redirects-DyChwDkG.mjs} +4 -4
- package/dist/{redirects-Bzm812bY.mjs.map → redirects-DyChwDkG.mjs.map} +1 -1
- package/dist/registry-Cqa2I749.mjs +1275 -0
- package/dist/registry-Cqa2I749.mjs.map +1 -0
- package/dist/{relation-Cbe3Sd3m.mjs → relation-BnHB0T4J.mjs} +3 -3
- package/dist/{relation-Cbe3Sd3m.mjs.map → relation-BnHB0T4J.mjs.map} +1 -1
- package/dist/{relations-Bw_E1TXo.mjs → relations-BBApI4e2.mjs} +3 -3
- package/dist/{relations-Bw_E1TXo.mjs.map → relations-BBApI4e2.mjs.map} +1 -1
- package/dist/{relations-G2ta7GIl.d.mts → relations-BHo5A8Zf.d.mts} +367 -33
- package/dist/relations-BHo5A8Zf.d.mts.map +1 -0
- package/dist/{relations-qHwpUe66.mjs → relations-D5sM5fW0.mjs} +6 -6
- package/dist/{relations-qHwpUe66.mjs.map → relations-D5sM5fW0.mjs.map} +1 -1
- package/dist/request-context.d.mts +1 -1
- package/dist/{request-meta-Df-jFbAd.mjs → request-meta-DgZJh3zG.mjs} +2 -2
- package/dist/{request-meta-Df-jFbAd.mjs.map → request-meta-DgZJh3zG.mjs.map} +1 -1
- package/dist/{resolve-DZzG4HqM.mjs → resolve-DOhVEvty.mjs} +2 -2
- package/dist/{resolve-DZzG4HqM.mjs.map → resolve-DOhVEvty.mjs.map} +1 -1
- package/dist/{runner-BaADli8l.d.mts → runner-BYHZn3Jy.d.mts} +16 -3
- package/dist/runner-BYHZn3Jy.d.mts.map +1 -0
- package/dist/{runner-BFTrkYhu.mjs → runner-C1T11CfC.mjs} +1190 -257
- package/dist/runner-C1T11CfC.mjs.map +1 -0
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +4 -3
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-2AOsSn2v.mjs +390 -0
- package/dist/schema-2AOsSn2v.mjs.map +1 -0
- package/dist/{schema-CiQXr6_F.mjs → schema-DZ5YVVZj.mjs} +3 -3
- package/dist/{schema-CiQXr6_F.mjs.map → schema-DZ5YVVZj.mjs.map} +1 -1
- package/dist/{scopes-CNJVT5i5.mjs → scopes-8ClPF2bC.mjs} +3 -3
- package/dist/{scopes-CNJVT5i5.mjs.map → scopes-8ClPF2bC.mjs.map} +1 -1
- package/dist/{search-FZhx5dDU.mjs → search-u1QYdOCG.mjs} +25 -15
- package/dist/search-u1QYdOCG.mjs.map +1 -0
- package/dist/{secrets-DLd8RXee.mjs → secrets-BEgsAQEU.mjs} +3 -3
- package/dist/{secrets-DLd8RXee.mjs.map → secrets-BEgsAQEU.mjs.map} +1 -1
- package/dist/{sections-BrIrMUCn.mjs → sections-DmHviGyM.mjs} +3 -3
- package/dist/{sections-BrIrMUCn.mjs.map → sections-DmHviGyM.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +24 -22
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +16 -15
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-CobNfOvb.mjs → seo-AFpVvTqr.mjs} +5 -3
- package/dist/seo-AFpVvTqr.mjs.map +1 -0
- package/dist/{seo-DTHRntNC.mjs → seo-Dq4G-RrO.mjs} +2 -2
- package/dist/{seo-DTHRntNC.mjs.map → seo-Dq4G-RrO.mjs.map} +1 -1
- package/dist/{service-B-pYGPgR.mjs → service-BpArayPr.mjs} +3 -3
- package/dist/{service-B-pYGPgR.mjs.map → service-BpArayPr.mjs.map} +1 -1
- package/dist/{session-user-BvM9-zNl.mjs → session-user-BEorKVT-.mjs} +1 -1
- package/dist/{session-user-BvM9-zNl.mjs.map → session-user-BEorKVT-.mjs.map} +1 -1
- package/dist/{settings-DPHau-WH.mjs → settings-DE9xtzqd.mjs} +3 -3
- package/dist/{settings-DPHau-WH.mjs.map → settings-DE9xtzqd.mjs.map} +1 -1
- package/dist/{settings-kJhGiQ4z.mjs → settings-xRdoUZr0.mjs} +4 -4
- package/dist/{settings-kJhGiQ4z.mjs.map → settings-xRdoUZr0.mjs.map} +1 -1
- package/dist/{setup-complete-u50Qm8rY.mjs → setup-complete-BnT8pq_C.mjs} +1 -1
- package/dist/{setup-complete-u50Qm8rY.mjs.map → setup-complete-BnT8pq_C.mjs.map} +1 -1
- package/dist/{setup-nonce-DCjiiOD2.mjs → setup-nonce-CBitf_oQ.mjs} +1 -1
- package/dist/{setup-nonce-DCjiiOD2.mjs.map → setup-nonce-CBitf_oQ.mjs.map} +1 -1
- package/dist/{single-flight-cache-DW3w11Pu.mjs → single-flight-cache-B0eLKS6K.mjs} +2 -2
- package/dist/{single-flight-cache-DW3w11Pu.mjs.map → single-flight-cache-B0eLKS6K.mjs.map} +1 -1
- package/dist/{site-url-I-lJ5kma.mjs → site-url-DjpIXMXJ.mjs} +2 -2
- package/dist/{site-url-I-lJ5kma.mjs.map → site-url-DjpIXMXJ.mjs.map} +1 -1
- package/dist/slugify-B994zkXC.mjs +17 -0
- package/dist/slugify-B994zkXC.mjs.map +1 -0
- package/dist/{snapshot-I_fjLDaD.mjs → snapshot-BBlGkb2q.mjs} +1 -1
- package/dist/{snapshot-I_fjLDaD.mjs.map → snapshot-BBlGkb2q.mjs.map} +1 -1
- package/dist/{ssrf-CTa6v21i.mjs → ssrf-Cpvg_n3A.mjs} +1 -1
- package/dist/{ssrf-CTa6v21i.mjs.map → ssrf-Cpvg_n3A.mjs.map} +1 -1
- package/dist/{state-CQvoefYP.mjs → state-DYwPue40.mjs} +1 -1
- package/dist/{state-CQvoefYP.mjs.map → state-DYwPue40.mjs.map} +1 -1
- package/dist/{status-CIsmlC1E.mjs → status-CWVYpbD8.mjs} +1 -1
- package/dist/{status-CIsmlC1E.mjs.map → status-CWVYpbD8.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +3 -3
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/target-DixqqV5i.mjs +27 -0
- package/dist/target-DixqqV5i.mjs.map +1 -0
- package/dist/{taxonomies-CLN1N92E.mjs → taxonomies-BlrncHi9.mjs} +75 -64
- package/dist/taxonomies-BlrncHi9.mjs.map +1 -0
- package/dist/{taxonomies-CKuaZER5.mjs → taxonomies-OHUIiIpI.mjs} +280 -56
- package/dist/taxonomies-OHUIiIpI.mjs.map +1 -0
- package/dist/{taxonomy-pOrB2kZe.mjs → taxonomy-BqCZ1TdU.mjs} +131 -78
- package/dist/taxonomy-BqCZ1TdU.mjs.map +1 -0
- package/dist/{tokens-DagaJGK8.mjs → tokens-C39klX8R.mjs} +2 -2
- package/dist/{tokens-DagaJGK8.mjs.map → tokens-C39klX8R.mjs.map} +1 -1
- package/dist/{transport-DVsa5YYo.mjs → transport-DBJwa-Si.mjs} +1 -1
- package/dist/{transport-DVsa5YYo.mjs.map → transport-DBJwa-Si.mjs.map} +1 -1
- package/dist/{transport-CAwTaTp_.d.mts → transport-RQE36APG.d.mts} +1 -1
- package/dist/{transport-CAwTaTp_.d.mts.map → transport-RQE36APG.d.mts.map} +1 -1
- package/dist/{trusted-proxy-D20_7fyX.mjs → trusted-proxy-CEVekpby.mjs} +1 -1
- package/dist/{trusted-proxy-D20_7fyX.mjs.map → trusted-proxy-CEVekpby.mjs.map} +1 -1
- package/dist/{types-NBI3rF-_.mjs → types-75KIqzL9.mjs} +18 -2
- package/dist/types-75KIqzL9.mjs.map +1 -0
- package/dist/{types-BjxTKxrZ.mjs → types-BW1syrlw.mjs} +1 -1
- package/dist/{types-BjxTKxrZ.mjs.map → types-BW1syrlw.mjs.map} +1 -1
- package/dist/{types-DLG4IFEQ.d.mts → types-BcqdcT-r.d.mts} +1 -1
- package/dist/{types-DLG4IFEQ.d.mts.map → types-BcqdcT-r.d.mts.map} +1 -1
- package/dist/{types-LoagAGzH.d.mts → types-BntycPTF.d.mts} +1 -1
- package/dist/{types-LoagAGzH.d.mts.map → types-BntycPTF.d.mts.map} +1 -1
- package/dist/{types-BnFP1M3J.d.mts → types-C2pMvO62.d.mts} +32 -10
- package/dist/types-C2pMvO62.d.mts.map +1 -0
- package/dist/{types-Cd8iuS1J.mjs → types-CQPWhPxE.mjs} +9 -12
- package/dist/types-CQPWhPxE.mjs.map +1 -0
- package/dist/{types-C8oz_ayJ.d.mts → types-CXnOzDe1.d.mts} +1 -1
- package/dist/{types-C8oz_ayJ.d.mts.map → types-CXnOzDe1.d.mts.map} +1 -1
- package/dist/types-CtzytVXn.mjs.map +1 -1
- package/dist/{types-BGWPMiBO.d.mts → types-DN64_zzZ.d.mts} +44 -5
- package/dist/types-DN64_zzZ.d.mts.map +1 -0
- package/dist/{types-Dc4ntQjQ.d.mts → types-DQIDRKF2.d.mts} +124 -1
- package/dist/types-DQIDRKF2.d.mts.map +1 -0
- package/dist/{types-CbUcuxxc.d.mts → types-DWRhk9xZ.d.mts} +1 -1
- package/dist/{types-CbUcuxxc.d.mts.map → types-DWRhk9xZ.d.mts.map} +1 -1
- package/dist/{types-BK6U01Ro.d.mts → types-Dfk8KI5R.d.mts} +1 -1
- package/dist/{types-BK6U01Ro.d.mts.map → types-Dfk8KI5R.d.mts.map} +1 -1
- package/dist/{types-Cz8hNch2.d.mts → types-DrJrVCT1.d.mts} +25 -5
- package/dist/types-DrJrVCT1.d.mts.map +1 -0
- package/dist/{upload-attempts-D8QAFLUJ.mjs → upload-attempts-D4FS8LT6.mjs} +1 -1
- package/dist/{upload-attempts-D8QAFLUJ.mjs.map → upload-attempts-D4FS8LT6.mjs.map} +1 -1
- package/dist/url-pattern-BHS0V5Ng.mjs +33 -0
- package/dist/url-pattern-BHS0V5Ng.mjs.map +1 -0
- package/dist/{user-DBrq4UVq.mjs → user-DJZfUJVE.mjs} +3 -3
- package/dist/{user-DBrq4UVq.mjs.map → user-DJZfUJVE.mjs.map} +1 -1
- package/dist/{utils-hLzi_2Fn.mjs → utils-D4vD_Y98.mjs} +3 -3
- package/dist/{utils-hLzi_2Fn.mjs.map → utils-D4vD_Y98.mjs.map} +1 -1
- package/dist/{validate-mKo3RxBM.mjs → validate-BMLBvxOC.mjs} +24 -5
- package/dist/validate-BMLBvxOC.mjs.map +1 -0
- package/dist/{validate-C3oSmA7F.d.mts → validate-CTqt5nuE.d.mts} +15 -7
- package/dist/validate-CTqt5nuE.d.mts.map +1 -0
- package/dist/{validation-BgpucsvF.mjs → validation-npvENUCB.mjs} +11 -10
- package/dist/{validation-BgpucsvF.mjs.map → validation-npvENUCB.mjs.map} +1 -1
- package/dist/version-DB_fqCSI.mjs +7 -0
- package/dist/{version-C4PnpmoV.mjs.map → version-DB_fqCSI.mjs.map} +1 -1
- package/dist/{widgets-CJJtkiKh.mjs → widgets-BD3ho7Rq.mjs} +3 -3
- package/dist/{widgets-CJJtkiKh.mjs.map → widgets-BD3ho7Rq.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-9PPSy8X6.mjs → wxr-taxonomies-B1R3vBoD.mjs} +13 -32
- package/dist/{wxr-taxonomies-9PPSy8X6.mjs.map → wxr-taxonomies-B1R3vBoD.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts → wxr-taxonomies-Ca68766D.d.mts} +1 -1
- package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts.map → wxr-taxonomies-Ca68766D.d.mts.map} +1 -1
- package/dist/{zod-generator-CG64Hnbh.mjs → zod-generator-LC-on_Vb.mjs} +52 -5
- package/dist/zod-generator-LC-on_Vb.mjs.map +1 -0
- package/package.json +32 -8
- package/src/api/errors.ts +12 -0
- package/src/api/handlers/content.ts +127 -29
- package/src/api/handlers/dashboard.ts +12 -2
- package/src/api/handlers/index.ts +16 -0
- package/src/api/handlers/manifest.ts +2 -0
- package/src/api/handlers/media-allowlist.ts +13 -2
- package/src/api/handlers/media-usage-work.ts +188 -0
- package/src/api/handlers/media-usage.ts +5 -3
- package/src/api/handlers/schema.ts +9 -3
- package/src/api/handlers/seo.ts +5 -2
- package/src/api/handlers/taxonomies.ts +345 -82
- package/src/api/media-usage-write-fence.ts +64 -0
- package/src/api/openapi/document.ts +199 -0
- package/src/api/schemas/common.ts +3 -1
- package/src/api/schemas/content.ts +122 -1
- package/src/api/schemas/media-usage.ts +133 -0
- package/src/api/schemas/schema.ts +78 -3
- package/src/api/schemas/taxonomies.ts +40 -8
- package/src/api/types.ts +1 -0
- package/src/astro/integration/index.ts +101 -56
- package/src/astro/integration/routes.ts +34 -4
- package/src/astro/integration/runtime.ts +18 -0
- package/src/astro/integration/virtual-modules.ts +25 -1
- package/src/astro/integration/vite-config.ts +16 -1
- package/src/astro/middleware/media-usage-write-fence.ts +30 -0
- package/src/astro/middleware/redirect.ts +12 -2
- package/src/astro/middleware/scoped-db.ts +21 -0
- package/src/astro/middleware.ts +140 -16
- package/src/astro/routes/api/admin/media-usage/collection-deletions/index.ts +22 -0
- package/src/astro/routes/api/admin/media-usage/collection-deletions/retry.ts +22 -0
- package/src/astro/routes/api/admin/media-usage/work/index.ts +26 -0
- package/src/astro/routes/api/admin/media-usage/work/retry.ts +26 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +4 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +4 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +4 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +4 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +0 -4
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +67 -31
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -10
- package/src/astro/routes/api/mcp.ts +1 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +7 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +1 -7
- package/src/astro/routes/api/setup/dev-bypass.ts +2 -7
- package/src/astro/routes/api/setup/index.ts +5 -13
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +1 -0
- package/src/astro/routes/api/taxonomies/[name]/translations.ts +48 -0
- package/src/astro/routes/api/taxonomies/[name].ts +102 -0
- package/src/astro/types.ts +25 -4
- package/src/cleanup.ts +6 -12
- package/src/cli/commands/doctor.ts +193 -6
- package/src/cli/commands/export-seed.ts +4 -1
- package/src/cli/commands/migrate.ts +593 -0
- package/src/cli/index.ts +2 -23
- package/src/client/index.ts +119 -0
- package/src/components/Block.astro +12 -0
- package/src/components/InlinePortableTextEditor.tsx +89 -1
- package/src/content/converters/mark-safety.ts +120 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +25 -16
- package/src/content/converters/prosemirror-to-portable-text.ts +12 -2
- package/src/content-list-query.ts +29 -0
- package/src/database/index.ts +8 -2
- package/src/database/migrations/060_collection_admin_config.ts +16 -0
- package/src/database/migrations/061_media_usage_cleanup.ts +77 -0
- package/src/database/migrations/062_media_usage_cleanup_fence.ts +295 -0
- package/src/database/migrations/063_media_usage_incremental_work.ts +397 -0
- package/src/database/migrations/064_fts_plain_text.ts +253 -0
- package/src/database/migrations/065_media_usage_collection_deletion.ts +85 -0
- package/src/database/migrations/066_media_usage_reconciliation.ts +115 -0
- package/src/database/migrations/067_indexed_content_fields.ts +39 -0
- package/src/database/migrations/068_content_taxonomy_entry_groups.ts +61 -0
- package/src/database/migrations/069_collection_title_date_fields.ts +45 -0
- package/src/database/migrations/070_collection_routable.ts +18 -0
- package/src/database/migrations/policy.ts +70 -0
- package/src/database/migrations/runner.ts +71 -3
- package/src/database/repositories/content.ts +861 -96
- package/src/database/repositories/media-usage-work.ts +848 -0
- package/src/database/repositories/media-usage.ts +1502 -138
- package/src/database/repositories/revision.ts +17 -6
- package/src/database/repositories/taxonomy.ts +234 -103
- package/src/database/repositories/types.ts +51 -2
- package/src/database/types.ts +134 -16
- package/src/db/adapters.ts +78 -2
- package/src/db/index.ts +6 -1
- package/src/db/libsql-migrations.ts +72 -0
- package/src/db/postgres-migrations.ts +59 -0
- package/src/db/sqlite-migrations.ts +37 -0
- package/src/emdash-runtime.ts +271 -35
- package/src/fields/file.ts +7 -4
- package/src/fields/image.ts +8 -1
- package/src/fields/types.ts +18 -5
- package/src/i18n/config.ts +32 -0
- package/src/i18n/normalize.ts +31 -0
- package/src/i18n/repair-locale-casing.ts +0 -14
- package/src/i18n/taxonomy-locale-diagnostic.ts +47 -0
- package/src/import/wxr-taxonomies.ts +7 -47
- package/src/index.ts +32 -3
- package/src/loader.ts +119 -65
- package/src/mcp/server.ts +242 -34
- package/src/media/usage/activation.ts +626 -0
- package/src/media/usage/capture-triggers.ts +543 -0
- package/src/media/usage/cleanup.ts +305 -0
- package/src/media/usage/collection-deletion-processor.ts +356 -0
- package/src/media/usage/collection-deletion.ts +836 -0
- package/src/media/usage/content-fields.ts +40 -3
- package/src/media/usage/content-refresh.ts +356 -47
- package/src/media/usage/content-repair.ts +180 -61
- package/src/media/usage/content-snapshots.ts +85 -105
- package/src/media/usage/projection-fingerprint.ts +129 -0
- package/src/media/usage/reconciliation-processor.ts +372 -0
- package/src/media/usage/reconciliation.ts +1117 -0
- package/src/media/usage/source-key.ts +4 -0
- package/src/media/usage/types.ts +2 -0
- package/src/media/usage/work-processor.ts +248 -0
- package/src/menus/index.ts +246 -141
- package/src/migrations/config-loader.ts +205 -0
- package/src/migrations/direct-executor.ts +141 -0
- package/src/migrations/identity.ts +46 -0
- package/src/migrations/index.ts +19 -0
- package/src/migrations/integration-metadata.ts +67 -0
- package/src/migrations/manifest-builder.ts +55 -0
- package/src/migrations/manifest-writer.ts +63 -0
- package/src/migrations/manifest.ts +197 -0
- package/src/migrations/protocol.ts +59 -0
- package/src/migrations/target.ts +35 -0
- package/src/plugin-types.ts +8 -0
- package/src/plugins/adapt-sandbox-entry.ts +24 -5
- package/src/plugins/context.ts +24 -7
- package/src/plugins/index.ts +9 -0
- package/src/plugins/routes.ts +43 -1
- package/src/plugins/sandbox/index.ts +10 -1
- package/src/plugins/sandbox/types.ts +78 -1
- package/src/plugins/scheduler/node.ts +13 -1
- package/src/plugins/scheduler/types.ts +2 -0
- package/src/plugins/types.ts +39 -2
- package/src/query.ts +29 -37
- package/src/scheduler-health.ts +46 -0
- package/src/schema/registry.ts +777 -255
- package/src/schema/types.ts +50 -8
- package/src/schema/url-pattern.ts +33 -0
- package/src/schema/zod-generator.ts +77 -5
- package/src/search/fts-manager.ts +117 -76
- package/src/search/query.ts +32 -11
- package/src/search/types.ts +2 -0
- package/src/seed/apply.ts +68 -15
- package/src/seed/types.ts +11 -3
- package/src/seed/validate.ts +52 -2
- package/src/taxonomies/index.ts +77 -75
- package/src/taxonomies/term-counts.ts +21 -8
- package/src/utils/slugify.ts +1 -29
- package/src/virtual-modules.d.ts +24 -0
- package/dist/adapters-BMyNSSZi.d.mts.map +0 -1
- package/dist/api-D2hunekQ.mjs.map +0 -1
- package/dist/apply-kwNEJW2l.mjs.map +0 -1
- package/dist/config-DyF3OKg_.mjs.map +0 -1
- package/dist/content-CJ94NEx5.mjs.map +0 -1
- package/dist/content-refresh-Xw7aKmou.mjs +0 -1629
- package/dist/content-refresh-Xw7aKmou.mjs.map +0 -1
- package/dist/context-C_1nrqnW.mjs.map +0 -1
- package/dist/dashboard-DINnNVVU.mjs.map +0 -1
- package/dist/errors-BucQ8VYz.mjs.map +0 -1
- package/dist/fts-manager-BITakW_R.mjs.map +0 -1
- package/dist/index-M37iz29I.d.mts.map +0 -1
- package/dist/index-MGw47uWq.d.mts.map +0 -1
- package/dist/loader-CGugY7Zl.mjs.map +0 -1
- package/dist/media-OhYL-7Io.mjs.map +0 -1
- package/dist/media-allowlist-D-peH1H5.mjs.map +0 -1
- package/dist/media-usage-BlxfTkN0.mjs.map +0 -1
- package/dist/menus-Y54RDPIN.mjs.map +0 -1
- package/dist/options-CA4eVX6P.d.mts.map +0 -1
- package/dist/query-C5w7PsRQ.mjs.map +0 -1
- package/dist/redirects-G8wAhWAf.mjs.map +0 -1
- package/dist/registry-Dbodb4Uo.mjs +0 -840
- package/dist/registry-Dbodb4Uo.mjs.map +0 -1
- package/dist/relations-G2ta7GIl.d.mts.map +0 -1
- package/dist/runner-BFTrkYhu.mjs.map +0 -1
- package/dist/runner-BaADli8l.d.mts.map +0 -1
- package/dist/search-FZhx5dDU.mjs.map +0 -1
- package/dist/seo-CobNfOvb.mjs.map +0 -1
- package/dist/slugify-C_tqlU4G.mjs +0 -30
- package/dist/slugify-C_tqlU4G.mjs.map +0 -1
- package/dist/taxonomies-CKuaZER5.mjs.map +0 -1
- package/dist/taxonomies-CLN1N92E.mjs.map +0 -1
- package/dist/taxonomy-pOrB2kZe.mjs.map +0 -1
- package/dist/types-BGWPMiBO.d.mts.map +0 -1
- package/dist/types-BnFP1M3J.d.mts.map +0 -1
- package/dist/types-Cd8iuS1J.mjs.map +0 -1
- package/dist/types-Cz8hNch2.d.mts.map +0 -1
- package/dist/types-Dc4ntQjQ.d.mts.map +0 -1
- package/dist/types-NBI3rF-_.mjs.map +0 -1
- package/dist/validate-C3oSmA7F.d.mts.map +0 -1
- package/dist/validate-mKo3RxBM.mjs.map +0 -1
- package/dist/version-C4PnpmoV.mjs +0 -7
- package/dist/zod-generator-CG64Hnbh.mjs.map +0 -1
- /package/dist/{api-tokens-Dai9dND0.mjs → api-tokens-rIXC6LdE.mjs} +0 -0
- /package/dist/{ssrf-Cj568Dgs.mjs → ssrf-DpXgKsna.mjs} +0 -0
- /package/dist/{types-DKuWPYV5.mjs → types-CP1CflWf.mjs} +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { sql, type Kysely } from "kysely";
|
|
2
2
|
import { ulid } from "ulidx";
|
|
3
3
|
|
|
4
|
+
import type { ContentFieldFilterValue, ContentFieldFilters } from "../../content-list-query.js";
|
|
4
5
|
import { invalidateCollectionCache } from "../../object-cache/index.js";
|
|
6
|
+
import { isIndexableFieldType, type FieldType } from "../../schema/types.js";
|
|
5
7
|
import { buildFtsPrefixMatch, buildSlugGlobPrefix } from "../../search/match.js";
|
|
6
8
|
import { chunks, SQL_BATCH_SIZE } from "../../utils/chunks.js";
|
|
7
9
|
import { isMissingTableError } from "../../utils/db-errors.js";
|
|
@@ -16,10 +18,13 @@ import type {
|
|
|
16
18
|
FindManyResult,
|
|
17
19
|
ContentItem,
|
|
18
20
|
ContentDateField,
|
|
21
|
+
ContentBylineFilter,
|
|
19
22
|
} from "./types.js";
|
|
20
23
|
import {
|
|
24
|
+
ContentCollectionNotFoundError,
|
|
21
25
|
ContentMutationConflictError,
|
|
22
26
|
EmDashValidationError,
|
|
27
|
+
InvalidCursorError,
|
|
23
28
|
ScheduledNotDueError,
|
|
24
29
|
encodeCursor,
|
|
25
30
|
decodeCursor,
|
|
@@ -28,9 +33,25 @@ import {
|
|
|
28
33
|
// Regex pattern for ULID validation
|
|
29
34
|
const ULID_PATTERN = /^[0-9A-Z]{26}$/;
|
|
30
35
|
const SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
|
|
36
|
+
const MAX_DRAFT_STAGE_ATTEMPTS = 32;
|
|
31
37
|
|
|
32
38
|
// LIKE wildcards that must be escaped so user search input is matched literally.
|
|
33
39
|
const LIKE_WILDCARD_RE = /[\\%_]/g;
|
|
40
|
+
const MAX_INDEXED_FIELD_FILTERS = 20;
|
|
41
|
+
const MAX_IN_FILTER_VALUES = SQL_BATCH_SIZE;
|
|
42
|
+
const MAX_FILTER_STRING_LENGTH = 2048;
|
|
43
|
+
|
|
44
|
+
type NormalizedFilterScalar = string | number;
|
|
45
|
+
|
|
46
|
+
type ResolvedFieldFilter =
|
|
47
|
+
| { column: string; kind: "null" }
|
|
48
|
+
| { column: string; kind: "exact"; value: NormalizedFilterScalar }
|
|
49
|
+
| { column: string; kind: "in"; values: NormalizedFilterScalar[] }
|
|
50
|
+
| {
|
|
51
|
+
column: string;
|
|
52
|
+
kind: "range";
|
|
53
|
+
bounds: Partial<Record<"gt" | "gte" | "lt" | "lte", NormalizedFilterScalar>>;
|
|
54
|
+
};
|
|
34
55
|
|
|
35
56
|
function nullableColumnMatch(column: string, value: string | null): ReturnType<typeof sql> {
|
|
36
57
|
validateIdentifier(column, "content column");
|
|
@@ -69,6 +90,31 @@ function matchesPublication(
|
|
|
69
90
|
);
|
|
70
91
|
}
|
|
71
92
|
|
|
93
|
+
function matchesLifecyclePublication(
|
|
94
|
+
observed: ContentItem,
|
|
95
|
+
existing: ContentItem,
|
|
96
|
+
liveRevisionId: string,
|
|
97
|
+
publishedAt: string,
|
|
98
|
+
updatedAt: string,
|
|
99
|
+
): boolean {
|
|
100
|
+
if (
|
|
101
|
+
observed.version !== existing.version + 1 ||
|
|
102
|
+
observed.status !== "published" ||
|
|
103
|
+
observed.slug !== existing.slug ||
|
|
104
|
+
observed.liveRevisionId !== liveRevisionId ||
|
|
105
|
+
observed.draftRevisionId !== null ||
|
|
106
|
+
observed.scheduledAt !== null ||
|
|
107
|
+
observed.publishedAt !== publishedAt ||
|
|
108
|
+
observed.updatedAt !== updatedAt
|
|
109
|
+
) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return Object.entries(existing.data).every(([key, value]) =>
|
|
114
|
+
sameStoredValue(observed.data[key], value),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
72
118
|
function matchesPublicationFence(observed: ContentItem, existing: ContentItem): boolean {
|
|
73
119
|
return (
|
|
74
120
|
observed.version === existing.version &&
|
|
@@ -85,6 +131,51 @@ function isConfirmedStatementFailure(error: unknown): boolean {
|
|
|
85
131
|
return typeof code === "string" && (code.startsWith("SQLITE_") || SQLSTATE_PATTERN.test(code));
|
|
86
132
|
}
|
|
87
133
|
|
|
134
|
+
interface ResolvedOrderField {
|
|
135
|
+
column: string;
|
|
136
|
+
indexedCustomField: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
type IndexedOrderValue = string | number | null;
|
|
140
|
+
|
|
141
|
+
interface IndexedFieldCursorPayload {
|
|
142
|
+
version: 1;
|
|
143
|
+
field: string;
|
|
144
|
+
value: IndexedOrderValue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function encodeIndexedFieldCursor(field: string, value: IndexedOrderValue, id: string): string {
|
|
148
|
+
const payload: IndexedFieldCursorPayload = { version: 1, field, value };
|
|
149
|
+
return encodeCursor(JSON.stringify(payload), id);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function decodeIndexedFieldCursor(
|
|
153
|
+
cursor: string,
|
|
154
|
+
field: string,
|
|
155
|
+
): { value: IndexedOrderValue; id: string } {
|
|
156
|
+
const { orderValue, id } = decodeCursor(cursor);
|
|
157
|
+
let payload: unknown;
|
|
158
|
+
try {
|
|
159
|
+
payload = JSON.parse(orderValue);
|
|
160
|
+
} catch {
|
|
161
|
+
throw new InvalidCursorError(cursor);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (payload === null || typeof payload !== "object") {
|
|
165
|
+
throw new InvalidCursorError(cursor);
|
|
166
|
+
}
|
|
167
|
+
const candidate = payload as Partial<IndexedFieldCursorPayload>;
|
|
168
|
+
const validValue =
|
|
169
|
+
candidate.value === null ||
|
|
170
|
+
typeof candidate.value === "string" ||
|
|
171
|
+
typeof candidate.value === "number";
|
|
172
|
+
if (candidate.version !== 1 || candidate.field !== field || !validValue) {
|
|
173
|
+
throw new InvalidCursorError(cursor);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { value: candidate.value as IndexedOrderValue, id };
|
|
177
|
+
}
|
|
178
|
+
|
|
88
179
|
/**
|
|
89
180
|
* Whitelist mapping a public date-filter field to its physical column. Keeping
|
|
90
181
|
* this separate from `mapOrderField` makes the filterable set explicit and
|
|
@@ -97,6 +188,29 @@ const DATE_FILTER_COLUMNS: Record<ContentDateField, "created_at" | "updated_at"
|
|
|
97
188
|
publishedAt: "published_at",
|
|
98
189
|
};
|
|
99
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Built-in sort fields → their physical columns. A closed set that blocks
|
|
193
|
+
* sorting by arbitrary columns; per-collection fields are allowed
|
|
194
|
+
* separately via `mapOrderField`'s `sortableExtras`.
|
|
195
|
+
*/
|
|
196
|
+
const ORDER_FIELD_COLUMNS: Record<string, string> = {
|
|
197
|
+
createdAt: "created_at",
|
|
198
|
+
updatedAt: "updated_at",
|
|
199
|
+
publishedAt: "published_at",
|
|
200
|
+
scheduledAt: "scheduled_at",
|
|
201
|
+
deletedAt: "deleted_at",
|
|
202
|
+
title: "title",
|
|
203
|
+
name: "name",
|
|
204
|
+
slug: "slug",
|
|
205
|
+
status: "status",
|
|
206
|
+
locale: "locale",
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/** True when `field` maps to a system column and needs no per-collection resolution. */
|
|
210
|
+
export function isSystemOrderField(field: string): boolean {
|
|
211
|
+
return field in ORDER_FIELD_COLUMNS;
|
|
212
|
+
}
|
|
213
|
+
|
|
100
214
|
/**
|
|
101
215
|
* System columns that exist in every ec_* table
|
|
102
216
|
*/
|
|
@@ -144,6 +258,16 @@ function serializeValue(value: unknown): unknown {
|
|
|
144
258
|
return value;
|
|
145
259
|
}
|
|
146
260
|
|
|
261
|
+
function writableContentData(data: Record<string, unknown>): Record<string, unknown> {
|
|
262
|
+
const writable: Record<string, unknown> = {};
|
|
263
|
+
for (const [key, value] of Object.entries(data)) {
|
|
264
|
+
if (SYSTEM_COLUMNS.has(key)) continue;
|
|
265
|
+
validateIdentifier(key, "content field name");
|
|
266
|
+
writable[key] = value;
|
|
267
|
+
}
|
|
268
|
+
return writable;
|
|
269
|
+
}
|
|
270
|
+
|
|
147
271
|
/**
|
|
148
272
|
* Deserialize a value from database storage
|
|
149
273
|
* Attempts to parse JSON strings that look like objects/arrays
|
|
@@ -185,7 +309,7 @@ export class ContentRepository {
|
|
|
185
309
|
* Create a new content item
|
|
186
310
|
*/
|
|
187
311
|
async create(input: CreateContentInput): Promise<ContentItem> {
|
|
188
|
-
const id = ulid();
|
|
312
|
+
const id = input.id ?? ulid();
|
|
189
313
|
const now = new Date().toISOString();
|
|
190
314
|
|
|
191
315
|
const {
|
|
@@ -283,7 +407,7 @@ export class ContentRepository {
|
|
|
283
407
|
* (optionally scoped to a locale) and appends a numeric suffix (`-1`,
|
|
284
408
|
* `-2`, etc.) on collision to guarantee uniqueness.
|
|
285
409
|
*
|
|
286
|
-
* Returns
|
|
410
|
+
* Returns null when slug normalization cannot produce a value.
|
|
287
411
|
*/
|
|
288
412
|
async generateUniqueSlug(type: string, text: string, locale?: string): Promise<string | null> {
|
|
289
413
|
const baseSlug = slugify(text);
|
|
@@ -557,7 +681,13 @@ export class ContentRepository {
|
|
|
557
681
|
// Determine ordering
|
|
558
682
|
const orderField = options.orderBy?.field || "createdAt";
|
|
559
683
|
const orderDirection = options.orderBy?.direction || "desc";
|
|
560
|
-
const
|
|
684
|
+
const resolvedOrderField = await this.resolveOrderField(
|
|
685
|
+
type,
|
|
686
|
+
orderField,
|
|
687
|
+
options.sortableExtras,
|
|
688
|
+
);
|
|
689
|
+
const dbField = resolvedOrderField.column;
|
|
690
|
+
const resolvedFieldFilters = await this.resolveFieldFilters(type, options.where?.fieldFilters);
|
|
561
691
|
|
|
562
692
|
// Validate order direction to prevent injection
|
|
563
693
|
const safeOrderDirection = orderDirection.toLowerCase() === "asc" ? "ASC" : "DESC";
|
|
@@ -584,40 +714,90 @@ export class ContentRepository {
|
|
|
584
714
|
|
|
585
715
|
query = this.applySearchFilter(query, options.where, type);
|
|
586
716
|
query = this.applyDateFilter(query, options.where);
|
|
717
|
+
query = this.applyBylineFilter(query, options.where, type);
|
|
718
|
+
query = this.applyFieldFilters(query, resolvedFieldFilters);
|
|
587
719
|
|
|
588
720
|
// Handle cursor pagination — decodeCursor throws InvalidCursorError
|
|
589
721
|
// on malformed input; let it propagate so handlers surface a
|
|
590
722
|
// structured INVALID_CURSOR rather than silently returning page 1.
|
|
591
723
|
if (options.cursor) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
724
|
+
if (resolvedOrderField.indexedCustomField) {
|
|
725
|
+
const { value, id: cursorId } = decodeIndexedFieldCursor(options.cursor, orderField);
|
|
726
|
+
const isPresent = sql<boolean>`${sql.ref(dbField)} IS NOT NULL`;
|
|
727
|
+
const falseLiteral = sql<boolean>`FALSE`;
|
|
728
|
+
const trueLiteral = sql<boolean>`TRUE`;
|
|
729
|
+
if (safeOrderDirection === "ASC" && value === null) {
|
|
730
|
+
query = query.where(sql<boolean>`
|
|
731
|
+
(${isPresent}) > ${falseLiteral}
|
|
732
|
+
OR ((${isPresent}) = ${falseLiteral} AND ${sql.ref("id")} > ${cursorId})
|
|
733
|
+
`);
|
|
734
|
+
} else if (safeOrderDirection === "DESC" && value === null) {
|
|
735
|
+
query = query.where(sql<boolean>`
|
|
736
|
+
(${isPresent}) = ${falseLiteral} AND ${sql.ref("id")} < ${cursorId}
|
|
737
|
+
`);
|
|
738
|
+
} else if (safeOrderDirection === "ASC") {
|
|
739
|
+
query = query.where(sql<boolean>`
|
|
740
|
+
(${isPresent}, ${sql.ref(dbField)}, ${sql.ref("id")})
|
|
741
|
+
> (${trueLiteral}, ${value}, ${cursorId})
|
|
742
|
+
`);
|
|
743
|
+
} else {
|
|
744
|
+
query = query.where(sql<boolean>`
|
|
745
|
+
(${isPresent}, ${sql.ref(dbField)}, ${sql.ref("id")})
|
|
746
|
+
< (${trueLiteral}, ${value}, ${cursorId})
|
|
747
|
+
`);
|
|
748
|
+
}
|
|
601
749
|
} else {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
750
|
+
const { orderValue, id: cursorId } = decodeCursor(options.cursor);
|
|
751
|
+
|
|
752
|
+
if (safeOrderDirection === "DESC") {
|
|
753
|
+
query = query.where((eb) =>
|
|
754
|
+
eb.or([
|
|
755
|
+
eb(dbField as any, "<", orderValue),
|
|
756
|
+
eb.and([eb(dbField as any, "=", orderValue), eb("id", "<", cursorId)]),
|
|
757
|
+
]),
|
|
758
|
+
);
|
|
759
|
+
} else {
|
|
760
|
+
query = query.where((eb) =>
|
|
761
|
+
eb.or([
|
|
762
|
+
eb(dbField as any, ">", orderValue),
|
|
763
|
+
eb.and([eb(dbField as any, "=", orderValue), eb("id", ">", cursorId)]),
|
|
764
|
+
]),
|
|
765
|
+
);
|
|
766
|
+
}
|
|
608
767
|
}
|
|
609
768
|
}
|
|
610
769
|
|
|
611
770
|
// Apply ordering and limit
|
|
612
|
-
|
|
613
|
-
.
|
|
614
|
-
|
|
615
|
-
|
|
771
|
+
const indexedOrderFilter = resolvedOrderField.indexedCustomField
|
|
772
|
+
? resolvedFieldFilters.find((filter) => filter.column === dbField)
|
|
773
|
+
: undefined;
|
|
774
|
+
if (resolvedOrderField.indexedCustomField && !indexedOrderFilter) {
|
|
775
|
+
query = query.orderBy(
|
|
776
|
+
sql<boolean>`${sql.ref(dbField)} IS NOT NULL`,
|
|
777
|
+
safeOrderDirection === "ASC" ? "asc" : "desc",
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
if (indexedOrderFilter?.kind !== "null") {
|
|
781
|
+
query = query.orderBy(dbField as any, safeOrderDirection === "ASC" ? "asc" : "desc");
|
|
782
|
+
}
|
|
783
|
+
query = query.orderBy("id", safeOrderDirection === "ASC" ? "asc" : "desc").limit(limit + 1);
|
|
616
784
|
|
|
617
785
|
// Run the page fetch and the unbounded count together — the UI needs
|
|
618
786
|
// both to render a stable denominator (kept on every page intentionally),
|
|
619
787
|
// and issuing them in parallel on SQLite is essentially free.
|
|
620
|
-
|
|
788
|
+
//
|
|
789
|
+
// Settled rather than raced: a collection whose table is missing rejects
|
|
790
|
+
// both, and `Promise.all` returns on the first. The loser stays in flight
|
|
791
|
+
// holding a pooled connection, so a Postgres pool destroyed in that window
|
|
792
|
+
// never finishes closing.
|
|
793
|
+
const [rowsResult, countResult] = await Promise.allSettled([
|
|
794
|
+
query.execute(),
|
|
795
|
+
this.countWithResolvedFilters(type, options.where, resolvedFieldFilters),
|
|
796
|
+
]);
|
|
797
|
+
if (rowsResult.status === "rejected") throw rowsResult.reason;
|
|
798
|
+
if (countResult.status === "rejected") throw countResult.reason;
|
|
799
|
+
const rows = rowsResult.value;
|
|
800
|
+
const total = countResult.value;
|
|
621
801
|
const hasMore = rows.length > limit;
|
|
622
802
|
const items = rows.slice(0, limit);
|
|
623
803
|
|
|
@@ -629,11 +809,26 @@ export class ContentRepository {
|
|
|
629
809
|
if (hasMore && items.length > 0) {
|
|
630
810
|
const lastRow = items.at(-1) as Record<string, unknown>;
|
|
631
811
|
const lastOrderValue = lastRow[dbField];
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
812
|
+
if (resolvedOrderField.indexedCustomField) {
|
|
813
|
+
if (
|
|
814
|
+
lastOrderValue !== null &&
|
|
815
|
+
typeof lastOrderValue !== "string" &&
|
|
816
|
+
typeof lastOrderValue !== "number"
|
|
817
|
+
) {
|
|
818
|
+
throw new EmDashValidationError(`Invalid indexed value for order field: ${orderField}`);
|
|
819
|
+
}
|
|
820
|
+
mappedResult.nextCursor = encodeIndexedFieldCursor(
|
|
821
|
+
orderField,
|
|
822
|
+
lastOrderValue,
|
|
823
|
+
String(lastRow.id),
|
|
824
|
+
);
|
|
825
|
+
} else {
|
|
826
|
+
const orderStr =
|
|
827
|
+
typeof lastOrderValue === "string" || typeof lastOrderValue === "number"
|
|
828
|
+
? String(lastOrderValue)
|
|
829
|
+
: "";
|
|
830
|
+
mappedResult.nextCursor = encodeCursor(orderStr, String(lastRow.id));
|
|
831
|
+
}
|
|
637
832
|
}
|
|
638
833
|
|
|
639
834
|
return mappedResult;
|
|
@@ -676,11 +871,8 @@ export class ContentRepository {
|
|
|
676
871
|
|
|
677
872
|
// Update data fields (skip system columns to prevent injection via data)
|
|
678
873
|
if (input.data !== undefined && typeof input.data === "object") {
|
|
679
|
-
for (const [key, value] of Object.entries(input.data)) {
|
|
680
|
-
|
|
681
|
-
validateIdentifier(key, "content field name");
|
|
682
|
-
updates[key] = serializeValue(value);
|
|
683
|
-
}
|
|
874
|
+
for (const [key, value] of Object.entries(writableContentData(input.data))) {
|
|
875
|
+
updates[key] = serializeValue(value);
|
|
684
876
|
}
|
|
685
877
|
}
|
|
686
878
|
|
|
@@ -697,18 +889,6 @@ export class ContentRepository {
|
|
|
697
889
|
.where("deleted_at" as never, "is", null)
|
|
698
890
|
.execute();
|
|
699
891
|
|
|
700
|
-
// Re-stamp the taxonomy pivot only when a denormalized column actually
|
|
701
|
-
// moved (status/publishedAt/scheduledAt). A plain content edit bumps
|
|
702
|
-
// `updated_at` — which is not denormalized — so it needs no pivot write,
|
|
703
|
-
// keeping the common edit path free of taxonomy write amplification.
|
|
704
|
-
if (
|
|
705
|
-
input.status !== undefined ||
|
|
706
|
-
input.publishedAt !== undefined ||
|
|
707
|
-
input.scheduledAt !== undefined
|
|
708
|
-
) {
|
|
709
|
-
await this.restampEntryPivot(type, id);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
892
|
if (hasColumnWrites) invalidateCollectionCache(type);
|
|
713
893
|
|
|
714
894
|
const updated = await this.findById(type, id);
|
|
@@ -719,6 +899,159 @@ export class ContentRepository {
|
|
|
719
899
|
return updated;
|
|
720
900
|
}
|
|
721
901
|
|
|
902
|
+
/**
|
|
903
|
+
* Update plugin-authored fields without letting content columns diverge
|
|
904
|
+
* from the revision pointers that publication promotes.
|
|
905
|
+
*/
|
|
906
|
+
async updateDraftAware(
|
|
907
|
+
type: string,
|
|
908
|
+
id: string,
|
|
909
|
+
input: UpdateContentInput,
|
|
910
|
+
): Promise<ContentItem> {
|
|
911
|
+
const data = input.data ? writableContentData(input.data) : {};
|
|
912
|
+
const stagedSlug = typeof input.slug === "string" ? input.slug : undefined;
|
|
913
|
+
const hasDraftUpdate = Object.keys(data).length > 0 || stagedSlug !== undefined;
|
|
914
|
+
|
|
915
|
+
if (!hasDraftUpdate) {
|
|
916
|
+
return this.update(type, id, { ...input, data });
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const collectionRows = await this.db
|
|
920
|
+
.selectFrom("_emdash_collections as collection")
|
|
921
|
+
.leftJoin("_emdash_fields as field", "field.collection_id", "collection.id")
|
|
922
|
+
.select(["collection.supports", "field.slug as fieldSlug"])
|
|
923
|
+
.where("collection.slug", "=", type)
|
|
924
|
+
.execute();
|
|
925
|
+
const supportsRaw = collectionRows[0]?.supports;
|
|
926
|
+
const supports: unknown = supportsRaw ? JSON.parse(supportsRaw) : [];
|
|
927
|
+
if (!Array.isArray(supports) || !supports.includes("revisions")) {
|
|
928
|
+
return this.update(type, id, { ...input, data });
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const fieldSlugs = new Set(collectionRows.map((row) => row.fieldSlug).filter(Boolean));
|
|
932
|
+
for (const field of Object.keys(data)) {
|
|
933
|
+
if (!fieldSlugs.has(field)) {
|
|
934
|
+
throw new EmDashValidationError(`Unknown field '${field}' in collection '${type}'`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const revisionRepo = new RevisionRepository(this.db);
|
|
939
|
+
let existing = await this.findById(type, id);
|
|
940
|
+
|
|
941
|
+
for (let attempt = 0; existing && attempt < MAX_DRAFT_STAGE_ATTEMPTS; attempt++) {
|
|
942
|
+
let baseData = existing.data;
|
|
943
|
+
if (existing.draftRevisionId) {
|
|
944
|
+
const draft = await revisionRepo.findById(existing.draftRevisionId);
|
|
945
|
+
if (draft) baseData = draft.data;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const mergedData = { ...baseData, ...data };
|
|
949
|
+
if (stagedSlug !== undefined) mergedData._slug = stagedSlug;
|
|
950
|
+
const revision = await revisionRepo.create({
|
|
951
|
+
collection: type,
|
|
952
|
+
entryId: id,
|
|
953
|
+
data: mergedData,
|
|
954
|
+
...(input.authorId ? { authorId: input.authorId } : {}),
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
let staged: boolean;
|
|
958
|
+
try {
|
|
959
|
+
staged = await this.replaceDraftRevisionForUpdate(type, id, revision.id, existing, input);
|
|
960
|
+
} catch (error) {
|
|
961
|
+
await this.deleteUnstagedRevision(revisionRepo, type, id, revision.id);
|
|
962
|
+
throw error;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (staged) {
|
|
966
|
+
const updated = await this.findById(type, id);
|
|
967
|
+
if (!updated) throw new Error("Content not found");
|
|
968
|
+
const draftData: Record<string, unknown> = {};
|
|
969
|
+
for (const [key, value] of Object.entries(mergedData)) {
|
|
970
|
+
if (!key.startsWith("_")) draftData[key] = value;
|
|
971
|
+
}
|
|
972
|
+
return { ...updated, data: { ...updated.data, ...draftData } };
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
await this.deleteUnstagedRevision(revisionRepo, type, id, revision.id);
|
|
976
|
+
existing = await this.findById(type, id);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (!existing) throw new Error("Content not found");
|
|
980
|
+
throw new ContentMutationConflictError();
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
private async deleteUnstagedRevision(
|
|
984
|
+
revisionRepo: RevisionRepository,
|
|
985
|
+
type: string,
|
|
986
|
+
id: string,
|
|
987
|
+
revisionId: string,
|
|
988
|
+
): Promise<void> {
|
|
989
|
+
try {
|
|
990
|
+
await revisionRepo.deleteIfUnreferenced(type, id, revisionId);
|
|
991
|
+
} catch (error) {
|
|
992
|
+
console.error(`[content] Failed to clean up unstaged revision ${revisionId}:`, error);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private async replaceDraftRevisionForUpdate(
|
|
997
|
+
type: string,
|
|
998
|
+
id: string,
|
|
999
|
+
revisionId: string,
|
|
1000
|
+
expected: ContentItem,
|
|
1001
|
+
input: UpdateContentInput,
|
|
1002
|
+
): Promise<boolean> {
|
|
1003
|
+
const tableName = getTableName(type);
|
|
1004
|
+
const assignments = [sql`draft_revision_id = ${revisionId}`];
|
|
1005
|
+
let liveMetadataChanged = false;
|
|
1006
|
+
|
|
1007
|
+
if (input.status !== undefined) {
|
|
1008
|
+
assignments.push(sql`status = ${input.status}`);
|
|
1009
|
+
liveMetadataChanged = true;
|
|
1010
|
+
}
|
|
1011
|
+
if (input.slug === null) {
|
|
1012
|
+
assignments.push(sql`slug = NULL`);
|
|
1013
|
+
liveMetadataChanged = true;
|
|
1014
|
+
}
|
|
1015
|
+
if (input.publishedAt !== undefined) {
|
|
1016
|
+
assignments.push(sql`published_at = ${input.publishedAt}`);
|
|
1017
|
+
liveMetadataChanged = true;
|
|
1018
|
+
}
|
|
1019
|
+
if (input.scheduledAt !== undefined) {
|
|
1020
|
+
assignments.push(sql`scheduled_at = ${input.scheduledAt}`);
|
|
1021
|
+
liveMetadataChanged = true;
|
|
1022
|
+
}
|
|
1023
|
+
if (input.authorId !== undefined) {
|
|
1024
|
+
assignments.push(sql`author_id = ${input.authorId}`);
|
|
1025
|
+
liveMetadataChanged = true;
|
|
1026
|
+
}
|
|
1027
|
+
if (input.primaryBylineId !== undefined) {
|
|
1028
|
+
assignments.push(sql`primary_byline_id = ${input.primaryBylineId}`);
|
|
1029
|
+
liveMetadataChanged = true;
|
|
1030
|
+
}
|
|
1031
|
+
if (liveMetadataChanged) assignments.push(sql`updated_at = ${new Date().toISOString()}`);
|
|
1032
|
+
assignments.push(sql`version = version + 1`);
|
|
1033
|
+
|
|
1034
|
+
const result = await sql`
|
|
1035
|
+
UPDATE ${sql.ref(tableName)}
|
|
1036
|
+
SET ${sql.join(assignments, sql`, `)}
|
|
1037
|
+
WHERE id = ${id}
|
|
1038
|
+
AND deleted_at IS NULL
|
|
1039
|
+
AND version = ${expected.version}
|
|
1040
|
+
AND ${nullableColumnMatch("live_revision_id", expected.liveRevisionId)}
|
|
1041
|
+
AND ${nullableColumnMatch("draft_revision_id", expected.draftRevisionId)}
|
|
1042
|
+
AND EXISTS (
|
|
1043
|
+
SELECT 1 FROM revisions
|
|
1044
|
+
WHERE revisions.id = ${revisionId}
|
|
1045
|
+
AND revisions.collection = ${type}
|
|
1046
|
+
AND revisions.entry_id = ${id}
|
|
1047
|
+
)
|
|
1048
|
+
`.execute(this.db);
|
|
1049
|
+
|
|
1050
|
+
const changed = (result.numAffectedRows ?? 0n) > 0n;
|
|
1051
|
+
if (changed && liveMetadataChanged) invalidateCollectionCache(type);
|
|
1052
|
+
return changed;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
722
1055
|
/**
|
|
723
1056
|
* Delete content (soft delete - moves to trash)
|
|
724
1057
|
*/
|
|
@@ -735,7 +1068,6 @@ export class ContentRepository {
|
|
|
735
1068
|
|
|
736
1069
|
const changed = (result.numAffectedRows ?? 0n) > 0n;
|
|
737
1070
|
if (changed) {
|
|
738
|
-
await this.restampEntryPivot(type, id);
|
|
739
1071
|
invalidateCollectionCache(type);
|
|
740
1072
|
}
|
|
741
1073
|
return changed;
|
|
@@ -758,36 +1090,10 @@ export class ContentRepository {
|
|
|
758
1090
|
const restored = result.rows[0];
|
|
759
1091
|
if (!restored) return null;
|
|
760
1092
|
|
|
761
|
-
await this.restampEntryPivot(type, id);
|
|
762
1093
|
invalidateCollectionCache(type);
|
|
763
1094
|
return this.mapRow(type, restored);
|
|
764
1095
|
}
|
|
765
1096
|
|
|
766
|
-
/**
|
|
767
|
-
* Re-stamp the denormalized filter + sort columns on every
|
|
768
|
-
* `content_taxonomies` pivot row for an entry from its authoritative `ec_*`
|
|
769
|
-
* row (migration 051). Called after any mutation that moves one of those
|
|
770
|
-
* columns so a taxonomy-filtered listing can seek the entry directly.
|
|
771
|
-
*
|
|
772
|
-
* A single correlated `UPDATE` reads the post-mutation values from `ec_*`, so
|
|
773
|
-
* the pivot converges to the authoritative row. This is NOT atomic with the
|
|
774
|
-
* `ec_*` mutation on D1 (no transactions), which is why the read path
|
|
775
|
-
* re-checks the real predicates on the joined `ec_*` row. Untagged entries
|
|
776
|
-
* have no pivot rows, so the statement is a cheap no-op for them.
|
|
777
|
-
*/
|
|
778
|
-
private async restampEntryPivot(type: string, id: string): Promise<void> {
|
|
779
|
-
const tableName = getTableName(type);
|
|
780
|
-
await sql`
|
|
781
|
-
UPDATE content_taxonomies
|
|
782
|
-
SET (status, scheduled_at, deleted_at, locale, published_at, created_at) = (
|
|
783
|
-
SELECT status, scheduled_at, deleted_at, locale, published_at, created_at
|
|
784
|
-
FROM ${sql.ref(tableName)}
|
|
785
|
-
WHERE ${sql.ref(tableName)}.id = ${id}
|
|
786
|
-
)
|
|
787
|
-
WHERE collection = ${type} AND entry_id = ${id}
|
|
788
|
-
`.execute(this.db);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
1097
|
/**
|
|
792
1098
|
* Permanently delete content (cannot be undone)
|
|
793
1099
|
*/
|
|
@@ -953,7 +1259,7 @@ export class ContentRepository {
|
|
|
953
1259
|
eb.or(
|
|
954
1260
|
columns.map((col) => {
|
|
955
1261
|
validateIdentifier(col, "search column");
|
|
956
|
-
return
|
|
1262
|
+
return sql<boolean>`lower(CAST(${sql.ref(col)} AS TEXT)) LIKE lower(${pattern}) ESCAPE '\\'`;
|
|
957
1263
|
}),
|
|
958
1264
|
),
|
|
959
1265
|
);
|
|
@@ -984,10 +1290,128 @@ export class ContentRepository {
|
|
|
984
1290
|
return next;
|
|
985
1291
|
}
|
|
986
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Apply the optional byline filter as a correlated (NOT) EXISTS against
|
|
1295
|
+
* `_emdash_content_bylines`.
|
|
1296
|
+
*
|
|
1297
|
+
* Correlating from the content table preserves the outer sort index so
|
|
1298
|
+
* `LIMIT` can short-circuit. `mode: "none"` tests the junction rather than
|
|
1299
|
+
* `primary_byline_id` because the two are written in the same call but
|
|
1300
|
+
* are not atomically consistent, so the junction is authoritative.
|
|
1301
|
+
*
|
|
1302
|
+
* Whether a credit *renders* is locale-scoped; whether one *exists* is
|
|
1303
|
+
* not. Both are needed: the first decides what the filter matches, the
|
|
1304
|
+
* second decides whether the author fallback applies at all.
|
|
1305
|
+
*/
|
|
1306
|
+
private applyBylineFilter<QB extends { where: (cb: (eb: any) => unknown) => QB }>(
|
|
1307
|
+
query: QB,
|
|
1308
|
+
where: { bylineFilter?: ContentBylineFilter } | undefined,
|
|
1309
|
+
type: string,
|
|
1310
|
+
): QB {
|
|
1311
|
+
const filter = where?.bylineFilter;
|
|
1312
|
+
if (!filter) return query;
|
|
1313
|
+
const tableName = getTableName(type);
|
|
1314
|
+
const idColumn = `${tableName}.id`;
|
|
1315
|
+
const authorColumn = `${tableName}.author_id`;
|
|
1316
|
+
const localeColumn = `${tableName}.locale`;
|
|
1317
|
+
|
|
1318
|
+
// An explicit credit that actually renders — optionally within a given
|
|
1319
|
+
// set of translation groups. The junction stores a group, but a credit
|
|
1320
|
+
// resolves only where that group has a byline row at the locale the
|
|
1321
|
+
// list is scoped to, so this repeats the join `getContentBylinesMany`
|
|
1322
|
+
// makes. `locale` falls back to each entry's own when the list spans
|
|
1323
|
+
// locales.
|
|
1324
|
+
const creditRenders = (eb: any, bylineIds?: string[]) => {
|
|
1325
|
+
let sub = eb
|
|
1326
|
+
.selectFrom("_emdash_content_bylines as cb")
|
|
1327
|
+
.innerJoin("_emdash_bylines as b", "b.translation_group", "cb.byline_id")
|
|
1328
|
+
.select("cb.id")
|
|
1329
|
+
.where("cb.collection_slug", "=", type)
|
|
1330
|
+
.whereRef("cb.content_id", "=", idColumn);
|
|
1331
|
+
sub = filter.locale
|
|
1332
|
+
? sub.where("b.locale", "=", filter.locale)
|
|
1333
|
+
: sub.whereRef("b.locale", "=", localeColumn);
|
|
1334
|
+
if (bylineIds) sub = sub.where("cb.byline_id", "in", bylineIds);
|
|
1335
|
+
return eb.exists(sub);
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
// Whether the entry carries an explicit credit at all, at any locale.
|
|
1339
|
+
// Deliberately not locale-scoped: the author fallback is suppressed by
|
|
1340
|
+
// the presence of a junction row, even one that renders nothing here
|
|
1341
|
+
// (`hydrateBylinesMany` gates inference on `primaryBylineId`).
|
|
1342
|
+
const hasExplicitCredit = (eb: any) =>
|
|
1343
|
+
eb.exists(
|
|
1344
|
+
eb
|
|
1345
|
+
.selectFrom("_emdash_content_bylines as cb")
|
|
1346
|
+
.select("cb.id")
|
|
1347
|
+
.where("cb.collection_slug", "=", type)
|
|
1348
|
+
.whereRef("cb.content_id", "=", idColumn),
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
// The entry's author owns a byline row — optionally within a given set
|
|
1352
|
+
// of translation groups — at the locale the list is scoped to. Matching
|
|
1353
|
+
// the locale is what keeps the filter agreeing with the list: an
|
|
1354
|
+
// inferred credit renders only when the author's byline has a row at
|
|
1355
|
+
// that locale (`hydrateBylinesMany` -> `findByUserIds`), and byline
|
|
1356
|
+
// translations start life with a null `user_id`, so a group translated
|
|
1357
|
+
// into the locale but not re-linked resolves to no credit. `locale`
|
|
1358
|
+
// falls back to each entry's own when the list spans locales.
|
|
1359
|
+
const authorHasByline = (eb: any, bylineIds?: string[]) => {
|
|
1360
|
+
let sub = eb
|
|
1361
|
+
.selectFrom("_emdash_bylines as b")
|
|
1362
|
+
.select("b.id")
|
|
1363
|
+
.whereRef("b.user_id", "=", authorColumn);
|
|
1364
|
+
sub = filter.locale
|
|
1365
|
+
? sub.where("b.locale", "=", filter.locale)
|
|
1366
|
+
: sub.whereRef("b.locale", "=", localeColumn);
|
|
1367
|
+
if (bylineIds) sub = sub.where("b.translation_group", "in", bylineIds);
|
|
1368
|
+
return eb.exists(sub);
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
if (filter.mode === "none") {
|
|
1372
|
+
return query.where((eb: any) => {
|
|
1373
|
+
const uncredited = eb.not(creditRenders(eb));
|
|
1374
|
+
// With inference on, "no byline" means none is rendered, so an
|
|
1375
|
+
// entry that falls through to an author byline is excluded too.
|
|
1376
|
+
return filter.includeInferred
|
|
1377
|
+
? eb.and([uncredited, eb.or([hasExplicitCredit(eb), eb.not(authorHasByline(eb))])])
|
|
1378
|
+
: uncredited;
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
const bylineIds = filter.bylineIds ?? [];
|
|
1383
|
+
if (bylineIds.length === 0) {
|
|
1384
|
+
// A filter that resolved to no ids must match nothing rather than
|
|
1385
|
+
// silently degrade to "no filter" and return the whole collection.
|
|
1386
|
+
// `1 = 0` rather than a bound `false`: better-sqlite3 refuses to
|
|
1387
|
+
// bind JS booleans.
|
|
1388
|
+
return query.where(() => sql<boolean>`1 = 0`);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
return query.where((eb: any) => {
|
|
1392
|
+
if (!filter.includeInferred) return creditRenders(eb, bylineIds);
|
|
1393
|
+
// Inference applies only where no explicit credit exists, so an
|
|
1394
|
+
// entry credited to someone else never matches on its author.
|
|
1395
|
+
return eb.or([
|
|
1396
|
+
creditRenders(eb, bylineIds),
|
|
1397
|
+
eb.and([eb.not(hasExplicitCredit(eb)), authorHasByline(eb, bylineIds)]),
|
|
1398
|
+
]);
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
|
|
987
1402
|
/**
|
|
988
1403
|
* Count content items
|
|
989
1404
|
*/
|
|
990
1405
|
async count(type: string, where?: FindManyOptions["where"]): Promise<number> {
|
|
1406
|
+
const resolvedFieldFilters = await this.resolveFieldFilters(type, where?.fieldFilters);
|
|
1407
|
+
return this.countWithResolvedFilters(type, where, resolvedFieldFilters);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
private async countWithResolvedFilters(
|
|
1411
|
+
type: string,
|
|
1412
|
+
where: FindManyOptions["where"] | undefined,
|
|
1413
|
+
resolvedFieldFilters: ResolvedFieldFilter[],
|
|
1414
|
+
): Promise<number> {
|
|
991
1415
|
const tableName = getTableName(type);
|
|
992
1416
|
|
|
993
1417
|
let query = this.db
|
|
@@ -1009,6 +1433,8 @@ export class ContentRepository {
|
|
|
1009
1433
|
|
|
1010
1434
|
query = this.applySearchFilter(query, where, type);
|
|
1011
1435
|
query = this.applyDateFilter(query, where);
|
|
1436
|
+
query = this.applyBylineFilter(query, where, type);
|
|
1437
|
+
query = this.applyFieldFilters(query, resolvedFieldFilters);
|
|
1012
1438
|
|
|
1013
1439
|
const result = await query.executeTakeFirst();
|
|
1014
1440
|
return Number(result?.count || 0);
|
|
@@ -1037,8 +1463,16 @@ export class ContentRepository {
|
|
|
1037
1463
|
// get overall statistics for a content type in a single query
|
|
1038
1464
|
async getStats(
|
|
1039
1465
|
type: string,
|
|
1040
|
-
|
|
1466
|
+
now = new Date(),
|
|
1467
|
+
): Promise<{
|
|
1468
|
+
total: number;
|
|
1469
|
+
published: number;
|
|
1470
|
+
draft: number;
|
|
1471
|
+
scheduled: number;
|
|
1472
|
+
overdueScheduled: number;
|
|
1473
|
+
}> {
|
|
1041
1474
|
const tableName = getTableName(type);
|
|
1475
|
+
const nowIso = now.toISOString();
|
|
1042
1476
|
|
|
1043
1477
|
const result = await this.db
|
|
1044
1478
|
.selectFrom(tableName as keyof Database)
|
|
@@ -1047,6 +1481,9 @@ export class ContentRepository {
|
|
|
1047
1481
|
eb.fn.sum(eb.case().when("status", "=", "published").then(1).else(0).end()).as("published"),
|
|
1048
1482
|
eb.fn.sum(eb.case().when("status", "=", "draft").then(1).else(0).end()).as("draft"),
|
|
1049
1483
|
sql<number>`SUM(CASE WHEN scheduled_at IS NOT NULL THEN 1 ELSE 0 END)`.as("scheduled"),
|
|
1484
|
+
sql<number>`SUM(CASE WHEN scheduled_at IS NOT NULL AND scheduled_at <= ${nowIso} THEN 1 ELSE 0 END)`.as(
|
|
1485
|
+
"overdue_scheduled",
|
|
1486
|
+
),
|
|
1050
1487
|
])
|
|
1051
1488
|
.where("deleted_at" as never, "is", null)
|
|
1052
1489
|
.executeTakeFirst();
|
|
@@ -1056,6 +1493,7 @@ export class ContentRepository {
|
|
|
1056
1493
|
published: Number(result?.published || 0),
|
|
1057
1494
|
draft: Number(result?.draft || 0),
|
|
1058
1495
|
scheduled: Number(result?.scheduled || 0),
|
|
1496
|
+
overdueScheduled: Number(result?.overdue_scheduled || 0),
|
|
1059
1497
|
};
|
|
1060
1498
|
}
|
|
1061
1499
|
|
|
@@ -1097,7 +1535,6 @@ export class ContentRepository {
|
|
|
1097
1535
|
AND deleted_at IS NULL
|
|
1098
1536
|
`.execute(this.db);
|
|
1099
1537
|
|
|
1100
|
-
await this.restampEntryPivot(type, id);
|
|
1101
1538
|
invalidateCollectionCache(type);
|
|
1102
1539
|
|
|
1103
1540
|
const updated = await this.findById(type, id);
|
|
@@ -1137,7 +1574,6 @@ export class ContentRepository {
|
|
|
1137
1574
|
AND deleted_at IS NULL
|
|
1138
1575
|
`.execute(this.db);
|
|
1139
1576
|
|
|
1140
|
-
await this.restampEntryPivot(type, id);
|
|
1141
1577
|
invalidateCollectionCache(type);
|
|
1142
1578
|
|
|
1143
1579
|
const updated = await this.findById(type, id);
|
|
@@ -1319,6 +1755,8 @@ export class ContentRepository {
|
|
|
1319
1755
|
* Syncs the draft revision's data into the content table columns so the
|
|
1320
1756
|
* content table always reflects the published version.
|
|
1321
1757
|
* If no draft revision exists, creates one from current data and publishes it.
|
|
1758
|
+
* When `promoteRevision` is false, publishes the current content-table data
|
|
1759
|
+
* by changing lifecycle metadata only.
|
|
1322
1760
|
*
|
|
1323
1761
|
* `publishedAt` (optional) overrides the publication timestamp. If omitted,
|
|
1324
1762
|
* the existing `published_at` is preserved (idempotent re-publish keeps the
|
|
@@ -1336,6 +1774,8 @@ export class ContentRepository {
|
|
|
1336
1774
|
publishedAt?: string,
|
|
1337
1775
|
requireDue = false,
|
|
1338
1776
|
expectedScheduledAt?: string,
|
|
1777
|
+
promoteRevision = true,
|
|
1778
|
+
requireSlug = true,
|
|
1339
1779
|
): Promise<ContentItem> {
|
|
1340
1780
|
const tableName = getTableName(type);
|
|
1341
1781
|
const now = new Date().toISOString();
|
|
@@ -1351,6 +1791,102 @@ export class ContentRepository {
|
|
|
1351
1791
|
) {
|
|
1352
1792
|
throw new ScheduledNotDueError();
|
|
1353
1793
|
}
|
|
1794
|
+
if (!promoteRevision && requireSlug && !existing.slug?.trim()) {
|
|
1795
|
+
throw new EmDashValidationError("Cannot publish routable content without a slug");
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
if (!promoteRevision) {
|
|
1799
|
+
const revisionRepo = new RevisionRepository(this.db);
|
|
1800
|
+
let provisionalRevisionId: string | null = null;
|
|
1801
|
+
try {
|
|
1802
|
+
let liveRevisionId = existing.liveRevisionId;
|
|
1803
|
+
if (!liveRevisionId) {
|
|
1804
|
+
const revision = await revisionRepo.create({
|
|
1805
|
+
collection: type,
|
|
1806
|
+
entryId: id,
|
|
1807
|
+
data: existing.data,
|
|
1808
|
+
});
|
|
1809
|
+
liveRevisionId = revision.id;
|
|
1810
|
+
provisionalRevisionId = revision.id;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
const intendedPublishedAt = publishedAt ?? existing.publishedAt ?? now;
|
|
1814
|
+
const duePredicate = requireDue
|
|
1815
|
+
? sql`AND scheduled_at IS NOT NULL AND scheduled_at <= ${now}`
|
|
1816
|
+
: sql``;
|
|
1817
|
+
let published = false;
|
|
1818
|
+
try {
|
|
1819
|
+
const result = await sql`
|
|
1820
|
+
UPDATE ${sql.ref(tableName)}
|
|
1821
|
+
SET live_revision_id = ${liveRevisionId},
|
|
1822
|
+
draft_revision_id = NULL,
|
|
1823
|
+
status = 'published',
|
|
1824
|
+
scheduled_at = NULL,
|
|
1825
|
+
published_at = ${intendedPublishedAt},
|
|
1826
|
+
updated_at = ${now},
|
|
1827
|
+
version = version + 1
|
|
1828
|
+
WHERE id = ${id}
|
|
1829
|
+
AND deleted_at IS NULL
|
|
1830
|
+
AND version = ${existing.version}
|
|
1831
|
+
AND status = ${existing.status}
|
|
1832
|
+
AND ${nullableColumnMatch("live_revision_id", existing.liveRevisionId)}
|
|
1833
|
+
AND ${nullableColumnMatch("draft_revision_id", existing.draftRevisionId)}
|
|
1834
|
+
AND ${nullableColumnMatch("scheduled_at", existing.scheduledAt)}
|
|
1835
|
+
${duePredicate}
|
|
1836
|
+
`.execute(this.db);
|
|
1837
|
+
published = (result.numAffectedRows ?? 0n) > 0n;
|
|
1838
|
+
} catch (error) {
|
|
1839
|
+
if (isConfirmedStatementFailure(error)) throw error;
|
|
1840
|
+
let observed: ContentItem | null;
|
|
1841
|
+
try {
|
|
1842
|
+
observed = await this.findById(type, id);
|
|
1843
|
+
} catch (reconciliationError) {
|
|
1844
|
+
throw new Error("Unable to confirm whether content publication completed", {
|
|
1845
|
+
cause: reconciliationError,
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
if (!observed) {
|
|
1849
|
+
throw new Error("Unable to confirm whether content publication completed", {
|
|
1850
|
+
cause: error,
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
published = matchesLifecyclePublication(
|
|
1854
|
+
observed,
|
|
1855
|
+
existing,
|
|
1856
|
+
liveRevisionId,
|
|
1857
|
+
intendedPublishedAt,
|
|
1858
|
+
now,
|
|
1859
|
+
);
|
|
1860
|
+
if (!published && matchesPublicationFence(observed, existing)) throw error;
|
|
1861
|
+
if (!published) {
|
|
1862
|
+
throw new Error("Unable to confirm whether content publication completed", {
|
|
1863
|
+
cause: error,
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
if (!published) {
|
|
1869
|
+
throw requireDue ? new ScheduledNotDueError() : new ContentMutationConflictError();
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
invalidateCollectionCache(type);
|
|
1873
|
+
const updated = await this.findById(type, id);
|
|
1874
|
+
if (!updated) throw new Error("Content not found");
|
|
1875
|
+
return updated;
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
if (provisionalRevisionId) {
|
|
1878
|
+
try {
|
|
1879
|
+
await revisionRepo.deleteIfUnreferenced(type, id, provisionalRevisionId);
|
|
1880
|
+
} catch (cleanupError) {
|
|
1881
|
+
console.error(
|
|
1882
|
+
`[content] Failed to clean up provisional revision ${provisionalRevisionId}:`,
|
|
1883
|
+
cleanupError,
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
throw error;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1354
1890
|
|
|
1355
1891
|
const revisionRepo = new RevisionRepository(this.db);
|
|
1356
1892
|
let provisionalRevisionId: string | null = null;
|
|
@@ -1374,6 +1910,9 @@ export class ContentRepository {
|
|
|
1374
1910
|
|
|
1375
1911
|
const stagedSlug = typeof revision.data._slug === "string" ? revision.data._slug : null;
|
|
1376
1912
|
const intendedSlug = stagedSlug ?? existing.slug;
|
|
1913
|
+
if (requireSlug && !intendedSlug?.trim()) {
|
|
1914
|
+
throw new EmDashValidationError("Cannot publish routable content without a slug");
|
|
1915
|
+
}
|
|
1377
1916
|
const intendedPublishedAt = publishedAt ?? existing.publishedAt ?? now;
|
|
1378
1917
|
if (stagedSlug !== null && stagedSlug !== existing.slug && existing.locale !== null) {
|
|
1379
1918
|
const conflict = await this.findBySlugIncludingTrashed(type, stagedSlug, existing.locale);
|
|
@@ -1464,8 +2003,6 @@ export class ContentRepository {
|
|
|
1464
2003
|
}
|
|
1465
2004
|
|
|
1466
2005
|
invalidateCollectionCache(type);
|
|
1467
|
-
await this.restampEntryPivot(type, id);
|
|
1468
|
-
|
|
1469
2006
|
const updated = await this.findById(type, id);
|
|
1470
2007
|
if (!updated) {
|
|
1471
2008
|
throw new Error("Content not found");
|
|
@@ -1531,7 +2068,6 @@ export class ContentRepository {
|
|
|
1531
2068
|
AND deleted_at IS NULL
|
|
1532
2069
|
`.execute(this.db);
|
|
1533
2070
|
|
|
1534
|
-
await this.restampEntryPivot(type, id);
|
|
1535
2071
|
invalidateCollectionCache(type);
|
|
1536
2072
|
|
|
1537
2073
|
const updated = await this.findById(type, id);
|
|
@@ -1688,28 +2224,257 @@ export class ContentRepository {
|
|
|
1688
2224
|
};
|
|
1689
2225
|
}
|
|
1690
2226
|
|
|
2227
|
+
private normalizeFilterScalar(
|
|
2228
|
+
field: string,
|
|
2229
|
+
type: FieldType,
|
|
2230
|
+
value: unknown,
|
|
2231
|
+
): NormalizedFilterScalar {
|
|
2232
|
+
if (type === "number" || type === "integer") {
|
|
2233
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2234
|
+
throw new EmDashValidationError(`Filter for field "${field}" must use a finite number`);
|
|
2235
|
+
}
|
|
2236
|
+
if (type === "integer" && !Number.isInteger(value)) {
|
|
2237
|
+
throw new EmDashValidationError(`Filter for field "${field}" must use an integer`);
|
|
2238
|
+
}
|
|
2239
|
+
return value;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
if (type === "boolean") {
|
|
2243
|
+
if (typeof value !== "boolean") {
|
|
2244
|
+
throw new EmDashValidationError(`Filter for field "${field}" must use a boolean`);
|
|
2245
|
+
}
|
|
2246
|
+
return value ? 1 : 0;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
if (typeof value !== "string") {
|
|
2250
|
+
throw new EmDashValidationError(`Filter for field "${field}" must use a string`);
|
|
2251
|
+
}
|
|
2252
|
+
if (value.length > MAX_FILTER_STRING_LENGTH) {
|
|
2253
|
+
throw new EmDashValidationError(
|
|
2254
|
+
`Filter value for field "${field}" exceeds ${MAX_FILTER_STRING_LENGTH} characters`,
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
return value;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
private normalizeFieldFilter(
|
|
2261
|
+
field: string,
|
|
2262
|
+
type: FieldType,
|
|
2263
|
+
value: ContentFieldFilterValue,
|
|
2264
|
+
): ResolvedFieldFilter {
|
|
2265
|
+
if (value === null) return { column: field, kind: "null" };
|
|
2266
|
+
if (typeof value !== "object") {
|
|
2267
|
+
return {
|
|
2268
|
+
column: field,
|
|
2269
|
+
kind: "exact",
|
|
2270
|
+
value: this.normalizeFilterScalar(field, type, value),
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
if (Array.isArray(value)) {
|
|
2274
|
+
throw new EmDashValidationError(`Invalid filter for field "${field}"`);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
const record = value as Record<string, unknown>;
|
|
2278
|
+
const keys = Object.keys(record);
|
|
2279
|
+
if (keys.length === 1 && keys[0] === "in") {
|
|
2280
|
+
if (!Array.isArray(record.in) || record.in.length === 0) {
|
|
2281
|
+
throw new EmDashValidationError(`IN filter for field "${field}" must not be empty`);
|
|
2282
|
+
}
|
|
2283
|
+
if (record.in.length > MAX_IN_FILTER_VALUES) {
|
|
2284
|
+
throw new EmDashValidationError(
|
|
2285
|
+
`IN filter for field "${field}" exceeds ${MAX_IN_FILTER_VALUES} values`,
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
return {
|
|
2289
|
+
column: field,
|
|
2290
|
+
kind: "in",
|
|
2291
|
+
values: record.in.map((entry) => this.normalizeFilterScalar(field, type, entry)),
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const rangeKeys = new Set(["gt", "gte", "lt", "lte"]);
|
|
2296
|
+
if (keys.length === 0 || keys.some((key) => !rangeKeys.has(key))) {
|
|
2297
|
+
throw new EmDashValidationError(`Invalid filter operator for field "${field}"`);
|
|
2298
|
+
}
|
|
2299
|
+
if (type === "boolean") {
|
|
2300
|
+
throw new EmDashValidationError(`Boolean field "${field}" does not support range filters`);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
const bounds: Partial<Record<"gt" | "gte" | "lt" | "lte", NormalizedFilterScalar>> = {};
|
|
2304
|
+
for (const key of keys as Array<"gt" | "gte" | "lt" | "lte">) {
|
|
2305
|
+
if (record[key] === undefined) continue;
|
|
2306
|
+
bounds[key] = this.normalizeFilterScalar(field, type, record[key]);
|
|
2307
|
+
}
|
|
2308
|
+
if (Object.keys(bounds).length === 0) {
|
|
2309
|
+
throw new EmDashValidationError(`Range filter for field "${field}" has no bounds`);
|
|
2310
|
+
}
|
|
2311
|
+
return { column: field, kind: "range", bounds };
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
private async collectionExists(type: string): Promise<boolean> {
|
|
2315
|
+
const collection = await this.db
|
|
2316
|
+
.selectFrom("_emdash_collections")
|
|
2317
|
+
.where("slug", "=", type)
|
|
2318
|
+
.select("id")
|
|
2319
|
+
.executeTakeFirst();
|
|
2320
|
+
return collection !== undefined;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
private async resolveFieldFilters(
|
|
2324
|
+
type: string,
|
|
2325
|
+
filters: ContentFieldFilters | undefined,
|
|
2326
|
+
): Promise<ResolvedFieldFilter[]> {
|
|
2327
|
+
const resolvedFilters = filters ?? {};
|
|
2328
|
+
const fields = Object.keys(resolvedFilters);
|
|
2329
|
+
if (fields.length === 0) return [];
|
|
2330
|
+
if (fields.length > MAX_INDEXED_FIELD_FILTERS) {
|
|
2331
|
+
if (!(await this.collectionExists(type))) return [];
|
|
2332
|
+
throw new EmDashValidationError(
|
|
2333
|
+
`Content list queries support at most ${MAX_INDEXED_FIELD_FILTERS} indexed field filters`,
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
const rows = await this.db
|
|
2337
|
+
.selectFrom("_emdash_fields as field")
|
|
2338
|
+
.innerJoin("_emdash_collections as collection", "collection.id", "field.collection_id")
|
|
2339
|
+
.where("collection.slug", "=", type)
|
|
2340
|
+
.where("field.slug", "in", fields)
|
|
2341
|
+
.where("field.indexed", "=", 1)
|
|
2342
|
+
.select(["field.slug", "field.type"])
|
|
2343
|
+
.execute();
|
|
2344
|
+
const metadata = new Map(rows.map((row) => [row.slug, row.type as FieldType]));
|
|
2345
|
+
|
|
2346
|
+
if (metadata.size === 0 && !(await this.collectionExists(type))) return [];
|
|
2347
|
+
|
|
2348
|
+
for (const field of fields) {
|
|
2349
|
+
try {
|
|
2350
|
+
validateIdentifier(field, "content filter field");
|
|
2351
|
+
} catch {
|
|
2352
|
+
throw new EmDashValidationError(`Invalid content filter field: ${field}`);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
const normalized = fields.map((field) => {
|
|
2357
|
+
const fieldType = metadata.get(field);
|
|
2358
|
+
if (!fieldType || !isIndexableFieldType(fieldType)) {
|
|
2359
|
+
throw new EmDashValidationError(
|
|
2360
|
+
`Cannot filter by field "${field}". Custom fields must be indexed before filtering.`,
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
return this.normalizeFieldFilter(field, fieldType, resolvedFilters[field]);
|
|
2364
|
+
});
|
|
2365
|
+
const operandCount = normalized.reduce((total, filter) => {
|
|
2366
|
+
if (filter.kind === "null") return total;
|
|
2367
|
+
if (filter.kind === "exact") return total + 1;
|
|
2368
|
+
if (filter.kind === "in") return total + filter.values.length;
|
|
2369
|
+
return total + Object.keys(filter.bounds).length;
|
|
2370
|
+
}, 0);
|
|
2371
|
+
if (operandCount > SQL_BATCH_SIZE) {
|
|
2372
|
+
throw new EmDashValidationError(
|
|
2373
|
+
`Indexed field filters have a total operand budget of ${SQL_BATCH_SIZE}`,
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
return normalized;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
private applyFieldFilters<QB extends { where: (cb: (eb: any) => unknown) => QB }>(
|
|
2380
|
+
query: QB,
|
|
2381
|
+
filters: ResolvedFieldFilter[],
|
|
2382
|
+
): QB {
|
|
2383
|
+
let next = query;
|
|
2384
|
+
for (const filter of filters) {
|
|
2385
|
+
const column = sql.ref(filter.column);
|
|
2386
|
+
const isPresent = sql<boolean>`${column} IS NOT NULL`;
|
|
2387
|
+
if (filter.kind === "null") {
|
|
2388
|
+
next = next.where(() => sql<boolean>`(${isPresent}) = FALSE AND ${column} IS NULL`);
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
if (filter.kind === "exact") {
|
|
2392
|
+
next = next.where(
|
|
2393
|
+
() => sql<boolean>`(${isPresent}) = TRUE AND ${column} = ${filter.value}`,
|
|
2394
|
+
);
|
|
2395
|
+
continue;
|
|
2396
|
+
}
|
|
2397
|
+
if (filter.kind === "in") {
|
|
2398
|
+
const values = sql.join(
|
|
2399
|
+
filter.values.map((value) => sql`${value}`),
|
|
2400
|
+
sql`, `,
|
|
2401
|
+
);
|
|
2402
|
+
next = next.where(() => sql<boolean>`(${isPresent}) = TRUE AND ${column} IN (${values})`);
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
next = next.where(() => sql<boolean>`(${isPresent}) = TRUE`);
|
|
2407
|
+
if (filter.bounds.gt !== undefined) {
|
|
2408
|
+
next = next.where(() => sql<boolean>`${column} > ${filter.bounds.gt}`);
|
|
2409
|
+
}
|
|
2410
|
+
if (filter.bounds.gte !== undefined) {
|
|
2411
|
+
next = next.where(() => sql<boolean>`${column} >= ${filter.bounds.gte}`);
|
|
2412
|
+
}
|
|
2413
|
+
if (filter.bounds.lt !== undefined) {
|
|
2414
|
+
next = next.where(() => sql<boolean>`${column} < ${filter.bounds.lt}`);
|
|
2415
|
+
}
|
|
2416
|
+
if (filter.bounds.lte !== undefined) {
|
|
2417
|
+
next = next.where(() => sql<boolean>`${column} <= ${filter.bounds.lte}`);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return next;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
1691
2423
|
/**
|
|
1692
2424
|
* Map order field names to database columns.
|
|
1693
2425
|
* Only allows known fields to prevent column enumeration via crafted orderBy values.
|
|
1694
2426
|
*/
|
|
1695
|
-
private mapOrderField(field: string): string {
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
}
|
|
2427
|
+
private mapOrderField(field: string, sortableExtras: readonly string[] = []): string {
|
|
2428
|
+
const mapped = ORDER_FIELD_COLUMNS[field];
|
|
2429
|
+
if (mapped) return mapped;
|
|
2430
|
+
|
|
2431
|
+
// A collection's configured titleField/dateField are allowed as
|
|
2432
|
+
// sort columns. The caller passes the collection's *actual* values (resolved
|
|
2433
|
+
// server-side, never client-supplied), so this stays a closed set per
|
|
2434
|
+
// request and doesn't reopen the column-enumeration hole. The slug is a
|
|
2435
|
+
// validated identifier that maps directly to the column.
|
|
2436
|
+
if (sortableExtras.includes(field)) {
|
|
2437
|
+
validateIdentifier(field, "order field");
|
|
2438
|
+
return field;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
throw new EmDashValidationError(`Invalid order field: ${field}`);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
private async resolveOrderField(
|
|
2445
|
+
type: string,
|
|
2446
|
+
field: string,
|
|
2447
|
+
sortableExtras: readonly string[] = [],
|
|
2448
|
+
): Promise<ResolvedOrderField> {
|
|
2449
|
+
try {
|
|
2450
|
+
return { column: this.mapOrderField(field, sortableExtras), indexedCustomField: false };
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
if (!(error instanceof EmDashValidationError)) throw error;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
const customField = await this.db
|
|
2456
|
+
.selectFrom("_emdash_collections as collection")
|
|
2457
|
+
.leftJoin("_emdash_fields as field", (join) =>
|
|
2458
|
+
join
|
|
2459
|
+
.onRef("field.collection_id", "=", "collection.id")
|
|
2460
|
+
.on("field.slug", "=", field)
|
|
2461
|
+
.on("field.indexed", "=", 1),
|
|
2462
|
+
)
|
|
2463
|
+
.where("collection.slug", "=", type)
|
|
2464
|
+
.select(["collection.id as collectionId", "field.slug as fieldSlug"])
|
|
2465
|
+
.executeTakeFirst();
|
|
1708
2466
|
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
throw new EmDashValidationError(`Invalid order field: ${field}`);
|
|
2467
|
+
if (!customField) {
|
|
2468
|
+
throw new ContentCollectionNotFoundError(type);
|
|
1712
2469
|
}
|
|
1713
|
-
|
|
2470
|
+
|
|
2471
|
+
if (!customField.fieldSlug) {
|
|
2472
|
+
throw new EmDashValidationError(
|
|
2473
|
+
`Invalid order field: ${field}. Custom fields must be indexed before sorting.`,
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
validateIdentifier(customField.fieldSlug, "content order field");
|
|
2478
|
+
return { column: customField.fieldSlug, indexedCustomField: true };
|
|
1714
2479
|
}
|
|
1715
2480
|
}
|