emdash 0.28.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-hUy8vOB-.d.mts → adapters-C0ONzkUP.d.mts} +10 -1
- package/dist/adapters-C0ONzkUP.d.mts.map +1 -0
- package/dist/{allowed-origins-CRsJ6BPM.mjs → allowed-origins-CCEi9bPI.mjs} +2 -2
- package/dist/{allowed-origins-CRsJ6BPM.mjs.map → allowed-origins-CCEi9bPI.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.d.mts.map +1 -1
- package/dist/api/route-utils.mjs +22 -21
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -4
- package/dist/{api-DxFSorgk.mjs → api-b8WIiGU4.mjs} +306 -154
- package/dist/api-b8WIiGU4.mjs.map +1 -0
- package/dist/api-tokens-Cvmixds7.mjs +3 -0
- package/dist/{api-tokens-DvqeZmVq.mjs → api-tokens-uPt8UDpx.mjs} +2 -2
- package/dist/{api-tokens-DvqeZmVq.mjs.map → api-tokens-uPt8UDpx.mjs.map} +1 -1
- package/dist/{apply-CDJqIjPU.mjs → apply-CmIJK9j8.mjs} +35 -38
- package/dist/apply-CmIJK9j8.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +97 -13
- 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 +56 -107
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.mjs +7 -7
- package/dist/astro/middleware/request-context.d.mts +3 -0
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +258 -31
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +47 -3
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +379 -117
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/PluginRegistry.d.mts +5 -1
- package/dist/astro/routes/PluginRegistry.d.mts.map +1 -1
- package/dist/astro/routes/PluginRegistry.mjs +2 -1
- package/dist/astro/routes/PluginRegistry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
- package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +11 -10
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +11 -10
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +11 -10
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +18 -17
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +19 -24
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +18 -17
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -13
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +11 -10
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/index.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/comments/index.mjs +11 -10
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
- package/dist/astro/routes/api/admin/media-usage/repair.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/repair.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +30 -0
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +44 -41
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +44 -41
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +49 -41
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts +8 -0
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +39 -0
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts +9 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +78 -0
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -0
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +43 -40
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +46 -41
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +49 -41
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +43 -40
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +49 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +43 -40
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +43 -40
- 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 +47 -42
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +43 -40
- 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 +48 -43
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +44 -41
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +43 -40
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +43 -40
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +4 -4
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +10 -9
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +6 -6
- package/dist/astro/routes/api/admin/users/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/accept.mjs +4 -4
- package/dist/astro/routes/api/auth/invite/complete.mjs +14 -13
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +11 -10
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +13 -12
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/send.mjs +13 -12
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +5 -5
- package/dist/astro/routes/api/auth/me.mjs +9 -8
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +6 -3
- package/dist/astro/routes/api/auth/mode.mjs.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +16 -6
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +11 -5
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +8 -7
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/options.mjs +14 -13
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +14 -13
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +13 -12
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +4 -4
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +70 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +14 -13
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -4
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +14 -13
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts +9 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +72 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +51 -0
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -4
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +20 -19
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -4
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -9
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/index.mjs +12 -10
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +8 -8
- package/dist/astro/routes/api/dev/emails.mjs +4 -4
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +19 -18
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +16 -16
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +12 -11
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +12 -11
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -16
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -11
- 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 +19 -18
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +29 -28
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +8 -9
- package/dist/astro/routes/api/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/mcp.d.mts.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +165 -52
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +10 -9
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/usage.d.mts +8 -0
- package/dist/astro/routes/api/media/_id_/usage.d.mts.map +1 -0
- package/dist/astro/routes/api/media/_id_/usage.mjs +37 -0
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -0
- package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +26 -10
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +4 -3
- package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
- package/dist/astro/routes/api/media/providers/index.mjs +4 -4
- package/dist/astro/routes/api/media/upload-url.mjs +11 -10
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.d.mts.map +1 -1
- package/dist/astro/routes/api/media.mjs +34 -14
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +10 -9
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +10 -9
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +7 -8
- package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
- package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
- package/dist/astro/routes/api/oauth/register.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
- package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
- package/dist/astro/routes/api/oauth/token.mjs +7 -7
- package/dist/astro/routes/api/openapi.json.mjs +71 -24
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +20 -32
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/index.mjs +13 -12
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +13 -12
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +14 -13
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +14 -13
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.d.mts +10 -0
- package/dist/astro/routes/api/relations/_id_/index.d.mts.map +1 -0
- package/dist/astro/routes/api/relations/_id_/index.mjs +70 -0
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -0
- package/dist/astro/routes/api/relations/_id_/translations.d.mts +8 -0
- package/dist/astro/routes/api/relations/_id_/translations.d.mts.map +1 -0
- package/dist/astro/routes/api/relations/_id_/translations.mjs +34 -0
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -0
- package/dist/astro/routes/api/relations/index.d.mts +9 -0
- package/dist/astro/routes/api/relations/index.d.mts.map +1 -0
- package/dist/astro/routes/api/relations/index.mjs +54 -0
- package/dist/astro/routes/api/relations/index.mjs.map +1 -0
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +43 -40
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +43 -40
- 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 +43 -40
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +43 -40
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +43 -40
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +9 -9
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +43 -40
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +43 -40
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +13 -12
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +14 -12
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +13 -12
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +8 -8
- package/dist/astro/routes/api/search/suggest.mjs +12 -11
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +12 -11
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts +9 -0
- package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +48 -0
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -0
- package/dist/astro/routes/api/settings/backups/archives/index.d.mts +8 -0
- package/dist/astro/routes/api/settings/backups/archives/index.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +23 -0
- package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -0
- package/dist/astro/routes/api/settings/backups/export.d.mts +8 -0
- package/dist/astro/routes/api/settings/backups/export.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/export.mjs +35 -0
- package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -0
- package/dist/astro/routes/api/settings/backups/index.d.mts +9 -0
- package/dist/astro/routes/api/settings/backups/index.d.mts.map +1 -0
- package/dist/astro/routes/api/settings/backups/index.mjs +52 -0
- package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -0
- package/dist/astro/routes/api/settings/email.mjs +6 -6
- package/dist/astro/routes/api/settings.mjs +16 -15
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +20 -16
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +19 -15
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +29 -29
- package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
- package/dist/astro/routes/api/setup/index.mjs +31 -30
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +5 -5
- package/dist/astro/routes/api/snapshot.mjs +9 -216
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +7 -7
- package/dist/astro/routes/api/typegen.mjs +6 -6
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts.map +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +4 -3
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs.map +1 -1
- package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts.map +1 -1
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +4 -3
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +9 -8
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +13 -12
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +13 -12
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/index.mjs +13 -12
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +4 -4
- package/dist/astro/routes/robots.txt.mjs +9 -9
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +12 -12
- package/dist/astro/routes/sitemap.xml.mjs +10 -10
- package/dist/astro/types.d.mts +40 -16
- 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-Dz88F_Fw.mjs → authorize-WxsiePzy.mjs} +7 -4
- package/dist/authorize-WxsiePzy.mjs.map +1 -0
- package/dist/backup-S8kFWtwD.mjs +241 -0
- package/dist/backup-S8kFWtwD.mjs.map +1 -0
- package/dist/{base64-CmWvODNW.mjs → base64-B-PsqheR.mjs} +8 -4
- package/dist/base64-B-PsqheR.mjs.map +1 -0
- package/dist/{byline-Bm8ZgUP9.mjs → byline-XEjchwzZ.mjs} +8 -8
- package/dist/{byline-Bm8ZgUP9.mjs.map → byline-XEjchwzZ.mjs.map} +1 -1
- package/dist/{byline-fields-CIx_GuN4.mjs → byline-fields-CdU_LTF1.mjs} +2 -2
- package/dist/{byline-fields-CIx_GuN4.mjs.map → byline-fields-CdU_LTF1.mjs.map} +1 -1
- package/dist/{byline-registry-BOjqDOim.mjs → byline-registry-BCuOp4UF.mjs} +3 -3
- package/dist/{byline-registry-BOjqDOim.mjs.map → byline-registry-BCuOp4UF.mjs.map} +1 -1
- package/dist/{bylines-6tm9mgQb.mjs → bylines-BJbT4gKS.mjs} +4 -4
- package/dist/{bylines-6tm9mgQb.mjs.map → bylines-BJbT4gKS.mjs.map} +1 -1
- package/dist/{bylines-CAZxGSrm.mjs → bylines-czseViYo.mjs} +8 -8
- package/dist/{bylines-CAZxGSrm.mjs.map → bylines-czseViYo.mjs.map} +1 -1
- package/dist/{cache-CEBAZjir.mjs → cache-CGCd6AVM.mjs} +3 -3
- package/dist/{cache-CEBAZjir.mjs.map → cache-CGCd6AVM.mjs.map} +1 -1
- package/dist/{challenge-store-anhwVER0.mjs → challenge-store-BFzgFRog.mjs} +1 -1
- package/dist/{challenge-store-anhwVER0.mjs.map → challenge-store-BFzgFRog.mjs.map} +1 -1
- package/dist/{chunks-B2isWQhr.mjs → chunks-BxXyunY-.mjs} +2 -2
- package/dist/{chunks-B2isWQhr.mjs.map → chunks-BxXyunY-.mjs.map} +1 -1
- package/dist/cli/index.mjs +142 -35
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +86 -3
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +20 -4
- package/dist/client/index.mjs.map +1 -1
- package/dist/{comment-CzeatwBV.mjs → comment-DPT0WKyd.mjs} +2 -2
- package/dist/{comment-CzeatwBV.mjs.map → comment-DPT0WKyd.mjs.map} +1 -1
- package/dist/{comment-reaction-Cj9QJY7I.mjs → comment-reaction-C65MldIB.mjs} +2 -2
- package/dist/{comment-reaction-Cj9QJY7I.mjs.map → comment-reaction-C65MldIB.mjs.map} +1 -1
- package/dist/{comments-BLE-jv5k.mjs → comments-Bz6sCbgD.mjs} +3 -3
- package/dist/{comments-BLE-jv5k.mjs.map → comments-Bz6sCbgD.mjs.map} +1 -1
- package/dist/{components-B_sATNUc.mjs → components-DZkL4Fx0.mjs} +1 -1
- package/dist/{components-B_sATNUc.mjs.map → components-DZkL4Fx0.mjs.map} +1 -1
- package/dist/{content-DhgEth4k.mjs → content-Ci04z2z-.mjs} +227 -34
- package/dist/content-Ci04z2z-.mjs.map +1 -0
- package/dist/{content-refresh-Br4gMzh8.mjs → content-refresh-D4khvC0R.mjs} +100 -8
- package/dist/content-refresh-D4khvC0R.mjs.map +1 -0
- package/dist/{context-BRIpHARC.mjs → context-B6hc7zJL.mjs} +156 -76
- package/dist/context-B6hc7zJL.mjs.map +1 -0
- package/dist/{cron-v4FTEFR_.mjs → cron-BlKIMD_e.mjs} +1 -1
- package/dist/{cron-v4FTEFR_.mjs.map → cron-BlKIMD_e.mjs.map} +1 -1
- package/dist/{dashboard-EvEKF5en.mjs → dashboard-C5NkXFbi.mjs} +6 -5
- package/dist/dashboard-C5NkXFbi.mjs.map +1 -0
- package/dist/database/pg-migration-lock.d.mts +21 -0
- package/dist/database/pg-migration-lock.d.mts.map +1 -0
- package/dist/database/pg-migration-lock.mjs +45 -0
- package/dist/database/pg-migration-lock.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/postgres.d.mts.map +1 -1
- package/dist/db/postgres.mjs +2 -8
- package/dist/db/postgres.mjs.map +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-CK46D-ly.mjs → db-errors-CcWLaRiR.mjs} +4 -2
- package/dist/db-errors-CcWLaRiR.mjs.map +1 -0
- package/dist/{default-CM_D83Mg.mjs → default-C4v9KP56.mjs} +1 -1
- package/dist/{default-CM_D83Mg.mjs.map → default-C4v9KP56.mjs.map} +1 -1
- package/dist/{device-flow-CfGP18Uw.mjs → device-flow-BeA6lUS1.mjs} +6 -7
- package/dist/device-flow-BeA6lUS1.mjs.map +1 -0
- package/dist/dialect-helpers-HxEQGl_1.mjs +175 -0
- package/dist/dialect-helpers-HxEQGl_1.mjs.map +1 -0
- package/dist/{email-console-CIf4Lfvv.mjs → email-console-C-9Ng8DM.mjs} +1 -1
- package/dist/{email-console-CIf4Lfvv.mjs.map → email-console-C-9Ng8DM.mjs.map} +1 -1
- package/dist/{enrich-BQ0mxJRs.mjs → enrich-CFJJgxs_.mjs} +2 -2
- package/dist/{enrich-BQ0mxJRs.mjs.map → enrich-CFJJgxs_.mjs.map} +1 -1
- package/dist/error-CEGF6UZb.mjs +90 -0
- package/dist/error-CEGF6UZb.mjs.map +1 -0
- package/dist/{error-CkAseSqZ.mjs → errors-DtEXIQQV.mjs} +24 -84
- package/dist/errors-DtEXIQQV.mjs.map +1 -0
- package/dist/{escape-DqD0aCKu.mjs → escape-CFedIB1C.mjs} +1 -1
- package/dist/{escape-DqD0aCKu.mjs.map → escape-CFedIB1C.mjs.map} +1 -1
- package/dist/{field-defs-cache-WUI88LX5.mjs → field-defs-cache-DvmlgP-D.mjs} +5 -5
- package/dist/{field-defs-cache-WUI88LX5.mjs.map → field-defs-cache-DvmlgP-D.mjs.map} +1 -1
- package/dist/{fts-manager-BbFaF_V8.mjs → fts-manager-DzqIBrrW.mjs} +27 -3
- package/dist/{fts-manager-BbFaF_V8.mjs.map → fts-manager-DzqIBrrW.mjs.map} +1 -1
- package/dist/{hash-DrvzIXcz.mjs → hash-DFFrkivP.mjs} +1 -1
- package/dist/{hash-DrvzIXcz.mjs.map → hash-DFFrkivP.mjs.map} +1 -1
- package/dist/hreflang-CIxH_LKz.d.mts +48 -0
- package/dist/hreflang-CIxH_LKz.d.mts.map +1 -0
- package/dist/hreflang-db2Tpb2o.mjs +102 -0
- package/dist/hreflang-db2Tpb2o.mjs.map +1 -0
- package/dist/{import-fYLf14Co.mjs → import-Dmkm8S1W.mjs} +6 -6
- package/dist/{import-fYLf14Co.mjs.map → import-Dmkm8S1W.mjs.map} +1 -1
- package/dist/{index-Bm-Gy8Mh.d.mts → index-BEui-cRd.d.mts} +3 -3
- package/dist/{index-Bm-Gy8Mh.d.mts.map → index-BEui-cRd.d.mts.map} +1 -1
- package/dist/{index-B9sIxwe4.d.mts → index-wYnC6u4o.d.mts} +881 -22
- package/dist/index-wYnC6u4o.d.mts.map +1 -0
- package/dist/index.d.mts +19 -19
- package/dist/index.mjs +69 -64
- 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-Djn3HWQS.mjs → load-Cx27ki1l.mjs} +2 -2
- package/dist/{load-Djn3HWQS.mjs.map → load-Cx27ki1l.mjs.map} +1 -1
- package/dist/{loader-CB6YHeul.mjs → loader-Be3ouI5L.mjs} +170 -21
- package/dist/loader-Be3ouI5L.mjs.map +1 -0
- package/dist/{manifest-schema-BHlgZM1W.mjs → manifest-schema-bCq54i7F.mjs} +18 -2
- package/dist/manifest-schema-bCq54i7F.mjs.map +1 -0
- package/dist/media/image-endpoint.d.mts +30 -2
- package/dist/media/image-endpoint.d.mts.map +1 -1
- package/dist/media/image-endpoint.mjs +30 -4
- package/dist/media/image-endpoint.mjs.map +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -2
- package/dist/media/local-runtime.d.mts +12 -12
- package/dist/media/local-runtime.mjs +8 -8
- package/dist/{media-BrXIaMFN.mjs → media-BjhhENaJ.mjs} +2 -2
- package/dist/media-BjhhENaJ.mjs.map +1 -0
- package/dist/{media-allowlist-CRHEeJ_i.mjs → media-allowlist-jUppPWFy.mjs} +2 -2
- package/dist/{media-allowlist-CRHEeJ_i.mjs.map → media-allowlist-jUppPWFy.mjs.map} +1 -1
- package/dist/media-kIV1IxFf.mjs +243 -0
- package/dist/media-kIV1IxFf.mjs.map +1 -0
- package/dist/media-upload-D2wk3EIt.mjs +145 -0
- package/dist/media-upload-D2wk3EIt.mjs.map +1 -0
- package/dist/{media-url-CKi5XSw4.mjs → media-url-BCm5vBn6.mjs} +1 -1
- package/dist/{media-url-CKi5XSw4.mjs.map → media-url-BCm5vBn6.mjs.map} +1 -1
- package/dist/media-usage-CljdO1mc.mjs +554 -0
- package/dist/media-usage-CljdO1mc.mjs.map +1 -0
- package/dist/{menus-b2T4clbO.mjs → menus-BcyElFtI.mjs} +177 -15
- package/dist/menus-BcyElFtI.mjs.map +1 -0
- package/dist/{menus-Cfivc2Ad.mjs → menus-CZyG6rvx.mjs} +3 -3
- package/dist/{menus-Cfivc2Ad.mjs.map → menus-CZyG6rvx.mjs.map} +1 -1
- package/dist/{mime-CCEzze7W.mjs → mime-DfIVjNkr.mjs} +1 -1
- package/dist/{mime-CCEzze7W.mjs.map → mime-DfIVjNkr.mjs.map} +1 -1
- package/dist/{mode-BM9QgEmt.mjs → mode-fiXRMfeA.mjs} +1 -1
- package/dist/{mode-BM9QgEmt.mjs.map → mode-fiXRMfeA.mjs.map} +1 -1
- package/dist/{normalize-CeglrBT9.mjs → normalize-C-SHXmra.mjs} +1 -1
- package/dist/{normalize-CeglrBT9.mjs.map → normalize-C-SHXmra.mjs.map} +1 -1
- package/dist/{oauth-authorization-CXWkIGT4.mjs → oauth-authorization-oqfbBuMH.mjs} +6 -7
- package/dist/oauth-authorization-oqfbBuMH.mjs.map +1 -0
- package/dist/{oauth-clients-BmTI5rsl.mjs → oauth-clients-BlrUHAsf.mjs} +1 -1
- package/dist/{oauth-clients-BmTI5rsl.mjs.map → oauth-clients-BlrUHAsf.mjs.map} +1 -1
- package/dist/{oauth-state-store-YLFLcH5F.mjs → oauth-state-store-DnvjRMy6.mjs} +2 -1
- package/dist/{oauth-state-store-YLFLcH5F.mjs.map → oauth-state-store-DnvjRMy6.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-Bsc_GsX-.mjs → oauth-user-lookup-oUllxvAv.mjs} +1 -1
- package/dist/{oauth-user-lookup-Bsc_GsX-.mjs.map → oauth-user-lookup-oUllxvAv.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{options-DTTML-Tx.mjs → options-BlmBHTvX.mjs} +1 -1
- package/dist/{options-DTTML-Tx.mjs.map → options-BlmBHTvX.mjs.map} +1 -1
- package/dist/{options-iMpRRxIe.d.mts → options-BqMkWfYa.d.mts} +6 -6
- package/dist/{options-iMpRRxIe.d.mts.map → options-BqMkWfYa.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs.map +1 -1
- package/dist/{parse-GmjecGct.mjs → parse-C_-6klII.mjs} +4 -12
- package/dist/{parse-GmjecGct.mjs.map → parse-C_-6klII.mjs.map} +1 -1
- package/dist/{passkey-config-De0ahnRg.mjs → passkey-config-C-SKpc0-.mjs} +1 -1
- package/dist/{passkey-config-De0ahnRg.mjs.map → passkey-config-C-SKpc0-.mjs.map} +1 -1
- package/dist/{patterns-BKmjvM7K.mjs → patterns-CiyXeDgr.mjs} +1 -1
- package/dist/{patterns-BKmjvM7K.mjs.map → patterns-CiyXeDgr.mjs.map} +1 -1
- package/dist/{placeholder-BSKwzRe4.d.mts → placeholder-Dop47JMM.d.mts} +1 -1
- package/dist/{placeholder-BSKwzRe4.d.mts.map → placeholder-Dop47JMM.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +21 -2
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-utils.d.mts +18 -16
- package/dist/plugin-utils.d.mts.map +1 -1
- package/dist/plugin-utils.mjs +8 -6
- package/dist/plugin-utils.mjs.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +15 -3
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/{portable-text-cCa0cGV6.mjs → portable-text-sDj92Kxv.mjs} +1 -1
- package/dist/{portable-text-cCa0cGV6.mjs.map → portable-text-sDj92Kxv.mjs.map} +1 -1
- package/dist/{preview-SSXNVPAC.mjs → preview-D4Jnbfx7.mjs} +2 -2
- package/dist/{preview-SSXNVPAC.mjs.map → preview-D4Jnbfx7.mjs.map} +1 -1
- package/dist/{public-url-Danv47cS.mjs → public-url-DSGTnJFw.mjs} +5 -3
- package/dist/public-url-DSGTnJFw.mjs.map +1 -0
- package/dist/{query-1Cp6U9oo.mjs → query-DR73ZNfm.mjs} +15 -15
- package/dist/query-DR73ZNfm.mjs.map +1 -0
- package/dist/{schema-CX9cfNkZ.mjs → query-Di7DOmPV.mjs} +5 -10
- package/dist/query-Di7DOmPV.mjs.map +1 -0
- package/dist/{rate-limit-C_Lf63Dd.mjs → rate-limit-CMj83JNW.mjs} +2 -2
- package/dist/{rate-limit-C_Lf63Dd.mjs.map → rate-limit-CMj83JNW.mjs.map} +1 -1
- package/dist/{redirect-CoW3G-0U.mjs → redirect-Botom7X6.mjs} +1 -1
- package/dist/{redirect-CoW3G-0U.mjs.map → redirect-Botom7X6.mjs.map} +1 -1
- package/dist/{redirect-IyfNirgS.mjs → redirect-CgLPYflR.mjs} +11 -5
- package/dist/redirect-CgLPYflR.mjs.map +1 -0
- package/dist/{redirects-Bs1g42Ri.mjs → redirects-B7t9bKsL.mjs} +6 -6
- package/dist/{redirects-Bs1g42Ri.mjs.map → redirects-B7t9bKsL.mjs.map} +1 -1
- package/dist/{redirects-DXEXBAII.mjs → redirects-BBq3n2Rj.mjs} +10 -143
- package/dist/redirects-BBq3n2Rj.mjs.map +1 -0
- package/dist/{registry-B57vp_X9.mjs → registry-FV15nLge.mjs} +99 -10
- package/dist/registry-FV15nLge.mjs.map +1 -0
- package/dist/relation-9M9j_UWo.mjs +334 -0
- package/dist/relation-9M9j_UWo.mjs.map +1 -0
- package/dist/{byline-fields-2Lp5Hbgf.mjs → relations-5_avdrN_.mjs} +60 -4
- package/dist/relations-5_avdrN_.mjs.map +1 -0
- package/dist/relations-BKvO39-9.mjs +397 -0
- package/dist/relations-BKvO39-9.mjs.map +1 -0
- package/dist/{byline-fields-CHE8UStY.d.mts → relations-Bh8jlFsU.d.mts} +486 -31
- package/dist/relations-Bh8jlFsU.d.mts.map +1 -0
- package/dist/{request-cache-KCNHp_RJ.mjs → request-cache-BSUptuJR.mjs} +1 -1
- package/dist/{request-cache-KCNHp_RJ.mjs.map → request-cache-BSUptuJR.mjs.map} +1 -1
- package/dist/{request-meta-DbvXaAvU.mjs → request-meta-DzXYYI-n.mjs} +2 -2
- package/dist/{request-meta-DbvXaAvU.mjs.map → request-meta-DzXYYI-n.mjs.map} +1 -1
- package/dist/{resolve-DR3wqMTy.mjs → resolve-Cd9dzclN.mjs} +1 -1
- package/dist/{resolve-DR3wqMTy.mjs.map → resolve-Cd9dzclN.mjs.map} +1 -1
- package/dist/{runner-DvFWoz8-.d.mts → runner-Dd8m8k9F.d.mts} +8 -2
- package/dist/runner-Dd8m8k9F.d.mts.map +1 -0
- package/dist/{runner-K8XK101B.mjs → runner-DfnZ5eUr.mjs} +355 -204
- package/dist/runner-DfnZ5eUr.mjs.map +1 -0
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +4 -4
- package/dist/schema-BXxlHeAf.mjs +9 -0
- package/dist/schema-BXxlHeAf.mjs.map +1 -0
- package/dist/scopes-Bl4IwHA-.mjs +27 -0
- package/dist/scopes-Bl4IwHA-.mjs.map +1 -0
- package/dist/{search-DZN2HZ-V.mjs → search-Bff-7jFt.mjs} +62 -12
- package/dist/search-Bff-7jFt.mjs.map +1 -0
- package/dist/{secrets-kp760LlD.mjs → secrets-CSwQIl4q.mjs} +4 -4
- package/dist/{secrets-kp760LlD.mjs.map → secrets-CSwQIl4q.mjs.map} +1 -1
- package/dist/{sections-CpF5SaGr.mjs → sections-CwW4s1al.mjs} +3 -3
- package/dist/{sections-CpF5SaGr.mjs.map → sections-CwW4s1al.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +23 -23
- package/dist/seo/index.d.mts +15 -2
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +22 -5
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-DXu2UQnC.mjs → seo-2cmFHZns.mjs} +2 -2
- package/dist/{seo-DXu2UQnC.mjs.map → seo-2cmFHZns.mjs.map} +1 -1
- package/dist/{seo-89-53DDs.mjs → seo-C3GDfT0V.mjs} +1 -1
- package/dist/{seo-89-53DDs.mjs.map → seo-C3GDfT0V.mjs.map} +1 -1
- package/dist/{service-DQmiBMzt.mjs → service-DkGTWGIi.mjs} +3 -3
- package/dist/{service-DQmiBMzt.mjs.map → service-DkGTWGIi.mjs.map} +1 -1
- package/dist/{session-user-Cascbax7.mjs → session-user-DbHqKDKe.mjs} +1 -1
- package/dist/{session-user-Cascbax7.mjs.map → session-user-DbHqKDKe.mjs.map} +1 -1
- package/dist/{settings-rZyHEcNT.mjs → settings-C4s8hFQm.mjs} +3 -3
- package/dist/{settings-rZyHEcNT.mjs.map → settings-C4s8hFQm.mjs.map} +1 -1
- package/dist/{settings-zhmdKyJQ.mjs → settings-CpA4lQFt.mjs} +6 -6
- package/dist/{settings-zhmdKyJQ.mjs.map → settings-CpA4lQFt.mjs.map} +1 -1
- package/dist/{setup-complete-Cb3LRyzy.mjs → setup-complete-CdIlqloh.mjs} +2 -2
- package/dist/{setup-complete-Cb3LRyzy.mjs.map → setup-complete-CdIlqloh.mjs.map} +1 -1
- package/dist/{setup-nonce-Z8x0tHf8.mjs → setup-nonce-DqiIPr-J.mjs} +1 -1
- package/dist/{setup-nonce-Z8x0tHf8.mjs.map → setup-nonce-DqiIPr-J.mjs.map} +1 -1
- package/dist/{single-flight-cache-BG3B_GhN.mjs → single-flight-cache-C2exrGAi.mjs} +2 -2
- package/dist/{single-flight-cache-BG3B_GhN.mjs.map → single-flight-cache-C2exrGAi.mjs.map} +1 -1
- package/dist/{site-url-BBfDTgZB.mjs → site-url-NGJT6NTU.mjs} +2 -2
- package/dist/{site-url-BBfDTgZB.mjs.map → site-url-NGJT6NTU.mjs.map} +1 -1
- package/dist/{slugify-Cce3dTdg.mjs → slugify-C_tqlU4G.mjs} +1 -1
- package/dist/{slugify-Cce3dTdg.mjs.map → slugify-C_tqlU4G.mjs.map} +1 -1
- package/dist/snapshot-BdpUJKD-.mjs +216 -0
- package/dist/snapshot-BdpUJKD-.mjs.map +1 -0
- package/dist/{ssrf-1g7usGBg.mjs → ssrf-CviKqWmq.mjs} +1 -1
- package/dist/{ssrf-1g7usGBg.mjs.map → ssrf-CviKqWmq.mjs.map} +1 -1
- package/dist/state-xxv6ZTMv.mjs +128 -0
- package/dist/state-xxv6ZTMv.mjs.map +1 -0
- package/dist/{status-CqJQX1QN.mjs → status-COfycGIU.mjs} +1 -1
- package/dist/{status-CqJQX1QN.mjs.map → status-COfycGIU.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-Bs13SN-F.mjs → taxonomies-Ce49uIzY.mjs} +13 -9
- package/dist/taxonomies-Ce49uIzY.mjs.map +1 -0
- package/dist/{taxonomies-kH9ABDUF.mjs → taxonomies-DjSKBZpq.mjs} +115 -36
- package/dist/taxonomies-DjSKBZpq.mjs.map +1 -0
- package/dist/{taxonomy-BxGxZZdu.mjs → taxonomy-DfVooU4W.mjs} +60 -13
- package/dist/taxonomy-DfVooU4W.mjs.map +1 -0
- package/dist/{tokens-BGT0TUUo.mjs → tokens-DVltpO2D.mjs} +2 -2
- package/dist/{tokens-BGT0TUUo.mjs.map → tokens-DVltpO2D.mjs.map} +1 -1
- package/dist/{transaction-qfqpPVpu.mjs → transaction-D0FOsb3X.mjs} +1 -1
- package/dist/{transaction-qfqpPVpu.mjs.map → transaction-D0FOsb3X.mjs.map} +1 -1
- package/dist/{transport-DPefPMcz.mjs → transport-CKrlqSpD.mjs} +1 -1
- package/dist/{transport-DPefPMcz.mjs.map → transport-CKrlqSpD.mjs.map} +1 -1
- package/dist/{transport-BMDWHSgZ.d.mts → transport-Vd7VdqHv.d.mts} +1 -1
- package/dist/{transport-BMDWHSgZ.d.mts.map → transport-Vd7VdqHv.d.mts.map} +1 -1
- package/dist/{trusted-proxy-VTPRiX2s.mjs → trusted-proxy-CwjQj0YG.mjs} +1 -1
- package/dist/{trusted-proxy-VTPRiX2s.mjs.map → trusted-proxy-CwjQj0YG.mjs.map} +1 -1
- package/dist/{types-CgvhU-hF.d.mts → types-B4vm81VH.d.mts} +1 -1
- package/dist/{types-CgvhU-hF.d.mts.map → types-B4vm81VH.d.mts.map} +1 -1
- package/dist/{types-ea9sAPck.d.mts → types-B97jg3aW.d.mts} +6 -1
- package/dist/{types-ea9sAPck.d.mts.map → types-B97jg3aW.d.mts.map} +1 -1
- package/dist/{types-Jo3UYd6b.mjs → types-BD40g7zz.mjs} +1 -1
- package/dist/{types-Jo3UYd6b.mjs.map → types-BD40g7zz.mjs.map} +1 -1
- package/dist/{types-DmHk7vAt.d.mts → types-BNfPHfay.d.mts} +1 -1
- package/dist/{types-DmHk7vAt.d.mts.map → types-BNfPHfay.d.mts.map} +1 -1
- package/dist/{types-BcLUUbbR.d.mts → types-BfYUPJdP.d.mts} +11 -3
- package/dist/{types-BcLUUbbR.d.mts.map → types-BfYUPJdP.d.mts.map} +1 -1
- package/dist/{types-CGbYowGQ.d.mts → types-BvB7gDOD.d.mts} +90 -4
- package/dist/types-BvB7gDOD.d.mts.map +1 -0
- package/dist/{types-vgEWF18A.d.mts → types-C7U0HlEr.d.mts} +1 -1
- package/dist/{types-vgEWF18A.d.mts.map → types-C7U0HlEr.d.mts.map} +1 -1
- package/dist/{types-CuW0zWDA.mjs → types-D1iJ3DpO.mjs} +3 -3
- package/dist/{types-CuW0zWDA.mjs.map → types-D1iJ3DpO.mjs.map} +1 -1
- package/dist/{types-_HFLRzTn.d.mts → types-DPKhI5ib.d.mts} +9 -1
- package/dist/types-DPKhI5ib.d.mts.map +1 -0
- package/dist/{types-g_aFIdW0.d.mts → types-Kmaxdarz.d.mts} +1 -1
- package/dist/{types-g_aFIdW0.d.mts.map → types-Kmaxdarz.d.mts.map} +1 -1
- package/dist/{types-ClUkdbWb.d.mts → types-iVeZ5g6N.d.mts} +1 -1
- package/dist/{types-ClUkdbWb.d.mts.map → types-iVeZ5g6N.d.mts.map} +1 -1
- package/dist/{types-tM44hEcf.mjs → types-o7xo7VgH.mjs} +1 -1
- package/dist/{types-tM44hEcf.mjs.map → types-o7xo7VgH.mjs.map} +1 -1
- package/dist/{user-BjAOvU3Z.mjs → user-Bh-L1qo6.mjs} +3 -3
- package/dist/{user-BjAOvU3Z.mjs.map → user-Bh-L1qo6.mjs.map} +1 -1
- package/dist/{utils-BJXElxdw.mjs → utils-BbUgmeZG.mjs} +3 -3
- package/dist/{utils-BJXElxdw.mjs.map → utils-BbUgmeZG.mjs.map} +1 -1
- package/dist/{validate-Z1b86_3B.d.mts → validate-DULfgpJP.d.mts} +5 -5
- package/dist/{validate-Z1b86_3B.d.mts.map → validate-DULfgpJP.d.mts.map} +1 -1
- package/dist/{validate-CWSnKVIJ.mjs → validate-V9nCwq_-.mjs} +3 -3
- package/dist/{validate-CWSnKVIJ.mjs.map → validate-V9nCwq_-.mjs.map} +1 -1
- package/dist/{validation-0l8-RYj4.mjs → validation-BsVUJfsP.mjs} +10 -10
- package/dist/{validation-0l8-RYj4.mjs.map → validation-BsVUJfsP.mjs.map} +1 -1
- package/dist/version-907opKac.mjs +7 -0
- package/dist/{version-qmeO1XZY.mjs.map → version-907opKac.mjs.map} +1 -1
- package/dist/{widgets-hWcIicX8.mjs → widgets-DGv1Z04V.mjs} +4 -4
- package/dist/{widgets-hWcIicX8.mjs.map → widgets-DGv1Z04V.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-0_uxSrmf.d.mts → wxr-taxonomies-B4tMuN5T.d.mts} +1 -1
- package/dist/{wxr-taxonomies-0_uxSrmf.d.mts.map → wxr-taxonomies-B4tMuN5T.d.mts.map} +1 -1
- package/dist/{wxr-taxonomies-CNFcsNCr.mjs → wxr-taxonomies-Cs1N6O8z.mjs} +4 -4
- package/dist/{wxr-taxonomies-CNFcsNCr.mjs.map → wxr-taxonomies-Cs1N6O8z.mjs.map} +1 -1
- package/dist/{zod-generator-CacrR-i8.mjs → zod-generator-B5prQ5M4.mjs} +3 -3
- package/dist/{zod-generator-CacrR-i8.mjs.map → zod-generator-B5prQ5M4.mjs.map} +1 -1
- package/package.json +12 -8
- package/src/api/authorize.ts +10 -1
- package/src/api/error.ts +5 -5
- package/src/api/errors.ts +25 -0
- package/src/api/handlers/backup.ts +312 -0
- package/src/api/handlers/content.ts +147 -3
- package/src/api/handlers/dashboard.ts +2 -0
- package/src/api/handlers/device-flow.ts +2 -3
- package/src/api/handlers/index.ts +22 -0
- package/src/api/handlers/marketplace.ts +38 -0
- package/src/api/handlers/media-upload.ts +210 -0
- package/src/api/handlers/media-usage.ts +289 -0
- package/src/api/handlers/oauth-authorization.ts +2 -6
- package/src/api/handlers/plugin-settings.ts +218 -0
- package/src/api/handlers/plugins.ts +41 -0
- package/src/api/handlers/registry.ts +39 -0
- package/src/api/handlers/relations.ts +474 -0
- package/src/api/handlers/snapshot.ts +21 -3
- package/src/api/handlers/taxonomies.ts +26 -4
- package/src/api/handlers/validate-media-fields.ts +1 -1
- package/src/api/openapi/document.ts +72 -5
- package/src/api/parse.ts +2 -16
- package/src/api/public-url.ts +4 -2
- package/src/api/schemas/comments.ts +6 -0
- package/src/api/schemas/common.ts +6 -2
- package/src/api/schemas/content.ts +8 -0
- package/src/api/schemas/index.ts +2 -0
- package/src/api/schemas/media-usage.ts +120 -0
- package/src/api/schemas/media.ts +32 -1
- package/src/api/schemas/relations.ts +109 -0
- package/src/api/schemas/search.ts +1 -0
- package/src/astro/integration/index.ts +39 -0
- package/src/astro/integration/routes.ts +40 -0
- package/src/astro/integration/runtime.ts +30 -0
- package/src/astro/integration/virtual-modules.ts +41 -7
- package/src/astro/integration/vite-config.ts +17 -1
- package/src/astro/middleware/auth.ts +70 -78
- package/src/astro/middleware/csp.ts +5 -2
- package/src/astro/middleware/request-context.ts +162 -20
- package/src/astro/middleware.ts +108 -40
- package/src/astro/public-plugin-api-routes.ts +2 -1
- package/src/astro/routes/PluginRegistry.tsx +5 -2
- package/src/astro/routes/admin.astro +56 -16
- package/src/astro/routes/api/admin/api-tokens/index.ts +2 -2
- package/src/astro/routes/api/admin/bylines/[id]/translations.ts +2 -5
- package/src/astro/routes/api/admin/comments/[id]/status.ts +1 -2
- package/src/astro/routes/api/admin/comments/index.ts +1 -2
- package/src/astro/routes/api/admin/media-usage/repair.ts +24 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +6 -0
- package/src/astro/routes/api/admin/plugins/[id]/mcp.ts +42 -0
- package/src/astro/routes/api/admin/plugins/[id]/settings.ts +80 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +2 -0
- package/src/astro/routes/api/admin/plugins/index.ts +9 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +2 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +2 -0
- package/src/astro/routes/api/admin/plugins/registry/install.ts +2 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +2 -4
- package/src/astro/routes/api/auth/mode.ts +6 -14
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +21 -5
- package/src/astro/routes/api/auth/oauth/[provider].ts +24 -7
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +13 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +2 -3
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +2 -3
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +95 -0
- package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts +47 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +4 -6
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id].ts +3 -4
- package/src/astro/routes/api/content/[collection]/authors.ts +2 -3
- package/src/astro/routes/api/content/[collection]/index.ts +6 -8
- package/src/astro/routes/api/content/[collection]/trash.ts +2 -3
- package/src/astro/routes/api/manifest.ts +2 -9
- package/src/astro/routes/api/mcp.ts +2 -1
- package/src/astro/routes/api/media/[id]/usage.ts +30 -0
- package/src/astro/routes/api/media/[id].ts +17 -6
- package/src/astro/routes/api/media/file/[...key].ts +8 -0
- package/src/astro/routes/api/media.ts +21 -2
- package/src/astro/routes/api/oauth/authorize.ts +2 -6
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +17 -3
- package/src/astro/routes/api/relations/[id]/index.ts +84 -0
- package/src/astro/routes/api/relations/[id]/translations.ts +33 -0
- package/src/astro/routes/api/relations/index.ts +56 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +1 -7
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -2
- package/src/astro/routes/api/search/index.ts +2 -0
- package/src/astro/routes/api/settings/backups/archives/[name].ts +78 -0
- package/src/astro/routes/api/settings/backups/archives/index.ts +33 -0
- package/src/astro/routes/api/settings/backups/export.ts +43 -0
- package/src/astro/routes/api/settings/backups/index.ts +76 -0
- package/src/astro/routes/api/setup/admin-verify.ts +8 -4
- package/src/astro/routes/api/setup/admin.ts +11 -4
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +4 -1
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +5 -1
- package/src/astro/types.ts +50 -2
- package/src/auth/api-tokens.ts +1 -0
- package/src/auth/oauth-state-store.ts +3 -0
- package/src/auth/providers/github-admin.tsx +5 -6
- package/src/auth/providers/google-admin.tsx +5 -6
- package/src/auth/scopes.ts +3 -9
- package/src/cli/client-factory.ts +0 -1
- package/src/cli/commands/bundle-utils.ts +50 -1
- package/src/cli/commands/bundle.ts +9 -2
- package/src/cli/commands/export-seed.ts +1 -10
- package/src/cli/commands/media.ts +127 -0
- package/src/client/index.ts +123 -2
- package/src/client/transport.ts +1 -1
- package/src/comments/service.ts +1 -1
- package/src/comments/turnstile.ts +64 -0
- package/src/components/BlockquoteGroup.astro +34 -0
- package/src/components/CommentForm.astro +12 -5
- package/src/components/EmDashHead.astro +31 -9
- package/src/components/InlinePortableTextEditor.tsx +88 -33
- package/src/components/PortableText.astro +7 -1
- package/src/components/index.ts +2 -0
- package/src/components/marks/Link.astro +5 -1
- package/src/components/portable-text-blockquote-group.ts +68 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +32 -1
- package/src/database/dialect-helpers.ts +111 -3
- package/src/database/migrations/039_fix_fts5_triggers.ts +1 -1
- package/src/database/migrations/051_content_taxonomies_denorm.ts +120 -0
- package/src/database/migrations/052_media_usage_read_index.ts +26 -0
- package/src/database/migrations/053_plugin_mcp_tools.ts +21 -0
- package/src/database/migrations/runner.ts +52 -6
- package/src/database/pg-migration-lock.ts +72 -0
- package/src/database/repositories/audit.ts +2 -1
- package/src/database/repositories/comment.ts +1 -1
- package/src/database/repositories/content.ts +270 -37
- package/src/database/repositories/media-usage.ts +250 -1
- package/src/database/repositories/media.ts +1 -1
- package/src/database/repositories/plugin-storage.ts +29 -32
- package/src/database/repositories/redirect.ts +14 -2
- package/src/database/repositories/relation.ts +130 -5
- package/src/database/repositories/taxonomy.ts +66 -8
- package/src/database/repositories/types.ts +8 -0
- package/src/database/types.ts +23 -0
- package/src/db/adapters.ts +9 -0
- package/src/db/postgres.ts +5 -2
- package/src/emdash-runtime.ts +382 -70
- package/src/index.ts +6 -2
- package/src/loader.ts +293 -32
- package/src/mcp/server.ts +251 -8
- package/src/media/image-endpoint.ts +38 -2
- package/src/page/metadata.ts +27 -0
- package/src/plugin-types.ts +19 -0
- package/src/plugin-utils.ts +8 -6
- package/src/plugins/adapt-sandbox-entry.ts +30 -1
- package/src/plugins/context.ts +92 -0
- package/src/plugins/define-plugin.ts +17 -0
- package/src/plugins/index.ts +4 -0
- package/src/plugins/manifest-schema.ts +30 -1
- package/src/plugins/routes.ts +27 -1
- package/src/plugins/sandbox/runner-options.ts +16 -0
- package/src/plugins/sandbox/types.ts +6 -1
- package/src/plugins/state.ts +17 -1
- package/src/plugins/storage-query.ts +67 -33
- package/src/plugins/types.ts +100 -0
- package/src/schema/byline-registry.ts +1 -1
- package/src/schema/registry.ts +141 -5
- package/src/search/fts-manager.ts +34 -0
- package/src/search/match.ts +50 -0
- package/src/search/query.ts +94 -9
- package/src/seed/apply.ts +28 -29
- package/src/seo/hreflang.ts +192 -0
- package/src/seo/index.ts +22 -4
- package/src/storage/types.ts +6 -0
- package/src/taxonomies/index.ts +62 -45
- package/src/taxonomies/term-counts.ts +117 -0
- package/src/utils/base64.ts +7 -2
- package/src/utils/db-errors.ts +3 -1
- package/src/virtual-modules.d.ts +14 -0
- package/src/visual-editing/editable.ts +41 -35
- package/src/visual-editing/toolbar-bootstrap.ts +120 -0
- package/src/visual-editing/toolbar.ts +47 -1
- package/dist/adapters-hUy8vOB-.d.mts.map +0 -1
- package/dist/api-DxFSorgk.mjs.map +0 -1
- package/dist/api-tokens-DsvvCtdh.mjs +0 -3
- package/dist/apply-CDJqIjPU.mjs.map +0 -1
- package/dist/authorize-Dz88F_Fw.mjs.map +0 -1
- package/dist/base64-CmWvODNW.mjs.map +0 -1
- package/dist/byline-fields-2Lp5Hbgf.mjs.map +0 -1
- package/dist/byline-fields-CHE8UStY.d.mts.map +0 -1
- package/dist/content-DhgEth4k.mjs.map +0 -1
- package/dist/content-refresh-Br4gMzh8.mjs.map +0 -1
- package/dist/context-BRIpHARC.mjs.map +0 -1
- package/dist/dashboard-EvEKF5en.mjs.map +0 -1
- package/dist/db-errors-CK46D-ly.mjs.map +0 -1
- package/dist/device-flow-CfGP18Uw.mjs.map +0 -1
- package/dist/dialect-helpers-DRI5pyY3.mjs +0 -110
- package/dist/dialect-helpers-DRI5pyY3.mjs.map +0 -1
- package/dist/error-CkAseSqZ.mjs.map +0 -1
- package/dist/index-B9sIxwe4.d.mts.map +0 -1
- package/dist/loader-CB6YHeul.mjs.map +0 -1
- package/dist/manifest-schema-BHlgZM1W.mjs.map +0 -1
- package/dist/media-BrXIaMFN.mjs.map +0 -1
- package/dist/menus-b2T4clbO.mjs.map +0 -1
- package/dist/oauth-authorization-CXWkIGT4.mjs.map +0 -1
- package/dist/public-url-Danv47cS.mjs.map +0 -1
- package/dist/query-1Cp6U9oo.mjs.map +0 -1
- package/dist/redirect-IyfNirgS.mjs.map +0 -1
- package/dist/redirects-DXEXBAII.mjs.map +0 -1
- package/dist/registry-B57vp_X9.mjs.map +0 -1
- package/dist/runner-DvFWoz8-.d.mts.map +0 -1
- package/dist/runner-K8XK101B.mjs.map +0 -1
- package/dist/scheduled-publish-Drffrf2a.d.mts +0 -11
- package/dist/scheduled-publish-Drffrf2a.d.mts.map +0 -1
- package/dist/schema-CX9cfNkZ.mjs.map +0 -1
- package/dist/search-DZN2HZ-V.mjs.map +0 -1
- package/dist/taxonomies-Bs13SN-F.mjs.map +0 -1
- package/dist/taxonomies-kH9ABDUF.mjs.map +0 -1
- package/dist/taxonomy-BxGxZZdu.mjs.map +0 -1
- package/dist/types-CGbYowGQ.d.mts.map +0 -1
- package/dist/types-_HFLRzTn.d.mts.map +0 -1
- package/dist/version-qmeO1XZY.mjs +0 -7
- /package/dist/{ssrf-CmuGgCq3.mjs → ssrf-BB__xfNM.mjs} +0 -0
- /package/dist/{types-DROWRxYV.mjs → types-CK0LxPUQ.mjs} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape-
|
|
1
|
+
{"version":3,"file":"escape-CFedIB1C.mjs","names":[],"sources":["../src/api/escape.ts"],"sourcesContent":["/** HTML-escape a string to prevent XSS when interpolated into HTML/JS */\nexport function escapeHtml(str: string): string {\n\treturn str\n\t\t.replaceAll(\"&\", \"&\")\n\t\t.replaceAll(\"<\", \"<\")\n\t\t.replaceAll(\">\", \">\")\n\t\t.replaceAll('\"', \""\")\n\t\t.replaceAll(\"'\", \"'\");\n}\n"],"mappings":";;AACA,SAAgB,WAAW,KAAqB;AAC/C,QAAO,IACL,WAAW,KAAK,QAAQ,CACxB,WAAW,KAAK,OAAO,CACvB,WAAW,KAAK,OAAO,CACvB,WAAW,MAAK,SAAS,CACzB,WAAW,KAAK,SAAS"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as __exportAll } from "./runner-DfnZ5eUr.mjs";
|
|
2
2
|
import { t as after } from "./after-B1IIdH3Y.mjs";
|
|
3
3
|
import { getRequestContext } from "./request-context.mjs";
|
|
4
|
-
import { r as requestCached } from "./request-cache-
|
|
5
|
-
import { n as BylineSchemaRegistry } from "./byline-registry-
|
|
6
|
-
import { n as initWithLock, t as createInitLock } from "./init-lock-
|
|
4
|
+
import { r as requestCached } from "./request-cache-BSUptuJR.mjs";
|
|
5
|
+
import { n as BylineSchemaRegistry } from "./byline-registry-BCuOp4UF.mjs";
|
|
6
|
+
import { n as initWithLock, t as createInitLock } from "./init-lock-DJkX6Hto.mjs";
|
|
7
7
|
|
|
8
8
|
//#region src/bylines/field-defs-cache.ts
|
|
9
9
|
var field_defs_cache_exports = /* @__PURE__ */ __exportAll({ getBylineFieldDefs: () => getBylineFieldDefs });
|
|
@@ -74,4 +74,4 @@ async function getBylineFieldDefs(db) {
|
|
|
74
74
|
|
|
75
75
|
//#endregion
|
|
76
76
|
export { getBylineFieldDefs as n, field_defs_cache_exports as t };
|
|
77
|
-
//# sourceMappingURL=field-defs-cache-
|
|
77
|
+
//# sourceMappingURL=field-defs-cache-DvmlgP-D.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-defs-cache-
|
|
1
|
+
{"version":3,"file":"field-defs-cache-DvmlgP-D.mjs","names":[],"sources":["../src/bylines/field-defs-cache.ts"],"sourcesContent":["/**\n * Byline field-definitions cache\n *\n * Discussion #1174 / Phase 3. Two-tier cache for the byline custom-field\n * registry, mirroring the `settings/index.ts` pattern.\n *\n * **Tier 1 — per-isolate (globalThis).** Field definitions change rarely\n * but are read on every byline hydration (admin pages, content rendering,\n * API responses). Caching at the isolate level drops the SELECT-from-\n * `_emdash_byline_fields` from once-per-hydration to once-per-isolate-\n * after-bump. The cache holds the resolved *value* behind a reclaimable\n * single-flight lock (see `utils/single-flight-cache.ts`), never an\n * in-flight promise: concurrent cold-isolate readers coalesce onto one\n * query by polling the published value, so a reader whose request is\n * cancelled mid-query can never strand later byline hydrations on the\n * isolate (the workerd never-settling-promise hazard that produced 524s).\n *\n * Stored on globalThis under `Symbol.for(\"emdash:byline-field-defs\")` so\n * Vite SSR chunk duplication can't produce two independent caches (same\n * pattern as `request-cache.ts` and `request-context.ts`).\n *\n * **Tier 2 — per-request.** Wraps both the version read and the defs\n * fetch in `requestCached` so a single page render that hits byline\n * hydration multiple times (e.g. list view + individual byline lookups\n * in a sidebar) pays at most one version read and one defs fetch in\n * total. The defs cache key includes the version, so a (highly\n * unlikely) mid-request bump still produces a self-consistent view —\n * the second call sees a different key and refetches.\n *\n * **Invalidation.** `options.byline_fields_version` is bumped by every\n * `BylineSchemaRegistry` mutation (Phase 2). Each isolate independently\n * reads the persisted version on the next request and compares against\n * its cached version; mismatch triggers a refetch and overwrite. Other\n * isolates see the change within one request after the bump propagates.\n *\n * **Isolated databases bypass the global cache.** Playground and DO\n * preview sessions set `requestContext.dbIsIsolated = true`, signalling\n * the per-request `db` points at an isolated schema that may diverge\n * from the singleton. Schema-derived caches keyed by the singleton's\n * version would silently leak the singleton's defs into the isolated\n * request. We follow the `loader.ts:74` `getTaxonomyNames` precedent:\n * skip both reading from and writing to the global holder when the\n * request is isolated. The per-request cache (`requestCached`) is keyed\n * by the WeakMap'd `EmDashRequestContext`, so it can't cross-pollinate\n * between requests — it stays in play even for isolated DBs.\n *\n * **Why a versioned cache and not a TTL?** The version counter gives\n * deterministic invalidation without the staleness window a TTL would\n * impose. Field-definition changes need to be visible to the next\n * request, not eventually. The cost is one cheap `options` read per\n * request — cheaper than the field-defs fetch it replaces, and cheaper\n * than maintaining a TTL state machine.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { after } from \"../after.js\";\nimport type { Database } from \"../database/types.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { getRequestContext } from \"../request-context.js\";\nimport { BylineSchemaRegistry } from \"../schema/byline-registry.js\";\nimport type { BylineFieldDefinition } from \"../schema/types.js\";\nimport { createInitLock, type InitLock, initWithLock } from \"../utils/init-lock.js\";\n\ninterface FieldDefsHolder {\n\t/** Last resolved defs, valid only when `hasValue` is true. */\n\tvalue: BylineFieldDefinition[] | null;\n\t/** Presence flag, separate from `value` so an empty-array result still caches. */\n\thasValue: boolean;\n\t/** Persisted-version value that `value` was fetched against. */\n\tcachedVersion: number;\n\t/** Reclaimable single-flight lock so a cancelled owner can't wedge readers. */\n\tlock: InitLock;\n}\n\nconst HOLDER_KEY = Symbol.for(\"emdash:byline-field-defs\");\nconst g = globalThis as Record<symbol, unknown>;\nconst holder: FieldDefsHolder =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-cache.ts)\n\t(g[HOLDER_KEY] as FieldDefsHolder | undefined) ??\n\t(() => {\n\t\tconst h: FieldDefsHolder = {\n\t\t\tvalue: null,\n\t\t\thasValue: false,\n\t\t\tcachedVersion: -1,\n\t\t\tlock: createInitLock(),\n\t\t};\n\t\tg[HOLDER_KEY] = h;\n\t\treturn h;\n\t})();\n\nconst REQUEST_CACHE_KEY_VERSION = \"byline-fields-version\";\nconst REQUEST_CACHE_KEY_DEFS_PREFIX = \"byline-field-defs:\";\n\n/**\n * Reclaim window for the single-flight lock: if an owner holds it past\n * this without publishing (e.g. its request was cancelled and the\n * anchored fetch hasn't completed yet), the next reader reclaims and\n * refetches. `listFields` is a single fast SELECT, so this only needs to\n * cover a genuinely slow/stranded query. Mutable solely so tests can\n * shorten it; production never changes it.\n */\nlet reclaimDeadlineMs = 10_000;\n\n/**\n * Read the persisted `options.byline_fields_version` counter. Cached for\n * the duration of the current request via `requestCached`. Returns `0`\n * when the row is missing (matches `BylineSchemaRegistry.getVersion`).\n */\nasync function getBylineFieldsVersion(db: Kysely<Database>): Promise<number> {\n\treturn requestCached(REQUEST_CACHE_KEY_VERSION, () => new BylineSchemaRegistry(db).getVersion());\n}\n\n/**\n * Resolve registered byline custom-field definitions. Two-tier cache:\n * per-request via `requestCached`, then per-isolate via the global\n * holder.\n *\n * The global holder is bypassed for isolated requests (playground / DO\n * preview, which point at a divergent schema) and for dirty versions\n * (odd counter — see `BylineSchemaRegistry`'s class JSDoc — indicates\n * an in-flight or crashed mutation). Both bypass paths still hit the\n * per-request cache, so a single render dedupes within itself.\n *\n * Always returns an array. Empty = no custom fields registered.\n */\nexport async function getBylineFieldDefs(db: Kysely<Database>): Promise<BylineFieldDefinition[]> {\n\tconst isolated = getRequestContext()?.dbIsIsolated === true;\n\tconst version = await getBylineFieldsVersion(db);\n\tconst dirty = version % 2 !== 0;\n\treturn requestCached(`${REQUEST_CACHE_KEY_DEFS_PREFIX}${version}`, async () => {\n\t\tif (isolated || dirty) {\n\t\t\treturn new BylineSchemaRegistry(db).listFields();\n\t\t}\n\t\t// Per-isolate single-flight cache keyed on the persisted version.\n\t\t// Coalesce concurrent cold readers via the lock and read the\n\t\t// published value; never await another request's in-flight promise\n\t\t// (a cancelled owner would otherwise strand every later byline\n\t\t// hydration on the isolate). The fetch is anchored so a cancelled\n\t\t// originator still drives it to completion and populates the cache.\n\t\treturn initWithLock<BylineFieldDefinition[]>(\n\t\t\tholder.lock,\n\t\t\t() => (holder.hasValue && holder.cachedVersion === version ? holder.value : null),\n\t\t\t(isCurrentClaim) =>\n\t\t\t\t(async () => {\n\t\t\t\t\tconst defs = await new BylineSchemaRegistry(db).listFields();\n\t\t\t\t\t// Publish only while still the current claim, and never\n\t\t\t\t\t// regress over a newer version a concurrent reader stored.\n\t\t\t\t\tif (isCurrentClaim() && version >= holder.cachedVersion) {\n\t\t\t\t\t\tholder.value = defs;\n\t\t\t\t\t\tholder.hasValue = true;\n\t\t\t\t\t\tholder.cachedVersion = version;\n\t\t\t\t\t}\n\t\t\t\t\treturn defs;\n\t\t\t\t})(),\n\t\t\t{ deadlineMs: reclaimDeadlineMs, anchor: (promise) => after(() => promise) },\n\t\t);\n\t});\n}\n\n/**\n * Test/internal helper: clear the per-isolate cache. Useful for unit\n * tests that mutate the registry directly and need to force a refetch\n * without going through the full version-bump path.\n *\n * Production code paths should rely on the version counter for\n * invalidation — calling this from a write path would bypass the\n * coordination that lets other isolates see the change.\n */\nexport function resetBylineFieldDefsCacheForTests(): void {\n\tholder.value = null;\n\tholder.hasValue = false;\n\tholder.cachedVersion = -1;\n\tholder.lock.ownerStartedAt = null;\n\tholder.lock.generation = 0;\n\treclaimDeadlineMs = 10_000;\n}\n\n/**\n * Test-only: shorten the single-flight reclaim window so a \"stranded\n * owner\" scenario can be exercised without waiting out the production\n * deadline. Reset by `resetBylineFieldDefsCacheForTests`.\n *\n * @internal\n */\nexport function setBylineFieldDefsReclaimDeadlineForTests(ms: number): void {\n\treclaimDeadlineMs = ms;\n}\n"],"mappings":";;;;;;;;;AA2EA,MAAM,aAAa,OAAO,IAAI,2BAA2B;AACzD,MAAM,IAAI;AACV,MAAM,SAEJ,EAAE,sBACI;CACN,MAAM,IAAqB;EAC1B,OAAO;EACP,UAAU;EACV,eAAe;EACf,MAAM,gBAAgB;EACtB;AACD,GAAE,cAAc;AAChB,QAAO;IACJ;AAEL,MAAM,4BAA4B;AAClC,MAAM,gCAAgC;;;;;;;;;AAUtC,IAAI,oBAAoB;;;;;;AAOxB,eAAe,uBAAuB,IAAuC;AAC5E,QAAO,cAAc,iCAAiC,IAAI,qBAAqB,GAAG,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;AAgBjG,eAAsB,mBAAmB,IAAwD;CAChG,MAAM,WAAW,mBAAmB,EAAE,iBAAiB;CACvD,MAAM,UAAU,MAAM,uBAAuB,GAAG;CAChD,MAAM,QAAQ,UAAU,MAAM;AAC9B,QAAO,cAAc,GAAG,gCAAgC,WAAW,YAAY;AAC9E,MAAI,YAAY,MACf,QAAO,IAAI,qBAAqB,GAAG,CAAC,YAAY;AAQjD,SAAO,aACN,OAAO,YACA,OAAO,YAAY,OAAO,kBAAkB,UAAU,OAAO,QAAQ,OAC3E,oBACC,YAAY;GACZ,MAAM,OAAO,MAAM,IAAI,qBAAqB,GAAG,CAAC,YAAY;AAG5D,OAAI,gBAAgB,IAAI,WAAW,OAAO,eAAe;AACxD,WAAO,QAAQ;AACf,WAAO,WAAW;AAClB,WAAO,gBAAgB;;AAExB,UAAO;MACJ,EACL;GAAE,YAAY;GAAmB,SAAS,YAAY,YAAY,QAAQ;GAAE,CAC5E;GACA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as __exportAll } from "./runner-DfnZ5eUr.mjs";
|
|
2
2
|
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { d as tableExists, s as isSqlite } from "./dialect-helpers-HxEQGl_1.mjs";
|
|
4
4
|
import { sql } from "kysely";
|
|
5
5
|
|
|
6
6
|
//#region src/search/fts-manager.ts
|
|
@@ -227,6 +227,30 @@ var FTSManager = class {
|
|
|
227
227
|
return (await this.db.selectFrom("_emdash_fields").select("slug").where("collection_id", "=", collection.id).where("searchable", "=", 1).execute()).map((f) => f.slug);
|
|
228
228
|
}
|
|
229
229
|
/**
|
|
230
|
+
* Whether a collection has a user-defined `title` field.
|
|
231
|
+
*
|
|
232
|
+
* `title` is not a system column on `ec_*` tables -- it exists only when a
|
|
233
|
+
* collection defines a field with slug `title`. Search and suggestion SQL
|
|
234
|
+
* that selects `c.title` must check this first; otherwise collections
|
|
235
|
+
* without a title field raise "no such column: c.title" (#1178).
|
|
236
|
+
*/
|
|
237
|
+
async hasTitleColumn(collectionSlug) {
|
|
238
|
+
return (await this.getCollectionsWithTitleColumn([collectionSlug])).has(collectionSlug);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Bulk variant of `hasTitleColumn()`: which of the given collections have
|
|
242
|
+
* a user-defined `title` field. One query instead of one `hasTitleColumn`
|
|
243
|
+
* round-trip pair per collection -- callers that check this once per
|
|
244
|
+
* collection in a loop (multi-collection search, suggestions) should use
|
|
245
|
+
* this instead (AGENTS.md: "one query beats two").
|
|
246
|
+
*/
|
|
247
|
+
async getCollectionsWithTitleColumn(collectionSlugs) {
|
|
248
|
+
if (collectionSlugs.length === 0) return /* @__PURE__ */ new Set();
|
|
249
|
+
const rows = await this.db.selectFrom("_emdash_fields as f").innerJoin("_emdash_collections as c", "c.id", "f.collection_id").select(["c.slug as collection_slug"]).where("f.slug", "=", "title").execute();
|
|
250
|
+
const withTitle = new Set(rows.map((r) => r.collection_slug));
|
|
251
|
+
return new Set(collectionSlugs.filter((slug) => withTitle.has(slug)));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
230
254
|
* Enable search for a collection.
|
|
231
255
|
*
|
|
232
256
|
* Uses rebuildIndex to ensure a clean state -- drop any existing FTS
|
|
@@ -336,4 +360,4 @@ var FTSManager = class {
|
|
|
336
360
|
|
|
337
361
|
//#endregion
|
|
338
362
|
export { fts_manager_exports as n, FTSManager as t };
|
|
339
|
-
//# sourceMappingURL=fts-manager-
|
|
363
|
+
//# sourceMappingURL=fts-manager-DzqIBrrW.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fts-manager-BbFaF_V8.mjs","names":["dialectTableExists"],"sources":["../src/search/fts-manager.ts"],"sourcesContent":["/**\n * FTS5 Manager\n *\n * Manages FTS5 virtual tables and triggers for search indexing.\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { isSqlite, tableExists as dialectTableExists } from \"../database/dialect-helpers.js\";\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport type { SearchConfig } from \"./types.js\";\n\n/**\n * FTS5 Manager\n *\n * Handles creation, deletion, and management of FTS5 virtual tables\n * for full-text search on content collections.\n */\nexport class FTSManager {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Validate a collection slug and its searchable field names.\n\t * Must be called before any raw SQL interpolation.\n\t */\n\tprivate validateInputs(collectionSlug: string, searchableFields?: string[]): void {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\tif (searchableFields) {\n\t\t\tfor (const field of searchableFields) {\n\t\t\t\tvalidateIdentifier(field, \"searchable field name\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the FTS table name for a collection\n\t * Uses _emdash_ prefix to clearly mark as internal/system table\n\t */\n\tgetFtsTableName(collectionSlug: string): string {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\treturn `_emdash_fts_${collectionSlug}`;\n\t}\n\n\t/**\n\t * Get the content table name for a collection\n\t */\n\tgetContentTableName(collectionSlug: string): string {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\treturn `ec_${collectionSlug}`;\n\t}\n\n\t/**\n\t * Check if an FTS table exists for a collection\n\t */\n\tasync ftsTableExists(collectionSlug: string): Promise<boolean> {\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\treturn dialectTableExists(this.db, ftsTable);\n\t}\n\n\t/**\n\t * Create an FTS5 virtual table for a collection.\n\t * FTS5 is SQLite-only; on other dialects this is a no-op.\n\t *\n\t * @param collectionSlug - The collection slug\n\t * @param searchableFields - Array of field names to index\n\t * @param weights - Optional field weights for ranking\n\t */\n\tasync createFtsTable(\n\t\tcollectionSlug: string,\n\t\tsearchableFields: string[],\n\t\t_weights?: Record<string, number>,\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\n\t\t// Build the column list for FTS5\n\t\t// id and locale are UNINDEXED (used for joining/filtering, not searched)\n\t\tconst columns = [\"id UNINDEXED\", \"locale UNINDEXED\", ...searchableFields].join(\", \");\n\n\t\t// Create the FTS5 virtual table.\n\t\t// `content='<table>'` makes this an *external content* FTS5 table:\n\t\t// the inverted index lives in the FTS shadow tables, but the actual\n\t\t// row data lives in the backing content table. The triggers in\n\t\t// `createTriggers` keep the index in sync; they MUST use the\n\t\t// external-content-safe `'delete'` command (see notes there) to\n\t\t// avoid `SQLITE_CORRUPT_VTAB` on UPDATE/DELETE.\n\t\t// tokenize='porter unicode61' enables stemming (run matches running, ran, etc.)\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE VIRTUAL TABLE IF NOT EXISTS \"${ftsTable}\" USING fts5(\n\t\t\t\t${columns},\n\t\t\t\tcontent='${contentTable}',\n\t\t\t\tcontent_rowid='rowid',\n\t\t\t\ttokenize='porter unicode61'\n\t\t\t)\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Create triggers for automatic sync\n\t\tawait this.createTriggers(collectionSlug, searchableFields);\n\t}\n\n\t/**\n\t * Create triggers to keep FTS table in sync with content table.\n\t *\n\t * The insert and update triggers only add rows to the FTS index when\n\t * `deleted_at IS NULL`. This keeps soft-deleted content out of the\n\t * search index and ensures the FTS row count matches the non-deleted\n\t * content count (which `verifyAndRepairIndex` relies on).\n\t *\n\t * IMPORTANT: The FTS5 virtual table is created with `content='ec_<slug>'`\n\t * which makes it an *external content* FTS5 table. For external-content\n\t * tables, removing a row must use the documented `'delete'` command and\n\t * supply the OLD column values explicitly, e.g.:\n\t *\n\t * INSERT INTO fts(fts, rowid, col1, col2)\n\t * VALUES('delete', OLD.rowid, OLD.col1, OLD.col2);\n\t *\n\t * Using `DELETE FROM fts WHERE rowid = OLD.rowid` is the correct form\n\t * for *contentless* tables but is unsafe for external-content tables:\n\t * FTS5 then reads column values from the backing content table, which\n\t * in an AFTER UPDATE trigger already holds the NEW values. The wrong\n\t * tokens get removed and the inverted index drifts out of sync until\n\t * SQLite raises `SQLITE_CORRUPT_VTAB` on the next mutation. See\n\t * https://www.sqlite.org/fts5.html#external_content_tables.\n\t *\n\t * The UPDATE and DELETE triggers gate the `'delete'` on\n\t * `OLD.deleted_at IS NULL` because the INSERT trigger never indexed\n\t * rows that were already soft-deleted. Issuing `'delete'` for a rowid\n\t * that was never inserted into the FTS index is itself a corruption\n\t * trigger -- FTS5's `'delete'` is not a no-op on missing rowids and\n\t * raises `SQLITE_CORRUPT_VTAB`. Affected paths include restore-from-\n\t * trash (UPDATE where `OLD.deleted_at IS NOT NULL`), permanent-delete\n\t * from trash (DELETE on a soft-deleted row), and any edit on a row\n\t * that's currently in the trash.\n\t */\n\tprivate async createTriggers(collectionSlug: string, searchableFields: string[]): Promise<void> {\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tif (searchableFields.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot create FTS triggers for collection \"${collectionSlug}\": no searchable fields. ` +\n\t\t\t\t\t`Mark at least one field as searchable before enabling search.`,\n\t\t\t);\n\t\t}\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fieldList = searchableFields.join(\", \");\n\t\tconst newFieldList = searchableFields.map((f) => `NEW.${f}`).join(\", \");\n\t\t// `'delete'` takes the FTS5 virtual table name as the first column,\n\t\t// then the rowid being removed, then the OLD value of every column\n\t\t// declared on the FTS5 table (in declaration order: id, locale,\n\t\t// then each searchable field).\n\t\tconst oldFieldList = searchableFields.map((f) => `OLD.${f}`).join(\", \");\n\n\t\t// Insert trigger - only index non-deleted content\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_insert\" \n\t\t\tAFTER INSERT ON \"${contentTable}\" \n\t\t\tWHEN NEW.deleted_at IS NULL\n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\t\tVALUES (NEW.rowid, NEW.id, NEW.locale, ${newFieldList});\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Update trigger - remove the old row from the FTS index using the\n\t\t// external-content-safe `'delete'` command (which uses OLD column\n\t\t// values, captured before the row was modified), then re-insert\n\t\t// the new values when the row is still visible.\n\t\t//\n\t\t// `'delete'` is gated on `OLD.deleted_at IS NULL` because rows that\n\t\t// were soft-deleted are not in the FTS index (the INSERT trigger\n\t\t// skips them). Issuing `'delete'` for a missing rowid raises\n\t\t// `SQLITE_CORRUPT_VTAB`, which would break restore-from-trash and\n\t\t// edits to soft-deleted rows.\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_update\" \n\t\t\tAFTER UPDATE ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(\"${ftsTable}\", rowid, id, locale, ${fieldList})\n\t\t\t\tSELECT 'delete', OLD.rowid, OLD.id, OLD.locale, ${oldFieldList}\n\t\t\t\tWHERE OLD.deleted_at IS NULL;\n\t\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\t\tSELECT NEW.rowid, NEW.id, NEW.locale, ${newFieldList}\n\t\t\t\tWHERE NEW.deleted_at IS NULL;\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Delete trigger - same external-content-safe `'delete'` form,\n\t\t// gated on `OLD.deleted_at IS NULL` for the same reason as the\n\t\t// UPDATE trigger: permanent-delete from trash hits a row whose\n\t\t// `deleted_at` is already set and which was never indexed.\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_delete\" \n\t\t\tAFTER DELETE ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(\"${ftsTable}\", rowid, id, locale, ${fieldList})\n\t\t\t\tSELECT 'delete', OLD.rowid, OLD.id, OLD.locale, ${oldFieldList}\n\t\t\t\tWHERE OLD.deleted_at IS NULL;\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\t}\n\n\t/**\n\t * Drop triggers for a collection\n\t */\n\tprivate async dropTriggers(collectionSlug: string): Promise<void> {\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_insert\"`).execute(this.db);\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_update\"`).execute(this.db);\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_delete\"`).execute(this.db);\n\t}\n\n\t/**\n\t * Drop the FTS table and triggers for a collection\n\t */\n\tasync dropFtsTable(collectionSlug: string): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\t// Drop triggers first\n\t\tawait this.dropTriggers(collectionSlug);\n\n\t\t// Drop the FTS table\n\t\tawait sql.raw(`DROP TABLE IF EXISTS \"${ftsTable}\"`).execute(this.db);\n\t}\n\n\t/**\n\t * Rebuild the FTS index for a collection\n\t *\n\t * This is useful after bulk imports or if the index gets out of sync.\n\t */\n\tasync rebuildIndex(\n\t\tcollectionSlug: string,\n\t\tsearchableFields: string[],\n\t\tweights?: Record<string, number>,\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\t// Drop existing table and triggers\n\t\tawait this.dropFtsTable(collectionSlug);\n\n\t\t// Recreate table and triggers\n\t\tawait this.createFtsTable(collectionSlug, searchableFields, weights);\n\n\t\t// Populate from existing content\n\t\tawait this.populateFromContent(collectionSlug, searchableFields);\n\t}\n\n\t/**\n\t * Populate the FTS table from existing content\n\t */\n\tasync populateFromContent(collectionSlug: string, searchableFields: string[]): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fieldList = searchableFields.join(\", \");\n\n\t\t// Insert all existing content into FTS table\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\tSELECT rowid, id, locale, ${fieldList} FROM \"${contentTable}\"\n\t\t\tWHERE deleted_at IS NULL\n\t\t`)\n\t\t\t.execute(this.db);\n\t}\n\n\t/**\n\t * Get the search configuration for a collection\n\t */\n\tasync getSearchConfig(collectionSlug: string): Promise<SearchConfig | null> {\n\t\tconst result = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"search_config\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!result?.search_config) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(result.search_config);\n\t\t\tif (\n\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\tparsed === null ||\n\t\t\t\t!(\"enabled\" in parsed) ||\n\t\t\t\ttypeof parsed.enabled !== \"boolean\"\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst config: SearchConfig = { enabled: parsed.enabled };\n\t\t\tif (\"weights\" in parsed && typeof parsed.weights === \"object\" && parsed.weights !== null) {\n\t\t\t\t// weights is a JSON-parsed object — safe to treat as Record<string, number>\n\t\t\t\tconst weights: Record<string, number> = {};\n\t\t\t\tfor (const [k, v] of Object.entries(parsed.weights)) {\n\t\t\t\t\tif (typeof v === \"number\") {\n\t\t\t\t\t\tweights[k] = v;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconfig.weights = weights;\n\t\t\t}\n\t\t\treturn config;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Update the search configuration for a collection\n\t */\n\tasync setSearchConfig(collectionSlug: string, config: SearchConfig): Promise<void> {\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t.set({ search_config: JSON.stringify(config) })\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Get searchable fields for a collection\n\t */\n\tasync getSearchableFields(collectionSlug: string): Promise<string[]> {\n\t\tconst collection = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collection) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst fields = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.select(\"slug\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.where(\"searchable\", \"=\", 1)\n\t\t\t.execute();\n\n\t\treturn fields.map((f) => f.slug);\n\t}\n\n\t/**\n\t * Enable search for a collection.\n\t *\n\t * Uses rebuildIndex to ensure a clean state -- drop any existing FTS\n\t * table/triggers, recreate them, and populate from content. This avoids\n\t * duplicate rows when triggers have already populated the index (e.g.\n\t * during seeding where content is inserted before search is enabled).\n\t */\n\tasync enableSearch(\n\t\tcollectionSlug: string,\n\t\toptions?: { weights?: Record<string, number> },\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) {\n\t\t\tthrow new Error(\"Full-text search is only available with SQLite databases\");\n\t\t}\n\t\t// Get searchable fields\n\t\tconst searchableFields = await this.getSearchableFields(collectionSlug);\n\n\t\tif (searchableFields.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No searchable fields defined for collection \"${collectionSlug}\". ` +\n\t\t\t\t\t`Mark at least one field as searchable before enabling search.`,\n\t\t\t);\n\t\t}\n\n\t\t// Rebuild from scratch to ensure clean state (no duplicate rows)\n\t\tawait this.rebuildIndex(collectionSlug, searchableFields, options?.weights);\n\n\t\t// Update search config\n\t\tawait this.setSearchConfig(collectionSlug, {\n\t\t\tenabled: true,\n\t\t\tweights: options?.weights,\n\t\t});\n\t}\n\n\t/**\n\t * Disable search for a collection\n\t *\n\t * Drops the FTS table and triggers.\n\t */\n\tasync disableSearch(collectionSlug: string): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tawait this.dropFtsTable(collectionSlug);\n\t\tconst existing = await this.getSearchConfig(collectionSlug);\n\t\tawait this.setSearchConfig(collectionSlug, { enabled: false, weights: existing?.weights });\n\t}\n\n\t/**\n\t * Get index statistics for a collection\n\t */\n\tasync getIndexStats(\n\t\tcollectionSlug: string,\n\t): Promise<{ indexed: number; lastRebuilt?: string } | null> {\n\t\tif (!isSqlite(this.db)) return null;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst ftsDocsizeTable = `${ftsTable}_docsize`;\n\n\t\t// Check if table exists\n\t\tif (!(await this.ftsTableExists(collectionSlug))) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Count indexed rows\n\t\tconst result = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM \"${sql.raw(ftsDocsizeTable)}\"\n\t\t`.execute(this.db);\n\n\t\treturn {\n\t\t\tindexed: result.rows[0]?.count ?? 0,\n\t\t};\n\t}\n\n\t/**\n\t * Verify FTS index integrity and rebuild if drift is detected.\n\t *\n\t * Cheap belt-and-braces check, run lazily on the first search request\n\t * per isolate. The expensive cases (corrupted indexes from pre-fix\n\t * EmDash versions, broken legacy triggers) are handled at boot time by\n\t * migration `039_fix_fts5_triggers`, not here. This routine sticks to:\n\t *\n\t * 1. FTS table missing while config says search is enabled -> rebuild.\n\t * 2. Row count mismatch between content table and FTS docsize -> rebuild.\n\t *\n\t * Returns true if the index was rebuilt, false if it was healthy.\n\t */\n\tasync verifyAndRepairIndex(collectionSlug: string): Promise<boolean> {\n\t\tif (!isSqlite(this.db)) return false;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst ftsDocsizeTable = `${ftsTable}_docsize`;\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fields = await this.getSearchableFields(collectionSlug);\n\t\tconst config = await this.getSearchConfig(collectionSlug);\n\n\t\tif (!(await this.ftsTableExists(collectionSlug))) {\n\t\t\tif (!config?.enabled || fields.length === 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconsole.warn(`FTS index for \"${collectionSlug}\" is missing. Rebuilding.`);\n\t\t\tawait this.rebuildIndex(collectionSlug, fields, config.weights);\n\t\t\treturn true;\n\t\t}\n\n\t\t// Row count parity check. For external-content FTS tables, COUNT(*)\n\t\t// on the virtual table is answered from the backing content table\n\t\t// (including soft-deleted rows), so we use the docsize shadow table\n\t\t// which tracks rows actually present in the full-text index.\n\t\tconst contentCount = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(contentTable)}\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst ftsCount = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM \"${sql.raw(ftsDocsizeTable)}\"\n\t\t`.execute(this.db);\n\n\t\tconst contentRows = contentCount.rows[0]?.count ?? 0;\n\t\tconst ftsRows = ftsCount.rows[0]?.count ?? 0;\n\n\t\tif (contentRows !== ftsRows) {\n\t\t\tconsole.warn(\n\t\t\t\t`FTS index for \"${collectionSlug}\" has ${ftsRows} rows but content table has ${contentRows}. Rebuilding.`,\n\t\t\t);\n\t\t\tif (fields.length > 0) {\n\t\t\t\tawait this.rebuildIndex(collectionSlug, fields, config?.weights);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Verify and repair FTS indexes for all search-enabled collections.\n\t *\n\t * Intended to run at startup to auto-heal any corruption from\n\t * previous process crashes.\n\t */\n\tasync verifyAndRepairAll(): Promise<number> {\n\t\tif (!isSqlite(this.db)) return 0;\n\n\t\tconst collections = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"slug\")\n\t\t\t.where(\"search_config\", \"is not\", null)\n\t\t\t.execute();\n\n\t\tlet repaired = 0;\n\t\tfor (const { slug } of collections) {\n\t\t\tconst config = await this.getSearchConfig(slug);\n\t\t\tif (!config?.enabled) continue;\n\n\t\t\ttry {\n\t\t\t\tconst wasRepaired = await this.verifyAndRepairIndex(slug);\n\t\t\t\tif (wasRepaired) repaired++;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`Failed to verify/repair FTS index for \"${slug}\":`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn repaired;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fts-manager-DzqIBrrW.mjs","names":["dialectTableExists"],"sources":["../src/search/fts-manager.ts"],"sourcesContent":["/**\n * FTS5 Manager\n *\n * Manages FTS5 virtual tables and triggers for search indexing.\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { isSqlite, tableExists as dialectTableExists } from \"../database/dialect-helpers.js\";\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport type { SearchConfig } from \"./types.js\";\n\n/**\n * FTS5 Manager\n *\n * Handles creation, deletion, and management of FTS5 virtual tables\n * for full-text search on content collections.\n */\nexport class FTSManager {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Validate a collection slug and its searchable field names.\n\t * Must be called before any raw SQL interpolation.\n\t */\n\tprivate validateInputs(collectionSlug: string, searchableFields?: string[]): void {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\tif (searchableFields) {\n\t\t\tfor (const field of searchableFields) {\n\t\t\t\tvalidateIdentifier(field, \"searchable field name\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the FTS table name for a collection\n\t * Uses _emdash_ prefix to clearly mark as internal/system table\n\t */\n\tgetFtsTableName(collectionSlug: string): string {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\treturn `_emdash_fts_${collectionSlug}`;\n\t}\n\n\t/**\n\t * Get the content table name for a collection\n\t */\n\tgetContentTableName(collectionSlug: string): string {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\treturn `ec_${collectionSlug}`;\n\t}\n\n\t/**\n\t * Check if an FTS table exists for a collection\n\t */\n\tasync ftsTableExists(collectionSlug: string): Promise<boolean> {\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\treturn dialectTableExists(this.db, ftsTable);\n\t}\n\n\t/**\n\t * Create an FTS5 virtual table for a collection.\n\t * FTS5 is SQLite-only; on other dialects this is a no-op.\n\t *\n\t * @param collectionSlug - The collection slug\n\t * @param searchableFields - Array of field names to index\n\t * @param weights - Optional field weights for ranking\n\t */\n\tasync createFtsTable(\n\t\tcollectionSlug: string,\n\t\tsearchableFields: string[],\n\t\t_weights?: Record<string, number>,\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\n\t\t// Build the column list for FTS5\n\t\t// id and locale are UNINDEXED (used for joining/filtering, not searched)\n\t\tconst columns = [\"id UNINDEXED\", \"locale UNINDEXED\", ...searchableFields].join(\", \");\n\n\t\t// Create the FTS5 virtual table.\n\t\t// `content='<table>'` makes this an *external content* FTS5 table:\n\t\t// the inverted index lives in the FTS shadow tables, but the actual\n\t\t// row data lives in the backing content table. The triggers in\n\t\t// `createTriggers` keep the index in sync; they MUST use the\n\t\t// external-content-safe `'delete'` command (see notes there) to\n\t\t// avoid `SQLITE_CORRUPT_VTAB` on UPDATE/DELETE.\n\t\t// tokenize='porter unicode61' enables stemming (run matches running, ran, etc.)\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE VIRTUAL TABLE IF NOT EXISTS \"${ftsTable}\" USING fts5(\n\t\t\t\t${columns},\n\t\t\t\tcontent='${contentTable}',\n\t\t\t\tcontent_rowid='rowid',\n\t\t\t\ttokenize='porter unicode61'\n\t\t\t)\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Create triggers for automatic sync\n\t\tawait this.createTriggers(collectionSlug, searchableFields);\n\t}\n\n\t/**\n\t * Create triggers to keep FTS table in sync with content table.\n\t *\n\t * The insert and update triggers only add rows to the FTS index when\n\t * `deleted_at IS NULL`. This keeps soft-deleted content out of the\n\t * search index and ensures the FTS row count matches the non-deleted\n\t * content count (which `verifyAndRepairIndex` relies on).\n\t *\n\t * IMPORTANT: The FTS5 virtual table is created with `content='ec_<slug>'`\n\t * which makes it an *external content* FTS5 table. For external-content\n\t * tables, removing a row must use the documented `'delete'` command and\n\t * supply the OLD column values explicitly, e.g.:\n\t *\n\t * INSERT INTO fts(fts, rowid, col1, col2)\n\t * VALUES('delete', OLD.rowid, OLD.col1, OLD.col2);\n\t *\n\t * Using `DELETE FROM fts WHERE rowid = OLD.rowid` is the correct form\n\t * for *contentless* tables but is unsafe for external-content tables:\n\t * FTS5 then reads column values from the backing content table, which\n\t * in an AFTER UPDATE trigger already holds the NEW values. The wrong\n\t * tokens get removed and the inverted index drifts out of sync until\n\t * SQLite raises `SQLITE_CORRUPT_VTAB` on the next mutation. See\n\t * https://www.sqlite.org/fts5.html#external_content_tables.\n\t *\n\t * The UPDATE and DELETE triggers gate the `'delete'` on\n\t * `OLD.deleted_at IS NULL` because the INSERT trigger never indexed\n\t * rows that were already soft-deleted. Issuing `'delete'` for a rowid\n\t * that was never inserted into the FTS index is itself a corruption\n\t * trigger -- FTS5's `'delete'` is not a no-op on missing rowids and\n\t * raises `SQLITE_CORRUPT_VTAB`. Affected paths include restore-from-\n\t * trash (UPDATE where `OLD.deleted_at IS NOT NULL`), permanent-delete\n\t * from trash (DELETE on a soft-deleted row), and any edit on a row\n\t * that's currently in the trash.\n\t */\n\tprivate async createTriggers(collectionSlug: string, searchableFields: string[]): Promise<void> {\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tif (searchableFields.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot create FTS triggers for collection \"${collectionSlug}\": no searchable fields. ` +\n\t\t\t\t\t`Mark at least one field as searchable before enabling search.`,\n\t\t\t);\n\t\t}\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fieldList = searchableFields.join(\", \");\n\t\tconst newFieldList = searchableFields.map((f) => `NEW.${f}`).join(\", \");\n\t\t// `'delete'` takes the FTS5 virtual table name as the first column,\n\t\t// then the rowid being removed, then the OLD value of every column\n\t\t// declared on the FTS5 table (in declaration order: id, locale,\n\t\t// then each searchable field).\n\t\tconst oldFieldList = searchableFields.map((f) => `OLD.${f}`).join(\", \");\n\n\t\t// Insert trigger - only index non-deleted content\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_insert\" \n\t\t\tAFTER INSERT ON \"${contentTable}\" \n\t\t\tWHEN NEW.deleted_at IS NULL\n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\t\tVALUES (NEW.rowid, NEW.id, NEW.locale, ${newFieldList});\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Update trigger - remove the old row from the FTS index using the\n\t\t// external-content-safe `'delete'` command (which uses OLD column\n\t\t// values, captured before the row was modified), then re-insert\n\t\t// the new values when the row is still visible.\n\t\t//\n\t\t// `'delete'` is gated on `OLD.deleted_at IS NULL` because rows that\n\t\t// were soft-deleted are not in the FTS index (the INSERT trigger\n\t\t// skips them). Issuing `'delete'` for a missing rowid raises\n\t\t// `SQLITE_CORRUPT_VTAB`, which would break restore-from-trash and\n\t\t// edits to soft-deleted rows.\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_update\" \n\t\t\tAFTER UPDATE ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(\"${ftsTable}\", rowid, id, locale, ${fieldList})\n\t\t\t\tSELECT 'delete', OLD.rowid, OLD.id, OLD.locale, ${oldFieldList}\n\t\t\t\tWHERE OLD.deleted_at IS NULL;\n\t\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\t\tSELECT NEW.rowid, NEW.id, NEW.locale, ${newFieldList}\n\t\t\t\tWHERE NEW.deleted_at IS NULL;\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Delete trigger - same external-content-safe `'delete'` form,\n\t\t// gated on `OLD.deleted_at IS NULL` for the same reason as the\n\t\t// UPDATE trigger: permanent-delete from trash hits a row whose\n\t\t// `deleted_at` is already set and which was never indexed.\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_delete\" \n\t\t\tAFTER DELETE ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(\"${ftsTable}\", rowid, id, locale, ${fieldList})\n\t\t\t\tSELECT 'delete', OLD.rowid, OLD.id, OLD.locale, ${oldFieldList}\n\t\t\t\tWHERE OLD.deleted_at IS NULL;\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\t}\n\n\t/**\n\t * Drop triggers for a collection\n\t */\n\tprivate async dropTriggers(collectionSlug: string): Promise<void> {\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_insert\"`).execute(this.db);\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_update\"`).execute(this.db);\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_delete\"`).execute(this.db);\n\t}\n\n\t/**\n\t * Drop the FTS table and triggers for a collection\n\t */\n\tasync dropFtsTable(collectionSlug: string): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\t// Drop triggers first\n\t\tawait this.dropTriggers(collectionSlug);\n\n\t\t// Drop the FTS table\n\t\tawait sql.raw(`DROP TABLE IF EXISTS \"${ftsTable}\"`).execute(this.db);\n\t}\n\n\t/**\n\t * Rebuild the FTS index for a collection\n\t *\n\t * This is useful after bulk imports or if the index gets out of sync.\n\t */\n\tasync rebuildIndex(\n\t\tcollectionSlug: string,\n\t\tsearchableFields: string[],\n\t\tweights?: Record<string, number>,\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\t// Drop existing table and triggers\n\t\tawait this.dropFtsTable(collectionSlug);\n\n\t\t// Recreate table and triggers\n\t\tawait this.createFtsTable(collectionSlug, searchableFields, weights);\n\n\t\t// Populate from existing content\n\t\tawait this.populateFromContent(collectionSlug, searchableFields);\n\t}\n\n\t/**\n\t * Populate the FTS table from existing content\n\t */\n\tasync populateFromContent(collectionSlug: string, searchableFields: string[]): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fieldList = searchableFields.join(\", \");\n\n\t\t// Insert all existing content into FTS table\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\tSELECT rowid, id, locale, ${fieldList} FROM \"${contentTable}\"\n\t\t\tWHERE deleted_at IS NULL\n\t\t`)\n\t\t\t.execute(this.db);\n\t}\n\n\t/**\n\t * Get the search configuration for a collection\n\t */\n\tasync getSearchConfig(collectionSlug: string): Promise<SearchConfig | null> {\n\t\tconst result = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"search_config\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!result?.search_config) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(result.search_config);\n\t\t\tif (\n\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\tparsed === null ||\n\t\t\t\t!(\"enabled\" in parsed) ||\n\t\t\t\ttypeof parsed.enabled !== \"boolean\"\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst config: SearchConfig = { enabled: parsed.enabled };\n\t\t\tif (\"weights\" in parsed && typeof parsed.weights === \"object\" && parsed.weights !== null) {\n\t\t\t\t// weights is a JSON-parsed object — safe to treat as Record<string, number>\n\t\t\t\tconst weights: Record<string, number> = {};\n\t\t\t\tfor (const [k, v] of Object.entries(parsed.weights)) {\n\t\t\t\t\tif (typeof v === \"number\") {\n\t\t\t\t\t\tweights[k] = v;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconfig.weights = weights;\n\t\t\t}\n\t\t\treturn config;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Update the search configuration for a collection\n\t */\n\tasync setSearchConfig(collectionSlug: string, config: SearchConfig): Promise<void> {\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t.set({ search_config: JSON.stringify(config) })\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Get searchable fields for a collection\n\t */\n\tasync getSearchableFields(collectionSlug: string): Promise<string[]> {\n\t\tconst collection = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collection) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst fields = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.select(\"slug\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.where(\"searchable\", \"=\", 1)\n\t\t\t.execute();\n\n\t\treturn fields.map((f) => f.slug);\n\t}\n\n\t/**\n\t * Whether a collection has a user-defined `title` field.\n\t *\n\t * `title` is not a system column on `ec_*` tables -- it exists only when a\n\t * collection defines a field with slug `title`. Search and suggestion SQL\n\t * that selects `c.title` must check this first; otherwise collections\n\t * without a title field raise \"no such column: c.title\" (#1178).\n\t */\n\tasync hasTitleColumn(collectionSlug: string): Promise<boolean> {\n\t\tconst withTitle = await this.getCollectionsWithTitleColumn([collectionSlug]);\n\t\treturn withTitle.has(collectionSlug);\n\t}\n\n\t/**\n\t * Bulk variant of `hasTitleColumn()`: which of the given collections have\n\t * a user-defined `title` field. One query instead of one `hasTitleColumn`\n\t * round-trip pair per collection -- callers that check this once per\n\t * collection in a loop (multi-collection search, suggestions) should use\n\t * this instead (AGENTS.md: \"one query beats two\").\n\t */\n\tasync getCollectionsWithTitleColumn(collectionSlugs: string[]): Promise<Set<string>> {\n\t\tif (collectionSlugs.length === 0) return new Set();\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_fields as f\")\n\t\t\t.innerJoin(\"_emdash_collections as c\", \"c.id\", \"f.collection_id\")\n\t\t\t.select([\"c.slug as collection_slug\"])\n\t\t\t.where(\"f.slug\", \"=\", \"title\")\n\t\t\t.execute();\n\n\t\tconst withTitle = new Set(rows.map((r) => r.collection_slug));\n\t\treturn new Set(collectionSlugs.filter((slug) => withTitle.has(slug)));\n\t}\n\n\t/**\n\t * Enable search for a collection.\n\t *\n\t * Uses rebuildIndex to ensure a clean state -- drop any existing FTS\n\t * table/triggers, recreate them, and populate from content. This avoids\n\t * duplicate rows when triggers have already populated the index (e.g.\n\t * during seeding where content is inserted before search is enabled).\n\t */\n\tasync enableSearch(\n\t\tcollectionSlug: string,\n\t\toptions?: { weights?: Record<string, number> },\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) {\n\t\t\tthrow new Error(\"Full-text search is only available with SQLite databases\");\n\t\t}\n\t\t// Get searchable fields\n\t\tconst searchableFields = await this.getSearchableFields(collectionSlug);\n\n\t\tif (searchableFields.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No searchable fields defined for collection \"${collectionSlug}\". ` +\n\t\t\t\t\t`Mark at least one field as searchable before enabling search.`,\n\t\t\t);\n\t\t}\n\n\t\t// Rebuild from scratch to ensure clean state (no duplicate rows)\n\t\tawait this.rebuildIndex(collectionSlug, searchableFields, options?.weights);\n\n\t\t// Update search config\n\t\tawait this.setSearchConfig(collectionSlug, {\n\t\t\tenabled: true,\n\t\t\tweights: options?.weights,\n\t\t});\n\t}\n\n\t/**\n\t * Disable search for a collection\n\t *\n\t * Drops the FTS table and triggers.\n\t */\n\tasync disableSearch(collectionSlug: string): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tawait this.dropFtsTable(collectionSlug);\n\t\tconst existing = await this.getSearchConfig(collectionSlug);\n\t\tawait this.setSearchConfig(collectionSlug, { enabled: false, weights: existing?.weights });\n\t}\n\n\t/**\n\t * Get index statistics for a collection\n\t */\n\tasync getIndexStats(\n\t\tcollectionSlug: string,\n\t): Promise<{ indexed: number; lastRebuilt?: string } | null> {\n\t\tif (!isSqlite(this.db)) return null;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst ftsDocsizeTable = `${ftsTable}_docsize`;\n\n\t\t// Check if table exists\n\t\tif (!(await this.ftsTableExists(collectionSlug))) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Count indexed rows\n\t\tconst result = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM \"${sql.raw(ftsDocsizeTable)}\"\n\t\t`.execute(this.db);\n\n\t\treturn {\n\t\t\tindexed: result.rows[0]?.count ?? 0,\n\t\t};\n\t}\n\n\t/**\n\t * Verify FTS index integrity and rebuild if drift is detected.\n\t *\n\t * Cheap belt-and-braces check, run lazily on the first search request\n\t * per isolate. The expensive cases (corrupted indexes from pre-fix\n\t * EmDash versions, broken legacy triggers) are handled at boot time by\n\t * migration `039_fix_fts5_triggers`, not here. This routine sticks to:\n\t *\n\t * 1. FTS table missing while config says search is enabled -> rebuild.\n\t * 2. Row count mismatch between content table and FTS docsize -> rebuild.\n\t *\n\t * Returns true if the index was rebuilt, false if it was healthy.\n\t */\n\tasync verifyAndRepairIndex(collectionSlug: string): Promise<boolean> {\n\t\tif (!isSqlite(this.db)) return false;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst ftsDocsizeTable = `${ftsTable}_docsize`;\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fields = await this.getSearchableFields(collectionSlug);\n\t\tconst config = await this.getSearchConfig(collectionSlug);\n\n\t\tif (!(await this.ftsTableExists(collectionSlug))) {\n\t\t\tif (!config?.enabled || fields.length === 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconsole.warn(`FTS index for \"${collectionSlug}\" is missing. Rebuilding.`);\n\t\t\tawait this.rebuildIndex(collectionSlug, fields, config.weights);\n\t\t\treturn true;\n\t\t}\n\n\t\t// Row count parity check. For external-content FTS tables, COUNT(*)\n\t\t// on the virtual table is answered from the backing content table\n\t\t// (including soft-deleted rows), so we use the docsize shadow table\n\t\t// which tracks rows actually present in the full-text index.\n\t\tconst contentCount = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(contentTable)}\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst ftsCount = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM \"${sql.raw(ftsDocsizeTable)}\"\n\t\t`.execute(this.db);\n\n\t\tconst contentRows = contentCount.rows[0]?.count ?? 0;\n\t\tconst ftsRows = ftsCount.rows[0]?.count ?? 0;\n\n\t\tif (contentRows !== ftsRows) {\n\t\t\tconsole.warn(\n\t\t\t\t`FTS index for \"${collectionSlug}\" has ${ftsRows} rows but content table has ${contentRows}. Rebuilding.`,\n\t\t\t);\n\t\t\tif (fields.length > 0) {\n\t\t\t\tawait this.rebuildIndex(collectionSlug, fields, config?.weights);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Verify and repair FTS indexes for all search-enabled collections.\n\t *\n\t * Intended to run at startup to auto-heal any corruption from\n\t * previous process crashes.\n\t */\n\tasync verifyAndRepairAll(): Promise<number> {\n\t\tif (!isSqlite(this.db)) return 0;\n\n\t\tconst collections = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"slug\")\n\t\t\t.where(\"search_config\", \"is not\", null)\n\t\t\t.execute();\n\n\t\tlet repaired = 0;\n\t\tfor (const { slug } of collections) {\n\t\t\tconst config = await this.getSearchConfig(slug);\n\t\t\tif (!config?.enabled) continue;\n\n\t\t\ttry {\n\t\t\t\tconst wasRepaired = await this.verifyAndRepairIndex(slug);\n\t\t\t\tif (wasRepaired) repaired++;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`Failed to verify/repair FTS index for \"${slug}\":`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn repaired;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash-
|
|
1
|
+
{"version":3,"file":"hash-DFFrkivP.mjs","names":[],"sources":["../src/utils/hash.ts"],"sourcesContent":["/**\n * SHA-256 hash of a string, truncated to 16 hex chars (64 bits).\n * For cache invalidation / ETags — not for security.\n */\nexport async function hashString(content: string): Promise<string> {\n\tconst buf = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(content));\n\treturn Array.from(new Uint8Array(buf).slice(0, 8), (b) => b.toString(16).padStart(2, \"0\")).join(\n\t\t\"\",\n\t);\n}\n\n/**\n * Compute content hash using Web Crypto API\n *\n * Uses SHA-1 which is the fastest option in SubtleCrypto.\n * SHA-1 is cryptographically weak but fine for content deduplication\n * where we only need to detect identical files, not resist attacks.\n *\n * Returns hex string prefixed with \"sha1:\" for future-proofing\n */\nexport async function computeContentHash(content: Uint8Array | ArrayBuffer): Promise<string> {\n\t// SubtleCrypto.digest() requires BufferSource (ArrayBuffer | ArrayBufferView<ArrayBuffer>).\n\t// Uint8Array.buffer is ArrayBufferLike which may include SharedArrayBuffer in the type system,\n\t// so we ensure we have a plain ArrayBuffer.\n\tlet buf: ArrayBuffer;\n\tif (content instanceof ArrayBuffer) {\n\t\tbuf = content;\n\t} else {\n\t\tbuf = new ArrayBuffer(content.byteLength);\n\t\tnew Uint8Array(buf).set(content);\n\t}\n\tconst hashBuffer = await crypto.subtle.digest(\"SHA-1\", buf);\n\tconst hashArray = new Uint8Array(hashBuffer);\n\tconst hashHex = Array.from(hashArray, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\treturn `sha1:${hashHex}`;\n}\n"],"mappings":";;;;;AAIA,eAAsB,WAAW,SAAkC;CAClE,MAAM,MAAM,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,aAAa,CAAC,OAAO,QAAQ,CAAC;AACpF,QAAO,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KAC1F,GACA;;;;;;;;;;;AAYF,eAAsB,mBAAmB,SAAoD;CAI5F,IAAI;AACJ,KAAI,mBAAmB,YACtB,OAAM;MACA;AACN,QAAM,IAAI,YAAY,QAAQ,WAAW;AACzC,MAAI,WAAW,IAAI,CAAC,IAAI,QAAQ;;CAEjC,MAAM,aAAa,MAAM,OAAO,OAAO,OAAO,SAAS,IAAI;CAC3D,MAAM,YAAY,IAAI,WAAW,WAAW;AAE5C,QAAO,QADS,MAAM,KAAK,YAAY,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { t as Database } from "./types-DPKhI5ib.mjs";
|
|
2
|
+
import { Kysely } from "kysely";
|
|
3
|
+
|
|
4
|
+
//#region src/seo/hreflang.d.ts
|
|
5
|
+
/** A single alternate link, ready to render as `<link rel="alternate">`. */
|
|
6
|
+
interface HreflangAlternate {
|
|
7
|
+
/** Locale code, or `"x-default"` for the default variant */
|
|
8
|
+
hreflang: string;
|
|
9
|
+
/** Absolute URL of the variant */
|
|
10
|
+
href: string;
|
|
11
|
+
}
|
|
12
|
+
interface HreflangOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Absolute site origin (e.g. `https://example.com`) used to build
|
|
15
|
+
* the alternate URLs. Falls back to the site settings URL; when
|
|
16
|
+
* neither is available the result is empty, since hreflang
|
|
17
|
+
* requires fully-qualified URLs.
|
|
18
|
+
*/
|
|
19
|
+
siteUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve hreflang alternates for a content entry.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```astro
|
|
26
|
+
* ---
|
|
27
|
+
* import { getHreflangAlternates } from "emdash";
|
|
28
|
+
*
|
|
29
|
+
* const alternates = await getHreflangAlternates("posts", entry.data.id, {
|
|
30
|
+
* siteUrl: Astro.url.origin,
|
|
31
|
+
* });
|
|
32
|
+
* ---
|
|
33
|
+
* <head>
|
|
34
|
+
* {alternates.map((a) => <link rel="alternate" hreflang={a.hreflang} href={a.href} />)}
|
|
35
|
+
* </head>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function getHreflangAlternates(collection: string, entryId: string, options?: HreflangOptions): Promise<HreflangAlternate[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve hreflang alternates with an explicit db handle.
|
|
41
|
+
*
|
|
42
|
+
* @internal Use `getHreflangAlternates()` in templates. This variant is
|
|
43
|
+
* for routes/components that already have a database handle.
|
|
44
|
+
*/
|
|
45
|
+
declare function getHreflangAlternatesWithDb(db: Kysely<Database>, collection: string, entryId: string, options?: HreflangOptions): Promise<HreflangAlternate[]>;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { getHreflangAlternatesWithDb as i, HreflangOptions as n, getHreflangAlternates as r, HreflangAlternate as t };
|
|
48
|
+
//# sourceMappingURL=hreflang-CIxH_LKz.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hreflang-CIxH_LKz.d.mts","names":[],"sources":["../src/seo/hreflang.ts"],"mappings":";;;;;UA4DiB,iBAAA;EAsCN;EApCV,QAAA;EAoC2B;EAlC3B,IAAA;AAAA;AAAA,UAGgB,eAAA;EAgDL;;;;;;EAzCX,OAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoBqB,qBAAA,CACrB,UAAA,UACA,OAAA,UACA,OAAA,GAAS,eAAA,GACP,OAAA,CAAQ,iBAAA;;;;;;;iBAgBW,2BAAA,CACrB,EAAA,EAAI,MAAA,CAAO,QAAA,GACX,UAAA,UACA,OAAA,UACA,OAAA,GAAS,eAAA,GACP,OAAA,CAAQ,iBAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { n as getI18nConfig, r as isI18nEnabled } from "./config-CVssduLe.mjs";
|
|
2
|
+
import { t as ContentRepository } from "./content-Ci04z2z-.mjs";
|
|
3
|
+
import { r as requestCached } from "./request-cache-BSUptuJR.mjs";
|
|
4
|
+
import { n as localizePath, t as interpolateUrlPattern } from "./resolve-Cd9dzclN.mjs";
|
|
5
|
+
import { n as getCollectionInfoWithDb } from "./query-Di7DOmPV.mjs";
|
|
6
|
+
import { a as getSiteSettingsWithDb } from "./settings-CpA4lQFt.mjs";
|
|
7
|
+
|
|
8
|
+
//#region src/seo/hreflang.ts
|
|
9
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
10
|
+
const ABSOLUTE_URL_RE = /^https?:\/\//i;
|
|
11
|
+
/**
|
|
12
|
+
* IDs of variants flagged `noindex` in the SEO panel. Entries without
|
|
13
|
+
* an `_emdash_seo` row are indexable by default (same as the sitemap).
|
|
14
|
+
* The id list is bounded by the number of configured locales, so no
|
|
15
|
+
* chunking is needed.
|
|
16
|
+
*/
|
|
17
|
+
async function findNoindexIds(db, collection, ids) {
|
|
18
|
+
if (ids.length === 0) return /* @__PURE__ */ new Set();
|
|
19
|
+
const rows = await db.selectFrom("_emdash_seo").select("content_id").where("collection", "=", collection).where("content_id", "in", ids).where("seo_no_index", "=", 1).execute();
|
|
20
|
+
return new Set(rows.map((r) => r.content_id));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve hreflang alternates for a content entry.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```astro
|
|
27
|
+
* ---
|
|
28
|
+
* import { getHreflangAlternates } from "emdash";
|
|
29
|
+
*
|
|
30
|
+
* const alternates = await getHreflangAlternates("posts", entry.data.id, {
|
|
31
|
+
* siteUrl: Astro.url.origin,
|
|
32
|
+
* });
|
|
33
|
+
* ---
|
|
34
|
+
* <head>
|
|
35
|
+
* {alternates.map((a) => <link rel="alternate" hreflang={a.hreflang} href={a.href} />)}
|
|
36
|
+
* </head>
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
async function getHreflangAlternates(collection, entryId, options = {}) {
|
|
40
|
+
if (!isI18nEnabled()) return [];
|
|
41
|
+
return requestCached(`hreflang:${collection}:${entryId}:${options.siteUrl ?? ""}`, async () => {
|
|
42
|
+
const { getDb } = await import("./loader-Be3ouI5L.mjs").then((n) => n.o);
|
|
43
|
+
return getHreflangAlternatesWithDb(await getDb(), collection, entryId, options);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve hreflang alternates with an explicit db handle.
|
|
48
|
+
*
|
|
49
|
+
* @internal Use `getHreflangAlternates()` in templates. This variant is
|
|
50
|
+
* for routes/components that already have a database handle.
|
|
51
|
+
*/
|
|
52
|
+
async function getHreflangAlternatesWithDb(db, collection, entryId, options = {}) {
|
|
53
|
+
if (!isI18nEnabled()) return [];
|
|
54
|
+
let siteUrl = options.siteUrl;
|
|
55
|
+
if (!siteUrl) siteUrl = (await getSiteSettingsWithDb(db)).url;
|
|
56
|
+
if (!siteUrl || !ABSOLUTE_URL_RE.test(siteUrl)) return [];
|
|
57
|
+
siteUrl = siteUrl.replace(TRAILING_SLASH_RE, "");
|
|
58
|
+
const repo = new ContentRepository(db);
|
|
59
|
+
const item = await repo.findByIdOrSlug(collection, entryId);
|
|
60
|
+
if (!item) return [];
|
|
61
|
+
const group = item.translationGroup || item.id;
|
|
62
|
+
let variants = await repo.findTranslations(collection, group);
|
|
63
|
+
if (variants.length === 0) variants = [item];
|
|
64
|
+
let published = variants.filter((v) => v.status === "published");
|
|
65
|
+
if (published.length === 0) return [];
|
|
66
|
+
const noindexIds = await findNoindexIds(db, collection, published.map((v) => v.id));
|
|
67
|
+
if (noindexIds.has(item.id)) return [];
|
|
68
|
+
published = published.filter((v) => !noindexIds.has(v.id));
|
|
69
|
+
const urlPattern = (await getCollectionInfoWithDb(db, collection))?.urlPattern ?? null;
|
|
70
|
+
const resolved = [];
|
|
71
|
+
for (const variant of published) {
|
|
72
|
+
const locale = variant.locale || "en";
|
|
73
|
+
const localized = await localizePath(interpolateUrlPattern({
|
|
74
|
+
pattern: urlPattern,
|
|
75
|
+
collection,
|
|
76
|
+
slug: variant.slug || variant.id,
|
|
77
|
+
id: variant.id
|
|
78
|
+
}), locale);
|
|
79
|
+
if (localized === null) continue;
|
|
80
|
+
resolved.push({
|
|
81
|
+
locale,
|
|
82
|
+
href: `${siteUrl}${localized}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (resolved.length === 0) return [];
|
|
86
|
+
resolved.sort((a, b) => a.locale.localeCompare(b.locale));
|
|
87
|
+
const alternates = resolved.map((r) => ({
|
|
88
|
+
hreflang: r.locale,
|
|
89
|
+
href: r.href
|
|
90
|
+
}));
|
|
91
|
+
const defaultLocale = getI18nConfig()?.defaultLocale;
|
|
92
|
+
const xDefault = resolved.find((r) => r.locale === defaultLocale) ?? resolved[0];
|
|
93
|
+
if (xDefault) alternates.push({
|
|
94
|
+
hreflang: "x-default",
|
|
95
|
+
href: xDefault.href
|
|
96
|
+
});
|
|
97
|
+
return alternates;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
export { getHreflangAlternatesWithDb as n, getHreflangAlternates as t };
|
|
102
|
+
//# sourceMappingURL=hreflang-db2Tpb2o.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hreflang-db2Tpb2o.mjs","names":[],"sources":["../src/seo/hreflang.ts"],"sourcesContent":["/**\n * hreflang alternate resolution for translated content (#1690).\n *\n * Mirrors the sitemap route's semantics so the render path and the\n * sitemap always agree:\n *\n * - Alternates are emitted per published locale variant of a\n * translation group, including a self-referencing entry (Google\n * recommends the page annotate itself).\n * - `x-default` points at the default-locale variant, falling back to\n * the first routable variant when the default locale has no\n * translation.\n * - Variants whose locale isn't in the configured `i18n.locales` list\n * are dropped: linking to a route the site can't serve is worse than\n * no link at all.\n * - Variants flagged `noindex` in the SEO panel are excluded — the\n * sitemap doesn't list them, and pointing search engines at a page\n * that asks not to be indexed is a contradictory signal. When the\n * entry itself is noindex the result is empty: a page opting out of\n * indexing shouldn't announce an hreflang set at all.\n * - When i18n is disabled (or no absolute site URL can be resolved,\n * which hreflang requires) the result is empty and no queries run.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport type { Database } from \"../database/types.js\";\nimport { getI18nConfig, isI18nEnabled } from \"../i18n/config.js\";\nimport { interpolateUrlPattern, localizePath } from \"../i18n/resolve.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { getCollectionInfoWithDb } from \"../schema/query.js\";\nimport { getSiteSettingsWithDb } from \"../settings/index.js\";\n\nconst TRAILING_SLASH_RE = /\\/$/;\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i;\n\n/**\n * IDs of variants flagged `noindex` in the SEO panel. Entries without\n * an `_emdash_seo` row are indexable by default (same as the sitemap).\n * The id list is bounded by the number of configured locales, so no\n * chunking is needed.\n */\nasync function findNoindexIds(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tids: string[],\n): Promise<Set<string>> {\n\tif (ids.length === 0) return new Set();\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_seo\")\n\t\t.select(\"content_id\")\n\t\t.where(\"collection\", \"=\", collection)\n\t\t.where(\"content_id\", \"in\", ids)\n\t\t.where(\"seo_no_index\", \"=\", 1)\n\t\t.execute();\n\treturn new Set(rows.map((r) => r.content_id));\n}\n\n/** A single alternate link, ready to render as `<link rel=\"alternate\">`. */\nexport interface HreflangAlternate {\n\t/** Locale code, or `\"x-default\"` for the default variant */\n\threflang: string;\n\t/** Absolute URL of the variant */\n\thref: string;\n}\n\nexport interface HreflangOptions {\n\t/**\n\t * Absolute site origin (e.g. `https://example.com`) used to build\n\t * the alternate URLs. Falls back to the site settings URL; when\n\t * neither is available the result is empty, since hreflang\n\t * requires fully-qualified URLs.\n\t */\n\tsiteUrl?: string;\n}\n\n/**\n * Resolve hreflang alternates for a content entry.\n *\n * @example\n * ```astro\n * ---\n * import { getHreflangAlternates } from \"emdash\";\n *\n * const alternates = await getHreflangAlternates(\"posts\", entry.data.id, {\n * siteUrl: Astro.url.origin,\n * });\n * ---\n * <head>\n * {alternates.map((a) => <link rel=\"alternate\" hreflang={a.hreflang} href={a.href} />)}\n * </head>\n * ```\n */\nexport async function getHreflangAlternates(\n\tcollection: string,\n\tentryId: string,\n\toptions: HreflangOptions = {},\n): Promise<HreflangAlternate[]> {\n\tif (!isI18nEnabled()) return [];\n\tconst key = `hreflang:${collection}:${entryId}:${options.siteUrl ?? \"\"}`;\n\treturn requestCached(key, async () => {\n\t\tconst { getDb } = await import(\"../loader.js\");\n\t\tconst db = await getDb();\n\t\treturn getHreflangAlternatesWithDb(db, collection, entryId, options);\n\t});\n}\n\n/**\n * Resolve hreflang alternates with an explicit db handle.\n *\n * @internal Use `getHreflangAlternates()` in templates. This variant is\n * for routes/components that already have a database handle.\n */\nexport async function getHreflangAlternatesWithDb(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\toptions: HreflangOptions = {},\n): Promise<HreflangAlternate[]> {\n\tif (!isI18nEnabled()) return [];\n\n\tlet siteUrl = options.siteUrl;\n\tif (!siteUrl) {\n\t\tconst settings = await getSiteSettingsWithDb(db);\n\t\tsiteUrl = settings.url;\n\t}\n\t// hreflang requires fully-qualified URLs; a relative site URL would\n\t// produce invalid output (and EmDashHead's isSafeHref would drop it).\n\tif (!siteUrl || !ABSOLUTE_URL_RE.test(siteUrl)) return [];\n\tsiteUrl = siteUrl.replace(TRAILING_SLASH_RE, \"\");\n\n\tconst repo = new ContentRepository(db);\n\tconst item = await repo.findByIdOrSlug(collection, entryId);\n\tif (!item) return [];\n\n\t// Legacy rows imported before i18n may have no translation_group;\n\t// treat them as a single-variant group.\n\tconst group = item.translationGroup || item.id;\n\tlet variants = await repo.findTranslations(collection, group);\n\tif (variants.length === 0) variants = [item];\n\n\tlet published = variants.filter((v) => v.status === \"published\");\n\tif (published.length === 0) return [];\n\n\t// Exclude noindex variants, matching the sitemap's `_emdash_seo`\n\t// filter so head and sitemap agree on which variants are\n\t// discoverable. When the requested entry itself is noindex, emit\n\t// nothing: a page opting out of indexing shouldn't announce an\n\t// hreflang set.\n\tconst noindexIds = await findNoindexIds(\n\t\tdb,\n\t\tcollection,\n\t\tpublished.map((v) => v.id),\n\t);\n\tif (noindexIds.has(item.id)) return [];\n\tpublished = published.filter((v) => !noindexIds.has(v.id));\n\n\tconst info = await getCollectionInfoWithDb(db, collection);\n\tconst urlPattern = info?.urlPattern ?? null;\n\n\tconst resolved: Array<{ locale: string; href: string }> = [];\n\tfor (const variant of published) {\n\t\tconst locale = variant.locale || \"en\";\n\t\tconst path = interpolateUrlPattern({\n\t\t\tpattern: urlPattern,\n\t\t\tcollection,\n\t\t\tslug: variant.slug || variant.id,\n\t\t\tid: variant.id,\n\t\t});\n\t\tconst localized = await localizePath(path, locale);\n\t\tif (localized === null) continue;\n\t\tresolved.push({ locale, href: `${siteUrl}${localized}` });\n\t}\n\tif (resolved.length === 0) return [];\n\n\tresolved.sort((a, b) => a.locale.localeCompare(b.locale));\n\tconst alternates: HreflangAlternate[] = resolved.map((r) => ({\n\t\threflang: r.locale,\n\t\thref: r.href,\n\t}));\n\n\t// x-default: default-locale variant, else the first routable one.\n\t// Same fallback the sitemap route uses.\n\tconst defaultLocale = getI18nConfig()?.defaultLocale;\n\tconst xDefault = resolved.find((r) => r.locale === defaultLocale) ?? resolved[0];\n\tif (xDefault) {\n\t\talternates.push({ hreflang: \"x-default\", href: xDefault.href });\n\t}\n\n\treturn alternates;\n}\n"],"mappings":";;;;;;;;AAkCA,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;;;;;;;AAQxB,eAAe,eACd,IACA,YACA,KACuB;AACvB,KAAI,IAAI,WAAW,EAAG,wBAAO,IAAI,KAAK;CACtC,MAAM,OAAO,MAAM,GACjB,WAAW,cAAc,CACzB,OAAO,aAAa,CACpB,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,cAAc,MAAM,IAAI,CAC9B,MAAM,gBAAgB,KAAK,EAAE,CAC7B,SAAS;AACX,QAAO,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,WAAW,CAAC;;;;;;;;;;;;;;;;;;;AAsC9C,eAAsB,sBACrB,YACA,SACA,UAA2B,EAAE,EACE;AAC/B,KAAI,CAAC,eAAe,CAAE,QAAO,EAAE;AAE/B,QAAO,cADK,YAAY,WAAW,GAAG,QAAQ,GAAG,QAAQ,WAAW,MAC1C,YAAY;EACrC,MAAM,EAAE,UAAU,MAAM,OAAO;AAE/B,SAAO,4BADI,MAAM,OAAO,EACe,YAAY,SAAS,QAAQ;GACnE;;;;;;;;AASH,eAAsB,4BACrB,IACA,YACA,SACA,UAA2B,EAAE,EACE;AAC/B,KAAI,CAAC,eAAe,CAAE,QAAO,EAAE;CAE/B,IAAI,UAAU,QAAQ;AACtB,KAAI,CAAC,QAEJ,YADiB,MAAM,sBAAsB,GAAG,EAC7B;AAIpB,KAAI,CAAC,WAAW,CAAC,gBAAgB,KAAK,QAAQ,CAAE,QAAO,EAAE;AACzD,WAAU,QAAQ,QAAQ,mBAAmB,GAAG;CAEhD,MAAM,OAAO,IAAI,kBAAkB,GAAG;CACtC,MAAM,OAAO,MAAM,KAAK,eAAe,YAAY,QAAQ;AAC3D,KAAI,CAAC,KAAM,QAAO,EAAE;CAIpB,MAAM,QAAQ,KAAK,oBAAoB,KAAK;CAC5C,IAAI,WAAW,MAAM,KAAK,iBAAiB,YAAY,MAAM;AAC7D,KAAI,SAAS,WAAW,EAAG,YAAW,CAAC,KAAK;CAE5C,IAAI,YAAY,SAAS,QAAQ,MAAM,EAAE,WAAW,YAAY;AAChE,KAAI,UAAU,WAAW,EAAG,QAAO,EAAE;CAOrC,MAAM,aAAa,MAAM,eACxB,IACA,YACA,UAAU,KAAK,MAAM,EAAE,GAAG,CAC1B;AACD,KAAI,WAAW,IAAI,KAAK,GAAG,CAAE,QAAO,EAAE;AACtC,aAAY,UAAU,QAAQ,MAAM,CAAC,WAAW,IAAI,EAAE,GAAG,CAAC;CAG1D,MAAM,cADO,MAAM,wBAAwB,IAAI,WAAW,GACjC,cAAc;CAEvC,MAAM,WAAoD,EAAE;AAC5D,MAAK,MAAM,WAAW,WAAW;EAChC,MAAM,SAAS,QAAQ,UAAU;EAOjC,MAAM,YAAY,MAAM,aANX,sBAAsB;GAClC,SAAS;GACT;GACA,MAAM,QAAQ,QAAQ,QAAQ;GAC9B,IAAI,QAAQ;GACZ,CAAC,EACyC,OAAO;AAClD,MAAI,cAAc,KAAM;AACxB,WAAS,KAAK;GAAE;GAAQ,MAAM,GAAG,UAAU;GAAa,CAAC;;AAE1D,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAEpC,UAAS,MAAM,GAAG,MAAM,EAAE,OAAO,cAAc,EAAE,OAAO,CAAC;CACzD,MAAM,aAAkC,SAAS,KAAK,OAAO;EAC5D,UAAU,EAAE;EACZ,MAAM,EAAE;EACR,EAAE;CAIH,MAAM,gBAAgB,eAAe,EAAE;CACvC,MAAM,WAAW,SAAS,MAAM,MAAM,EAAE,WAAW,cAAc,IAAI,SAAS;AAC9E,KAAI,SACH,YAAW,KAAK;EAAE,UAAU;EAAa,MAAM,SAAS;EAAM,CAAC;AAGhE,QAAO"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { s as invalidateCommentObjectCache } from "./object-cache-Bok5j2ae.mjs";
|
|
2
|
-
import { n as slugify } from "./slugify-
|
|
3
|
-
import { t as OptionsRepository } from "./options-
|
|
4
|
-
import { o as invalidateSiteSettingsCache } from "./settings-
|
|
5
|
-
import { a as validateExternalUrl, n as resolveAndValidateExternalUrl, r as ssrfSafeFetch } from "./ssrf-
|
|
6
|
-
import { _ as sanitizeFieldSlug, a as getFilenameFromUrl, c as isInternalMetaKey, d as mapMetaKeyToField, f as mapPostTypeToCollection, h as relativizeContentLinks, i as checkSchemaCompatibility, l as isInternalPostType, m as normalizeUrl$1, n as FEATURED_IMAGE_FIELD, o as guessMimeType, p as mapWpStatus, r as buildAttachmentMap, s as inferMetaType, t as BASE_REQUIRED_FIELDS, u as isPluginBookkeepingMeta } from "./utils-
|
|
2
|
+
import { n as slugify } from "./slugify-C_tqlU4G.mjs";
|
|
3
|
+
import { t as OptionsRepository } from "./options-BlmBHTvX.mjs";
|
|
4
|
+
import { o as invalidateSiteSettingsCache } from "./settings-CpA4lQFt.mjs";
|
|
5
|
+
import { a as validateExternalUrl, n as resolveAndValidateExternalUrl, r as ssrfSafeFetch } from "./ssrf-CviKqWmq.mjs";
|
|
6
|
+
import { _ as sanitizeFieldSlug, a as getFilenameFromUrl, c as isInternalMetaKey, d as mapMetaKeyToField, f as mapPostTypeToCollection, h as relativizeContentLinks, i as checkSchemaCompatibility, l as isInternalPostType, m as normalizeUrl$1, n as FEATURED_IMAGE_FIELD, o as guessMimeType, p as mapWpStatus, r as buildAttachmentMap, s as inferMetaType, t as BASE_REQUIRED_FIELDS, u as isPluginBookkeepingMeta } from "./utils-BbUgmeZG.mjs";
|
|
7
7
|
import { ulid } from "ulidx";
|
|
8
8
|
import sax from "sax";
|
|
9
9
|
import { gutenbergToPortableText } from "@emdash-cms/gutenberg-to-portable-text";
|
|
@@ -2017,4 +2017,4 @@ registerSource(wxrSource);
|
|
|
2017
2017
|
|
|
2018
2018
|
//#endregion
|
|
2019
2019
|
export { parseWxrString as C, parseWxr as S, importSiteSettings as _, fetchPluginOptions as a, importReusableBlocksAsSections as b, parseWxrDate as c, getAllSources as d, getFileSources as f, registerSource as g, probeUrl as h, fetchPluginMenus as i, wxrSource as l, getUrlSources as m, fetchPluginCommentsPage as n, fetchPluginTaxonomies as o, getSource as p, fetchPluginContentPage as r, wordpressRestSource as s, fetchPluginComments as t, clearSources as u, parseSiteSettingsFromPlugin as v, importMenusFromPlugin as x, importCommentsFromPlugin as y };
|
|
2020
|
-
//# sourceMappingURL=import-
|
|
2020
|
+
//# sourceMappingURL=import-Dmkm8S1W.mjs.map
|