emdash 0.35.0 → 0.37.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/activation-K0kGBWuJ.mjs +382 -0
- package/dist/activation-K0kGBWuJ.mjs.map +1 -0
- package/dist/{adapters-Bb1vG41D.d.mts → adapters-BDQwg236.d.mts} +6 -4
- package/dist/{adapters-Bb1vG41D.d.mts.map → adapters-BDQwg236.d.mts.map} +1 -1
- package/dist/{allowed-origins-DW7sYyqg.mjs → allowed-origins-BGglilcs.mjs} +2 -2
- package/dist/{allowed-origins-DW7sYyqg.mjs.map → allowed-origins-BGglilcs.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +23 -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 +5 -5
- package/dist/api-Dz6rgoa3.mjs +10040 -0
- package/dist/api-Dz6rgoa3.mjs.map +1 -0
- package/dist/{api-tokens-KO24G1Fd.mjs → api-tokens-BSsQACNe.mjs} +2 -2
- package/dist/{api-tokens-KO24G1Fd.mjs.map → api-tokens-BSsQACNe.mjs.map} +1 -1
- package/dist/{apply-kUDt80-G.mjs → apply-B4SIOxR1.mjs} +34 -35
- package/dist/{apply-kUDt80-G.mjs.map → apply-B4SIOxR1.mjs.map} +1 -1
- package/dist/astro/image-endpoint.mjs +2 -2
- package/dist/astro/image-endpoint.mjs.map +1 -1
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +128 -44
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +10 -7
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs +3 -3
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +11 -12
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +237 -1412
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/index.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +16 -16
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +16 -16
- package/dist/astro/routes/api/admin/bylines/index.mjs +16 -16
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -13
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/media-usage/activation.d.mts +9 -0
- package/dist/astro/routes/api/admin/media-usage/activation.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/activation.mjs +46 -0
- package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/progress.d.mts +9 -0
- package/dist/astro/routes/api/admin/media-usage/progress.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/progress.mjs +44 -0
- package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +16 -11
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +55 -49
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +55 -49
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +54 -48
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +54 -48
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +54 -48
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +54 -48
- 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 +60 -50
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +179 -139
- 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 +61 -51
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts +8 -0
- package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +93 -0
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -0
- package/dist/astro/routes/api/admin/plugins/updates.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +53 -47
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +53 -47
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
- package/dist/astro/routes/api/admin/users/index.mjs +6 -6
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
- package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
- package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
- package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/me.mjs +19 -9
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +2 -2
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
- package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
- package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
- package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -13
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +14 -5
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +13 -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 +17 -15
- 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 +17 -15
- 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 +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +14 -14
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +14 -5
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
- package/dist/astro/routes/api/dashboard.mjs +8 -8
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +17 -17
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +13 -13
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -10
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +17 -17
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +27 -27
- package/dist/astro/routes/api/manifest.mjs +4 -4
- package/dist/astro/routes/api/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +188 -65
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
- package/dist/astro/routes/api/media/_id_/replace.d.mts +8 -0
- package/dist/astro/routes/api/media/_id_/replace.d.mts.map +1 -0
- package/dist/astro/routes/api/media/_id_/replace.mjs +91 -0
- package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -0
- package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
- package/dist/astro/routes/api/media/_id_/usage.mjs +18 -13
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +21 -13
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +3 -2
- package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
- package/dist/astro/routes/api/media/folders/_id_.d.mts +10 -0
- package/dist/astro/routes/api/media/folders/_id_.d.mts.map +1 -0
- package/dist/astro/routes/api/media/folders/_id_.mjs +52 -0
- package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -0
- package/dist/astro/routes/api/media/folders/index.d.mts +9 -0
- package/dist/astro/routes/api/media/folders/index.d.mts.map +1 -0
- package/dist/astro/routes/api/media/folders/index.mjs +41 -0
- package/dist/astro/routes/api/media/folders/index.mjs.map +1 -0
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +16 -14
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.d.mts.map +1 -1
- package/dist/astro/routes/api/media.mjs +59 -27
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +20 -13
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +15 -11
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -11
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +16 -12
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +21 -14
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +15 -11
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
- package/dist/astro/routes/api/oauth/token.mjs +5 -5
- package/dist/astro/routes/api/openapi.json.mjs +253 -40
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
- package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
- package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
- package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
- package/dist/astro/routes/api/redirects/index.mjs +11 -11
- package/dist/astro/routes/api/relations/_id_/index.mjs +17 -15
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +12 -10
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +17 -15
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +53 -47
- 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 +53 -47
- 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 +53 -47
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +53 -47
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +53 -47
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +53 -47
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +10 -8
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +53 -47
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +53 -47
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +10 -10
- package/dist/astro/routes/api/search/index.mjs +9 -9
- package/dist/astro/routes/api/search/rebuild.mjs +10 -10
- package/dist/astro/routes/api/search/stats.mjs +5 -5
- package/dist/astro/routes/api/search/suggest.mjs +9 -9
- package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
- package/dist/astro/routes/api/sections/index.mjs +9 -9
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +5 -5
- package/dist/astro/routes/api/settings/backups/export.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/index.mjs +7 -7
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings/email.mjs.map +1 -1
- package/dist/astro/routes/api/settings.d.mts.map +1 -1
- package/dist/astro/routes/api/settings.mjs +20 -16
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
- package/dist/astro/routes/api/setup/admin.mjs +11 -11
- package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -28
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +32 -30
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +7 -7
- package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +20 -16
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +21 -17
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -18
- 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 +20 -16
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +14 -14
- package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_.mjs +26 -19
- package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +20 -16
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -8
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +17 -13
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +15 -12
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -6
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +15 -12
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +7 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
- package/dist/astro/routes/sitemap.xml.mjs +8 -8
- package/dist/astro/types.d.mts +37 -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-DUElxgOQ.mjs → authorize-Bkf_UHhD.mjs} +2 -2
- package/dist/{authorize-DUElxgOQ.mjs.map → authorize-Bkf_UHhD.mjs.map} +1 -1
- package/dist/{backup-DWYpJlLj.mjs → backup-rwVAl9NI.mjs} +5 -5
- package/dist/{backup-DWYpJlLj.mjs.map → backup-rwVAl9NI.mjs.map} +1 -1
- package/dist/{bundle-vn56sLos.mjs → bundle-C8S2Zl6U.mjs} +4 -4
- package/dist/{bundle-vn56sLos.mjs.map → bundle-C8S2Zl6U.mjs.map} +1 -1
- package/dist/{byline-yjvWq4ip.mjs → byline-DuhF7NWl.mjs} +27 -14
- package/dist/byline-DuhF7NWl.mjs.map +1 -0
- package/dist/{byline-fields-txaLGoME.mjs → byline-fields-BovY4KYM.mjs} +2 -2
- package/dist/{byline-fields-txaLGoME.mjs.map → byline-fields-BovY4KYM.mjs.map} +1 -1
- package/dist/{byline-registry-DDun8sxh.mjs → byline-registry-Bpu9-MVs.mjs} +2 -2
- package/dist/{byline-registry-DDun8sxh.mjs.map → byline-registry-Bpu9-MVs.mjs.map} +1 -1
- package/dist/{bylines-DjXS5p0P.mjs → bylines-CJ5nd6Lw.mjs} +7 -7
- package/dist/{bylines-DjXS5p0P.mjs.map → bylines-CJ5nd6Lw.mjs.map} +1 -1
- package/dist/{bylines-Bu7pchQ4.mjs → bylines-RwBAeOF1.mjs} +3 -3
- package/dist/{bylines-Bu7pchQ4.mjs.map → bylines-RwBAeOF1.mjs.map} +1 -1
- package/dist/{cache-C46-3Irl.mjs → cache-BF5Kvn59.mjs} +4 -4
- package/dist/{cache-C46-3Irl.mjs.map → cache-BF5Kvn59.mjs.map} +1 -1
- package/dist/capture-triggers-CNW1q3HK.mjs +362 -0
- package/dist/capture-triggers-CNW1q3HK.mjs.map +1 -0
- package/dist/{challenge-store-SIL5DsF2.mjs → challenge-store-DQN_Yrck.mjs} +5 -3
- package/dist/challenge-store-DQN_Yrck.mjs.map +1 -0
- package/dist/chrome-tags-D8fiZG94.mjs +24 -0
- package/dist/chrome-tags-D8fiZG94.mjs.map +1 -0
- package/dist/{chunks-fiZ8G9RJ.mjs → chunks-C4szrD8o.mjs} +2 -2
- package/dist/{chunks-fiZ8G9RJ.mjs.map → chunks-C4szrD8o.mjs.map} +1 -1
- package/dist/cli/index.mjs +37 -53
- 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 +54 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/{collection-deletion-DQPvMD8R.mjs → collection-deletion-Bj07NHbj.mjs} +13 -356
- package/dist/collection-deletion-Bj07NHbj.mjs.map +1 -0
- package/dist/{comment-reaction-D-2J84UE.mjs → comment-reaction-BhrvSHml.mjs} +2 -2
- package/dist/{comment-reaction-D-2J84UE.mjs.map → comment-reaction-BhrvSHml.mjs.map} +1 -1
- package/dist/{comment-Dxlep6cE.mjs → comment-vxOzWbZN.mjs} +2 -2
- package/dist/{comment-Dxlep6cE.mjs.map → comment-vxOzWbZN.mjs.map} +1 -1
- package/dist/{comments-CTfZtLmM.mjs → comments-Ct4ensnf.mjs} +2 -2
- package/dist/{comments-CTfZtLmM.mjs.map → comments-Ct4ensnf.mjs.map} +1 -1
- package/dist/{components-CQy3lh9x.mjs → components-DJbo1z0S.mjs} +1 -1
- package/dist/{components-CQy3lh9x.mjs.map → components-DJbo1z0S.mjs.map} +1 -1
- package/dist/{config-COQzTe5Q.d.mts → config-B9eZ340f.d.mts} +1 -1
- package/dist/{config-COQzTe5Q.d.mts.map → config-B9eZ340f.d.mts.map} +1 -1
- package/dist/config-it29yDLA.mjs +1163 -0
- package/dist/config-it29yDLA.mjs.map +1 -0
- package/dist/{content-ZHCc1ngN.mjs → content-DjvM0T92.mjs} +86 -45
- package/dist/content-DjvM0T92.mjs.map +1 -0
- package/dist/{content-list-query-wJ2WN8Vp.d.mts → content-list-query-Btqd4uV2.d.mts} +1 -1
- package/dist/{content-list-query-wJ2WN8Vp.d.mts.map → content-list-query-Btqd4uV2.d.mts.map} +1 -1
- package/dist/{content-refresh-B_8SJzuU.mjs → content-refresh-Bwp-8yGy.mjs} +1139 -111
- package/dist/content-refresh-Bwp-8yGy.mjs.map +1 -0
- package/dist/{media-usage-BZCydfvu.mjs → content-repair-Czdp7jz5.mjs} +5 -196
- package/dist/content-repair-Czdp7jz5.mjs.map +1 -0
- package/dist/{context-ZldbZcle.mjs → context-CX6gxOLW.mjs} +55 -20
- package/dist/context-CX6gxOLW.mjs.map +1 -0
- package/dist/{cron-Y9aLtgrA.mjs → cron-C6o7VppJ.mjs} +1 -1
- package/dist/{cron-Y9aLtgrA.mjs.map → cron-C6o7VppJ.mjs.map} +1 -1
- package/dist/{dashboard-DvHbk_cv.mjs → dashboard-C2kTeeRI.mjs} +5 -5
- package/dist/{dashboard-DvHbk_cv.mjs.map → dashboard-C2kTeeRI.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +3 -3
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql-migrations.d.mts +1 -1
- package/dist/db/libsql-migrations.mjs +4 -4
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +1 -1
- package/dist/db/postgres-migrations.mjs +4 -4
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/postgres.d.mts.map +1 -1
- package/dist/db/postgres.mjs +19 -3
- package/dist/db/postgres.mjs.map +1 -1
- package/dist/db/sqlite-migrations.d.mts +1 -1
- package/dist/db/sqlite-migrations.mjs +4 -4
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +3 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{default-DdDl3fxv.mjs → default-Clra32mw.mjs} +1 -1
- package/dist/{default-DdDl3fxv.mjs.map → default-Clra32mw.mjs.map} +1 -1
- package/dist/{deferred-tasks-SDWxrgzJ.d.mts → deferred-tasks-CvDETTJO.d.mts} +1 -1
- package/dist/{deferred-tasks-SDWxrgzJ.d.mts.map → deferred-tasks-CvDETTJO.d.mts.map} +1 -1
- package/dist/dev-typegen-DzOPfsNj.mjs +29 -0
- package/dist/dev-typegen-DzOPfsNj.mjs.map +1 -0
- package/dist/{device-flow-C0GdD_z8.mjs → device-flow-D2oCWvLJ.mjs} +5 -5
- package/dist/{device-flow-C0GdD_z8.mjs.map → device-flow-D2oCWvLJ.mjs.map} +1 -1
- package/dist/{direct-executor-CDXeJCSs.mjs → direct-executor-Bfa5_ToE.mjs} +3 -3
- package/dist/{direct-executor-CDXeJCSs.mjs.map → direct-executor-Bfa5_ToE.mjs.map} +1 -1
- package/dist/{dist-D_6CKR8z.mjs → dist-89xCrGqi.mjs} +1 -1
- package/dist/{dist-D_6CKR8z.mjs.map → dist-89xCrGqi.mjs.map} +1 -1
- package/dist/{email-console-BHNjIwiB.mjs → email-console-Dzb6uGYa.mjs} +1 -1
- package/dist/{email-console-BHNjIwiB.mjs.map → email-console-Dzb6uGYa.mjs.map} +1 -1
- package/dist/{enrich-_ySMEvEE.mjs → enrich-BX0IDp4z.mjs} +3 -3
- package/dist/{enrich-_ySMEvEE.mjs.map → enrich-BX0IDp4z.mjs.map} +1 -1
- package/dist/{error-DupyJZE2.mjs → error-DFrQjppn.mjs} +2 -2
- package/dist/{error-DupyJZE2.mjs.map → error-DFrQjppn.mjs.map} +1 -1
- package/dist/{errors-CLUPm7aD.mjs → errors-DsVoDUjJ.mjs} +24 -2
- package/dist/errors-DsVoDUjJ.mjs.map +1 -0
- package/dist/{escape-DUY2r2Zs.mjs → escape-HmHTC1Is.mjs} +1 -1
- package/dist/{escape-DUY2r2Zs.mjs.map → escape-HmHTC1Is.mjs.map} +1 -1
- package/dist/{field-defs-cache-BzwBlJrR.mjs → field-defs-cache-BM4SIRqH.mjs} +5 -5
- package/dist/{field-defs-cache-BzwBlJrR.mjs.map → field-defs-cache-BM4SIRqH.mjs.map} +1 -1
- package/dist/focal-point-BeWFo6Xx.mjs +23 -0
- package/dist/focal-point-BeWFo6Xx.mjs.map +1 -0
- package/dist/{fts-manager-DyZEohZC.mjs → fts-manager-Do1BlW2r.mjs} +3 -3
- package/dist/{fts-manager-DyZEohZC.mjs.map → fts-manager-Do1BlW2r.mjs.map} +1 -1
- package/dist/{hash-Cg0TXJPV.mjs → hash-3JJPlHF2.mjs} +1 -1
- package/dist/{hash-Cg0TXJPV.mjs.map → hash-3JJPlHF2.mjs.map} +1 -1
- package/dist/hook-result-DCh2WUpe.d.mts +31 -0
- package/dist/hook-result-DCh2WUpe.d.mts.map +1 -0
- package/dist/{hreflang-DwT2E0Sa.d.mts → hreflang-B6hbAuuO.d.mts} +2 -2
- package/dist/{hreflang-DwT2E0Sa.d.mts.map → hreflang-B6hbAuuO.d.mts.map} +1 -1
- package/dist/{hreflang-BBe83kKa.mjs → hreflang-DQbPZliA.mjs} +7 -7
- package/dist/{hreflang-BBe83kKa.mjs.map → hreflang-DQbPZliA.mjs.map} +1 -1
- package/dist/{identity-Ys6Dlc9C.mjs → identity-DksvEazf.mjs} +3 -3
- package/dist/{identity-Ys6Dlc9C.mjs.map → identity-DksvEazf.mjs.map} +1 -1
- package/dist/{import-DPkLmeXO.mjs → import-CJpaBBBT.mjs} +6 -6
- package/dist/{import-DPkLmeXO.mjs.map → import-CJpaBBBT.mjs.map} +1 -1
- package/dist/{index-BVBZj4-7.d.mts → index-BJNMCWmu.d.mts} +267 -82
- package/dist/index-BJNMCWmu.d.mts.map +1 -0
- package/dist/{index-C9KuQyO-.d.mts → index-Du8K7bWa.d.mts} +3 -3
- package/dist/{index-C9KuQyO-.d.mts.map → index-Du8K7bWa.d.mts.map} +1 -1
- package/dist/index.d.mts +22 -21
- package/dist/index.mjs +79 -73
- package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
- package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
- package/dist/{load-YJ2xl3ra.mjs → load-CawHjTOH.mjs} +2 -2
- package/dist/{load-YJ2xl3ra.mjs.map → load-CawHjTOH.mjs.map} +1 -1
- package/dist/{loader-CdG_Ci3H.mjs → loader-Z8vl7zTq.mjs} +48 -13
- package/dist/loader-Z8vl7zTq.mjs.map +1 -0
- package/dist/{manifest-builder-B252yQUf.mjs → manifest-builder-COtzuca2.mjs} +2 -2
- package/dist/{manifest-builder-B252yQUf.mjs.map → manifest-builder-COtzuca2.mjs.map} +1 -1
- package/dist/{manifest-schema-BXbUAHod.mjs → manifest-schema-BNWwR8Vd.mjs} +5 -5
- package/dist/manifest-schema-BNWwR8Vd.mjs.map +1 -0
- package/dist/media/image-endpoint.d.mts +4 -2
- package/dist/media/image-endpoint.d.mts.map +1 -1
- package/dist/media/image-endpoint.mjs +6 -4
- package/dist/media/image-endpoint.mjs.map +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +13 -3
- package/dist/media/index.mjs.map +1 -1
- package/dist/media/local-runtime.d.mts +14 -12
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +12 -6
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-CzhAn2--.mjs → media-BBm8Wg3f.mjs} +69 -12
- package/dist/media-BBm8Wg3f.mjs.map +1 -0
- package/dist/{media-BWWWb8kg.mjs → media-BHjus86Y.mjs} +133 -9
- package/dist/media-BHjus86Y.mjs.map +1 -0
- package/dist/{media-allowlist-D6x-Us02.mjs → media-allowlist-CoTBWoDp.mjs} +3 -2
- package/dist/media-allowlist-CoTBWoDp.mjs.map +1 -0
- package/dist/media-folders-BGkATs8n.mjs +210 -0
- package/dist/media-folders-BGkATs8n.mjs.map +1 -0
- package/dist/{media-upload-CHBVn6m-.mjs → media-upload-BCidR-EY.mjs} +9 -9
- package/dist/{media-upload-CHBVn6m-.mjs.map → media-upload-BCidR-EY.mjs.map} +1 -1
- package/dist/{media-url-BvoX88FA.mjs → media-url-DHoPr0hq.mjs} +1 -1
- package/dist/{media-url-BvoX88FA.mjs.map → media-url-DHoPr0hq.mjs.map} +1 -1
- package/dist/media-usage-CdY5jhSR.mjs +1833 -0
- package/dist/media-usage-CdY5jhSR.mjs.map +1 -0
- package/dist/media-usage-activation-BgeMtK5C.mjs +78 -0
- package/dist/media-usage-activation-BgeMtK5C.mjs.map +1 -0
- package/dist/{media-usage-work-CLo_6tct.mjs → media-usage-work-CnGCdHxx.mjs} +4 -4
- package/dist/{media-usage-work-CLo_6tct.mjs.map → media-usage-work-CnGCdHxx.mjs.map} +1 -1
- package/dist/{media-usage-work-DrCbqBcl.mjs → media-usage-work-D6D9sMCA.mjs} +221 -59
- package/dist/media-usage-work-D6D9sMCA.mjs.map +1 -0
- package/dist/{media-usage-write-fence-aHEgN1zB.mjs → media-usage-write-fence-hjeD4u7s.mjs} +2 -2
- package/dist/{media-usage-write-fence-aHEgN1zB.mjs.map → media-usage-write-fence-hjeD4u7s.mjs.map} +1 -1
- package/dist/{menus-DSo5QeW0.mjs → menus-6qSwTNxG.mjs} +375 -107
- package/dist/menus-6qSwTNxG.mjs.map +1 -0
- package/dist/{menus-TCNSd2vp.mjs → menus-uvPLXMZf.mjs} +4 -4
- package/dist/{menus-TCNSd2vp.mjs.map → menus-uvPLXMZf.mjs.map} +1 -1
- package/dist/migrations/index.d.mts +1 -1
- package/dist/migrations/index.mjs +3 -3
- package/dist/{mime-DfIVjNkr.mjs → mime-CUIG6x84.mjs} +1 -1
- package/dist/mime-CUIG6x84.mjs.map +1 -0
- package/dist/{mode-BDoH_WcF.mjs → mode-DN_WneRx.mjs} +1 -1
- package/dist/{mode-BDoH_WcF.mjs.map → mode-DN_WneRx.mjs.map} +1 -1
- package/dist/node-sqlite-compat-BuJXIeMz.mjs +46 -0
- package/dist/node-sqlite-compat-BuJXIeMz.mjs.map +1 -0
- package/dist/{normalize-Bc-s_r-a.mjs → normalize-C2xNjTSG.mjs} +36 -5
- package/dist/normalize-C2xNjTSG.mjs.map +1 -0
- package/dist/{oauth-authorization-iOikmrJd.mjs → oauth-authorization-Dj6Kwc21.mjs} +5 -5
- package/dist/{oauth-authorization-iOikmrJd.mjs.map → oauth-authorization-Dj6Kwc21.mjs.map} +1 -1
- package/dist/{oauth-clients-CvI0nHMJ.mjs → oauth-clients-FD7I0J8m.mjs} +1 -1
- package/dist/{oauth-clients-CvI0nHMJ.mjs.map → oauth-clients-FD7I0J8m.mjs.map} +1 -1
- package/dist/{oauth-state-store-Ba8KZlwW.mjs → oauth-state-store-DkmDOmHs.mjs} +1 -1
- package/dist/{oauth-state-store-Ba8KZlwW.mjs.map → oauth-state-store-DkmDOmHs.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-C5SQwGYC.mjs → oauth-user-lookup-CvIwGpYd.mjs} +1 -1
- package/dist/{oauth-user-lookup-C5SQwGYC.mjs.map → oauth-user-lookup-CvIwGpYd.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{object-cache-71tPfV4A.mjs → object-cache-DVKnyOfy.mjs} +19 -3
- package/dist/object-cache-DVKnyOfy.mjs.map +1 -0
- package/dist/{options-CBXIHUoT.mjs → options-BHPeIbBd.mjs} +1 -1
- package/dist/{options-CBXIHUoT.mjs.map → options-BHPeIbBd.mjs.map} +1 -1
- package/dist/{options--hhz-jZ1.d.mts → options-CYb4y7hM.d.mts} +24 -11
- package/dist/options-CYb4y7hM.d.mts.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-DFbuc-Fh.mjs → parse-Ckpg-Qfp.mjs} +2 -2
- package/dist/{parse-DFbuc-Fh.mjs.map → parse-Ckpg-Qfp.mjs.map} +1 -1
- package/dist/{passkey-config-DFAmUyuE.mjs → passkey-config-O_lj3y0k.mjs} +1 -1
- package/dist/{passkey-config-DFAmUyuE.mjs.map → passkey-config-O_lj3y0k.mjs.map} +1 -1
- package/dist/{patterns-CiyXeDgr.mjs → patterns-D9iDO3j8.mjs} +1 -1
- package/dist/{patterns-CiyXeDgr.mjs.map → patterns-D9iDO3j8.mjs.map} +1 -1
- package/dist/{placeholder-Dp0K3RkD.d.mts → placeholder-4X9BrRrp.d.mts} +7 -1
- package/dist/placeholder-4X9BrRrp.d.mts.map +1 -0
- package/dist/plugin-types.d.mts +8 -3
- 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 +2 -2
- package/dist/{policy-TluUIxuu.mjs → policy-Dhtwntrt.mjs} +2 -2
- package/dist/{policy-TluUIxuu.mjs.map → policy-Dhtwntrt.mjs.map} +1 -1
- package/dist/{portable-text-imBW5cHK.mjs → portable-text-U85hOafW.mjs} +1 -1
- package/dist/{portable-text-imBW5cHK.mjs.map → portable-text-U85hOafW.mjs.map} +1 -1
- package/dist/{preview-PW-nOnq4.mjs → preview-B_vJLvf8.mjs} +2 -2
- package/dist/{preview-PW-nOnq4.mjs.map → preview-B_vJLvf8.mjs.map} +1 -1
- package/dist/{protocol-DjsJHTlK.d.mts → protocol-JdD2YhY9.d.mts} +2 -2
- package/dist/{protocol-DjsJHTlK.d.mts.map → protocol-JdD2YhY9.d.mts.map} +1 -1
- package/dist/{public-url-CWoFhQT8.mjs → public-url-CarhZJgf.mjs} +1 -1
- package/dist/{public-url-CWoFhQT8.mjs.map → public-url-CarhZJgf.mjs.map} +1 -1
- package/dist/{query-zTVAR1Sl.mjs → query-BZKZcG5b.mjs} +15 -15
- package/dist/{query-zTVAR1Sl.mjs.map → query-BZKZcG5b.mjs.map} +1 -1
- package/dist/{query-BWs7O5Il.mjs → query-BndaFxxp.mjs} +5 -5
- package/dist/{query-BWs7O5Il.mjs.map → query-BndaFxxp.mjs.map} +1 -1
- package/dist/{rate-limit-Bx4g9m3p.mjs → rate-limit-BZmATC6W.mjs} +2 -2
- package/dist/{rate-limit-Bx4g9m3p.mjs.map → rate-limit-BZmATC6W.mjs.map} +1 -1
- package/dist/{redirect-COsS6SWJ.mjs → redirect-CZkbozu9.mjs} +2 -2
- package/dist/{redirect-COsS6SWJ.mjs.map → redirect-CZkbozu9.mjs.map} +1 -1
- package/dist/{redirect-BgAOGHDq.mjs → redirect-CzqnLxK-.mjs} +1 -1
- package/dist/{redirect-BgAOGHDq.mjs.map → redirect-CzqnLxK-.mjs.map} +1 -1
- package/dist/{redirects-DTDHWH31.mjs → redirects-BLNOWQTn.mjs} +64 -29
- package/dist/redirects-BLNOWQTn.mjs.map +1 -0
- package/dist/{redirects-DyChwDkG.mjs → redirects-CqzNM0KM.mjs} +5 -5
- package/dist/{redirects-DyChwDkG.mjs.map → redirects-CqzNM0KM.mjs.map} +1 -1
- package/dist/{registry-Cqa2I749.mjs → registry-AZAsRsor.mjs} +30 -126
- package/dist/registry-AZAsRsor.mjs.map +1 -0
- package/dist/{relation-BnHB0T4J.mjs → relation-04DfKRTO.mjs} +2 -2
- package/dist/{relation-BnHB0T4J.mjs.map → relation-04DfKRTO.mjs.map} +1 -1
- package/dist/{relations-BBApI4e2.mjs → relations-C4htk5sb.mjs} +7 -7
- package/dist/relations-C4htk5sb.mjs.map +1 -0
- package/dist/{relations-D5sM5fW0.mjs → relations-CcFwDJdJ.mjs} +4 -4
- package/dist/{relations-D5sM5fW0.mjs.map → relations-CcFwDJdJ.mjs.map} +1 -1
- package/dist/{relations-BHo5A8Zf.d.mts → relations-DnRK4YIT.d.mts} +347 -54
- package/dist/relations-DnRK4YIT.d.mts.map +1 -0
- package/dist/{request-cache-BSUptuJR.mjs → request-cache-DH9zdCOD.mjs} +1 -1
- package/dist/{request-cache-BSUptuJR.mjs.map → request-cache-DH9zdCOD.mjs.map} +1 -1
- package/dist/request-context.d.mts +1 -1
- package/dist/{request-meta-DgZJh3zG.mjs → request-meta-DYxLlnmz.mjs} +2 -2
- package/dist/{request-meta-DgZJh3zG.mjs.map → request-meta-DYxLlnmz.mjs.map} +1 -1
- package/dist/{resolve-DOhVEvty.mjs → resolve-prZ3mP7l.mjs} +1 -1
- package/dist/{resolve-DOhVEvty.mjs.map → resolve-prZ3mP7l.mjs.map} +1 -1
- package/dist/{runner-C1T11CfC.mjs → runner-BLIDKpmA.mjs} +471 -290
- package/dist/runner-BLIDKpmA.mjs.map +1 -0
- package/dist/{runner-BYHZn3Jy.d.mts → runner-YKfIMnaF.d.mts} +3 -2
- package/dist/runner-YKfIMnaF.d.mts.map +1 -0
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-DZ5YVVZj.mjs → schema-CGnBeLiW.mjs} +3 -3
- package/dist/{schema-DZ5YVVZj.mjs.map → schema-CGnBeLiW.mjs.map} +1 -1
- package/dist/{schema-2AOsSn2v.mjs → schema-DYr-Vd-L.mjs} +36 -6
- package/dist/{schema-2AOsSn2v.mjs.map → schema-DYr-Vd-L.mjs.map} +1 -1
- package/dist/{scopes-8ClPF2bC.mjs → scopes-BhWvBavj.mjs} +3 -3
- package/dist/{scopes-8ClPF2bC.mjs.map → scopes-BhWvBavj.mjs.map} +1 -1
- package/dist/{search-u1QYdOCG.mjs → search-C-qDk4kx.mjs} +4 -4
- package/dist/{search-u1QYdOCG.mjs.map → search-C-qDk4kx.mjs.map} +1 -1
- package/dist/{secrets-BEgsAQEU.mjs → secrets-rEtHt-6z.mjs} +3 -3
- package/dist/{secrets-BEgsAQEU.mjs.map → secrets-rEtHt-6z.mjs.map} +1 -1
- package/dist/{sections-DmHviGyM.mjs → sections-B_nYvlwn.mjs} +2 -2
- package/dist/{sections-DmHviGyM.mjs.map → sections-B_nYvlwn.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +25 -23
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +17 -15
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-Dq4G-RrO.mjs → seo-926NRIgt.mjs} +3 -3
- package/dist/{seo-Dq4G-RrO.mjs.map → seo-926NRIgt.mjs.map} +1 -1
- package/dist/{seo-AFpVvTqr.mjs → seo-CVWkdv6x.mjs} +1 -1
- package/dist/{seo-AFpVvTqr.mjs.map → seo-CVWkdv6x.mjs.map} +1 -1
- package/dist/{service-BpArayPr.mjs → service-CJl3ZAYs.mjs} +3 -3
- package/dist/{service-BpArayPr.mjs.map → service-CJl3ZAYs.mjs.map} +1 -1
- package/dist/{session-user-BEorKVT-.mjs → session-user-BlMgqCHS.mjs} +1 -1
- package/dist/{session-user-BEorKVT-.mjs.map → session-user-BlMgqCHS.mjs.map} +1 -1
- package/dist/{settings-xRdoUZr0.mjs → settings--wobrkkC.mjs} +21 -8
- package/dist/settings--wobrkkC.mjs.map +1 -0
- package/dist/{settings-DE9xtzqd.mjs → settings-Cwm6o2jY.mjs} +3 -3
- package/dist/{settings-DE9xtzqd.mjs.map → settings-Cwm6o2jY.mjs.map} +1 -1
- package/dist/{setup-complete-BnT8pq_C.mjs → setup-complete-DQbClIN2.mjs} +2 -2
- package/dist/{setup-complete-BnT8pq_C.mjs.map → setup-complete-DQbClIN2.mjs.map} +1 -1
- package/dist/{setup-nonce-CBitf_oQ.mjs → setup-nonce-DZCFS2sn.mjs} +1 -1
- package/dist/{setup-nonce-CBitf_oQ.mjs.map → setup-nonce-DZCFS2sn.mjs.map} +1 -1
- package/dist/{single-flight-cache-B0eLKS6K.mjs → single-flight-cache-BPuGPdcN.mjs} +2 -2
- package/dist/{single-flight-cache-B0eLKS6K.mjs.map → single-flight-cache-BPuGPdcN.mjs.map} +1 -1
- package/dist/{site-url-DjpIXMXJ.mjs → site-url-BrxA_E9H.mjs} +3 -3
- package/dist/{site-url-DjpIXMXJ.mjs.map → site-url-BrxA_E9H.mjs.map} +1 -1
- package/dist/{snapshot-BBlGkb2q.mjs → snapshot--OCDTqdb.mjs} +1 -1
- package/dist/{snapshot-BBlGkb2q.mjs.map → snapshot--OCDTqdb.mjs.map} +1 -1
- package/dist/{ssrf-Cpvg_n3A.mjs → ssrf-5sIM5jzl.mjs} +23 -6
- package/dist/{ssrf-Cpvg_n3A.mjs.map → ssrf-5sIM5jzl.mjs.map} +1 -1
- package/dist/{state-DYwPue40.mjs → state-BpFVsJZB.mjs} +12 -4
- package/dist/state-BpFVsJZB.mjs.map +1 -0
- package/dist/{status-CWVYpbD8.mjs → status-B777-dXz.mjs} +1 -1
- package/dist/{status-CWVYpbD8.mjs.map → status-B777-dXz.mjs.map} +1 -1
- package/dist/storage/local.d.mts +2 -1
- package/dist/storage/local.d.mts.map +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/local.mjs.map +1 -1
- package/dist/storage/s3.d.mts +2 -1
- package/dist/storage/s3.d.mts.map +1 -1
- package/dist/storage/s3.mjs +3 -2
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{target-DixqqV5i.mjs → target-BmR0iTwz.mjs} +1 -1
- package/dist/{target-DixqqV5i.mjs.map → target-BmR0iTwz.mjs.map} +1 -1
- package/dist/{taxonomies-OHUIiIpI.mjs → taxonomies-DkAkMupr.mjs} +17 -14
- package/dist/taxonomies-DkAkMupr.mjs.map +1 -0
- package/dist/{taxonomies-BlrncHi9.mjs → taxonomies-Wfy_Lg12.mjs} +43 -17
- package/dist/taxonomies-Wfy_Lg12.mjs.map +1 -0
- package/dist/{taxonomy-BqCZ1TdU.mjs → taxonomy-tJepplLL.mjs} +5 -5
- package/dist/{taxonomy-BqCZ1TdU.mjs.map → taxonomy-tJepplLL.mjs.map} +1 -1
- package/dist/{tokens-C39klX8R.mjs → tokens-BCO1oruF.mjs} +1 -1
- package/dist/{tokens-C39klX8R.mjs.map → tokens-BCO1oruF.mjs.map} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs → transaction-0KOpOyMu.mjs} +2 -1
- package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-0KOpOyMu.mjs.map} +1 -1
- package/dist/{transport-DBJwa-Si.mjs → transport-Dr3qtwXC.mjs} +4 -3
- package/dist/{transport-DBJwa-Si.mjs.map → transport-Dr3qtwXC.mjs.map} +1 -1
- package/dist/{transport-RQE36APG.d.mts → transport-xh6YKW2G.d.mts} +1 -1
- package/dist/{transport-RQE36APG.d.mts.map → transport-xh6YKW2G.d.mts.map} +1 -1
- package/dist/{trusted-proxy-CEVekpby.mjs → trusted-proxy-BR5F1Otp.mjs} +1 -1
- package/dist/{trusted-proxy-CEVekpby.mjs.map → trusted-proxy-BR5F1Otp.mjs.map} +1 -1
- package/dist/{types-BW1syrlw.mjs → types-3qNFPlci.mjs} +1 -1
- package/dist/{types-BW1syrlw.mjs.map → types-3qNFPlci.mjs.map} +1 -1
- package/dist/types-75KIqzL9.mjs.map +1 -1
- package/dist/{types-DrJrVCT1.d.mts → types-B6GrPic4.d.mts} +9 -3
- package/dist/{types-DrJrVCT1.d.mts.map → types-B6GrPic4.d.mts.map} +1 -1
- package/dist/{types-DN64_zzZ.d.mts → types-BuHW4xDE.d.mts} +11 -10
- package/dist/types-BuHW4xDE.d.mts.map +1 -0
- package/dist/{types-DWRhk9xZ.d.mts → types-C00oyCZp.d.mts} +1 -1
- package/dist/{types-DWRhk9xZ.d.mts.map → types-C00oyCZp.d.mts.map} +1 -1
- package/dist/{types-BntycPTF.d.mts → types-CDtkNSFQ.d.mts} +1 -1
- package/dist/{types-BntycPTF.d.mts.map → types-CDtkNSFQ.d.mts.map} +1 -1
- package/dist/{types-Dfk8KI5R.d.mts → types-CPeXo1wF.d.mts} +1 -1
- package/dist/{types-Dfk8KI5R.d.mts.map → types-CPeXo1wF.d.mts.map} +1 -1
- package/dist/types-CQPWhPxE.mjs.map +1 -1
- package/dist/{types-BcqdcT-r.d.mts → types-CXFkwEGC.d.mts} +3 -2
- package/dist/types-CXFkwEGC.d.mts.map +1 -0
- package/dist/{types-CtzytVXn.mjs → types-DM3vioBY.mjs} +1 -1
- package/dist/{types-CtzytVXn.mjs.map → types-DM3vioBY.mjs.map} +1 -1
- package/dist/{types-CXnOzDe1.d.mts → types-DczELMop.d.mts} +1 -1
- package/dist/{types-CXnOzDe1.d.mts.map → types-DczELMop.d.mts.map} +1 -1
- package/dist/types-DfuTR6dL.mjs +3 -0
- package/dist/{types-DQIDRKF2.d.mts → types-FV23hEoW.d.mts} +10 -1
- package/dist/types-FV23hEoW.d.mts.map +1 -0
- package/dist/{types-C2pMvO62.d.mts → types-xDMtUFnO.d.mts} +2 -1
- package/dist/types-xDMtUFnO.d.mts.map +1 -0
- package/dist/{upload-attempts-D4FS8LT6.mjs → upload-attempts-BsqoQ1fn.mjs} +1 -1
- package/dist/{upload-attempts-D4FS8LT6.mjs.map → upload-attempts-BsqoQ1fn.mjs.map} +1 -1
- package/dist/{url-pattern-BHS0V5Ng.mjs → url-pattern-D4c2czpJ.mjs} +1 -1
- package/dist/{url-pattern-BHS0V5Ng.mjs.map → url-pattern-D4c2czpJ.mjs.map} +1 -1
- package/dist/{user-DJZfUJVE.mjs → user-BTZ40aq6.mjs} +2 -2
- package/dist/{user-DJZfUJVE.mjs.map → user-BTZ40aq6.mjs.map} +1 -1
- package/dist/{utils-D4vD_Y98.mjs → utils-BKvNjjhY.mjs} +1 -1
- package/dist/{utils-D4vD_Y98.mjs.map → utils-BKvNjjhY.mjs.map} +1 -1
- package/dist/{validate-BMLBvxOC.mjs → validate-DE_LRwo9.mjs} +2 -2
- package/dist/{validate-BMLBvxOC.mjs.map → validate-DE_LRwo9.mjs.map} +1 -1
- package/dist/{validate-CTqt5nuE.d.mts → validate-DUP27OYD.d.mts} +5 -5
- package/dist/{validate-CTqt5nuE.d.mts.map → validate-DUP27OYD.d.mts.map} +1 -1
- package/dist/{validation-npvENUCB.mjs → validation-BMImFJ11.mjs} +10 -8
- package/dist/{validation-npvENUCB.mjs.map → validation-BMImFJ11.mjs.map} +1 -1
- package/dist/version-Bii3suaO.mjs +7 -0
- package/dist/{version-DB_fqCSI.mjs.map → version-Bii3suaO.mjs.map} +1 -1
- package/dist/{widgets-BD3ho7Rq.mjs → widgets-D3gJ10xU.mjs} +18 -5
- package/dist/widgets-D3gJ10xU.mjs.map +1 -0
- package/dist/{wxr-taxonomies-Ca68766D.d.mts → wxr-taxonomies-B2Og_6rU.d.mts} +1 -1
- package/dist/{wxr-taxonomies-Ca68766D.d.mts.map → wxr-taxonomies-B2Og_6rU.d.mts.map} +1 -1
- package/dist/{wxr-taxonomies-B1R3vBoD.mjs → wxr-taxonomies-Cxngsjwo.mjs} +4 -4
- package/dist/{wxr-taxonomies-B1R3vBoD.mjs.map → wxr-taxonomies-Cxngsjwo.mjs.map} +1 -1
- package/dist/{zod-generator-LC-on_Vb.mjs → zod-generator-BuYSQJB7.mjs} +32 -22
- package/dist/zod-generator-BuYSQJB7.mjs.map +1 -0
- package/package.json +20 -9
- package/src/api/errors.ts +22 -0
- package/src/api/handlers/content.ts +49 -11
- package/src/api/handlers/index.ts +18 -0
- package/src/api/handlers/manifest.ts +161 -12
- package/src/api/handlers/media-allowlist.ts +1 -0
- package/src/api/handlers/media-folders.ts +130 -0
- package/src/api/handlers/media-usage-activation.ts +102 -0
- package/src/api/handlers/media-usage.ts +108 -0
- package/src/api/handlers/media.ts +92 -2
- package/src/api/handlers/registry.ts +483 -561
- package/src/api/handlers/taxonomies.ts +16 -9
- package/src/api/openapi/document.ts +277 -7
- package/src/api/parse.ts +1 -1
- package/src/api/schemas/auth.ts +4 -4
- package/src/api/schemas/bylines.ts +3 -3
- package/src/api/schemas/comments.ts +1 -1
- package/src/api/schemas/common.ts +3 -2
- package/src/api/schemas/content.ts +78 -22
- package/src/api/schemas/media-usage.ts +77 -0
- package/src/api/schemas/media.ts +121 -0
- package/src/api/schemas/redirects.ts +6 -1
- package/src/api/schemas/schema.ts +4 -0
- package/src/api/schemas/setup.ts +1 -1
- package/src/api/schemas/taxonomies.ts +2 -2
- package/src/api/schemas/users.ts +1 -1
- package/src/api/types.ts +23 -10
- package/src/astro/dev-typegen.ts +35 -0
- package/src/astro/image-endpoint.ts +2 -2
- package/src/astro/integration/dev-typegen.ts +91 -0
- package/src/astro/integration/index.ts +9 -44
- package/src/astro/integration/routes.ts +23 -0
- package/src/astro/integration/vite-config.ts +23 -9
- package/src/astro/middleware/auth.ts +4 -0
- package/src/astro/middleware.ts +5 -7
- package/src/astro/routes/api/admin/api-tokens/index.ts +4 -1
- package/src/astro/routes/api/admin/media-usage/activation.ts +42 -0
- package/src/astro/routes/api/admin/media-usage/progress.ts +37 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +1 -1
- package/src/astro/routes/api/admin/oauth-clients/index.ts +1 -1
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/artifact.ts +247 -101
- package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/verify.ts +60 -0
- package/src/astro/routes/api/auth/me.ts +14 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +8 -2
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +2 -1
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +6 -2
- package/src/astro/routes/api/manifest.ts +1 -1
- package/src/astro/routes/api/media/[id]/replace.ts +114 -0
- package/src/astro/routes/api/media/[id].ts +3 -0
- package/src/astro/routes/api/media/file/[...key].ts +4 -1
- package/src/astro/routes/api/media/folders/[id].ts +55 -0
- package/src/astro/routes/api/media/folders/index.ts +37 -0
- package/src/astro/routes/api/media/upload-url.ts +8 -4
- package/src/astro/routes/api/media.ts +66 -10
- package/src/astro/routes/api/menus/[name]/items/[id].ts +7 -2
- package/src/astro/routes/api/menus/[name]/items.ts +4 -1
- package/src/astro/routes/api/menus/[name]/reorder.ts +4 -1
- package/src/astro/routes/api/menus/[name]/translations.ts +4 -1
- package/src/astro/routes/api/menus/[name].ts +7 -2
- package/src/astro/routes/api/menus/index.ts +4 -1
- package/src/astro/routes/api/settings/email.ts +1 -1
- package/src/astro/routes/api/settings.ts +4 -1
- package/src/astro/routes/api/setup/dev-bypass.ts +12 -0
- package/src/astro/routes/api/taxonomies/[name]/reorder.ts +4 -1
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +4 -1
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +7 -2
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +4 -1
- package/src/astro/routes/api/taxonomies/[name].ts +7 -2
- package/src/astro/routes/api/taxonomies/index.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +3 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +5 -2
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +3 -1
- package/src/astro/routes/api/widget-areas/[name].ts +3 -1
- package/src/astro/routes/api/widget-areas/index.ts +3 -1
- package/src/astro/types.ts +34 -6
- package/src/auth/challenge-store.ts +3 -1
- package/src/aws-sdk.d.ts +1 -0
- package/src/cache/chrome-tags.ts +24 -0
- package/src/client/index.ts +140 -2
- package/src/client/transport.ts +6 -2
- package/src/components/EmDashImage.astro +156 -51
- package/src/components/EmDashMedia.astro +2 -1
- package/src/components/Gallery.astro +7 -0
- package/src/components/InlinePortableTextEditor.tsx +136 -2
- package/src/components/LiveSearch.astro +23 -0
- package/src/components/inline-code-block.tsx +180 -49
- package/src/components/widgets/Archives.astro +2 -37
- package/src/content/converters/gallery.ts +2 -0
- package/src/content/converters/index.ts +2 -0
- package/src/content/converters/mark-safety.ts +1 -0
- package/src/content/converters/portable-text-identity.ts +139 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +202 -69
- package/src/content/converters/prosemirror-to-portable-text.ts +96 -25
- package/src/content/converters/types.ts +3 -0
- package/src/database/connection.ts +2 -31
- package/src/database/index.ts +1 -1
- package/src/database/json-recordset.ts +16 -0
- package/src/database/migrations/017_authorization_codes.ts +14 -4
- package/src/database/migrations/019_i18n.ts +1 -1
- package/src/database/migrations/071_restore_content_bylines_table.ts +43 -0
- package/src/database/migrations/072_media_folders.ts +72 -0
- package/src/database/migrations/073_media_focal_point.ts +57 -0
- package/src/database/migrations/074_content_deleted_scheduled_index.ts +55 -0
- package/src/database/migrations/runner.ts +11 -3
- package/src/database/repositories/byline.ts +32 -16
- package/src/database/repositories/content.ts +119 -51
- package/src/database/repositories/media-folders.ts +115 -0
- package/src/database/repositories/media-usage-work.ts +395 -111
- package/src/database/repositories/media-usage.ts +1104 -47
- package/src/database/repositories/media.ts +156 -29
- package/src/database/repositories/plugin-storage.ts +74 -9
- package/src/database/repositories/types.ts +8 -7
- package/src/database/transaction.ts +1 -0
- package/src/database/types.ts +13 -0
- package/src/db/adapters.ts +10 -3
- package/src/db/node-sqlite-compat.ts +78 -0
- package/src/db/postgres.ts +23 -0
- package/src/db/sqlite.ts +3 -3
- package/src/emdash-runtime.ts +186 -251
- package/src/fields/boolean.ts +1 -1
- package/src/fields/datetime.ts +1 -1
- package/src/fields/file.ts +1 -1
- package/src/fields/image.ts +7 -1
- package/src/fields/integer.ts +1 -1
- package/src/fields/json.ts +1 -1
- package/src/fields/multiselect.ts +1 -1
- package/src/fields/number.ts +1 -1
- package/src/fields/portable-text.ts +1 -1
- package/src/fields/richtext.ts +1 -1
- package/src/fields/select.ts +1 -1
- package/src/fields/slug.ts +1 -1
- package/src/fields/text.ts +1 -1
- package/src/fields/textarea.ts +1 -1
- package/src/fields/types.ts +8 -4
- package/src/index.ts +30 -3
- package/src/loader.ts +63 -10
- package/src/mcp/server.ts +228 -24
- package/src/media/focal-point.ts +37 -0
- package/src/media/image-endpoint.ts +5 -2
- package/src/media/local-runtime.ts +8 -0
- package/src/media/mime.ts +1 -0
- package/src/media/normalize.ts +25 -4
- package/src/media/types.ts +10 -0
- package/src/media/usage/activation.ts +162 -35
- package/src/media/usage/capture-triggers.ts +19 -3
- package/src/media/usage/cleanup.ts +9 -8
- package/src/media/usage/collection-deletion-processor.ts +7 -3
- package/src/media/usage/collection-deletion.ts +10 -0
- package/src/media/usage/content-refresh.ts +258 -39
- package/src/media/usage/content-snapshots.ts +128 -31
- package/src/media/usage/extractor.ts +32 -13
- package/src/media/usage/maintenance-engine.ts +166 -0
- package/src/media/usage/reconciliation-processor.ts +106 -29
- package/src/media/usage/reconciliation.ts +90 -7
- package/src/media/usage/work-processor.ts +342 -101
- package/src/menus/index.ts +16 -0
- package/src/object-cache/index.ts +44 -2
- package/src/plugin-types.ts +18 -1
- package/src/plugins/hooks.ts +4 -1
- package/src/plugins/index.ts +7 -0
- package/src/plugins/manager.ts +2 -1
- package/src/plugins/manifest-schema.ts +1 -1
- package/src/plugins/routes.ts +3 -1
- package/src/plugins/sandbox/hook-result.ts +52 -0
- package/src/plugins/sandbox/index.ts +10 -0
- package/src/plugins/save-rejection.ts +20 -0
- package/src/plugins/scheduler/node.ts +1 -13
- package/src/plugins/scheduler/types.ts +0 -2
- package/src/plugins/state.ts +13 -3
- package/src/plugins/types.ts +6 -0
- package/src/registry/artifact-checksum.ts +15 -0
- package/src/registry/artifact-fetch.ts +530 -0
- package/src/registry/artifact-verification.ts +85 -0
- package/src/registry/authoritative-records.ts +180 -0
- package/src/registry/config.ts +24 -6
- package/src/registry/record-labels.ts +28 -0
- package/src/registry/types.ts +12 -15
- package/src/schema/registry.ts +26 -3
- package/src/schema/types.ts +1 -0
- package/src/schema/zod-generator.ts +28 -17
- package/src/security/ssrf.ts +26 -4
- package/src/seed/apply.ts +4 -4
- package/src/settings/index.ts +16 -0
- package/src/storage/local.ts +1 -0
- package/src/storage/s3.ts +2 -0
- package/src/storage/types.ts +2 -0
- package/src/taxonomies/index.ts +40 -20
- package/src/ui.ts +2 -0
- package/src/widgets/archives.ts +67 -0
- package/src/widgets/index.ts +16 -0
- package/dist/api-BeBglVxU.mjs +0 -4695
- package/dist/api-BeBglVxU.mjs.map +0 -1
- package/dist/byline-yjvWq4ip.mjs.map +0 -1
- package/dist/challenge-store-SIL5DsF2.mjs.map +0 -1
- package/dist/collection-deletion-DQPvMD8R.mjs.map +0 -1
- package/dist/content-ZHCc1ngN.mjs.map +0 -1
- package/dist/content-refresh-B_8SJzuU.mjs.map +0 -1
- package/dist/context-ZldbZcle.mjs.map +0 -1
- package/dist/errors-CLUPm7aD.mjs.map +0 -1
- package/dist/index-BVBZj4-7.d.mts.map +0 -1
- package/dist/loader-CdG_Ci3H.mjs.map +0 -1
- package/dist/manifest-schema-BXbUAHod.mjs.map +0 -1
- package/dist/media-BWWWb8kg.mjs.map +0 -1
- package/dist/media-CzhAn2--.mjs.map +0 -1
- package/dist/media-allowlist-D6x-Us02.mjs.map +0 -1
- package/dist/media-usage-BZCydfvu.mjs.map +0 -1
- package/dist/media-usage-work-DrCbqBcl.mjs.map +0 -1
- package/dist/menus-DSo5QeW0.mjs.map +0 -1
- package/dist/mime-DfIVjNkr.mjs.map +0 -1
- package/dist/normalize-Bc-s_r-a.mjs.map +0 -1
- package/dist/object-cache-71tPfV4A.mjs.map +0 -1
- package/dist/options--hhz-jZ1.d.mts.map +0 -1
- package/dist/placeholder-Dp0K3RkD.d.mts.map +0 -1
- package/dist/redirects-DTDHWH31.mjs.map +0 -1
- package/dist/registry-Cqa2I749.mjs.map +0 -1
- package/dist/relations-BBApI4e2.mjs.map +0 -1
- package/dist/relations-BHo5A8Zf.d.mts.map +0 -1
- package/dist/runner-BYHZn3Jy.d.mts.map +0 -1
- package/dist/runner-C1T11CfC.mjs.map +0 -1
- package/dist/settings-xRdoUZr0.mjs.map +0 -1
- package/dist/state-DYwPue40.mjs.map +0 -1
- package/dist/taxonomies-BlrncHi9.mjs.map +0 -1
- package/dist/taxonomies-OHUIiIpI.mjs.map +0 -1
- package/dist/types-BcqdcT-r.d.mts.map +0 -1
- package/dist/types-C2pMvO62.d.mts.map +0 -1
- package/dist/types-CP1CflWf.mjs +0 -3
- package/dist/types-DN64_zzZ.d.mts.map +0 -1
- package/dist/types-DQIDRKF2.d.mts.map +0 -1
- package/dist/version-DB_fqCSI.mjs +0 -7
- package/dist/widgets-BD3ho7Rq.mjs.map +0 -1
- package/dist/zod-generator-LC-on_Vb.mjs.map +0 -1
- /package/dist/{api-tokens-rIXC6LdE.mjs → api-tokens-BfYts-sq.mjs} +0 -0
- /package/dist/{ssrf-DpXgKsna.mjs → ssrf-Dg3cIT-b.mjs} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.mjs","names":[],"sources":["../../src/storage/local.ts"],"sourcesContent":["/**\n * Local Filesystem Storage Implementation\n *\n * For development and testing. Stores files in a local directory.\n */\n\nimport { createReadStream, existsSync } from \"node:fs\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nimport mime from \"mime/lite\";\n\n/** Type guard for Node.js ErrnoException */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nimport type {\n\tStorage,\n\tLocalStorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\n/** Pattern to remove leading slashes */\nconst LEADING_SLASH_PATTERN = /^\\//;\n\n/** Pattern to remove trailing slashes */\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/**\n * Local filesystem storage implementation\n */\nexport class LocalStorage implements Storage {\n\t/** Resolved absolute base directory for all stored files */\n\tprivate directory: string;\n\tprivate baseUrl: string;\n\n\tconstructor(config: LocalStorageConfig) {\n\t\tthis.directory = path.resolve(config.directory);\n\t\tthis.baseUrl = config.baseUrl.replace(TRAILING_SLASH_PATTERN, \"\");\n\t}\n\n\t/**\n\t * Resolve a storage key to an absolute file path, ensuring it stays\n\t * within the configured storage directory. Uses path.resolve() for\n\t * canonical resolution rather than regex stripping.\n\t *\n\t * @throws EmDashStorageError if the resolved path escapes the base directory\n\t */\n\tprivate getFilePath(key: string): string {\n\t\tconst normalizedKey = key.replace(LEADING_SLASH_PATTERN, \"\");\n\t\tconst resolved = path.resolve(this.directory, normalizedKey);\n\n\t\t// Verify the resolved path is within the base directory\n\t\tif (!resolved.startsWith(this.directory + path.sep) && resolved !== this.directory) {\n\t\t\tthrow new EmDashStorageError(\"Invalid file path\", \"INVALID_PATH\");\n\t\t}\n\n\t\treturn resolved;\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(options.key);\n\t\t\tconst dir = path.dirname(filePath);\n\n\t\t\t// Ensure directory exists\n\t\t\tawait fs.mkdir(dir, { recursive: true });\n\n\t\t\t// Convert body to buffer\n\t\t\tlet buffer: Buffer;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbuffer = Buffer.concat(chunks);\n\t\t\t} else if (options.body instanceof Uint8Array) {\n\t\t\t\tbuffer = Buffer.from(options.body);\n\t\t\t} else {\n\t\t\t\tbuffer = options.body;\n\t\t\t}\n\n\t\t\tawait fs.writeFile(filePath, buffer);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: buffer.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(`Failed to upload file: ${options.key}`, \"UPLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst nodeStream = createReadStream(filePath);\n\n\t\t\t// Convert Node.js stream to web ReadableStream\n\t\t\t// Readable.toWeb returns ReadableStream (which is ReadableStream<unknown>),\n\t\t\t// but Node ReadStreams produce Buffer/Uint8Array chunks\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- Readable.toWeb returns ReadableStream<unknown>; Node ReadStreams produce Uint8Array chunks\n\t\t\tconst webStream: ReadableStream<Uint8Array> = Readable.toWeb(\n\t\t\t\tnodeStream,\n\t\t\t) as ReadableStream<Uint8Array>;\n\n\t\t\t// Infer content type from extension\n\t\t\tconst ext = path.extname(key).toLowerCase();\n\t\t\tconst contentType = getContentType(ext);\n\n\t\t\treturn {\n\t\t\t\tbody: webStream,\n\t\t\t\tcontentType,\n\t\t\t\tsize: stat.size,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof EmDashStorageError) throw error;\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\t\t\tawait fs.unlink(filePath);\n\t\t} catch (error) {\n\t\t\t// Ignore \"file not found\" errors (idempotent delete)\n\t\t\tif (!isNodeError(error) || error.code !== \"ENOENT\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\t\t\tawait fs.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\tconst prefix = options.prefix || \"\";\n\t\t\tconst searchDir = path.resolve(this.directory, path.dirname(prefix));\n\n\t\t\t// Validate the search directory stays within the base directory\n\t\t\tif (!searchDir.startsWith(this.directory + path.sep) && searchDir !== this.directory) {\n\t\t\t\tthrow new EmDashStorageError(\"Invalid list prefix\", \"INVALID_PATH\");\n\t\t\t}\n\n\t\t\tconst prefixBase = path.basename(prefix);\n\n\t\t\t// Ensure directory exists\n\t\t\ttry {\n\t\t\t\tawait fs.access(searchDir);\n\t\t\t} catch {\n\t\t\t\treturn { files: [] };\n\t\t\t}\n\n\t\t\tconst entries = await fs.readdir(searchDir, { withFileTypes: true });\n\t\t\tconst files: ListResult[\"files\"] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.isFile() && entry.name.startsWith(prefixBase)) {\n\t\t\t\t\tconst key = path.join(path.dirname(prefix), entry.name);\n\t\t\t\t\tconst filePath = path.join(searchDir, entry.name);\n\t\t\t\t\tconst stat = await fs.stat(filePath);\n\n\t\t\t\t\tfiles.push({\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tsize: stat.size,\n\t\t\t\t\t\tlastModified: stat.mtime,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by last modified (newest first)\n\t\t\tfiles.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n\n\t\t\t// Apply limit and cursor (simple implementation)\n\t\t\tconst startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;\n\t\t\tconst limit = options.limit || 1000;\n\t\t\tconst paginatedFiles = files.slice(startIndex, startIndex + limit);\n\t\t\tconst hasMore = startIndex + limit < files.length;\n\n\t\t\treturn {\n\t\t\t\tfiles: paginatedFiles,\n\t\t\t\tnextCursor: hasMore ? String(startIndex + limit) : undefined,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(_options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\t// Local storage doesn't support signed URLs\n\t\tthrow new EmDashStorageError(\n\t\t\t\"Local storage does not support signed upload URLs. \" +\n\t\t\t\t\"Upload files directly through the API.\",\n\t\t\t\"NOT_SUPPORTED\",\n\t\t);\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\treturn `${this.baseUrl}/${key}`;\n\t}\n}\n\n/**\n * Get content type from file extension\n */\nfunction getContentType(ext: string): string {\n\treturn mime.getType(ext) ?? \"application/octet-stream\";\n}\n\n/**\n * Create local storage adapter\n * This is the factory function called at runtime\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\tconst directory = typeof config.directory === \"string\" ? config.directory : \"\";\n\tconst baseUrl = typeof config.baseUrl === \"string\" ? config.baseUrl : \"\";\n\treturn new LocalStorage({ directory, baseUrl });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local.mjs","names":[],"sources":["../../src/storage/local.ts"],"sourcesContent":["/**\n * Local Filesystem Storage Implementation\n *\n * For development and testing. Stores files in a local directory.\n */\n\nimport { createReadStream, existsSync } from \"node:fs\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nimport mime from \"mime/lite\";\n\n/** Type guard for Node.js ErrnoException */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nimport type {\n\tStorage,\n\tLocalStorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\n/** Pattern to remove leading slashes */\nconst LEADING_SLASH_PATTERN = /^\\//;\n\n/** Pattern to remove trailing slashes */\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/**\n * Local filesystem storage implementation\n */\nexport class LocalStorage implements Storage {\n\t/** Resolved absolute base directory for all stored files */\n\tprivate directory: string;\n\tprivate baseUrl: string;\n\n\tconstructor(config: LocalStorageConfig) {\n\t\tthis.directory = path.resolve(config.directory);\n\t\tthis.baseUrl = config.baseUrl.replace(TRAILING_SLASH_PATTERN, \"\");\n\t}\n\n\t/**\n\t * Resolve a storage key to an absolute file path, ensuring it stays\n\t * within the configured storage directory. Uses path.resolve() for\n\t * canonical resolution rather than regex stripping.\n\t *\n\t * @throws EmDashStorageError if the resolved path escapes the base directory\n\t */\n\tprivate getFilePath(key: string): string {\n\t\tconst normalizedKey = key.replace(LEADING_SLASH_PATTERN, \"\");\n\t\tconst resolved = path.resolve(this.directory, normalizedKey);\n\n\t\t// Verify the resolved path is within the base directory\n\t\tif (!resolved.startsWith(this.directory + path.sep) && resolved !== this.directory) {\n\t\t\tthrow new EmDashStorageError(\"Invalid file path\", \"INVALID_PATH\");\n\t\t}\n\n\t\treturn resolved;\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t\tcacheControl?: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(options.key);\n\t\t\tconst dir = path.dirname(filePath);\n\n\t\t\t// Ensure directory exists\n\t\t\tawait fs.mkdir(dir, { recursive: true });\n\n\t\t\t// Convert body to buffer\n\t\t\tlet buffer: Buffer;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbuffer = Buffer.concat(chunks);\n\t\t\t} else if (options.body instanceof Uint8Array) {\n\t\t\t\tbuffer = Buffer.from(options.body);\n\t\t\t} else {\n\t\t\t\tbuffer = options.body;\n\t\t\t}\n\n\t\t\tawait fs.writeFile(filePath, buffer);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: buffer.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(`Failed to upload file: ${options.key}`, \"UPLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst nodeStream = createReadStream(filePath);\n\n\t\t\t// Convert Node.js stream to web ReadableStream\n\t\t\t// Readable.toWeb returns ReadableStream (which is ReadableStream<unknown>),\n\t\t\t// but Node ReadStreams produce Buffer/Uint8Array chunks\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- Readable.toWeb returns ReadableStream<unknown>; Node ReadStreams produce Uint8Array chunks\n\t\t\tconst webStream: ReadableStream<Uint8Array> = Readable.toWeb(\n\t\t\t\tnodeStream,\n\t\t\t) as ReadableStream<Uint8Array>;\n\n\t\t\t// Infer content type from extension\n\t\t\tconst ext = path.extname(key).toLowerCase();\n\t\t\tconst contentType = getContentType(ext);\n\n\t\t\treturn {\n\t\t\t\tbody: webStream,\n\t\t\t\tcontentType,\n\t\t\t\tsize: stat.size,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof EmDashStorageError) throw error;\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\t\t\tawait fs.unlink(filePath);\n\t\t} catch (error) {\n\t\t\t// Ignore \"file not found\" errors (idempotent delete)\n\t\t\tif (!isNodeError(error) || error.code !== \"ENOENT\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\t\t\tawait fs.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\tconst prefix = options.prefix || \"\";\n\t\t\tconst searchDir = path.resolve(this.directory, path.dirname(prefix));\n\n\t\t\t// Validate the search directory stays within the base directory\n\t\t\tif (!searchDir.startsWith(this.directory + path.sep) && searchDir !== this.directory) {\n\t\t\t\tthrow new EmDashStorageError(\"Invalid list prefix\", \"INVALID_PATH\");\n\t\t\t}\n\n\t\t\tconst prefixBase = path.basename(prefix);\n\n\t\t\t// Ensure directory exists\n\t\t\ttry {\n\t\t\t\tawait fs.access(searchDir);\n\t\t\t} catch {\n\t\t\t\treturn { files: [] };\n\t\t\t}\n\n\t\t\tconst entries = await fs.readdir(searchDir, { withFileTypes: true });\n\t\t\tconst files: ListResult[\"files\"] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.isFile() && entry.name.startsWith(prefixBase)) {\n\t\t\t\t\tconst key = path.join(path.dirname(prefix), entry.name);\n\t\t\t\t\tconst filePath = path.join(searchDir, entry.name);\n\t\t\t\t\tconst stat = await fs.stat(filePath);\n\n\t\t\t\t\tfiles.push({\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tsize: stat.size,\n\t\t\t\t\t\tlastModified: stat.mtime,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by last modified (newest first)\n\t\t\tfiles.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n\n\t\t\t// Apply limit and cursor (simple implementation)\n\t\t\tconst startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;\n\t\t\tconst limit = options.limit || 1000;\n\t\t\tconst paginatedFiles = files.slice(startIndex, startIndex + limit);\n\t\t\tconst hasMore = startIndex + limit < files.length;\n\n\t\t\treturn {\n\t\t\t\tfiles: paginatedFiles,\n\t\t\t\tnextCursor: hasMore ? String(startIndex + limit) : undefined,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(_options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\t// Local storage doesn't support signed URLs\n\t\tthrow new EmDashStorageError(\n\t\t\t\"Local storage does not support signed upload URLs. \" +\n\t\t\t\t\"Upload files directly through the API.\",\n\t\t\t\"NOT_SUPPORTED\",\n\t\t);\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\treturn `${this.baseUrl}/${key}`;\n\t}\n}\n\n/**\n * Get content type from file extension\n */\nfunction getContentType(ext: string): string {\n\treturn mime.getType(ext) ?? \"application/octet-stream\";\n}\n\n/**\n * Create local storage adapter\n * This is the factory function called at runtime\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\tconst directory = typeof config.directory === \"string\" ? config.directory : \"\";\n\tconst baseUrl = typeof config.baseUrl === \"string\" ? config.baseUrl : \"\";\n\treturn new LocalStorage({ directory, baseUrl });\n}\n"],"mappings":"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"}
|
package/dist/storage/s3.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-
|
|
1
|
+
import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-CXFkwEGC.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/storage/s3.d.ts
|
|
4
4
|
/**
|
|
@@ -21,6 +21,7 @@ declare class S3Storage implements Storage {
|
|
|
21
21
|
key: string;
|
|
22
22
|
body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
|
|
23
23
|
contentType: string;
|
|
24
|
+
cacheControl?: string;
|
|
24
25
|
}): Promise<UploadResult>;
|
|
25
26
|
download(key: string): Promise<DownloadResult>;
|
|
26
27
|
delete(key: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.d.mts","names":[],"sources":["../../src/storage/s3.ts"],"mappings":";;;;;;;;AA2HA;iBA3DgB,eAAA,CAAgB,OAAA,EAAS,MAAA,oBAA0B,eAAA;;;;cA2DtD,SAAA,YAAqB,OAAA;EAAA,QACzB,MAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,QAAA;cAEI,MAAA,EAAQ,eAAA;EA0Bd,MAAA,CAAO,OAAA;IACZ,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;EAAA,IACG,OAAA,CAAQ,YAAA;
|
|
1
|
+
{"version":3,"file":"s3.d.mts","names":[],"sources":["../../src/storage/s3.ts"],"mappings":";;;;;;;;AA2HA;iBA3DgB,eAAA,CAAgB,OAAA,EAAS,MAAA,oBAA0B,eAAA;;;;cA2DtD,SAAA,YAAqB,OAAA;EAAA,QACzB,MAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,QAAA;cAEI,MAAA,EAAQ,eAAA;EA0Bd,MAAA,CAAO,OAAA;IACZ,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;IACA,YAAA;EAAA,IACG,OAAA,CAAQ,YAAA;EAqCN,QAAA,CAAS,GAAA,WAAc,OAAA,CAAQ,cAAA;EAgC/B,MAAA,CAAO,GAAA,WAAc,OAAA;EAgBrB,MAAA,CAAO,GAAA,WAAc,OAAA;EAiBrB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA,CAAQ,UAAA;EA4BzC,kBAAA,CAAmB,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,eAAA;EAiChE,YAAA,CAAa,GAAA;AAAA;;;;;;iBAcE,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,OAAA"}
|
package/dist/storage/s3.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as EmDashStorageError } from "../types-
|
|
1
|
+
import { t as EmDashStorageError } from "../types-3qNFPlci.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
4
4
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
@@ -122,7 +122,8 @@ var S3Storage = class {
|
|
|
122
122
|
Bucket: this.bucket,
|
|
123
123
|
Key: options.key,
|
|
124
124
|
Body: body,
|
|
125
|
-
ContentType: options.contentType
|
|
125
|
+
ContentType: options.contentType,
|
|
126
|
+
CacheControl: options.cacheControl
|
|
126
127
|
}));
|
|
127
128
|
return {
|
|
128
129
|
key: options.key,
|
package/dist/storage/s3.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.mjs","names":[],"sources":["../../src/storage/s3.ts"],"sourcesContent":["/**\n * S3-Compatible Storage Implementation\n *\n * Uses the AWS SDK v3 for S3 operations.\n * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.\n */\n\nimport {\n\tS3Client,\n\ttype S3ClientConfig,\n\tPutObjectCommand,\n\tGetObjectCommand,\n\tDeleteObjectCommand,\n\tHeadObjectCommand,\n\tListObjectsV2Command,\n\ttype ListObjectsV2Response,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { z } from \"zod\";\n\nimport type {\n\tStorage,\n\tS3StorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\nconst ENV_KEYS = {\n\tendpoint: \"S3_ENDPOINT\",\n\tbucket: \"S3_BUCKET\",\n\taccessKeyId: \"S3_ACCESS_KEY_ID\",\n\tsecretAccessKey: \"S3_SECRET_ACCESS_KEY\",\n\tregion: \"S3_REGION\",\n\tpublicUrl: \"S3_PUBLIC_URL\",\n} as const satisfies Record<keyof S3StorageConfig, string>;\n\nfunction fail(msg: string): never {\n\tthrow new EmDashStorageError(msg, \"MISSING_S3_CONFIG\");\n}\n\nconst s3ConfigSchema = z.object({\n\tendpoint: z.url({ protocol: /^https?$/, error: \"is not a valid http/https URL\" }).optional(),\n\tbucket: z.string().optional(),\n\taccessKeyId: z.string().optional(),\n\tsecretAccessKey: z.string().optional(),\n\tregion: z.string().optional(),\n\tpublicUrl: z.string().optional(),\n});\n\nfunction isConfigKey(key: unknown): key is keyof S3StorageConfig {\n\treturn typeof key === \"string\" && key in ENV_KEYS;\n}\n\n/**\n * Build the merged config: for each field, use the explicit value if present,\n * otherwise fall back to the corresponding S3_* env var. Validate once on the\n * final merged result so a malformed env var never breaks the build when the\n * caller provides that field explicitly.\n */\nexport function resolveS3Config(partial: Record<string, unknown>): S3StorageConfig {\n\tconst raw: Record<string, unknown> = {};\n\tfor (const [field, envKey] of Object.entries(ENV_KEYS)) {\n\t\tconst explicit = partial[field];\n\t\tif (explicit !== undefined && explicit !== \"\") {\n\t\t\traw[field] = explicit;\n\t\t\tcontinue;\n\t\t}\n\t\tconst envVal = typeof process !== \"undefined\" && process.env ? process.env[envKey] : undefined;\n\t\tif (envVal !== undefined && envVal !== \"\") {\n\t\t\traw[field] = envVal;\n\t\t}\n\t}\n\n\tconst result = s3ConfigSchema.safeParse(raw);\n\tif (!result.success) {\n\t\tconst issue = result.error.issues[0];\n\t\tconst pathKey = issue?.path[0];\n\t\tif (!issue || !isConfigKey(pathKey)) fail(\"S3 config validation failed\");\n\t\tconst fromExplicit = partial[pathKey] !== undefined && partial[pathKey] !== \"\";\n\t\tconst label = fromExplicit ? `s3({ ${pathKey} })` : ENV_KEYS[pathKey];\n\t\tfail(`${label} ${issue.message}`);\n\t}\n\tconst merged = result.data;\n\n\tconst endpoint = merged.endpoint;\n\tconst bucket = merged.bucket;\n\tif (!endpoint || !bucket) {\n\t\tconst missing: string[] = [];\n\t\tif (!endpoint) missing.push(`endpoint: set ${ENV_KEYS.endpoint} or pass endpoint to s3({...})`);\n\t\tif (!bucket) missing.push(`bucket: set ${ENV_KEYS.bucket} or pass bucket to s3({...})`);\n\t\tfail(`missing required S3 config: ${missing.join(\"; \")}`);\n\t}\n\tconst accessKeyId = merged.accessKeyId;\n\tconst secretAccessKey = merged.secretAccessKey;\n\tif (accessKeyId && !secretAccessKey) {\n\t\tfail(\n\t\t\t`S3 credentials incomplete: accessKeyId is set but secretAccessKey is missing (set ${ENV_KEYS.secretAccessKey} or pass secretAccessKey to s3({...}))`,\n\t\t);\n\t}\n\tif (secretAccessKey && !accessKeyId) {\n\t\tfail(\n\t\t\t`S3 credentials incomplete: secretAccessKey is set but accessKeyId is missing (set ${ENV_KEYS.accessKeyId} or pass accessKeyId to s3({...}))`,\n\t\t);\n\t}\n\n\treturn { ...merged, endpoint, bucket };\n}\n\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/** Type guard for AWS SDK errors (have a `name` property) */\nfunction hasErrorName(error: unknown): error is Error & { name: string } {\n\treturn error instanceof Error && typeof error.name === \"string\";\n}\n\n/**\n * S3-compatible storage implementation\n */\nexport class S3Storage implements Storage {\n\tprivate client: S3Client;\n\tprivate bucket: string;\n\tprivate publicUrl?: string;\n\tprivate endpoint: string;\n\n\tconstructor(config: S3StorageConfig) {\n\t\tthis.bucket = config.bucket;\n\t\tthis.publicUrl = config.publicUrl;\n\t\tthis.endpoint = config.endpoint;\n\n\t\t// S3ClientConfig types `credentials` as required, but the SDK accepts\n\t\t// omitted credentials at runtime (falls back to the provider chain).\n\t\t/* eslint-disable typescript/no-unsafe-type-assertion -- upstream @aws-sdk/client-s3 overstates required fields */\n\t\tconst clientConfig = {\n\t\t\tendpoint: config.endpoint,\n\t\t\tregion: config.region || \"auto\",\n\t\t\t// Required for R2 and some S3-compatible services\n\t\t\tforcePathStyle: true,\n\t\t\t...(config.accessKeyId && config.secretAccessKey\n\t\t\t\t? {\n\t\t\t\t\t\tcredentials: {\n\t\t\t\t\t\t\taccessKeyId: config.accessKeyId,\n\t\t\t\t\t\t\tsecretAccessKey: config.secretAccessKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t} as S3ClientConfig;\n\t\t/* eslint-enable typescript/no-unsafe-type-assertion */\n\t\tthis.client = new S3Client(clientConfig);\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\t// Convert ReadableStream to Buffer if needed\n\t\t\tlet body: Buffer | Uint8Array;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbody = Buffer.concat(chunks);\n\t\t\t} else {\n\t\t\t\tbody = options.body;\n\t\t\t}\n\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: options.key,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: options.contentType,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: body.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(`Failed to upload file: ${options.key}`, \"UPLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Body) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\t// Convert SDK stream to web ReadableStream\n\t\t\tconst body = response.Body.transformToWebStream();\n\n\t\t\treturn {\n\t\t\t\tbody,\n\t\t\t\tcontentType: response.ContentType || \"application/octet-stream\",\n\t\t\t\tsize: response.ContentLength || 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror instanceof EmDashStorageError ||\n\t\t\t\t(hasErrorName(error) && error.name === \"NoSuchKey\")\n\t\t\t) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\", error);\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// S3 delete is idempotent, so we ignore \"not found\" errors\n\t\t\tif (!hasErrorName(error) || error.name !== \"NoSuchKey\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew HeadObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tif (hasErrorName(error) && error.name === \"NotFound\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to check file existence: ${key}`, \"HEAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- S3 client.send returns generic output; narrowing to ListObjectsV2Response\n\t\t\tconst response = (await this.client.send(\n\t\t\t\tnew ListObjectsV2Command({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tPrefix: options.prefix,\n\t\t\t\t\tMaxKeys: options.limit,\n\t\t\t\t\tContinuationToken: options.cursor,\n\t\t\t\t}),\n\t\t\t)) as ListObjectsV2Response;\n\n\t\t\treturn {\n\t\t\t\tfiles: (response.Contents || []).map(\n\t\t\t\t\t(item: { Key?: string; Size?: number; LastModified?: Date; ETag?: string }) => ({\n\t\t\t\t\t\tkey: item.Key!,\n\t\t\t\t\t\tsize: item.Size || 0,\n\t\t\t\t\t\tlastModified: item.LastModified || new Date(),\n\t\t\t\t\t\tetag: item.ETag,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tnextCursor: response.NextContinuationToken,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\ttry {\n\t\t\tconst expiresIn = options.expiresIn || 3600; // 1 hour default\n\n\t\t\tconst command = new PutObjectCommand({\n\t\t\t\tBucket: this.bucket,\n\t\t\t\tKey: options.key,\n\t\t\t\tContentType: options.contentType,\n\t\t\t\tContentLength: options.size,\n\t\t\t});\n\n\t\t\tconst url = await getSignedUrl(this.client, command, { expiresIn });\n\n\t\t\tconst expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tmethod: \"PUT\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": options.contentType,\n\t\t\t\t\t...(options.size !== undefined ? { \"Content-Length\": String(options.size) } : {}),\n\t\t\t\t},\n\t\t\t\texpiresAt,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to generate signed URL for: ${options.key}`,\n\t\t\t\t\"SIGNED_URL_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\tif (this.publicUrl) {\n\t\t\treturn `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, \"\")}/${key}`;\n\t\t}\n\t\t// No public URL configured; defer to the /_emdash/api/media/file route.\n\t\treturn `/_emdash/api/media/file/${key}`;\n\t}\n}\n\n/**\n * Create S3 storage adapter\n * This is the factory function called at runtime.\n * Config fields are merged with S3_* env vars; env vars fill in any missing fields.\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\treturn new S3Storage(resolveS3Config(config));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"s3.mjs","names":[],"sources":["../../src/storage/s3.ts"],"sourcesContent":["/**\n * S3-Compatible Storage Implementation\n *\n * Uses the AWS SDK v3 for S3 operations.\n * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.\n */\n\nimport {\n\tS3Client,\n\ttype S3ClientConfig,\n\tPutObjectCommand,\n\tGetObjectCommand,\n\tDeleteObjectCommand,\n\tHeadObjectCommand,\n\tListObjectsV2Command,\n\ttype ListObjectsV2Response,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { z } from \"zod\";\n\nimport type {\n\tStorage,\n\tS3StorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\nconst ENV_KEYS = {\n\tendpoint: \"S3_ENDPOINT\",\n\tbucket: \"S3_BUCKET\",\n\taccessKeyId: \"S3_ACCESS_KEY_ID\",\n\tsecretAccessKey: \"S3_SECRET_ACCESS_KEY\",\n\tregion: \"S3_REGION\",\n\tpublicUrl: \"S3_PUBLIC_URL\",\n} as const satisfies Record<keyof S3StorageConfig, string>;\n\nfunction fail(msg: string): never {\n\tthrow new EmDashStorageError(msg, \"MISSING_S3_CONFIG\");\n}\n\nconst s3ConfigSchema = z.object({\n\tendpoint: z.url({ protocol: /^https?$/, error: \"is not a valid http/https URL\" }).optional(),\n\tbucket: z.string().optional(),\n\taccessKeyId: z.string().optional(),\n\tsecretAccessKey: z.string().optional(),\n\tregion: z.string().optional(),\n\tpublicUrl: z.string().optional(),\n});\n\nfunction isConfigKey(key: unknown): key is keyof S3StorageConfig {\n\treturn typeof key === \"string\" && key in ENV_KEYS;\n}\n\n/**\n * Build the merged config: for each field, use the explicit value if present,\n * otherwise fall back to the corresponding S3_* env var. Validate once on the\n * final merged result so a malformed env var never breaks the build when the\n * caller provides that field explicitly.\n */\nexport function resolveS3Config(partial: Record<string, unknown>): S3StorageConfig {\n\tconst raw: Record<string, unknown> = {};\n\tfor (const [field, envKey] of Object.entries(ENV_KEYS)) {\n\t\tconst explicit = partial[field];\n\t\tif (explicit !== undefined && explicit !== \"\") {\n\t\t\traw[field] = explicit;\n\t\t\tcontinue;\n\t\t}\n\t\tconst envVal = typeof process !== \"undefined\" && process.env ? process.env[envKey] : undefined;\n\t\tif (envVal !== undefined && envVal !== \"\") {\n\t\t\traw[field] = envVal;\n\t\t}\n\t}\n\n\tconst result = s3ConfigSchema.safeParse(raw);\n\tif (!result.success) {\n\t\tconst issue = result.error.issues[0];\n\t\tconst pathKey = issue?.path[0];\n\t\tif (!issue || !isConfigKey(pathKey)) fail(\"S3 config validation failed\");\n\t\tconst fromExplicit = partial[pathKey] !== undefined && partial[pathKey] !== \"\";\n\t\tconst label = fromExplicit ? `s3({ ${pathKey} })` : ENV_KEYS[pathKey];\n\t\tfail(`${label} ${issue.message}`);\n\t}\n\tconst merged = result.data;\n\n\tconst endpoint = merged.endpoint;\n\tconst bucket = merged.bucket;\n\tif (!endpoint || !bucket) {\n\t\tconst missing: string[] = [];\n\t\tif (!endpoint) missing.push(`endpoint: set ${ENV_KEYS.endpoint} or pass endpoint to s3({...})`);\n\t\tif (!bucket) missing.push(`bucket: set ${ENV_KEYS.bucket} or pass bucket to s3({...})`);\n\t\tfail(`missing required S3 config: ${missing.join(\"; \")}`);\n\t}\n\tconst accessKeyId = merged.accessKeyId;\n\tconst secretAccessKey = merged.secretAccessKey;\n\tif (accessKeyId && !secretAccessKey) {\n\t\tfail(\n\t\t\t`S3 credentials incomplete: accessKeyId is set but secretAccessKey is missing (set ${ENV_KEYS.secretAccessKey} or pass secretAccessKey to s3({...}))`,\n\t\t);\n\t}\n\tif (secretAccessKey && !accessKeyId) {\n\t\tfail(\n\t\t\t`S3 credentials incomplete: secretAccessKey is set but accessKeyId is missing (set ${ENV_KEYS.accessKeyId} or pass accessKeyId to s3({...}))`,\n\t\t);\n\t}\n\n\treturn { ...merged, endpoint, bucket };\n}\n\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/** Type guard for AWS SDK errors (have a `name` property) */\nfunction hasErrorName(error: unknown): error is Error & { name: string } {\n\treturn error instanceof Error && typeof error.name === \"string\";\n}\n\n/**\n * S3-compatible storage implementation\n */\nexport class S3Storage implements Storage {\n\tprivate client: S3Client;\n\tprivate bucket: string;\n\tprivate publicUrl?: string;\n\tprivate endpoint: string;\n\n\tconstructor(config: S3StorageConfig) {\n\t\tthis.bucket = config.bucket;\n\t\tthis.publicUrl = config.publicUrl;\n\t\tthis.endpoint = config.endpoint;\n\n\t\t// S3ClientConfig types `credentials` as required, but the SDK accepts\n\t\t// omitted credentials at runtime (falls back to the provider chain).\n\t\t/* eslint-disable typescript/no-unsafe-type-assertion -- upstream @aws-sdk/client-s3 overstates required fields */\n\t\tconst clientConfig = {\n\t\t\tendpoint: config.endpoint,\n\t\t\tregion: config.region || \"auto\",\n\t\t\t// Required for R2 and some S3-compatible services\n\t\t\tforcePathStyle: true,\n\t\t\t...(config.accessKeyId && config.secretAccessKey\n\t\t\t\t? {\n\t\t\t\t\t\tcredentials: {\n\t\t\t\t\t\t\taccessKeyId: config.accessKeyId,\n\t\t\t\t\t\t\tsecretAccessKey: config.secretAccessKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t} as S3ClientConfig;\n\t\t/* eslint-enable typescript/no-unsafe-type-assertion */\n\t\tthis.client = new S3Client(clientConfig);\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t\tcacheControl?: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\t// Convert ReadableStream to Buffer if needed\n\t\t\tlet body: Buffer | Uint8Array;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbody = Buffer.concat(chunks);\n\t\t\t} else {\n\t\t\t\tbody = options.body;\n\t\t\t}\n\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: options.key,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: options.contentType,\n\t\t\t\t\tCacheControl: options.cacheControl,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: body.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(`Failed to upload file: ${options.key}`, \"UPLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Body) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\t// Convert SDK stream to web ReadableStream\n\t\t\tconst body = response.Body.transformToWebStream();\n\n\t\t\treturn {\n\t\t\t\tbody,\n\t\t\t\tcontentType: response.ContentType || \"application/octet-stream\",\n\t\t\t\tsize: response.ContentLength || 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror instanceof EmDashStorageError ||\n\t\t\t\t(hasErrorName(error) && error.name === \"NoSuchKey\")\n\t\t\t) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\", error);\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// S3 delete is idempotent, so we ignore \"not found\" errors\n\t\t\tif (!hasErrorName(error) || error.name !== \"NoSuchKey\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew HeadObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tif (hasErrorName(error) && error.name === \"NotFound\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to check file existence: ${key}`, \"HEAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- S3 client.send returns generic output; narrowing to ListObjectsV2Response\n\t\t\tconst response = (await this.client.send(\n\t\t\t\tnew ListObjectsV2Command({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tPrefix: options.prefix,\n\t\t\t\t\tMaxKeys: options.limit,\n\t\t\t\t\tContinuationToken: options.cursor,\n\t\t\t\t}),\n\t\t\t)) as ListObjectsV2Response;\n\n\t\t\treturn {\n\t\t\t\tfiles: (response.Contents || []).map(\n\t\t\t\t\t(item: { Key?: string; Size?: number; LastModified?: Date; ETag?: string }) => ({\n\t\t\t\t\t\tkey: item.Key!,\n\t\t\t\t\t\tsize: item.Size || 0,\n\t\t\t\t\t\tlastModified: item.LastModified || new Date(),\n\t\t\t\t\t\tetag: item.ETag,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tnextCursor: response.NextContinuationToken,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\ttry {\n\t\t\tconst expiresIn = options.expiresIn || 3600; // 1 hour default\n\n\t\t\tconst command = new PutObjectCommand({\n\t\t\t\tBucket: this.bucket,\n\t\t\t\tKey: options.key,\n\t\t\t\tContentType: options.contentType,\n\t\t\t\tContentLength: options.size,\n\t\t\t});\n\n\t\t\tconst url = await getSignedUrl(this.client, command, { expiresIn });\n\n\t\t\tconst expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tmethod: \"PUT\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": options.contentType,\n\t\t\t\t\t...(options.size !== undefined ? { \"Content-Length\": String(options.size) } : {}),\n\t\t\t\t},\n\t\t\t\texpiresAt,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to generate signed URL for: ${options.key}`,\n\t\t\t\t\"SIGNED_URL_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\tif (this.publicUrl) {\n\t\t\treturn `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, \"\")}/${key}`;\n\t\t}\n\t\t// No public URL configured; defer to the /_emdash/api/media/file route.\n\t\treturn `/_emdash/api/media/file/${key}`;\n\t}\n}\n\n/**\n * Create S3 storage adapter\n * This is the factory function called at runtime.\n * Config fields are merged with S3_* env vars; env vars fill in any missing fields.\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\treturn new S3Storage(resolveS3Config(config));\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"target-
|
|
1
|
+
{"version":3,"file":"target-BmR0iTwz.mjs","names":[],"sources":["../src/migrations/target.ts"],"sourcesContent":["import type { MigrationTarget } from \"./protocol.js\";\n\nconst ENVIRONMENT_VARIABLE_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction encodeHex(bytes: Uint8Array): string {\n\treturn Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function createMigrationTarget(\n\tkind: string,\n\tlabel: string,\n\tidentity: readonly string[],\n): Promise<MigrationTarget> {\n\tconst encoded = new TextEncoder().encode(JSON.stringify({ kind, identity }));\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", encoded);\n\treturn {\n\t\tkind,\n\t\tlabel,\n\t\tfingerprint: encodeHex(new Uint8Array(digest)),\n\t};\n}\n\nexport function requireMigrationEnvironment(\n\tname: unknown,\n\tenv: Readonly<Record<string, string | undefined>>,\n): string {\n\tif (typeof name !== \"string\" || !ENVIRONMENT_VARIABLE_PATTERN.test(name)) {\n\t\tthrow new Error(\"Migration credential environment variable name is invalid.\");\n\t}\n\tconst value = env[name];\n\tif (!value) {\n\t\tthrow new Error(`Migration credential environment variable ${name} is not set.`);\n\t}\n\treturn value;\n}\n"],"mappings":";AAEA,MAAM,+BAA+B;AAErC,SAAS,UAAU,OAA2B;AAC7C,QAAO,MAAM,KAAK,QAAQ,SAAS,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG;;AAGhF,eAAsB,sBACrB,MACA,OACA,UAC2B;CAC3B,MAAM,UAAU,IAAI,aAAa,CAAC,OAAO,KAAK,UAAU;EAAE;EAAM;EAAU,CAAC,CAAC;CAC5E,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,QAAQ;AAC7D,QAAO;EACN;EACA;EACA,aAAa,UAAU,IAAI,WAAW,OAAO,CAAC;EAC9C;;AAGF,SAAgB,4BACf,MACA,KACS;AACT,KAAI,OAAO,SAAS,YAAY,CAAC,6BAA6B,KAAK,KAAK,CACvE,OAAM,IAAI,MAAM,6DAA6D;CAE9E,MAAM,QAAQ,IAAI;AAClB,KAAI,CAAC,MACJ,OAAM,IAAI,MAAM,6CAA6C,KAAK,cAAc;AAEjF,QAAO"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { c as __exportAll } from "./runner-
|
|
1
|
+
import { c as __exportAll } from "./runner-BLIDKpmA.mjs";
|
|
2
2
|
import { a as resolveConfiguredLocale, r as getI18nConfig } from "./config-C3EBstHi.mjs";
|
|
3
|
-
import { t as withTransaction } from "./transaction-
|
|
4
|
-
import { t as TaxonomyRepository } from "./taxonomy-
|
|
5
|
-
import {
|
|
3
|
+
import { t as withTransaction } from "./transaction-0KOpOyMu.mjs";
|
|
4
|
+
import { t as TaxonomyRepository } from "./taxonomy-tJepplLL.mjs";
|
|
5
|
+
import { d as invalidateTermCache, p as fetchVisibleTermCounts, u as invalidateTaxonomyDefsCache } from "./taxonomies-Wfy_Lg12.mjs";
|
|
6
6
|
import { sql } from "kysely";
|
|
7
7
|
import { ulid } from "ulidx";
|
|
8
8
|
|
|
@@ -168,8 +168,8 @@ async function handleTaxonomyList(db, options = {}) {
|
|
|
168
168
|
/**
|
|
169
169
|
* Get a single taxonomy definition by name.
|
|
170
170
|
*
|
|
171
|
-
* Definitions are per-locale, so `locale` picks which one
|
|
172
|
-
*
|
|
171
|
+
* Definitions are per-locale, so `locale` picks which one. Without it the
|
|
172
|
+
* configured default locale wins, then the lowest locale code.
|
|
173
173
|
*/
|
|
174
174
|
async function handleTaxonomyGet(db, name, options = {}) {
|
|
175
175
|
try {
|
|
@@ -202,12 +202,8 @@ async function handleTaxonomyCreate(db, input) {
|
|
|
202
202
|
message: "Taxonomy name must start with a letter and contain only lowercase letters, numbers, and underscores"
|
|
203
203
|
}
|
|
204
204
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (collectionError) return {
|
|
208
|
-
success: false,
|
|
209
|
-
error: collectionError
|
|
210
|
-
};
|
|
205
|
+
let hierarchical = input.hierarchical ?? false;
|
|
206
|
+
let collections = [...new Set(input.collections ?? [])];
|
|
211
207
|
let translationGroup = null;
|
|
212
208
|
if (input.translationOf) {
|
|
213
209
|
const source = await db.selectFrom("_emdash_taxonomy_defs").selectAll().where("id", "=", input.translationOf).executeTakeFirst();
|
|
@@ -219,7 +215,14 @@ async function handleTaxonomyCreate(db, input) {
|
|
|
219
215
|
}
|
|
220
216
|
};
|
|
221
217
|
translationGroup = source.translation_group ?? source.id;
|
|
218
|
+
if (input.hierarchical === void 0) hierarchical = source.hierarchical === 1;
|
|
219
|
+
if (input.collections === void 0) collections = [...new Set(defCollections(source))];
|
|
222
220
|
}
|
|
221
|
+
const collectionError = await validateCollections(db, collections);
|
|
222
|
+
if (collectionError) return {
|
|
223
|
+
success: false,
|
|
224
|
+
error: collectionError
|
|
225
|
+
};
|
|
223
226
|
if (await db.selectFrom("_emdash_taxonomy_defs").select("id").where("name", "=", input.name).where("locale", "=", locale).executeTakeFirst()) return {
|
|
224
227
|
success: false,
|
|
225
228
|
error: {
|
|
@@ -233,7 +236,7 @@ async function handleTaxonomyCreate(db, input) {
|
|
|
233
236
|
name: input.name,
|
|
234
237
|
label: input.label,
|
|
235
238
|
label_singular: input.labelSingular ?? null,
|
|
236
|
-
hierarchical:
|
|
239
|
+
hierarchical: hierarchical ? 1 : 0,
|
|
237
240
|
collections: JSON.stringify(collections),
|
|
238
241
|
locale,
|
|
239
242
|
translation_group: translationGroup ?? id
|
|
@@ -831,4 +834,4 @@ async function handleTermDelete(db, taxonomyName, termSlug, options = {}) {
|
|
|
831
834
|
|
|
832
835
|
//#endregion
|
|
833
836
|
export { handleTaxonomyList as a, handleTermDelete as c, handleTermReorder as d, handleTermTranslations as f, handleTaxonomyGet as i, handleTermGet as l, taxonomies_exports as m, handleTaxonomyDefTranslations as n, handleTaxonomyUpdate as o, handleTermUpdate as p, handleTaxonomyDelete as r, handleTermCreate as s, handleTaxonomyCreate as t, handleTermList as u };
|
|
834
|
-
//# sourceMappingURL=taxonomies-
|
|
837
|
+
//# sourceMappingURL=taxonomies-DkAkMupr.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taxonomies-DkAkMupr.mjs","names":[],"sources":["../src/api/handlers/taxonomies.ts"],"sourcesContent":["/**\n * Taxonomy and term CRUD handlers.\n *\n * i18n: terms and defs are per-locale. `(name, slug, locale)` is unique for\n * terms; `(name, locale)` for defs. Translations of the same term/def share a\n * `translation_group`. The content_taxonomies pivot stores translation_groups\n * so assignments span every locale of a post.\n */\n\nimport { sql, type Kysely, type Selectable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { TaxonomyRepository, type SiblingPosition } from \"../../database/repositories/taxonomy.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database, TaxonomyDefTable } from \"../../database/types.js\";\nimport { getI18nConfig, resolveConfiguredLocale } from \"../../i18n/config.js\";\nimport { invalidateTaxonomyDefsCache, invalidateTermCache } from \"../../taxonomies/index.js\";\nimport { fetchVisibleTermCounts } from \"../../taxonomies/term-counts.js\";\nimport type { ApiResult } from \"../types.js\";\n\nconst NAME_PATTERN = /^[a-z][a-z0-9_]*$/;\nconst MAX_GENERATED_TERM_SLUG_ATTEMPTS = 16;\n\nfunction isTermSlugUniqueViolation(error: unknown): boolean {\n\tconst message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\treturn (\n\t\t(message.includes(\"unique constraint failed\") || message.includes(\"duplicate key\")) &&\n\t\tmessage.includes(\"slug\")\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Response types\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyDef {\n\tid: string;\n\tname: string;\n\tlabel: string;\n\tlabelSingular?: string;\n\thierarchical: boolean;\n\tcollections: string[];\n\tlocale: string;\n\ttranslationGroup: string | null;\n}\n\nexport interface TaxonomyListResponse {\n\ttaxonomies: TaxonomyDef[];\n}\n\nexport interface TaxonomyResponse {\n\ttaxonomy: TaxonomyDef;\n}\n\nexport interface TermData {\n\tid: string;\n\tname: string;\n\tslug: string;\n\tlabel: string;\n\tparentId: string | null;\n\tdescription?: string;\n\tlocale: string;\n\ttranslationGroup: string | null;\n}\n\nexport interface TermWithCount extends TermData {\n\t/** Absent when the caller opted out of counts (`includeCounts: false`). */\n\tcount?: number;\n\tchildren: TermWithCount[];\n}\n\nexport interface TermListResponse {\n\tterms: TermWithCount[];\n}\n\nexport interface TermReorderResponse {\n\treordered: true;\n}\n\nexport interface TermResponse {\n\tterm: TermData;\n}\n\nexport interface TermGetResponse {\n\tterm: TermData & {\n\t\tcount: number;\n\t\tchildren: Array<{ id: string; slug: string; label: string }>;\n\t};\n}\n\nexport interface TermTranslationsResponse {\n\ttranslationGroup: string | null;\n\ttranslations: Array<{\n\t\tid: string;\n\t\tslug: string;\n\t\tlabel: string;\n\t\tlocale: string;\n\t}>;\n}\n\nexport interface TaxonomyDefTranslationsResponse {\n\ttranslationGroup: string | null;\n\ttranslations: Array<{\n\t\tid: string;\n\t\tname: string;\n\t\tlabel: string;\n\t\tlocale: string;\n\t}>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build tree structure from flat terms\n */\nfunction buildTree(flatTerms: TermWithCount[], resolveByGroup = false): TermWithCount[] {\n\t// `parentId` holds the parent's translation_group, so resolve links by group.\n\t// Key by (locale, group): a child's parent lives in the same locale, and an\n\t// unfiltered list mixes locales whose translated siblings share a group —\n\t// keying by group alone would collide and misattach children across locales.\n\tconst byLocaleGroup = new Map<string, TermWithCount>();\n\tconst roots: TermWithCount[] = [];\n\tfor (const term of flatTerms) {\n\t\tconst key = resolveByGroup\n\t\t\t? (term.translationGroup ?? term.id)\n\t\t\t: `${term.locale}::${term.translationGroup ?? term.id}`;\n\t\tbyLocaleGroup.set(key, term);\n\t}\n\tfor (const term of flatTerms) {\n\t\tconst parent = term.parentId\n\t\t\t? byLocaleGroup.get(resolveByGroup ? term.parentId : `${term.locale}::${term.parentId}`)\n\t\t\t: undefined;\n\t\tif (parent) {\n\t\t\tparent.children.push(term);\n\t\t} else {\n\t\t\troots.push(term);\n\t\t}\n\t}\n\treturn roots;\n}\n\ntype TaxonomyDefLookup =\n\t| { success: true; def: Selectable<TaxonomyDefTable> }\n\t| { success: false; error: { code: string; message: string } };\n\nfunction taxonomyDefNotFound(name: string, locale?: string): TaxonomyDefLookup {\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"NOT_FOUND\",\n\t\t\tmessage: `Taxonomy '${name}' not found${locale !== undefined ? ` in locale '${locale}'` : \"\"}`,\n\t\t},\n\t};\n}\n\n/** Look up the exact definition addressed by a mutation. */\nasync function requireTaxonomyDef(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlocale?: string,\n): Promise<TaxonomyDefLookup> {\n\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll().where(\"name\", \"=\", name);\n\tif (locale !== undefined) query = query.where(\"locale\", \"=\", locale);\n\tconst def = await query.orderBy(\"locale\", \"asc\").orderBy(\"id\", \"asc\").executeTakeFirst();\n\treturn def ? { success: true, def } : taxonomyDefNotFound(name, locale);\n}\n\n/**\n * Prefer the requested locale, then the configured default. Incomplete\n * translation groups fall back deterministically so their terms remain usable.\n */\nasync function requireTaxonomyDefWithFallback(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlocale?: string,\n): Promise<TaxonomyDefLookup> {\n\tconst defs = await db\n\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t.selectAll()\n\t\t.where(\"name\", \"=\", name)\n\t\t.orderBy(\"locale\", \"asc\")\n\t\t.orderBy(\"id\", \"asc\")\n\t\t.execute();\n\tconst defaultLocale = getI18nConfig()?.defaultLocale;\n\tconst def =\n\t\t(locale ? defs.find((candidate) => candidate.locale === locale) : undefined) ??\n\t\t(defaultLocale ? defs.find((candidate) => candidate.locale === defaultLocale) : undefined) ??\n\t\tdefs[0];\n\treturn def ? { success: true, def } : taxonomyDefNotFound(name, locale);\n}\n\n/** The subset of `slugs` that still has a row in `_emdash_collections`. */\nasync function findExistingCollections(\n\tdb: Kysely<Database>,\n\tslugs: readonly string[],\n): Promise<Set<string>> {\n\tif (slugs.length === 0) return new Set();\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"slug\")\n\t\t.where(\"slug\", \"in\", [...slugs])\n\t\t.execute();\n\treturn new Set(rows.map((r) => r.slug));\n}\n\n/**\n * Reject a `collections` list naming collections that don't exist. Shared by\n * create and update so both fail the same way instead of storing a reference\n * that reads back filtered out.\n */\nasync function validateCollections(\n\tdb: Kysely<Database>,\n\tcollections: readonly string[],\n): Promise<{ code: \"VALIDATION_ERROR\"; message: string } | null> {\n\tconst existing = await findExistingCollections(db, collections);\n\tconst invalid = collections.filter((slug) => !existing.has(slug));\n\tif (invalid.length === 0) return null;\n\treturn {\n\t\tcode: \"VALIDATION_ERROR\",\n\t\tmessage: `Unknown collection(s): ${invalid.join(\", \")}`,\n\t};\n}\n\n/**\n * Shape one definition row for a response, dropping collection references whose\n * collection is gone. Storage is untouched — re-creating the collection\n * re-links automatically, same as `handleTaxonomyList`.\n */\nasync function toTaxonomyResponse(\n\tdb: Kysely<Database>,\n\trow: Selectable<TaxonomyDefTable>,\n): Promise<TaxonomyResponse> {\n\tconst def = rowToDef(row);\n\tconst realCollections = await findExistingCollections(db, def.collections);\n\treturn {\n\t\ttaxonomy: {\n\t\t\t...def,\n\t\t\tcollections: def.collections.filter((slug) => realCollections.has(slug)),\n\t\t},\n\t};\n}\n\n/** Declared collections of a taxonomy def row (deduped, parsed from JSON). */\nfunction defCollections(def: Selectable<TaxonomyDefTable>): string[] {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- column stores a JSON string[] written by handleTaxonomyCreate\n\tconst parsed = def.collections ? (JSON.parse(def.collections) as string[]) : [];\n\treturn [...new Set(parsed)];\n}\n\nfunction rowToDef(row: Selectable<TaxonomyDefTable>): TaxonomyDef {\n\treturn {\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tlabel: row.label,\n\t\tlabelSingular: row.label_singular ?? undefined,\n\t\thierarchical: row.hierarchical === 1,\n\t\tcollections: row.collections ? JSON.parse(row.collections) : [],\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Taxonomy definition handlers\n// ---------------------------------------------------------------------------\n\n/**\n * List all taxonomy definitions\n */\nexport async function handleTaxonomyList(\n\tdb: Kysely<Database>,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TaxonomyListResponse>> {\n\ttry {\n\t\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\t\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll();\n\t\tif (locale !== undefined) query = query.where(\"locale\", \"=\", locale);\n\t\tconst [rows, collectionRows] = await Promise.all([\n\t\t\tquery.execute(),\n\t\t\tdb.selectFrom(\"_emdash_collections\").select(\"slug\").execute(),\n\t\t]);\n\n\t\t// Filter orphan collection references on read so the response stays\n\t\t// consistent with `schema_list_collections`. Storage is untouched —\n\t\t// re-creating the collection re-links automatically.\n\t\tconst realCollections = new Set(collectionRows.map((r) => r.slug));\n\n\t\tconst taxonomies: TaxonomyDef[] = rows.map((row) => {\n\t\t\tconst def = rowToDef(row);\n\t\t\treturn { ...def, collections: def.collections.filter((slug) => realCollections.has(slug)) };\n\t\t});\n\n\t\treturn { success: true, data: { taxonomies } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_LIST_ERROR\", message: \"Failed to list taxonomies\" },\n\t\t};\n\t}\n}\n\n/**\n * Get a single taxonomy definition by name.\n *\n * Definitions are per-locale, so `locale` picks which one. Without it the\n * configured default locale wins, then the lowest locale code.\n */\nexport async function handleTaxonomyGet(\n\tdb: Kysely<Database>,\n\tname: string,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TaxonomyResponse>> {\n\ttry {\n\t\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\t\tconst lookup = await requireTaxonomyDefWithFallback(db, name, locale);\n\t\tif (!lookup.success) return lookup;\n\n\t\treturn { success: true, data: await toTaxonomyResponse(db, lookup.def) };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_GET_ERROR\", message: \"Failed to get taxonomy\" },\n\t\t};\n\t}\n}\n\n/**\n * Create a new taxonomy definition\n */\nexport async function handleTaxonomyCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tname: string;\n\t\tlabel: string;\n\t\tlabelSingular?: string;\n\t\thierarchical?: boolean;\n\t\tcollections?: string[];\n\t\tlocale?: string;\n\t\ttranslationOf?: string;\n\t},\n): Promise<ApiResult<TaxonomyResponse>> {\n\ttry {\n\t\tconst locale = resolveConfiguredLocale(input.locale ?? getI18nConfig()?.defaultLocale ?? \"en\");\n\t\tif (!NAME_PATTERN.test(input.name)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Taxonomy name must start with a letter and contain only lowercase letters, numbers, and underscores\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tlet hierarchical = input.hierarchical ?? false;\n\t\tlet collections = [...new Set(input.collections ?? [])];\n\t\tlet translationGroup: string | null = null;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"id\", \"=\", input.translationOf)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (!source) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Source taxonomy for translation not found\" },\n\t\t\t\t};\n\t\t\t}\n\t\t\ttranslationGroup = source.translation_group ?? source.id;\n\t\t\tif (input.hierarchical === undefined) {\n\t\t\t\thierarchical = source.hierarchical === 1;\n\t\t\t}\n\t\t\tif (input.collections === undefined) {\n\t\t\t\tcollections = [...new Set(defCollections(source))];\n\t\t\t}\n\t\t}\n\n\t\tconst collectionError = await validateCollections(db, collections);\n\t\tif (collectionError) {\n\t\t\treturn { success: false, error: collectionError };\n\t\t}\n\n\t\t// Duplicate guard scoped to locale (so the same name can exist in ES\n\t\t// and EN).\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"name\", \"=\", input.name)\n\t\t\t.where(\"locale\", \"=\", locale)\n\t\t\t.executeTakeFirst();\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `Taxonomy '${input.name}' already exists in locale '${locale}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst id = ulid();\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_taxonomy_defs\")\n\t\t\t.values({\n\t\t\t\tid,\n\t\t\t\tname: input.name,\n\t\t\t\tlabel: input.label,\n\t\t\t\tlabel_singular: input.labelSingular ?? null,\n\t\t\t\thierarchical: hierarchical ? 1 : 0,\n\t\t\t\tcollections: JSON.stringify(collections),\n\t\t\t\tlocale,\n\t\t\t\ttranslation_group: translationGroup ?? id,\n\t\t\t})\n\t\t\t.execute();\n\n\t\t// A new def changes which taxonomies exist — drop the isolate-wide\n\t\t// defs/names caches so this isolate reflects it immediately.\n\t\tinvalidateTaxonomyDefsCache();\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirstOrThrow();\n\t\treturn { success: true, data: { taxonomy: rowToDef(row) } };\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message.includes(\"UNIQUE constraint failed\")) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"CONFLICT\", message: `Taxonomy '${input.name}' already exists` },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_CREATE_ERROR\", message: \"Failed to create taxonomy\" },\n\t\t};\n\t}\n}\n\n/**\n * Update a taxonomy definition.\n *\n * Writes the one row `(name, locale)` resolves to — every field here belongs to\n * a single locale's definition, so translating a taxonomy and then editing the\n * translation leaves the other locales alone. `name` and `locale` are immutable\n * (renaming would strand the terms, which are keyed on `name`).\n */\nexport async function handleTaxonomyUpdate(\n\tdb: Kysely<Database>,\n\tname: string,\n\tinput: {\n\t\tlabel?: string;\n\t\tlabelSingular?: string | null;\n\t\thierarchical?: boolean;\n\t\tcollections?: string[];\n\t\tlocale?: string;\n\t},\n): Promise<ApiResult<TaxonomyResponse>> {\n\ttry {\n\t\tconst locale = input.locale ? resolveConfiguredLocale(input.locale) : undefined;\n\t\tconst lookup = await requireTaxonomyDef(db, name, locale);\n\t\tif (!lookup.success) return lookup;\n\n\t\tconst collections = input.collections ? [...new Set(input.collections)] : undefined;\n\t\tif (collections !== undefined) {\n\t\t\tconst collectionError = await validateCollections(db, collections);\n\t\t\tif (collectionError) {\n\t\t\t\treturn { success: false, error: collectionError };\n\t\t\t}\n\t\t}\n\n\t\tconst updates: {\n\t\t\tlabel?: string;\n\t\t\tlabel_singular?: string | null;\n\t\t\thierarchical?: number;\n\t\t\tcollections?: string;\n\t\t} = {};\n\t\tif (input.label !== undefined) updates.label = input.label;\n\t\tif (input.labelSingular !== undefined) updates.label_singular = input.labelSingular;\n\t\tif (input.hierarchical !== undefined) updates.hierarchical = input.hierarchical ? 1 : 0;\n\t\tif (collections !== undefined) updates.collections = JSON.stringify(collections);\n\n\t\tif (Object.keys(updates).length > 0) {\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_taxonomy_defs\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"id\", \"=\", lookup.def.id)\n\t\t\t\t.execute();\n\t\t\tinvalidateTaxonomyDefsCache();\n\t\t}\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", lookup.def.id)\n\t\t\t.executeTakeFirstOrThrow();\n\n\t\treturn { success: true, data: await toTaxonomyResponse(db, row) };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_UPDATE_ERROR\", message: \"Failed to update taxonomy\" },\n\t\t};\n\t}\n}\n\n/**\n * Delete a taxonomy: every locale's definition, every term under that name in\n * every locale, and the term assignments those terms hold.\n *\n * There is no locale scope and no non-empty guard. Terms are keyed on `name`\n * rather than on a definition row, so leaving one locale's definition behind\n * would leave the taxonomy half-deleted — a definition with no terms, or terms\n * no definition describes.\n */\nexport async function handleTaxonomyDelete(\n\tdb: Kysely<Database>,\n\tname: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst lookup = await requireTaxonomyDef(db, name);\n\t\tif (!lookup.success) return lookup;\n\n\t\tawait withTransaction(db, async (trx) => {\n\t\t\t// `content_taxonomies.taxonomy_id` holds a term's translation_group, so\n\t\t\t// the assignments have to go before the terms they are matched against.\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"content_taxonomies\")\n\t\t\t\t.where(\"taxonomy_id\", \"in\", (eb) =>\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t\t\t\t.select(sql<string>`coalesce(translation_group, id)`.as(\"group\"))\n\t\t\t\t\t\t.where(\"name\", \"=\", name),\n\t\t\t\t)\n\t\t\t\t.execute();\n\t\t\tawait trx.deleteFrom(\"taxonomies\").where(\"name\", \"=\", name).execute();\n\t\t\tawait trx.deleteFrom(\"_emdash_taxonomy_defs\").where(\"name\", \"=\", name).execute();\n\t\t});\n\n\t\t// Covers the term caches too — see `invalidateTaxonomyDefsCache`.\n\t\tinvalidateTaxonomyDefsCache();\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch (error) {\n\t\tconsole.error(\"[taxonomies] delete failed:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_DELETE_ERROR\", message: \"Failed to delete taxonomy\" },\n\t\t};\n\t}\n}\n\n/**\n * List every locale translation of a taxonomy def (by id or translation_group).\n */\nexport async function handleTaxonomyDefTranslations(\n\tdb: Kysely<Database>,\n\tidOrGroup: string,\n): Promise<ApiResult<TaxonomyDefTranslationsResponse>> {\n\ttry {\n\t\tconst anchor = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where((eb) => eb.or([eb(\"id\", \"=\", idOrGroup), eb(\"translation_group\", \"=\", idOrGroup)]))\n\t\t\t.executeTakeFirst();\n\t\tif (!anchor) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Taxonomy not found\" },\n\t\t\t};\n\t\t}\n\t\tconst group = anchor.translation_group ?? anchor.id;\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"translation_group\", \"=\", group)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.execute();\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\ttranslationGroup: group,\n\t\t\t\ttranslations: rows.map((r) => ({\n\t\t\t\t\tid: r.id,\n\t\t\t\t\tname: r.name,\n\t\t\t\t\tlabel: r.label,\n\t\t\t\t\tlocale: r.locale,\n\t\t\t\t})),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TAXONOMY_TRANSLATIONS_ERROR\",\n\t\t\t\tmessage: \"Failed to list taxonomy translations\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Term handlers\n// ---------------------------------------------------------------------------\n\n/**\n * List all terms for a taxonomy (returns tree for hierarchical taxonomies)\n */\nexport async function handleTermList(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\toptions: { locale?: string; includeCounts?: boolean; resolveFallback?: boolean } = {},\n): Promise<ApiResult<TermListResponse>> {\n\ttry {\n\t\tif (options.resolveFallback && !options.locale) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"A locale is required when resolving taxonomy fallbacks\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\t// Definitions are per-locale but terms aren't bound to the def's locale —\n\t\t// use the active definition for its collection scope.\n\t\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\t\tconst lookup = await requireTaxonomyDefWithFallback(db, taxonomyName, locale);\n\t\tif (!lookup.success) return lookup;\n\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst terms =\n\t\t\toptions.resolveFallback && locale\n\t\t\t\t? await repo.findByNameResolved(\n\t\t\t\t\t\ttaxonomyName,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tgetI18nConfig()?.defaultLocale ?? \"en\",\n\t\t\t\t\t)\n\t\t\t\t: await repo.findByName(taxonomyName, { locale });\n\n\t\t// Counts match what visitors see on the public site: published (or\n\t\t// scheduled-and-due) entries that aren't soft-deleted, scoped to the\n\t\t// taxonomy's declared collections — one query for the whole list.\n\t\t// content_taxonomies.taxonomy_id stores the translation_group, so we\n\t\t// look up by group and map back to each term's id.\n\t\tconst includeCounts = options.includeCounts ?? true;\n\t\tconst countsByGroup = includeCounts\n\t\t\t? await fetchVisibleTermCounts(db, taxonomyName, defCollections(lookup.def), locale)\n\t\t\t: undefined;\n\n\t\tconst termData: TermWithCount[] = terms.map((term) => ({\n\t\t\tid: term.id,\n\t\t\tname: term.name,\n\t\t\tslug: term.slug,\n\t\t\tlabel: term.label,\n\t\t\tparentId: term.parentId,\n\t\t\tdescription: typeof term.data?.description === \"string\" ? term.data.description : undefined,\n\t\t\tchildren: [],\n\t\t\t...(countsByGroup && { count: countsByGroup.get(term.translationGroup ?? term.id) ?? 0 }),\n\t\t\tlocale: term.locale,\n\t\t\ttranslationGroup: term.translationGroup,\n\t\t}));\n\n\t\tconst isHierarchical = lookup.def.hierarchical === 1;\n\t\tconst result = isHierarchical\n\t\t\t? buildTree(termData, options.resolveFallback && !!locale)\n\t\t\t: termData;\n\t\treturn { success: true, data: { terms: result } };\n\t} catch (error) {\n\t\tconsole.error(\"[taxonomies] term list failed:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_LIST_ERROR\", message: \"Failed to list terms\" },\n\t\t};\n\t}\n}\n\n/**\n * Set the manual order of one sibling group.\n *\n * `ids` names the terms to move, in the desired order; each is a row id or a\n * translation_group and resolves to the latter, because a position belongs to a\n * term rather than to one of its locales. Reordering never reparents — moving a\n * term under a different parent is a term update.\n *\n * The group is `(taxonomy, parentId)` and has no locale: `parentId` is the\n * parent's translation_group (a row id is accepted and resolved), and `null`\n * selects the top level — which for a flat taxonomy is every term.\n *\n * `ids` may be a *subset* of the group, and the listed terms are permuted\n * within the positions they already occupy. A locale only renders the terms\n * translated into it, so a caller often cannot name every member; a member left\n * out keeps its place, which is also what makes a stale list harmless.\n */\nexport async function handleTermReorder(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\tinput: { parentId?: string | null; ids: string[] },\n): Promise<ApiResult<TermReorderResponse>> {\n\ttry {\n\t\tconst lookup = await requireTaxonomyDef(db, taxonomyName);\n\t\tif (!lookup.success) return lookup;\n\n\t\tconst repo = new TaxonomyRepository(db);\n\n\t\tlet parentId = input.parentId ?? null;\n\t\tif (parentId !== null) {\n\t\t\t// Children store the parent's translation_group, so a caller passing a\n\t\t\t// row id still addresses the right group. A parent whose anchor row is\n\t\t\t// gone keeps the value as given (see TaxonomyRepository.resolveParentRef).\n\t\t\tconst parent = await repo.findById(parentId);\n\t\t\tif (parent) parentId = parent.translationGroup ?? parent.id;\n\t\t}\n\n\t\t// Every locale's rows: membership is locale-agnostic, and a term the\n\t\t// caller can't see still holds its slot.\n\t\t// The order carries into how ties are resolved, so it must be stable\n\t\t// across calls; with locales interleaved a tie falls to whichever\n\t\t// locale's label sorts first, not to the order the caller rendered.\n\t\tconst members = await repo.findByName(taxonomyName, { parentId });\n\t\tconst siblings: SiblingPosition[] = [];\n\t\tconst seen = new Set<string>();\n\t\tconst resolve = new Map<string, string>();\n\t\tfor (const term of members) {\n\t\t\tconst group = term.translationGroup ?? term.id;\n\t\t\tif (!seen.has(group)) {\n\t\t\t\tseen.add(group);\n\t\t\t\tsiblings.push({ group, position: term.sortOrder });\n\t\t\t}\n\t\t\t// Both spellings resolve to the group: a row id, and the group itself for\n\t\t\t// a caller that already holds one — including when its anchor row is gone\n\t\t\t// and no member's id matches it.\n\t\t\tresolve.set(group, group);\n\t\t\tresolve.set(term.id, group);\n\t\t}\n\n\t\tconst groups: string[] = [];\n\t\tfor (const id of input.ids) {\n\t\t\tconst group = resolve.get(id);\n\t\t\tif (!group) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"REORDER_MISMATCH\",\n\t\t\t\t\t\tmessage: `Term \"${id}\" is not in the sibling group being reordered`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (groups.includes(group)) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"REORDER_MISMATCH\",\n\t\t\t\t\t\tmessage: `Term \"${id}\" is listed more than once`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tgroups.push(group);\n\t\t}\n\n\t\tawait repo.reorder(groups, siblings);\n\t\tinvalidateTermCache();\n\n\t\treturn { success: true, data: { reordered: true } };\n\t} catch (error) {\n\t\tconsole.error(\"[taxonomies] term reorder failed:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_REORDER_ERROR\", message: \"Failed to reorder terms\" },\n\t\t};\n\t}\n}\n\n/**\n * Validate a parent term reference for create/update.\n *\n * Returns `null` on success or a structured error message that callers\n * wrap in their own ApiResult.\n *\n * - `parentId === undefined` -> no-op (no parent change requested).\n * - `parentId === null` -> caller intends to detach; no-op here.\n * - parent must exist (FK exists -> term row not soft-deleted).\n * - parent must live in the same taxonomy.\n * - reject a parent in the term's own translation_group (self-parent),\n * including the create path: a translation inherits its source's group, so\n * `selfGroup` carries that prospective group when `termId` is absent.\n * - on update, walk up the parent chain to detect cycles.\n */\nasync function validateParentTerm(\n\trepo: TaxonomyRepository,\n\ttaxonomyName: string,\n\ttermId: string | undefined,\n\tparentId: string | null | undefined,\n\tselfGroup?: string | null,\n): Promise<{ code: \"VALIDATION_ERROR\"; message: string } | null> {\n\tif (parentId === undefined || parentId === null) return null;\n\n\tconst parent = await repo.findById(parentId);\n\tif (!parent) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Parent term '${parentId}' not found`,\n\t\t};\n\t}\n\tif (parent.name !== taxonomyName) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Parent term '${parentId}' belongs to taxonomy '${parent.name}', not '${taxonomyName}'`,\n\t\t};\n\t}\n\n\t// Parentage keys off translation_group (what `parent_id` persists), so the\n\t// self-parent and cycle checks compare groups, not row ids — picking a\n\t// sibling translation of the term as its parent is still self-parenting.\n\tconst parentGroup = parent.translationGroup ?? parent.id;\n\tlet termGroup: string | null = selfGroup ?? null;\n\tif (termId !== undefined) {\n\t\tconst term = await repo.findById(termId);\n\t\ttermGroup = term ? (term.translationGroup ?? term.id) : termId;\n\t}\n\tif (termGroup !== null && parentGroup === termGroup) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: \"A term cannot be its own parent\",\n\t\t};\n\t}\n\n\t// Walk up the parent chain. Two checks fold into one walk:\n\t// - Cycle detection (only on update — a non-existent term-being-\n\t// created can't be its own ancestor): if the walk revisits termId\n\t// the proposed parent makes the term a descendant of itself.\n\t// - Depth bound: refuse to extend a chain past MAX_DEPTH ancestors.\n\t// Runs on both create and update so a malicious or buggy caller\n\t// can't grow the tree without limit.\n\t//\n\t// The depth-exceeded error fires only when we hit the limit AND there\n\t// was still chain to walk — a legitimate chain of exactly MAX_DEPTH\n\t// ancestors exits with `cursor === null` and is accepted.\n\t// `parent_id` stores a translation_group, which always equals the anchor\n\t// row's id, so findById(cursor) still resolves each ancestor.\n\tconst MAX_DEPTH = 100;\n\tlet cursor: string | null = parent.parentId;\n\tlet steps = 0;\n\twhile (cursor !== null && steps < MAX_DEPTH) {\n\t\tif (termGroup !== null && cursor === termGroup) {\n\t\t\treturn {\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\tmessage: \"Cycle detected: cannot make a descendant the parent\",\n\t\t\t};\n\t\t}\n\t\tconst next = await repo.findById(cursor);\n\t\tif (!next) break;\n\t\tcursor = next.parentId;\n\t\tsteps++;\n\t}\n\tif (cursor !== null && steps >= MAX_DEPTH) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: \"Parent chain exceeds maximum depth\",\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Create a new term in a taxonomy\n */\nexport async function handleTermCreate(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\tinput: {\n\t\tslug?: string;\n\t\tlabel: string;\n\t\tparentId?: string | null;\n\t\tdescription?: string;\n\t\tlocale?: string;\n\t\ttranslationOf?: string;\n\t},\n): Promise<ApiResult<TermResponse>> {\n\tlet attemptedSlug = input.slug;\n\ttry {\n\t\tconst locale = resolveConfiguredLocale(input.locale ?? getI18nConfig()?.defaultLocale ?? \"en\");\n\t\t// Taxonomy definitions are per-locale, but terms can exist in any locale\n\t\t// regardless of whether the def has been translated there. Look up the\n\t\t// def across all locales — we only care that it *exists*.\n\t\tconst lookup = await requireTaxonomyDef(db, taxonomyName);\n\t\tif (!lookup.success) return lookup;\n\n\t\tconst repo = new TaxonomyRepository(db);\n\n\t\t// Coerce empty-string parentId to undefined (treat as \"no parent\").\n\t\tconst parentId =\n\t\t\tinput.parentId === \"\" || input.parentId === undefined ? undefined : input.parentId;\n\n\t\t// Conflict check is scoped to locale (per-locale slugs are unique).\n\t\tconst existing =\n\t\t\tinput.slug === undefined ? null : await repo.findBySlug(taxonomyName, input.slug, locale);\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `Term '${input.slug}' already exists in '${taxonomyName}' (${locale})`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// No locale re-pointing needed: `repo.create` persists the parent's\n\t\t// translation_group in `parent_id`, so a child stays nested under the\n\t\t// parent in every locale automatically — including parents translated\n\t\t// after the child was created.\n\n\t\t// A translation inherits its source's translation_group; pass that\n\t\t// prospective group so validateParentTerm can reject a parent in the same\n\t\t// group (cross-locale self-parent) even though the term doesn't exist yet.\n\t\tlet selfGroup: string | null = null;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await repo.findById(input.translationOf);\n\t\t\tselfGroup = source ? (source.translationGroup ?? source.id) : null;\n\t\t}\n\n\t\t// Validate parentId: must exist AND belong to the same taxonomy.\n\t\t// (Cycle check is N/A on create — the term doesn't exist yet.)\n\t\tconst parentError = await validateParentTerm(\n\t\t\trepo,\n\t\t\ttaxonomyName,\n\t\t\tundefined,\n\t\t\tparentId,\n\t\t\tselfGroup,\n\t\t);\n\t\tif (parentError) {\n\t\t\treturn { success: false, error: parentError };\n\t\t}\n\n\t\tconst create = (slug: string) =>\n\t\t\trepo.create({\n\t\t\t\tname: taxonomyName,\n\t\t\t\tslug,\n\t\t\t\tlabel: input.label,\n\t\t\t\tparentId: parentId ?? undefined,\n\t\t\t\tdata: input.description ? { description: input.description } : undefined,\n\t\t\t\tlocale,\n\t\t\t\ttranslationOf: input.translationOf,\n\t\t\t});\n\t\tlet term: Awaited<ReturnType<typeof create>> | undefined;\n\t\tlet lastSlugConflict: unknown;\n\t\tif (input.slug !== undefined) {\n\t\t\tterm = await create(input.slug);\n\t\t} else {\n\t\t\tfor (let attempt = 0; attempt < MAX_GENERATED_TERM_SLUG_ATTEMPTS; attempt++) {\n\t\t\t\tattemptedSlug = await repo.generateUniqueSlug(taxonomyName, input.label, locale);\n\t\t\t\ttry {\n\t\t\t\t\tterm = await create(attemptedSlug);\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!isTermSlugUniqueViolation(error)) throw error;\n\t\t\t\t\tlastSlugConflict = error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!term) throw lastSlugConflict ?? new Error(\"Failed to create taxonomy term\");\n\n\t\tinvalidateTermCache();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tterm: {\n\t\t\t\t\tid: term.id,\n\t\t\t\t\tname: term.name,\n\t\t\t\t\tslug: term.slug,\n\t\t\t\t\tlabel: term.label,\n\t\t\t\t\tparentId: term.parentId,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\ttypeof term.data?.description === \"string\" ? term.data.description : undefined,\n\t\t\t\t\tlocale: term.locale,\n\t\t\t\t\ttranslationGroup: term.translationGroup,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (isTermSlugUniqueViolation(error)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: `Term with slug '${attemptedSlug ?? \"(generated)\"}' already exists in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_CREATE_ERROR\", message: \"Failed to create term\" },\n\t\t};\n\t}\n}\n\n/**\n * Get a single term by slug\n */\nexport async function handleTermGet(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TermGetResponse>> {\n\ttry {\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\t\tconst term = await repo.findBySlug(taxonomyName, termSlug, locale);\n\n\t\tif (!term) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Term '${termSlug}' not found in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Count matches public visibility (published or scheduled-and-due, not\n\t\t// soft-deleted) scoped to the def's declared collections. The def lookup\n\t\t// falls back for incomplete translations; a term with no def at all still\n\t\t// resolves with count 0.\n\t\tconst lookup = await requireTaxonomyDefWithFallback(db, taxonomyName, locale);\n\t\tconst counts = await fetchVisibleTermCounts(\n\t\t\tdb,\n\t\t\ttaxonomyName,\n\t\t\tlookup.success ? defCollections(lookup.def) : [],\n\t\t\tlocale ?? term.locale,\n\t\t);\n\t\tconst count = counts.get(term.translationGroup ?? term.id) ?? 0;\n\t\t// Children share this term's translation_group as their parent_id; scope\n\t\t// to the term's own locale so the response stays within one locale's tree.\n\t\tconst children = await repo.findChildren(term.id, term.locale);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tterm: {\n\t\t\t\t\tid: term.id,\n\t\t\t\t\tname: term.name,\n\t\t\t\t\tslug: term.slug,\n\t\t\t\t\tlabel: term.label,\n\t\t\t\t\tparentId: term.parentId,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\ttypeof term.data?.description === \"string\" ? term.data.description : undefined,\n\t\t\t\t\tcount,\n\t\t\t\t\tchildren: children.map((c) => ({ id: c.id, slug: c.slug, label: c.label })),\n\t\t\t\t\tlocale: term.locale,\n\t\t\t\t\ttranslationGroup: term.translationGroup,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_GET_ERROR\", message: \"Failed to get term\" },\n\t\t};\n\t}\n}\n\n/** List every translation of a term (by id or translation_group). */\nexport async function handleTermTranslations(\n\tdb: Kysely<Database>,\n\tidOrGroup: string,\n): Promise<ApiResult<TermTranslationsResponse>> {\n\ttry {\n\t\tconst anchor = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where((eb) => eb.or([eb(\"id\", \"=\", idOrGroup), eb(\"translation_group\", \"=\", idOrGroup)]))\n\t\t\t.executeTakeFirst();\n\t\tif (!anchor) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Term not found\" },\n\t\t\t};\n\t\t}\n\t\tconst group = anchor.translation_group ?? anchor.id;\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where(\"translation_group\", \"=\", group)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.execute();\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\ttranslationGroup: group,\n\t\t\t\ttranslations: rows.map((r) => ({\n\t\t\t\t\tid: r.id,\n\t\t\t\t\tslug: r.slug,\n\t\t\t\t\tlabel: r.label,\n\t\t\t\t\tlocale: r.locale,\n\t\t\t\t})),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_TRANSLATIONS_ERROR\", message: \"Failed to list term translations\" },\n\t\t};\n\t}\n}\n\n/**\n * Update a term\n */\nexport async function handleTermUpdate(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\tinput: { slug?: string; label?: string; parentId?: string | null; description?: string },\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TermResponse>> {\n\ttry {\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\t\tconst term = await repo.findBySlug(taxonomyName, termSlug, locale);\n\n\t\tif (!term) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Term '${termSlug}' not found in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Coerce empty-string slug/parentId to undefined (treat as \"no change\").\n\t\t// `null` parentId is a valid request meaning \"detach from parent\".\n\t\tconst newSlug = input.slug === \"\" || input.slug === undefined ? undefined : input.slug;\n\t\tconst newParentId =\n\t\t\tinput.parentId === \"\" || input.parentId === undefined ? undefined : input.parentId;\n\n\t\t// Check if new slug conflicts (per-locale uniqueness).\n\t\tif (newSlug !== undefined && newSlug !== termSlug) {\n\t\t\tconst existing = await repo.findBySlug(taxonomyName, newSlug, locale);\n\t\t\tif (existing && existing.id !== term.id) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `Term with slug '${newSlug}' already exists in taxonomy '${taxonomyName}'`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Validate parentId: existence, same-taxonomy, no self-parent, no cycle.\n\t\tconst parentError = await validateParentTerm(repo, taxonomyName, term.id, newParentId);\n\t\tif (parentError) {\n\t\t\treturn { success: false, error: parentError };\n\t\t}\n\n\t\tconst updated = await repo.update(term.id, {\n\t\t\tslug: newSlug,\n\t\t\tlabel: input.label,\n\t\t\tparentId: newParentId,\n\t\t\tdata: input.description !== undefined ? { description: input.description } : undefined,\n\t\t});\n\n\t\tinvalidateTermCache();\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"TERM_UPDATE_ERROR\", message: \"Failed to update term\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tterm: {\n\t\t\t\t\tid: updated.id,\n\t\t\t\t\tname: updated.name,\n\t\t\t\t\tslug: updated.slug,\n\t\t\t\t\tlabel: updated.label,\n\t\t\t\t\tparentId: updated.parentId,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\ttypeof updated.data?.description === \"string\" ? updated.data.description : undefined,\n\t\t\t\t\tlocale: updated.locale,\n\t\t\t\t\ttranslationGroup: updated.translationGroup,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_UPDATE_ERROR\", message: \"Failed to update term\" },\n\t\t};\n\t}\n}\n\n/**\n * Delete a term\n */\nexport async function handleTermDelete(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;\n\t\tconst term = await repo.findBySlug(taxonomyName, termSlug, locale);\n\n\t\tif (!term) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Term '${termSlug}' not found in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Block deletion if the term's group still parents any child in any\n\t\t// locale — children store this group in parent_id, so removing the term\n\t\t// would orphan them (or null their parent_id via the self-FK).\n\t\tconst children = await repo.findChildren(term.id);\n\t\tif (children.length > 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Cannot delete term with children. Delete children first.\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst deleted = await repo.delete(term.id);\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"TERM_DELETE_ERROR\", message: \"Failed to delete term\" },\n\t\t\t};\n\t\t}\n\n\t\tinvalidateTermCache();\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_DELETE_ERROR\", message: \"Failed to delete term\" },\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { c as __exportAll } from "./runner-
|
|
1
|
+
import { c as __exportAll } from "./runner-BLIDKpmA.mjs";
|
|
2
2
|
import { t as validateIdentifier } from "./validate-BpwoDOgt.mjs";
|
|
3
3
|
import { i as compoundSelectLimit, n as buildStatusCondition } from "./dialect-helpers-BSbfaO3F.mjs";
|
|
4
4
|
import { r as getI18nConfig } from "./config-C3EBstHi.mjs";
|
|
5
5
|
import { getRequestContext } from "./request-context.mjs";
|
|
6
|
-
import { m as isObjectCacheActive, n as cachedQuery, p as invalidateTaxonomyObjectCache, r as contentCacheNamespaces, t as CacheNamespace } from "./object-cache-
|
|
7
|
-
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-
|
|
6
|
+
import { m as isObjectCacheActive, n as cachedQuery, p as invalidateTaxonomyObjectCache, r as contentCacheNamespaces, t as CacheNamespace } from "./object-cache-DVKnyOfy.mjs";
|
|
7
|
+
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-C4szrD8o.mjs";
|
|
8
8
|
import { n as isMissingTableError } from "./db-errors-Cl-sKYTm.mjs";
|
|
9
|
-
import { i as setRequestCacheEntry, n as peekRequestCache, r as requestCached } from "./request-cache-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { i as setRequestCacheEntry, n as peekRequestCache, r as requestCached } from "./request-cache-DH9zdCOD.mjs";
|
|
10
|
+
import { r as taxonomyTag } from "./chrome-tags-D8fiZG94.mjs";
|
|
11
|
+
import { i as resolveLocaleChain, r as resolveLocale } from "./resolve-prZ3mP7l.mjs";
|
|
12
|
+
import { l as resetTaxonomyNamesCache, s as getDb } from "./loader-Z8vl7zTq.mjs";
|
|
12
13
|
import { sql } from "kysely";
|
|
13
14
|
|
|
14
15
|
//#region src/taxonomies/term-counts.ts
|
|
@@ -127,6 +128,7 @@ var taxonomies_exports = /* @__PURE__ */ __exportAll({
|
|
|
127
128
|
getTaxonomyDef: () => getTaxonomyDef,
|
|
128
129
|
getTaxonomyDefs: () => getTaxonomyDefs,
|
|
129
130
|
getTaxonomyTerms: () => getTaxonomyTerms,
|
|
131
|
+
getTaxonomyTermsWithCacheHint: () => getTaxonomyTermsWithCacheHint,
|
|
130
132
|
getTerm: () => getTerm,
|
|
131
133
|
getTermsForEntries: () => getTermsForEntries,
|
|
132
134
|
invalidateTaxonomyDefsCache: () => invalidateTaxonomyDefsCache,
|
|
@@ -304,6 +306,18 @@ async function getTaxonomyTerms(taxonomyName, options = {}) {
|
|
|
304
306
|
const [terms, counts] = await Promise.all([getTermList(def, locale), getVisibleTermCounts(def.name, def.collections, locale)]);
|
|
305
307
|
return withCounts(terms, counts);
|
|
306
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Get all terms of a taxonomy with a Workers edge-cache hint.
|
|
311
|
+
*
|
|
312
|
+
* Use the returned `cacheHint` with `Astro.cache.set()` so pages that render
|
|
313
|
+
* a taxonomy facet can be purged automatically when taxonomy terms change.
|
|
314
|
+
*/
|
|
315
|
+
async function getTaxonomyTermsWithCacheHint(taxonomyName, options = {}) {
|
|
316
|
+
return {
|
|
317
|
+
data: await getTaxonomyTerms(taxonomyName, options),
|
|
318
|
+
cacheHint: { tags: [taxonomyTag(taxonomyName)] }
|
|
319
|
+
};
|
|
320
|
+
}
|
|
307
321
|
/** Terms without counts, under the cache keys the layout prefetch warms. */
|
|
308
322
|
function getTermList(def, locale) {
|
|
309
323
|
const localeKey = locale ?? "*";
|
|
@@ -375,17 +389,32 @@ function getVisibleTermCounts(taxonomyName, collections, locale) {
|
|
|
375
389
|
* Get a single term by (taxonomy, slug). Honours the fallback chain — if the
|
|
376
390
|
* slug exists in a fallback locale, we return that row (useful for deep-linking
|
|
377
391
|
* to a term page when the translation is missing).
|
|
392
|
+
*
|
|
393
|
+
* The term row and its visible-entry count are loaded and cached separately: the
|
|
394
|
+
* row depends only on the taxonomy epoch, while the count adds an aggregate over
|
|
395
|
+
* the whole assignment pivot and every counted collection's content epoch. A
|
|
396
|
+
* caller that renders no count passes `includeCounts: false` and skips it.
|
|
378
397
|
*/
|
|
379
398
|
async function getTerm(taxonomyName, slug, options = {}) {
|
|
380
399
|
const chain = resolveLocaleChain(options.locale);
|
|
381
|
-
const
|
|
400
|
+
const term = await getTermRow(taxonomyName, slug, chain);
|
|
401
|
+
if (!term) return null;
|
|
402
|
+
if (options.includeCounts === false) return term;
|
|
403
|
+
const counts = await getVisibleTermCounts(taxonomyName, (await getTaxonomyDef(taxonomyName, options))?.collections ?? [], chain[0] ?? term.locale);
|
|
404
|
+
return {
|
|
405
|
+
...term,
|
|
406
|
+
count: counts.get(term.translationGroup ?? term.id) ?? 0
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/** A single term with its children, without counts, under the taxonomy epoch. */
|
|
410
|
+
function getTermRow(taxonomyName, slug, chain) {
|
|
382
411
|
return cachedQuery({
|
|
383
|
-
namespace:
|
|
412
|
+
namespace: CacheNamespace.TAXONOMIES,
|
|
384
413
|
key: `term:${taxonomyName}:${slug}:${chain.join(",")}`,
|
|
385
|
-
load: () => loadTerm(taxonomyName, slug, chain
|
|
414
|
+
load: () => loadTerm(taxonomyName, slug, chain)
|
|
386
415
|
});
|
|
387
416
|
}
|
|
388
|
-
async function loadTerm(taxonomyName, slug, chain
|
|
417
|
+
async function loadTerm(taxonomyName, slug, chain) {
|
|
389
418
|
const db = await getDb();
|
|
390
419
|
let row;
|
|
391
420
|
const selectTerm = () => db.selectFrom("taxonomies").selectAll().where("name", "=", taxonomyName).where("slug", "=", slug);
|
|
@@ -401,9 +430,7 @@ async function loadTerm(taxonomyName, slug, chain, collections) {
|
|
|
401
430
|
let childrenQuery = db.selectFrom("taxonomies").selectAll().where("parent_id", "=", row.translation_group ?? row.id).orderBy("sort_order", "asc").orderBy("label", "asc");
|
|
402
431
|
const termLocale = row.locale;
|
|
403
432
|
if (termLocale) childrenQuery = childrenQuery.where("locale", "=", termLocale);
|
|
404
|
-
const
|
|
405
|
-
const count = counts.get(row.translation_group ?? row.id) ?? 0;
|
|
406
|
-
const children = childRows.map((child) => ({
|
|
433
|
+
const children = (await childrenQuery.execute()).map((child) => ({
|
|
407
434
|
id: child.id,
|
|
408
435
|
name: child.name,
|
|
409
436
|
slug: child.slug,
|
|
@@ -421,7 +448,6 @@ async function loadTerm(taxonomyName, slug, chain, collections) {
|
|
|
421
448
|
parentId: row.parent_id ?? void 0,
|
|
422
449
|
description: row.data ? JSON.parse(row.data).description : void 0,
|
|
423
450
|
children,
|
|
424
|
-
count,
|
|
425
451
|
locale: row.locale,
|
|
426
452
|
translationGroup: row.translation_group
|
|
427
453
|
};
|
|
@@ -619,7 +645,7 @@ function primeFoldedEntryTerms(collection, perEntry, options = {}) {
|
|
|
619
645
|
* the content query respect the active locale.
|
|
620
646
|
*/
|
|
621
647
|
async function getEntriesByTerm(collection, taxonomyName, termSlug, options = {}) {
|
|
622
|
-
const { getEmDashCollection } = await import("./query-
|
|
648
|
+
const { getEmDashCollection } = await import("./query-BZKZcG5b.mjs").then((n) => n.o);
|
|
623
649
|
const queryOptions = { where: { [taxonomyName]: termSlug } };
|
|
624
650
|
if (options.locale !== void 0) queryOptions.locale = options.locale;
|
|
625
651
|
const { entries } = await getEmDashCollection(collection, queryOptions);
|
|
@@ -668,5 +694,5 @@ function buildTree(flatTerms) {
|
|
|
668
694
|
}
|
|
669
695
|
|
|
670
696
|
//#endregion
|
|
671
|
-
export { getTaxonomyDefs as a,
|
|
672
|
-
//# sourceMappingURL=taxonomies-
|
|
697
|
+
export { getTaxonomyDefs as a, getTerm as c, invalidateTermCache as d, taxonomies_exports as f, getTaxonomyDef as i, getTermsForEntries as l, getEntriesByTerm as n, getTaxonomyTerms as o, fetchVisibleTermCounts as p, getEntryTerms as r, getTaxonomyTermsWithCacheHint as s, getAllTermsForEntries as t, invalidateTaxonomyDefsCache as u };
|
|
698
|
+
//# sourceMappingURL=taxonomies-Wfy_Lg12.mjs.map
|