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,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import { t as
|
|
7
|
-
import { t as OptionsRepository } from "./options-
|
|
8
|
-
import { t as withTransaction } from "./transaction-
|
|
9
|
-
import { t as enrichImageMetadata } from "./enrich-
|
|
10
|
-
import { t as SeoRepository } from "./seo-
|
|
11
|
-
import {
|
|
12
|
-
import { o as invalidateSiteSettingsCache } from "./settings-
|
|
13
|
-
import { i as stripCredentialHeaders, n as resolveAndValidateExternalUrl, t as SsrfError } from "./ssrf-
|
|
14
|
-
import { t as CronAccessImpl } from "./cron-
|
|
1
|
+
import { l as pluginDataExtractExpr, u as pluginDataOrderExpr } from "./dialect-helpers-HxEQGl_1.mjs";
|
|
2
|
+
import { t as ContentRepository } from "./content-Ci04z2z-.mjs";
|
|
3
|
+
import { a as encodeCursor, i as decodeCursor } from "./types-D1iJ3DpO.mjs";
|
|
4
|
+
import { t as MediaRepository } from "./media-BjhhENaJ.mjs";
|
|
5
|
+
import { t as UserRepository } from "./user-Bh-L1qo6.mjs";
|
|
6
|
+
import { t as TaxonomyRepository } from "./taxonomy-DfVooU4W.mjs";
|
|
7
|
+
import { t as OptionsRepository } from "./options-BlmBHTvX.mjs";
|
|
8
|
+
import { t as withTransaction } from "./transaction-D0FOsb3X.mjs";
|
|
9
|
+
import { t as enrichImageMetadata } from "./enrich-CFJJgxs_.mjs";
|
|
10
|
+
import { t as SeoRepository } from "./seo-2cmFHZns.mjs";
|
|
11
|
+
import { s as markContentMediaUsageCollectionStaleSafely } from "./content-refresh-D4khvC0R.mjs";
|
|
12
|
+
import { o as invalidateSiteSettingsCache } from "./settings-CpA4lQFt.mjs";
|
|
13
|
+
import { i as stripCredentialHeaders, n as resolveAndValidateExternalUrl, t as SsrfError } from "./ssrf-CviKqWmq.mjs";
|
|
14
|
+
import { t as CronAccessImpl } from "./cron-BlKIMD_e.mjs";
|
|
15
15
|
import { sql } from "kysely";
|
|
16
16
|
import { ulid } from "ulidx";
|
|
17
17
|
|
|
@@ -49,6 +49,14 @@ function isStartsWithFilter(value) {
|
|
|
49
49
|
return "startsWith" in value && typeof value.startsWith === "string";
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
+
* Escape LIKE pattern metacharacters so a startsWith prefix matches
|
|
53
|
+
* literally. Without this, `%` and `_` in the prefix act as wildcards
|
|
54
|
+
* (e.g. `{ startsWith: "50%" }` would match "50x off").
|
|
55
|
+
*/
|
|
56
|
+
function escapeLikePattern(value) {
|
|
57
|
+
return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
52
60
|
* Get the set of indexed fields from index declarations
|
|
53
61
|
*/
|
|
54
62
|
function getIndexedFields(indexes) {
|
|
@@ -70,59 +78,72 @@ function validateOrderByClause(orderBy, indexedFields, pluginId, collection) {
|
|
|
70
78
|
for (const field of Object.keys(orderBy)) if (!indexedFields.has(field)) throw new StorageQueryError(`Cannot order by non-indexed field '${field}'.`, field, `Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable ordering by this field.`);
|
|
71
79
|
}
|
|
72
80
|
/**
|
|
73
|
-
* SQL expression for extracting
|
|
81
|
+
* SQL expression for extracting a queryable field from the `_plugin_storage.data`
|
|
82
|
+
* column.
|
|
74
83
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
84
|
+
* Delegates to `pluginDataExtractExpr`, which validates the field name before
|
|
85
|
+
* interpolation and applies the dialect-correct extraction: a `::jsonb` cast on
|
|
86
|
+
* Postgres (the `data` column is `text`) plus an optional type-guarded
|
|
87
|
+
* `::numeric` cast so numeric comparisons don't fall back to lexical text
|
|
88
|
+
* ordering.
|
|
77
89
|
*/
|
|
78
|
-
function jsonExtract(db, field) {
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
function jsonExtract(db, field, options) {
|
|
91
|
+
return pluginDataExtractExpr(db, field, options);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* SQL expression for ordering by a `_plugin_storage.data` field.
|
|
95
|
+
*
|
|
96
|
+
* Delegates to `pluginDataOrderExpr`, which orders over the jsonb-native value
|
|
97
|
+
* on Postgres so numeric fields sort numerically (not lexically) while staying
|
|
98
|
+
* total across heterogeneous data. SQLite keeps `json_extract` (already numeric).
|
|
99
|
+
*/
|
|
100
|
+
function jsonOrderExtract(db, field) {
|
|
101
|
+
return pluginDataOrderExpr(db, field);
|
|
81
102
|
}
|
|
82
103
|
/**
|
|
83
104
|
* Build a WHERE clause condition for a single field
|
|
84
105
|
*/
|
|
85
106
|
function buildCondition(db, field, value) {
|
|
86
|
-
const
|
|
107
|
+
const extractFor = (numeric) => jsonExtract(db, field, { numeric });
|
|
87
108
|
if (value === null) return {
|
|
88
|
-
sql: `${
|
|
109
|
+
sql: `${extractFor(false)} IS NULL`,
|
|
89
110
|
params: []
|
|
90
111
|
};
|
|
91
|
-
if (typeof value === "
|
|
92
|
-
sql: `${
|
|
112
|
+
if (typeof value === "number") return {
|
|
113
|
+
sql: `${extractFor(true)} = ?`,
|
|
93
114
|
params: [value]
|
|
94
115
|
};
|
|
95
|
-
if (typeof value === "
|
|
96
|
-
sql: `${
|
|
116
|
+
if (typeof value === "string") return {
|
|
117
|
+
sql: `${extractFor(false)} = ?`,
|
|
97
118
|
params: [value]
|
|
98
119
|
};
|
|
99
|
-
if (
|
|
100
|
-
sql: `${
|
|
101
|
-
params: value
|
|
120
|
+
if (typeof value === "boolean") return {
|
|
121
|
+
sql: `${extractFor(false)} = ?`,
|
|
122
|
+
params: [value]
|
|
102
123
|
};
|
|
124
|
+
if (isInFilter(value)) {
|
|
125
|
+
const numeric = value.in.length > 0 && value.in.every((v) => typeof v === "number");
|
|
126
|
+
const placeholders = value.in.map(() => "?").join(", ");
|
|
127
|
+
return {
|
|
128
|
+
sql: `${extractFor(numeric)} IN (${placeholders})`,
|
|
129
|
+
params: value.in
|
|
130
|
+
};
|
|
131
|
+
}
|
|
103
132
|
if (isStartsWithFilter(value)) return {
|
|
104
|
-
sql: `${
|
|
105
|
-
params: [`${value.startsWith}%`]
|
|
133
|
+
sql: `${extractFor(false)} LIKE ? ESCAPE '\\'`,
|
|
134
|
+
params: [`${escapeLikePattern(value.startsWith)}%`]
|
|
106
135
|
};
|
|
107
136
|
if (isRangeFilter(value)) {
|
|
108
137
|
const conditions = [];
|
|
109
138
|
const params = [];
|
|
110
|
-
|
|
111
|
-
conditions.push(`${
|
|
112
|
-
params.push(
|
|
113
|
-
}
|
|
114
|
-
if (value.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (value.lt !== void 0) {
|
|
119
|
-
conditions.push(`${extract} < ?`);
|
|
120
|
-
params.push(value.lt);
|
|
121
|
-
}
|
|
122
|
-
if (value.lte !== void 0) {
|
|
123
|
-
conditions.push(`${extract} <= ?`);
|
|
124
|
-
params.push(value.lte);
|
|
125
|
-
}
|
|
139
|
+
const pushBound = (op, bound) => {
|
|
140
|
+
conditions.push(`${extractFor(typeof bound === "number")} ${op} ?`);
|
|
141
|
+
params.push(bound);
|
|
142
|
+
};
|
|
143
|
+
if (value.gt !== void 0) pushBound(">", value.gt);
|
|
144
|
+
if (value.gte !== void 0) pushBound(">=", value.gte);
|
|
145
|
+
if (value.lt !== void 0) pushBound("<", value.lt);
|
|
146
|
+
if (value.lte !== void 0) pushBound("<=", value.lte);
|
|
126
147
|
return {
|
|
127
148
|
sql: conditions.join(" AND "),
|
|
128
149
|
params
|
|
@@ -154,6 +175,21 @@ function buildWhereClause(db, where) {
|
|
|
154
175
|
//#endregion
|
|
155
176
|
//#region src/database/repositories/plugin-storage.ts
|
|
156
177
|
/**
|
|
178
|
+
* Interleave a `?`-placeholder SQL string with its params into a single
|
|
179
|
+
* boolean raw expression. Used as a WHERE predicate directly — wrapping it
|
|
180
|
+
* in `(...) = 1` breaks on Postgres, which has a strict boolean type (#920).
|
|
181
|
+
*/
|
|
182
|
+
function rawWhereExpr(sqlText, params) {
|
|
183
|
+
const parts = [];
|
|
184
|
+
let paramIndex = 0;
|
|
185
|
+
const sqlParts = sqlText.split("?");
|
|
186
|
+
for (let i = 0; i < sqlParts.length; i++) {
|
|
187
|
+
if (i > 0) parts.push(sql`${params[paramIndex++]}`);
|
|
188
|
+
if (sqlParts[i]) parts.push(sql.raw(sqlParts[i]));
|
|
189
|
+
}
|
|
190
|
+
return sql`(${sql.join(parts, sql.raw(""))})`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
157
193
|
* Plugin Storage Repository
|
|
158
194
|
*
|
|
159
195
|
* Implements the StorageCollection interface for a specific plugin and collection.
|
|
@@ -267,22 +303,13 @@ var PluginStorageRepository = class {
|
|
|
267
303
|
"created_at"
|
|
268
304
|
]).where("plugin_id", "=", this.pluginId).where("collection", "=", this.collection);
|
|
269
305
|
const whereResult = buildWhereClause(this.db, where);
|
|
270
|
-
if (whereResult.sql)
|
|
271
|
-
const whereSqlParts = [];
|
|
272
|
-
let paramIndex = 0;
|
|
273
|
-
const sqlParts = whereResult.sql.split("?");
|
|
274
|
-
for (let i = 0; i < sqlParts.length; i++) {
|
|
275
|
-
if (i > 0) whereSqlParts.push(sql`${whereResult.params[paramIndex++]}`);
|
|
276
|
-
if (sqlParts[i]) whereSqlParts.push(sql.raw(sqlParts[i]));
|
|
277
|
-
}
|
|
278
|
-
query = query.where(({ eb }) => eb(sql.join(whereSqlParts, sql.raw("")), "=", sql.raw("1")));
|
|
279
|
-
}
|
|
306
|
+
if (whereResult.sql) query = query.where(rawWhereExpr(whereResult.sql, whereResult.params));
|
|
280
307
|
if (cursor) {
|
|
281
308
|
const decoded = decodeCursor(cursor);
|
|
282
309
|
query = query.where(({ eb }) => eb(sql`(created_at, id)`, ">", sql`(${decoded.orderValue}, ${decoded.id})`));
|
|
283
310
|
}
|
|
284
311
|
if (Object.keys(orderBy).length > 0) for (const [field, direction] of Object.entries(orderBy)) {
|
|
285
|
-
const extract =
|
|
312
|
+
const extract = jsonOrderExtract(this.db, field);
|
|
286
313
|
const orderExpr = direction === "desc" ? sql`${sql.raw(extract)} desc` : sql`${sql.raw(extract)} asc`;
|
|
287
314
|
query = query.orderBy(orderExpr);
|
|
288
315
|
}
|
|
@@ -313,18 +340,10 @@ var PluginStorageRepository = class {
|
|
|
313
340
|
let query = this.db.selectFrom("_plugin_storage").select(sql`COUNT(*)`.as("count")).where("plugin_id", "=", this.pluginId).where("collection", "=", this.collection);
|
|
314
341
|
if (where && Object.keys(where).length > 0) {
|
|
315
342
|
const whereResult = buildWhereClause(this.db, where);
|
|
316
|
-
if (whereResult.sql)
|
|
317
|
-
const whereSqlParts = [];
|
|
318
|
-
let paramIndex = 0;
|
|
319
|
-
const sqlParts = whereResult.sql.split("?");
|
|
320
|
-
for (let i = 0; i < sqlParts.length; i++) {
|
|
321
|
-
if (i > 0) whereSqlParts.push(sql`${whereResult.params[paramIndex++]}`);
|
|
322
|
-
if (sqlParts[i]) whereSqlParts.push(sql.raw(sqlParts[i]));
|
|
323
|
-
}
|
|
324
|
-
query = query.where(({ eb }) => eb(sql.join(whereSqlParts, sql.raw("")), "=", sql.raw("1")));
|
|
325
|
-
}
|
|
343
|
+
if (whereResult.sql) query = query.where(rawWhereExpr(whereResult.sql, whereResult.params));
|
|
326
344
|
}
|
|
327
|
-
|
|
345
|
+
const result = await query.executeTakeFirst();
|
|
346
|
+
return Number(result?.count ?? 0);
|
|
328
347
|
}
|
|
329
348
|
};
|
|
330
349
|
|
|
@@ -418,6 +437,33 @@ async function assertSeoEnabled(seoRepo, collection, seo) {
|
|
|
418
437
|
return hasSeo;
|
|
419
438
|
}
|
|
420
439
|
/**
|
|
440
|
+
* Parse the `collections` JSON column into a string array (`[]` on anything
|
|
441
|
+
* else). Mirrors the guards in the Cloudflare/workerd bridges so an
|
|
442
|
+
* in-process plugin degrades on malformed data instead of crashing.
|
|
443
|
+
*/
|
|
444
|
+
function parseCollectionsColumn(value) {
|
|
445
|
+
if (!value) return [];
|
|
446
|
+
try {
|
|
447
|
+
const parsed = JSON.parse(value);
|
|
448
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
449
|
+
} catch {
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/** Map a repository `Taxonomy` row to the plugin-facing term shape. */
|
|
454
|
+
function taxonomyToTermInfo(term) {
|
|
455
|
+
return {
|
|
456
|
+
id: term.id,
|
|
457
|
+
taxonomy: term.name,
|
|
458
|
+
slug: term.slug,
|
|
459
|
+
label: term.label,
|
|
460
|
+
parentId: term.parentId,
|
|
461
|
+
data: term.data,
|
|
462
|
+
locale: term.locale,
|
|
463
|
+
translationGroup: term.translationGroup
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
421
467
|
* Create read-only content access
|
|
422
468
|
*/
|
|
423
469
|
function createContentAccess(db) {
|
|
@@ -436,7 +482,8 @@ function createContentAccess(db) {
|
|
|
436
482
|
createdAt: item.createdAt,
|
|
437
483
|
updatedAt: item.updatedAt,
|
|
438
484
|
locale: item.locale,
|
|
439
|
-
publishedAt: item.publishedAt
|
|
485
|
+
publishedAt: item.publishedAt,
|
|
486
|
+
scheduledAt: item.scheduledAt
|
|
440
487
|
};
|
|
441
488
|
if (await seoRepo.isEnabled(collection)) result.seo = await seoRepo.get(collection, item.id);
|
|
442
489
|
return result;
|
|
@@ -465,7 +512,8 @@ function createContentAccess(db) {
|
|
|
465
512
|
createdAt: item.createdAt,
|
|
466
513
|
updatedAt: item.updatedAt,
|
|
467
514
|
locale: item.locale,
|
|
468
|
-
publishedAt: item.publishedAt
|
|
515
|
+
publishedAt: item.publishedAt,
|
|
516
|
+
scheduledAt: item.scheduledAt
|
|
469
517
|
}));
|
|
470
518
|
if (items.length > 0 && await seoRepo.isEnabled(collection)) {
|
|
471
519
|
const seoMap = await seoRepo.getMany(collection, items.map((i) => i.id));
|
|
@@ -483,6 +531,32 @@ function createContentAccess(db) {
|
|
|
483
531
|
};
|
|
484
532
|
}
|
|
485
533
|
/**
|
|
534
|
+
* Create read-only taxonomy access (gated on `taxonomies:read`).
|
|
535
|
+
*/
|
|
536
|
+
function createTaxonomyAccess(db) {
|
|
537
|
+
const taxonomyRepo = new TaxonomyRepository(db);
|
|
538
|
+
return {
|
|
539
|
+
async getAll(options) {
|
|
540
|
+
let query = db.selectFrom("_emdash_taxonomy_defs").selectAll();
|
|
541
|
+
if (options?.locale !== void 0) query = query.where("locale", "=", options.locale);
|
|
542
|
+
return (await query.orderBy("name", "asc").execute()).map((row) => ({
|
|
543
|
+
name: row.name,
|
|
544
|
+
label: row.label,
|
|
545
|
+
labelSingular: row.label_singular,
|
|
546
|
+
hierarchical: row.hierarchical === 1,
|
|
547
|
+
collections: parseCollectionsColumn(row.collections),
|
|
548
|
+
locale: row.locale
|
|
549
|
+
}));
|
|
550
|
+
},
|
|
551
|
+
async getTerms(taxonomy, options) {
|
|
552
|
+
return (await taxonomyRepo.findByName(taxonomy, { locale: options?.locale })).map(taxonomyToTermInfo);
|
|
553
|
+
},
|
|
554
|
+
async getEntryTerms(collection, entryId, options) {
|
|
555
|
+
return (await taxonomyRepo.getTermsForEntry(collection, entryId, options?.taxonomy, options?.locale)).map(taxonomyToTermInfo);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
486
560
|
* Create full content access with write operations.
|
|
487
561
|
*
|
|
488
562
|
* `create` and `update` accept a reserved `seo` key in their `data`
|
|
@@ -516,7 +590,8 @@ function createContentAccessWithWrite(db) {
|
|
|
516
590
|
createdAt: item.createdAt,
|
|
517
591
|
updatedAt: item.updatedAt,
|
|
518
592
|
locale: item.locale,
|
|
519
|
-
publishedAt: item.publishedAt
|
|
593
|
+
publishedAt: item.publishedAt,
|
|
594
|
+
scheduledAt: item.scheduledAt
|
|
520
595
|
};
|
|
521
596
|
if (hasSeo) result.seo = seo !== void 0 ? await trxSeoRepo.upsert(collection, item.id, seo) : await trxSeoRepo.get(collection, item.id);
|
|
522
597
|
return result;
|
|
@@ -552,7 +627,8 @@ function createContentAccessWithWrite(db) {
|
|
|
552
627
|
createdAt: item.createdAt,
|
|
553
628
|
updatedAt: item.updatedAt,
|
|
554
629
|
locale: item.locale,
|
|
555
|
-
publishedAt: item.publishedAt
|
|
630
|
+
publishedAt: item.publishedAt,
|
|
631
|
+
scheduledAt: item.scheduledAt
|
|
556
632
|
};
|
|
557
633
|
if (hasSeo) result.seo = seo !== void 0 ? await trxSeoRepo.upsert(collection, item.id, seo) : await trxSeoRepo.get(collection, item.id);
|
|
558
634
|
return result;
|
|
@@ -800,7 +876,8 @@ function createSiteInfo(options) {
|
|
|
800
876
|
return {
|
|
801
877
|
name: options.siteName ?? "",
|
|
802
878
|
url: (options.siteUrl ?? "").replace(TRAILING_SLASH_RE, ""),
|
|
803
|
-
locale: options.locale ?? "en"
|
|
879
|
+
locale: options.locale ?? "en",
|
|
880
|
+
trailingSlash: options.trailingSlash ?? "ignore"
|
|
804
881
|
};
|
|
805
882
|
}
|
|
806
883
|
/**
|
|
@@ -897,6 +974,8 @@ var PluginContextFactory = class {
|
|
|
897
974
|
let content;
|
|
898
975
|
if (capabilities.has("content:write")) content = createContentAccessWithWrite(db);
|
|
899
976
|
else if (capabilities.has("content:read")) content = createContentAccess(db);
|
|
977
|
+
let taxonomies;
|
|
978
|
+
if (capabilities.has("taxonomies:read")) taxonomies = createTaxonomyAccess(db);
|
|
900
979
|
let media;
|
|
901
980
|
if (capabilities.has("media:write")) if (this.getUploadUrl || this.storage) media = createMediaAccessWithWrite(db, this.getUploadUrl, this.storage);
|
|
902
981
|
else {
|
|
@@ -928,6 +1007,7 @@ var PluginContextFactory = class {
|
|
|
928
1007
|
storage,
|
|
929
1008
|
kv,
|
|
930
1009
|
content,
|
|
1010
|
+
taxonomies,
|
|
931
1011
|
media,
|
|
932
1012
|
http,
|
|
933
1013
|
log,
|
|
@@ -941,5 +1021,5 @@ var PluginContextFactory = class {
|
|
|
941
1021
|
};
|
|
942
1022
|
|
|
943
1023
|
//#endregion
|
|
944
|
-
export {
|
|
945
|
-
//# sourceMappingURL=context-
|
|
1024
|
+
export { createUnrestrictedHttpAccess as a, createStorageAccess as i, createHttpAccess as n, PluginStorageRepository as o, createSiteInfo as r, PluginContextFactory as t };
|
|
1025
|
+
//# sourceMappingURL=context-B6hc7zJL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-B6hc7zJL.mjs","names":[],"sources":["../src/plugins/storage-query.ts","../src/database/repositories/plugin-storage.ts","../src/plugins/context.ts"],"sourcesContent":["/**\n * Plugin Storage Query Validation and Building\n *\n * Validates that queries only use indexed fields and builds SQL WHERE clauses.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage > Query Validation\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { pluginDataExtractExpr, pluginDataOrderExpr } from \"../database/dialect-helpers.js\";\nimport type { WhereClause, WhereValue, RangeFilter, InFilter, StartsWithFilter } from \"./types.js\";\n\n/**\n * Error thrown when querying non-indexed fields\n */\nexport class StorageQueryError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic field?: string,\n\t\tpublic suggestion?: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"StorageQueryError\";\n\t}\n}\n\n/**\n * Check if a value is a range filter\n */\nexport function isRangeFilter(value: WhereValue): value is RangeFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"gt\" in value || \"gte\" in value || \"lt\" in value || \"lte\" in value;\n}\n\n/**\n * Check if a value is an IN filter\n */\nexport function isInFilter(value: WhereValue): value is InFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"in\" in value && Array.isArray(value.in);\n}\n\n/**\n * Check if a value is a startsWith filter\n */\nexport function isStartsWithFilter(value: WhereValue): value is StartsWithFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"startsWith\" in value && typeof value.startsWith === \"string\";\n}\n\n/**\n * Escape LIKE pattern metacharacters so a startsWith prefix matches\n * literally. Without this, `%` and `_` in the prefix act as wildcards\n * (e.g. `{ startsWith: \"50%\" }` would match \"50x off\").\n */\nexport function escapeLikePattern(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n}\n\n/**\n * Get the set of indexed fields from index declarations\n */\nexport function getIndexedFields(indexes: Array<string | string[]>): Set<string> {\n\tconst fields = new Set<string>();\n\tfor (const index of indexes) {\n\t\tif (Array.isArray(index)) {\n\t\t\tfor (const field of index) {\n\t\t\t\tfields.add(field);\n\t\t\t}\n\t\t} else {\n\t\t\tfields.add(index);\n\t\t}\n\t}\n\treturn fields;\n}\n\n/**\n * Validate that all fields in a where clause are indexed\n */\nexport function validateWhereClause(\n\twhere: WhereClause,\n\tindexedFields: Set<string>,\n\tpluginId: string,\n\tcollection: string,\n): void {\n\tfor (const field of Object.keys(where)) {\n\t\tif (!indexedFields.has(field)) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Cannot query on non-indexed field '${field}'.`,\n\t\t\t\tfield,\n\t\t\t\t`Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable this query.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Validate orderBy fields are indexed\n */\nexport function validateOrderByClause(\n\torderBy: Record<string, \"asc\" | \"desc\">,\n\tindexedFields: Set<string>,\n\tpluginId: string,\n\tcollection: string,\n): void {\n\tfor (const field of Object.keys(orderBy)) {\n\t\tif (!indexedFields.has(field)) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Cannot order by non-indexed field '${field}'.`,\n\t\t\t\tfield,\n\t\t\t\t`Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable ordering by this field.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * SQL expression for extracting a queryable field from the `_plugin_storage.data`\n * column.\n *\n * Delegates to `pluginDataExtractExpr`, which validates the field name before\n * interpolation and applies the dialect-correct extraction: a `::jsonb` cast on\n * Postgres (the `data` column is `text`) plus an optional type-guarded\n * `::numeric` cast so numeric comparisons don't fall back to lexical text\n * ordering.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function jsonExtract(\n\tdb: Kysely<any>,\n\tfield: string,\n\toptions?: { numeric?: boolean },\n): string {\n\treturn pluginDataExtractExpr(db, field, options);\n}\n\n/**\n * SQL expression for ordering by a `_plugin_storage.data` field.\n *\n * Delegates to `pluginDataOrderExpr`, which orders over the jsonb-native value\n * on Postgres so numeric fields sort numerically (not lexically) while staying\n * total across heterogeneous data. SQLite keeps `json_extract` (already numeric).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function jsonOrderExtract(db: Kysely<any>, field: string): string {\n\treturn pluginDataOrderExpr(db, field);\n}\n\n/**\n * Build a WHERE clause condition for a single field\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildCondition(\n\tdb: Kysely<any>,\n\tfield: string,\n\tvalue: WhereValue,\n): { sql: string; params: unknown[] } {\n\t// Numeric-vs-text is decided per condition from the JS type of the bound\n\t// value. On Postgres a text extract compared to a bound number would sort\n\t// lexically (`'9' >= '10'` is TRUE); a type-guarded `::numeric` cast on the\n\t// extract fixes it. String/boolean operands keep text comparison. SQLite is\n\t// unaffected — `json_extract` already returns a typed value.\n\tconst extractFor = (numeric: boolean): string => jsonExtract(db, field, { numeric });\n\n\tif (value === null) {\n\t\treturn { sql: `${extractFor(false)} IS NULL`, params: [] };\n\t}\n\n\tif (typeof value === \"number\") {\n\t\treturn { sql: `${extractFor(true)} = ?`, params: [value] };\n\t}\n\n\tif (typeof value === \"string\") {\n\t\treturn { sql: `${extractFor(false)} = ?`, params: [value] };\n\t}\n\n\tif (typeof value === \"boolean\") {\n\t\t// JSON booleans are stored as true/false strings\n\t\treturn { sql: `${extractFor(false)} = ?`, params: [value] };\n\t}\n\n\tif (isInFilter(value)) {\n\t\tconst numeric = value.in.length > 0 && value.in.every((v) => typeof v === \"number\");\n\t\tconst placeholders = value.in.map(() => \"?\").join(\", \");\n\t\treturn {\n\t\t\tsql: `${extractFor(numeric)} IN (${placeholders})`,\n\t\t\tparams: value.in,\n\t\t};\n\t}\n\n\tif (isStartsWithFilter(value)) {\n\t\t// ESCAPE '\\' works on both SQLite and PostgreSQL. startsWith is a string\n\t\t// operation, so always compare as text.\n\t\treturn {\n\t\t\tsql: `${extractFor(false)} LIKE ? ESCAPE '\\\\'`,\n\t\t\tparams: [`${escapeLikePattern(value.startsWith)}%`],\n\t\t};\n\t}\n\n\tif (isRangeFilter(value)) {\n\t\tconst conditions: string[] = [];\n\t\tconst params: unknown[] = [];\n\n\t\t// Each bound is cast to numeric only when its own operand is a number, so\n\t\t// a mixed range (e.g. a string lower bound) stays correct per side.\n\t\tconst pushBound = (op: string, bound: string | number): void => {\n\t\t\tconditions.push(`${extractFor(typeof bound === \"number\")} ${op} ?`);\n\t\t\tparams.push(bound);\n\t\t};\n\n\t\tif (value.gt !== undefined) pushBound(\">\", value.gt);\n\t\tif (value.gte !== undefined) pushBound(\">=\", value.gte);\n\t\tif (value.lt !== undefined) pushBound(\"<\", value.lt);\n\t\tif (value.lte !== undefined) pushBound(\"<=\", value.lte);\n\n\t\treturn {\n\t\t\tsql: conditions.join(\" AND \"),\n\t\t\tparams,\n\t\t};\n\t}\n\n\tthrow new StorageQueryError(`Unknown filter type for field '${field}'`);\n}\n\n/**\n * Build a complete WHERE clause from a WhereClause object\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildWhereClause(\n\tdb: Kysely<any>,\n\twhere: WhereClause,\n): {\n\tsql: string;\n\tparams: unknown[];\n} {\n\tconst conditions: string[] = [];\n\tconst params: unknown[] = [];\n\n\tfor (const [field, value] of Object.entries(where)) {\n\t\tconst condition = buildCondition(db, field, value);\n\t\tconditions.push(condition.sql);\n\t\tparams.push(...condition.params);\n\t}\n\n\tif (conditions.length === 0) {\n\t\treturn { sql: \"\", params: [] };\n\t}\n\n\treturn {\n\t\tsql: conditions.join(\" AND \"),\n\t\tparams,\n\t};\n}\n\n/**\n * Build ORDER BY clause\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildOrderByClause(\n\tdb: Kysely<any>,\n\torderBy: Record<string, \"asc\" | \"desc\">,\n): string {\n\tconst clauses: string[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\tclauses.push(`${jsonOrderExtract(db, field)} ${direction.toUpperCase()}`);\n\t}\n\n\tif (clauses.length === 0) {\n\t\treturn \"\";\n\t}\n\n\treturn `ORDER BY ${clauses.join(\", \")}`;\n}\n","/**\n * Plugin Storage Repository\n *\n * Provides a document store API for plugin data storage.\n * Uses a single _plugin_storage table with JSON documents and expression indexes.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage > Full API Reference\n */\n\nimport type { Kysely, RawBuilder } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport {\n\tbuildWhereClause,\n\tvalidateWhereClause,\n\tvalidateOrderByClause,\n\tgetIndexedFields,\n\tjsonOrderExtract,\n} from \"../../plugins/storage-query.js\";\nimport type {\n\tStorageCollection,\n\tQueryOptions,\n\tPaginatedResult,\n\tWhereClause,\n} from \"../../plugins/types.js\";\nimport { withTransaction } from \"../transaction.js\";\nimport type { Database } from \"../types.js\";\nimport { encodeCursor, decodeCursor } from \"./types.js\";\n\n/**\n * Interleave a `?`-placeholder SQL string with its params into a single\n * boolean raw expression. Used as a WHERE predicate directly — wrapping it\n * in `(...) = 1` breaks on Postgres, which has a strict boolean type (#920).\n */\nfunction rawWhereExpr(sqlText: string, params: unknown[]): RawBuilder<boolean> {\n\tconst parts: ReturnType<typeof sql>[] = [];\n\tlet paramIndex = 0;\n\tconst sqlParts = sqlText.split(\"?\");\n\tfor (let i = 0; i < sqlParts.length; i++) {\n\t\tif (i > 0) {\n\t\t\tparts.push(sql`${params[paramIndex++]}`);\n\t\t}\n\t\tif (sqlParts[i]) {\n\t\t\tparts.push(sql.raw(sqlParts[i]));\n\t\t}\n\t}\n\treturn sql<boolean>`(${sql.join(parts, sql.raw(\"\"))})`;\n}\n\n/**\n * Plugin Storage Repository\n *\n * Implements the StorageCollection interface for a specific plugin and collection.\n */\nexport class PluginStorageRepository<T = unknown> implements StorageCollection<T> {\n\tprivate indexedFields: Set<string>;\n\n\tconstructor(\n\t\tprivate db: Kysely<Database>,\n\t\tprivate pluginId: string,\n\t\tprivate collection: string,\n\t\tindexes: Array<string | string[]>,\n\t) {\n\t\tthis.indexedFields = getIndexedFields(indexes);\n\t}\n\n\t/**\n\t * Get a document by ID\n\t */\n\tasync get(id: string): Promise<T | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(\"data\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\treturn JSON.parse(row.data) as T;\n\t}\n\n\t/**\n\t * Store a document\n\t */\n\tasync put(id: string, data: T): Promise<void> {\n\t\tconst now = new Date().toISOString();\n\t\tconst jsonData = JSON.stringify(data);\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t.values({\n\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\tcollection: this.collection,\n\t\t\t\tid,\n\t\t\t\tdata: jsonData,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.onConflict((oc) =>\n\t\t\t\toc.columns([\"plugin_id\", \"collection\", \"id\"]).doUpdateSet({\n\t\t\t\t\tdata: jsonData,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Delete a document\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Check if a document exists\n\t */\n\tasync exists(id: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Get multiple documents by ID\n\t */\n\tasync getMany(ids: string[]): Promise<Map<string, T>> {\n\t\tif (ids.length === 0) return new Map();\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"id\", \"data\"])\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.id, JSON.parse(row.data) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Store multiple documents\n\t */\n\tasync putMany(items: Array<{ id: string; data: T }>): Promise<void> {\n\t\tif (items.length === 0) return;\n\n\t\tconst now = new Date().toISOString();\n\n\t\t// SQLite doesn't support batch upserts well, so we do them one at a time\n\t\t// In a transaction for atomicity\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (const item of items) {\n\t\t\t\tconst jsonData = JSON.stringify(item.data);\n\t\t\t\tawait trx\n\t\t\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\t\t\tcollection: this.collection,\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\tcreated_at: now,\n\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t})\n\t\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\t\toc.columns([\"plugin_id\", \"collection\", \"id\"]).doUpdateSet({\n\t\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Delete multiple documents\n\t */\n\tasync deleteMany(ids: string[]): Promise<number> {\n\t\tif (ids.length === 0) return 0;\n\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Query documents with filters\n\t */\n\tasync query(options: QueryOptions = {}): Promise<PaginatedResult<{ id: string; data: T }>> {\n\t\tconst { where = {}, orderBy = {}, cursor } = options;\n\t\tconst limit = Math.min(options.limit ?? 50, 100);\n\n\t\t// Validate that all queried fields are indexed\n\t\tvalidateWhereClause(where, this.indexedFields, this.pluginId, this.collection);\n\t\tif (Object.keys(orderBy).length > 0) {\n\t\t\tvalidateOrderByClause(orderBy, this.indexedFields, this.pluginId, this.collection);\n\t\t}\n\n\t\t// Build base query\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"id\", \"data\", \"created_at\"])\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection);\n\n\t\t// Add JSON extraction WHERE conditions\n\t\tconst whereResult = buildWhereClause(this.db, where);\n\t\tif (whereResult.sql) {\n\t\t\tquery = query.where(rawWhereExpr(whereResult.sql, whereResult.params));\n\t\t}\n\n\t\t// Handle cursor-based pagination — throws on invalid cursor.\n\t\tif (cursor) {\n\t\t\tconst decoded = decodeCursor(cursor);\n\t\t\tquery = query.where(({ eb }) =>\n\t\t\t\teb(sql`(created_at, id)`, \">\", sql`(${decoded.orderValue}, ${decoded.id})`),\n\t\t\t);\n\t\t}\n\n\t\t// Build ORDER BY using sql template\n\t\tif (Object.keys(orderBy).length > 0) {\n\t\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\t\t// Order over the jsonb-native value on Postgres so numeric fields sort\n\t\t\t\t// numerically, not lexically. See pluginDataOrderExpr.\n\t\t\t\tconst extract = jsonOrderExtract(this.db, field);\n\t\t\t\tconst orderExpr =\n\t\t\t\t\tdirection === \"desc\" ? sql`${sql.raw(extract)} desc` : sql`${sql.raw(extract)} asc`;\n\t\t\t\tquery = query.orderBy(orderExpr);\n\t\t\t}\n\t\t} else {\n\t\t\t// Default ordering for consistent pagination\n\t\t\tquery = query.orderBy(\"created_at\", \"asc\").orderBy(\"id\", \"asc\");\n\t\t}\n\n\t\t// Apply limit (fetch one extra to detect if there's more)\n\t\tquery = query.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit).map((row) => ({\n\t\t\tid: row.id,\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tdata: JSON.parse(row.data) as T,\n\t\t}));\n\n\t\t// Generate cursor for next page if there are more results\n\t\tlet nextCursor: string | undefined;\n\t\tif (hasMore) {\n\t\t\tconst lastItem = rows[limit - 1];\n\t\t\tif (lastItem) {\n\t\t\t\tnextCursor = encodeCursor(lastItem.created_at, lastItem.id);\n\t\t\t}\n\t\t}\n\n\t\treturn { items, cursor: nextCursor, hasMore };\n\t}\n\n\t/**\n\t * Count documents matching a filter\n\t */\n\tasync count(where?: WhereClause): Promise<number> {\n\t\tif (where && Object.keys(where).length > 0) {\n\t\t\tvalidateWhereClause(where, this.indexedFields, this.pluginId, this.collection);\n\t\t}\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(sql<number>`COUNT(*)`.as(\"count\"))\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection);\n\n\t\t// Add JSON extraction WHERE conditions\n\t\tif (where && Object.keys(where).length > 0) {\n\t\t\tconst whereResult = buildWhereClause(this.db, where);\n\t\t\tif (whereResult.sql) {\n\t\t\t\tquery = query.where(rawWhereExpr(whereResult.sql, whereResult.params));\n\t\t\t}\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\t// Number() because the pg driver returns COUNT(*) (bigint) as a string.\n\t\treturn Number(result?.count ?? 0);\n\t}\n}\n\n/**\n * Create a scoped storage accessor for a plugin\n */\nexport function createPluginStorageAccessor(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tstorageConfig: Record<\n\t\tstring,\n\t\t{ indexes: Array<string | string[]>; uniqueIndexes?: Array<string | string[]> }\n\t>,\n): Record<string, StorageCollection> {\n\tconst accessor: Record<string, StorageCollection> = {};\n\n\tfor (const [collectionName, config] of Object.entries(storageConfig)) {\n\t\tconst allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];\n\t\taccessor[collectionName] = new PluginStorageRepository(\n\t\t\tdb,\n\t\t\tpluginId,\n\t\t\tcollectionName,\n\t\t\tallIndexes,\n\t\t);\n\t}\n\n\treturn accessor;\n}\n\n/**\n * Delete all storage data for a plugin\n */\nexport async function deleteAllPluginStorage(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_plugin_storage\")\n\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows ?? 0);\n}\n\n/**\n * Delete all storage data for a plugin collection\n */\nexport async function deletePluginCollection(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tcollection: string,\n): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_plugin_storage\")\n\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t.where(\"collection\", \"=\", collection)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows ?? 0);\n}\n","/**\n * Plugin Context v2\n *\n * Creates the unified context object provided to plugins in all hooks and routes.\n *\n */\n\nimport type { Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport { PluginStorageRepository } from \"../database/repositories/plugin-storage.js\";\nimport { SeoRepository } from \"../database/repositories/seo.js\";\nimport { TaxonomyRepository, type Taxonomy } from \"../database/repositories/taxonomy.js\";\nimport { UserRepository } from \"../database/repositories/user.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { Database } from \"../database/types.js\";\nimport {\n\tresolveAndValidateExternalUrl,\n\tSsrfError,\n\tstripCredentialHeaders,\n} from \"../import/ssrf.js\";\nimport { enrichImageMetadata } from \"../media/enrich.js\";\nimport { markContentMediaUsageCollectionStaleSafely } from \"../media/usage/content-refresh.js\";\nimport { invalidateSiteSettingsCache } from \"../settings/index.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport { CronAccessImpl } from \"./cron.js\";\nimport type { EmailPipeline } from \"./email.js\";\nimport type {\n\tResolvedPlugin,\n\tPluginContext,\n\tPluginStorageConfig,\n\tStorageCollection,\n\tKVAccess,\n\tCronAccess,\n\tEmailAccess,\n\tContentAccess,\n\tContentAccessWithWrite,\n\tMediaAccess,\n\tMediaAccessWithWrite,\n\tHttpAccess,\n\tLogAccess,\n\tSiteInfo,\n\tUserAccess,\n\tUserInfo,\n\tContentItem,\n\tContentItemSeoInput,\n\tContentWriteInput,\n\tMediaItem,\n\tPaginatedResult,\n\tQueryOptions,\n\tContentListOptions,\n\tMediaListOptions,\n\tTaxonomyAccess,\n\tTaxonomyDefInfo,\n\tTaxonomyTermInfo,\n\tTaxonomyReadOptions,\n} from \"./types.js\";\n\n// =============================================================================\n// KV Access\n// =============================================================================\n\n/**\n * Create KV accessor for a plugin\n * All keys are automatically prefixed with the plugin ID\n */\nexport function createKVAccess(optionsRepo: OptionsRepository, pluginId: string): KVAccess {\n\tconst prefix = `plugin:${pluginId}:`;\n\n\treturn {\n\t\tasync get<T>(key: string): Promise<T | null> {\n\t\t\treturn optionsRepo.get<T>(`${prefix}${key}`);\n\t\t},\n\n\t\tasync set(key: string, value: unknown): Promise<void> {\n\t\t\tawait optionsRepo.set(`${prefix}${key}`, value);\n\t\t},\n\n\t\tasync delete(key: string): Promise<boolean> {\n\t\t\treturn optionsRepo.delete(`${prefix}${key}`);\n\t\t},\n\n\t\tasync list(keyPrefix?: string): Promise<Array<{ key: string; value: unknown }>> {\n\t\t\tconst fullPrefix = `${prefix}${keyPrefix ?? \"\"}`;\n\t\t\tconst entriesMap = await optionsRepo.getByPrefix(fullPrefix);\n\t\t\tconst result: Array<{ key: string; value: unknown }> = [];\n\t\t\tfor (const [fullKey, value] of entriesMap) {\n\t\t\t\tresult.push({\n\t\t\t\t\tkey: fullKey.slice(prefix.length),\n\t\t\t\t\tvalue,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Storage Access\n// =============================================================================\n\n/**\n * Create storage collection accessor for a plugin\n * Wraps PluginStorageRepository with the v2 interface (no async iterators)\n */\nfunction createStorageCollection<T>(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tcollectionName: string,\n\tindexes: Array<string | string[]>,\n): StorageCollection<T> {\n\tconst repo = new PluginStorageRepository<T>(db, pluginId, collectionName, indexes);\n\n\treturn {\n\t\tget: (id) => repo.get(id),\n\t\tput: (id, data) => repo.put(id, data),\n\t\tdelete: (id) => repo.delete(id),\n\t\texists: (id) => repo.exists(id),\n\t\tgetMany: (ids) => repo.getMany(ids),\n\t\tputMany: (items) => repo.putMany(items),\n\t\tdeleteMany: (ids) => repo.deleteMany(ids),\n\t\tcount: (where) => repo.count(where),\n\n\t\t// Query returns PaginatedResult instead of the old format\n\t\tasync query(options?: QueryOptions): Promise<PaginatedResult<{ id: string; data: T }>> {\n\t\t\tconst result = await repo.query({\n\t\t\t\twhere: options?.where,\n\t\t\t\torderBy: options?.orderBy,\n\t\t\t\tlimit: options?.limit,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items,\n\t\t\t\tcursor: result.cursor,\n\t\t\t\thasMore: result.hasMore,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create storage accessor with all declared collections\n */\nexport function createStorageAccess<T extends PluginStorageConfig>(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tstorageConfig: T,\n): Record<string, StorageCollection> {\n\tconst storage: Record<string, StorageCollection> = {};\n\n\tfor (const [collectionName, config] of Object.entries(storageConfig)) {\n\t\tconst allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];\n\t\tstorage[collectionName] = createStorageCollection(db, pluginId, collectionName, allIndexes);\n\t}\n\n\treturn storage;\n}\n\n// =============================================================================\n// Content Access\n// =============================================================================\n\n/**\n * Extract `seo` from a plugin-supplied content write input and return both\n * parts. Mutates nothing — returns a new field map without the `seo` key.\n */\nfunction splitSeoFromInput(input: ContentWriteInput): {\n\tfields: Record<string, unknown>;\n\tseo: ContentItemSeoInput | undefined;\n} {\n\tconst { seo, ...fields } = input;\n\t// Reject non-object seo values rather than silently dropping them.\n\tif (seo !== undefined && (seo === null || typeof seo !== \"object\" || Array.isArray(seo))) {\n\t\tthrow new Error(\"content.seo must be an object\");\n\t}\n\treturn { fields, seo };\n}\n\n/**\n * Reject writing SEO to a collection that does not have it enabled.\n * Matches the REST API behavior (VALIDATION_ERROR).\n */\nasync function assertSeoEnabled(\n\tseoRepo: SeoRepository,\n\tcollection: string,\n\tseo: ContentItemSeoInput | undefined,\n): Promise<boolean> {\n\tconst hasSeo = await seoRepo.isEnabled(collection);\n\tif (seo !== undefined && !hasSeo) {\n\t\tthrow new Error(\n\t\t\t`Collection \"${collection}\" does not have SEO enabled. ` +\n\t\t\t\t`Remove the seo field or enable SEO on this collection.`,\n\t\t);\n\t}\n\treturn hasSeo;\n}\n\n/**\n * Parse the `collections` JSON column into a string array (`[]` on anything\n * else). Mirrors the guards in the Cloudflare/workerd bridges so an\n * in-process plugin degrades on malformed data instead of crashing.\n */\nfunction parseCollectionsColumn(value: string | null): string[] {\n\tif (!value) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(value);\n\t\treturn Array.isArray(parsed)\n\t\t\t? parsed.filter((item): item is string => typeof item === \"string\")\n\t\t\t: [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/** Map a repository `Taxonomy` row to the plugin-facing term shape. */\nfunction taxonomyToTermInfo(term: Taxonomy): TaxonomyTermInfo {\n\treturn {\n\t\tid: term.id,\n\t\ttaxonomy: term.name,\n\t\tslug: term.slug,\n\t\tlabel: term.label,\n\t\tparentId: term.parentId,\n\t\tdata: term.data,\n\t\tlocale: term.locale,\n\t\ttranslationGroup: term.translationGroup,\n\t};\n}\n\n/**\n * Create read-only content access\n */\nexport function createContentAccess(db: Kysely<Database>): ContentAccess {\n\tconst contentRepo = new ContentRepository(db);\n\tconst seoRepo = new SeoRepository(db);\n\n\treturn {\n\t\tasync get(collection: string, id: string): Promise<ContentItem | null> {\n\t\t\tconst item = await contentRepo.findById(collection, id);\n\t\t\tif (!item) return null;\n\n\t\t\tconst result: ContentItem = {\n\t\t\t\tid: item.id,\n\t\t\t\ttype: item.type,\n\t\t\t\tslug: item.slug,\n\t\t\t\tstatus: item.status,\n\t\t\t\tdata: item.data,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tlocale: item.locale,\n\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\tscheduledAt: item.scheduledAt,\n\t\t\t};\n\n\t\t\tif (await seoRepo.isEnabled(collection)) {\n\t\t\t\tresult.seo = await seoRepo.get(collection, item.id);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\n\t\tasync list(\n\t\t\tcollection: string,\n\t\t\toptions?: ContentListOptions,\n\t\t): Promise<PaginatedResult<ContentItem>> {\n\t\t\t// Convert orderBy format if provided\n\t\t\tlet orderBy: { field: string; direction: \"asc\" | \"desc\" } | undefined;\n\t\t\tif (options?.orderBy) {\n\t\t\t\tconst entries = Object.entries(options.orderBy);\n\t\t\t\tconst first = entries[0];\n\t\t\t\tif (first) {\n\t\t\t\t\torderBy = { field: first[0], direction: first[1] };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await contentRepo.findMany(collection, {\n\t\t\t\tlimit: options?.limit ?? 50,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\torderBy,\n\t\t\t\twhere: options?.where,\n\t\t\t});\n\n\t\t\tconst items: ContentItem[] = result.items.map((item) => ({\n\t\t\t\tid: item.id,\n\t\t\t\ttype: item.type,\n\t\t\t\tslug: item.slug,\n\t\t\t\tstatus: item.status,\n\t\t\t\tdata: item.data,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tlocale: item.locale,\n\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\tscheduledAt: item.scheduledAt,\n\t\t\t}));\n\n\t\t\tif (items.length > 0 && (await seoRepo.isEnabled(collection))) {\n\t\t\t\tconst seoMap = await seoRepo.getMany(\n\t\t\t\t\tcollection,\n\t\t\t\t\titems.map((i) => i.id),\n\t\t\t\t);\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tconst seo = seoMap.get(item.id);\n\t\t\t\t\tif (seo) item.seo = seo;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\titems,\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: !!result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create read-only taxonomy access (gated on `taxonomies:read`).\n */\nexport function createTaxonomyAccess(db: Kysely<Database>): TaxonomyAccess {\n\tconst taxonomyRepo = new TaxonomyRepository(db);\n\n\treturn {\n\t\tasync getAll(options?: TaxonomyReadOptions): Promise<TaxonomyDefInfo[]> {\n\t\t\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll();\n\t\t\tif (options?.locale !== undefined) query = query.where(\"locale\", \"=\", options.locale);\n\t\t\tconst rows = await query.orderBy(\"name\", \"asc\").execute();\n\t\t\treturn rows.map((row) => ({\n\t\t\t\tname: row.name,\n\t\t\t\tlabel: row.label,\n\t\t\t\tlabelSingular: row.label_singular,\n\t\t\t\thierarchical: row.hierarchical === 1,\n\t\t\t\tcollections: parseCollectionsColumn(row.collections),\n\t\t\t\tlocale: row.locale,\n\t\t\t}));\n\t\t},\n\n\t\tasync getTerms(taxonomy: string, options?: TaxonomyReadOptions): Promise<TaxonomyTermInfo[]> {\n\t\t\tconst terms = await taxonomyRepo.findByName(taxonomy, { locale: options?.locale });\n\t\t\treturn terms.map(taxonomyToTermInfo);\n\t\t},\n\n\t\tasync getEntryTerms(\n\t\t\tcollection: string,\n\t\t\tentryId: string,\n\t\t\toptions?: TaxonomyReadOptions & { taxonomy?: string },\n\t\t): Promise<TaxonomyTermInfo[]> {\n\t\t\tconst terms = await taxonomyRepo.getTermsForEntry(\n\t\t\t\tcollection,\n\t\t\t\tentryId,\n\t\t\t\toptions?.taxonomy,\n\t\t\t\toptions?.locale,\n\t\t\t);\n\t\t\treturn terms.map(taxonomyToTermInfo);\n\t\t},\n\t};\n}\n\n/**\n * Create full content access with write operations.\n *\n * `create` and `update` accept a reserved `seo` key in their `data`\n * argument. When present, it is routed to the core SEO panel\n * (`_emdash_seo`) via `SeoRepository.upsert`, in the same transaction as\n * the content write. The returned `ContentItem.seo` reflects the resulting\n * SEO state for SEO-enabled collections.\n */\nexport function createContentAccessWithWrite(db: Kysely<Database>): ContentAccessWithWrite {\n\tconst readAccess = createContentAccess(db);\n\n\treturn {\n\t\t...readAccess,\n\n\t\tasync create(collection: string, data: ContentWriteInput): Promise<ContentItem> {\n\t\t\tconst { fields, seo } = splitSeoFromInput(data);\n\t\t\tlet contentMutated = false;\n\n\t\t\ttry {\n\t\t\t\tconst created = await withTransaction(db, async (trx) => {\n\t\t\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\t\t\tconst trxSeoRepo = new SeoRepository(trx);\n\n\t\t\t\t\tconst hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);\n\n\t\t\t\t\tconst item = await trxContentRepo.create({\n\t\t\t\t\t\ttype: collection,\n\t\t\t\t\t\tdata: fields,\n\t\t\t\t\t});\n\t\t\t\t\tcontentMutated = true;\n\n\t\t\t\t\tconst result: ContentItem = {\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\ttype: item.type,\n\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\tstatus: item.status,\n\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t\t\tscheduledAt: item.scheduledAt,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (hasSeo) {\n\t\t\t\t\t\tresult.seo =\n\t\t\t\t\t\t\tseo !== undefined\n\t\t\t\t\t\t\t\t? await trxSeoRepo.upsert(collection, item.id, seo)\n\t\t\t\t\t\t\t\t: await trxSeoRepo.get(collection, item.id);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn result;\n\t\t\t\t});\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\treturn created;\n\t\t\t} catch (error) {\n\t\t\t\tif (contentMutated) {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\tasync update(collection: string, id: string, data: ContentWriteInput): Promise<ContentItem> {\n\t\t\tconst { fields, seo } = splitSeoFromInput(data);\n\t\t\tconst hasFieldUpdates = Object.keys(fields).length > 0;\n\t\t\tlet contentMutated = false;\n\n\t\t\ttry {\n\t\t\t\tconst updated = await withTransaction(db, async (trx) => {\n\t\t\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\t\t\tconst trxSeoRepo = new SeoRepository(trx);\n\n\t\t\t\t\tconst hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);\n\n\t\t\t\t\t// Pass the `data` payload to ContentRepository.update only when\n\t\t\t\t\t// there are field updates — passing an empty object would still\n\t\t\t\t\t// bump updated_at/version, but we want a seo-only call to touch\n\t\t\t\t\t// only the SEO table. ContentRepository.update handles the no-op\n\t\t\t\t\t// path by returning the current row.\n\t\t\t\t\tconst item = hasFieldUpdates\n\t\t\t\t\t\t? await trxContentRepo.update(collection, id, { data: fields })\n\t\t\t\t\t\t: await (async () => {\n\t\t\t\t\t\t\t\tconst existing = await trxContentRepo.findById(collection, id);\n\t\t\t\t\t\t\t\tif (!existing) throw new Error(\"Content not found\");\n\t\t\t\t\t\t\t\treturn existing;\n\t\t\t\t\t\t\t})();\n\t\t\t\t\tif (hasFieldUpdates) contentMutated = true;\n\n\t\t\t\t\tconst result: ContentItem = {\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\ttype: item.type,\n\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\tstatus: item.status,\n\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t\t\tscheduledAt: item.scheduledAt,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (hasSeo) {\n\t\t\t\t\t\tresult.seo =\n\t\t\t\t\t\t\tseo !== undefined\n\t\t\t\t\t\t\t\t? await trxSeoRepo.upsert(collection, item.id, seo)\n\t\t\t\t\t\t\t\t: await trxSeoRepo.get(collection, item.id);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn result;\n\t\t\t\t});\n\t\t\t\tif (hasFieldUpdates) {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\t}\n\t\t\t\treturn updated;\n\t\t\t} catch (error) {\n\t\t\t\tif (contentMutated) {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\tasync delete(collection: string, id: string): Promise<boolean> {\n\t\t\tconst contentRepo = new ContentRepository(db);\n\t\t\tconst deleted = await contentRepo.delete(collection, id);\n\t\t\tif (deleted) {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Media Access\n// =============================================================================\n\n/**\n * Create read-only media access\n */\nexport function createMediaAccess(db: Kysely<Database>): MediaAccess {\n\tconst mediaRepo = new MediaRepository(db);\n\n\treturn {\n\t\tasync get(id: string): Promise<MediaItem | null> {\n\t\t\tconst item = await mediaRepo.findById(id);\n\t\t\tif (!item) return null;\n\n\t\t\treturn {\n\t\t\t\tid: item.id,\n\t\t\t\tfilename: item.filename,\n\t\t\t\tmimeType: item.mimeType,\n\t\t\t\tsize: item.size,\n\t\t\t\t// Construct URL from storage key (or use a sensible default path)\n\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t};\n\t\t},\n\n\t\tasync list(options?: MediaListOptions): Promise<PaginatedResult<MediaItem>> {\n\t\t\tconst result = await mediaRepo.findMany({\n\t\t\t\tlimit: options?.limit ?? 50,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\tmimeType: options?.mimeType,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map((item) => ({\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\tmimeType: item.mimeType,\n\t\t\t\t\tsize: item.size,\n\t\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t})),\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: !!result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create full media access with write operations.\n *\n * `getUploadUrlFn` is optional: when omitted, `getUploadUrl()` is derived from\n * `storage` (create a pending record + a signed PUT URL), mirroring the REST\n * `/_emdash/api/media/upload-url` endpoint. `upload()` only needs `storage`.\n * If storage is not provided, both throw at call time.\n */\nexport function createMediaAccessWithWrite(\n\tdb: Kysely<Database>,\n\tgetUploadUrlFn:\n\t\t| ((filename: string, contentType: string) => Promise<{ uploadUrl: string; mediaId: string }>)\n\t\t| undefined,\n\tstorage?: Storage,\n): MediaAccessWithWrite {\n\tconst mediaRepo = new MediaRepository(db);\n\tconst readAccess = createMediaAccess(db);\n\n\tconst getUploadUrl =\n\t\tgetUploadUrlFn ??\n\t\t(async (filename: string, contentType: string) => {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Media getUploadUrl() requires a storage backend. Configure storage in PluginContextFactoryOptions.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst basename = filename.split(\"/\").pop() ?? filename;\n\t\t\tconst dotIdx = basename.lastIndexOf(\".\");\n\t\t\tconst ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : \"\";\n\t\t\tconst storageKey = `${ulid()}${ext}`;\n\n\t\t\tconst media = await mediaRepo.createPending({\n\t\t\t\tfilename: basename,\n\t\t\t\tmimeType: contentType,\n\t\t\t\tstorageKey,\n\t\t\t});\n\n\t\t\tconst signed = await storage.getSignedUploadUrl({\n\t\t\t\tkey: storageKey,\n\t\t\t\tcontentType,\n\t\t\t\texpiresIn: 3600,\n\t\t\t});\n\n\t\t\treturn { uploadUrl: signed.url, mediaId: media.id };\n\t\t});\n\n\treturn {\n\t\t...readAccess,\n\n\t\tgetUploadUrl,\n\n\t\tasync upload(\n\t\t\tfilename: string,\n\t\t\tcontentType: string,\n\t\t\tbytes: ArrayBuffer,\n\t\t): Promise<{ mediaId: string; storageKey: string; url: string }> {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Media upload() requires a storage backend. Configure storage in PluginContextFactoryOptions.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Generate a storage key with a unique prefix\n\t\t\tconst keyPrefix = ulid();\n\t\t\t// Extract extension from basename (ignore path separators)\n\t\t\tconst basename = filename.split(\"/\").pop() ?? filename;\n\t\t\tconst dotIdx = basename.lastIndexOf(\".\");\n\t\t\tconst ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : \"\";\n\t\t\tconst storageKey = `${keyPrefix}${ext}`;\n\n\t\t\t// Upload to storage first\n\t\t\tawait storage.upload({\n\t\t\t\tkey: storageKey,\n\t\t\t\tbody: new Uint8Array(bytes),\n\t\t\t\tcontentType,\n\t\t\t});\n\n\t\t\t// Derive dimensions + LQIP placeholders (no-op for non-images).\n\t\t\tconst enriched = await enrichImageMetadata(new Uint8Array(bytes), contentType);\n\n\t\t\t// Create DB record — clean up storage on failure\n\t\t\tlet media;\n\t\t\ttry {\n\t\t\t\tmedia = await mediaRepo.create({\n\t\t\t\t\tfilename: basename,\n\t\t\t\t\tmimeType: contentType,\n\t\t\t\t\tsize: bytes.byteLength,\n\t\t\t\t\tstorageKey,\n\t\t\t\t\tstatus: \"ready\",\n\t\t\t\t\twidth: enriched.width,\n\t\t\t\t\theight: enriched.height,\n\t\t\t\t\tblurhash: enriched.blurhash,\n\t\t\t\t\tdominantColor: enriched.dominantColor,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tawait storage.delete(storageKey);\n\t\t\t\t} catch {\n\t\t\t\t\t// Best-effort cleanup\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmediaId: media.id,\n\t\t\t\tstorageKey,\n\t\t\t\turl: `/_emdash/api/media/file/${storageKey}`,\n\t\t\t};\n\t\t},\n\n\t\tasync delete(id: string): Promise<boolean> {\n\t\t\tconst deleted = await mediaRepo.delete(id);\n\t\t\t// Plugins can delete media that's referenced by site settings\n\t\t\t// (`logo`, `favicon`, `seo.defaultOgImage`); the worker-scoped\n\t\t\t// resolved-URL cache must be dropped or it will keep serving\n\t\t\t// 404s. Matches the invalidation in\n\t\t\t// `EmDashRuntime.handleMediaDelete`.\n\t\t\tif (deleted) {\n\t\t\t\tinvalidateSiteSettingsCache();\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// HTTP Access\n// =============================================================================\n\n/** Maximum number of redirects to follow in plugin HTTP access */\nconst MAX_PLUGIN_REDIRECTS = 5;\n\n/**\n * Check if a hostname matches any pattern in the allowed list.\n * Patterns: \"*\" matches all, \"*.example.com\" matches subdomains AND bare \"example.com\",\n * \"api.example.com\" matches exactly.\n */\nfunction isHostAllowed(host: string, allowedHosts: string[]): boolean {\n\treturn allowedHosts.some((pattern) => {\n\t\tif (pattern === \"*\") return true;\n\t\tif (pattern.startsWith(\"*.\")) {\n\t\t\tconst suffix = pattern.slice(1); // \".example.com\"\n\t\t\t// Match subdomains (foo.example.com) and bare domain (example.com)\n\t\t\treturn host.endsWith(suffix) || host === pattern.slice(2);\n\t\t}\n\t\treturn host === pattern;\n\t});\n}\n\n/**\n * Create HTTP access with host validation.\n *\n * Uses redirect: \"manual\" to re-validate each redirect target against\n * the allowedHosts list, preventing redirects to unauthorized hosts.\n */\nexport function createHttpAccess(pluginId: string, allowedHosts: string[]): HttpAccess {\n\treturn {\n\t\tasync fetch(url: string, init?: RequestInit): Promise<Response> {\n\t\t\t// Deny by default — plugins must declare allowed hosts\n\t\t\tif (allowedHosts.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Plugin \"${pluginId}\" has no allowed hosts configured. ` +\n\t\t\t\t\t\t`Add hosts to the plugin's allowedHosts array to enable HTTP requests.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet currentUrl = url;\n\t\t\tlet currentInit = init;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\tconst hostname = new URL(currentUrl).hostname;\n\t\t\t\tif (!isHostAllowed(hostname, allowedHosts)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Plugin \"${pluginId}\" is not allowed to fetch from host \"${hostname}\". ` +\n\t\t\t\t\t\t\t`Allowed hosts: ${allowedHosts.join(\", \")}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t\t\t...currentInit,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t});\n\n\t\t\t\t// Not a redirect -- return directly\n\t\t\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Extract redirect target\n\t\t\t\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Resolve relative redirects; strip credentials on cross-origin hops\n\t\t\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new Error(`Plugin \"${pluginId}\": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);\n\t\t},\n\t};\n}\n\n/**\n * Create unrestricted HTTP access (for plugins with network:fetch:any capability).\n * No host validation, but applies SSRF protection on redirect targets to\n * prevent plugins from being tricked into reaching internal services.\n */\nexport function createUnrestrictedHttpAccess(pluginId: string): HttpAccess {\n\treturn {\n\t\tasync fetch(url: string, init?: RequestInit): Promise<Response> {\n\t\t\tlet currentUrl = url;\n\t\t\tlet currentInit = init;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\t// Validate each URL against SSRF rules (private IPs, metadata\n\t\t\t\t// endpoints, wildcard DNS, resolved-IP private ranges).\n\t\t\t\ttry {\n\t\t\t\t\tawait resolveAndValidateExternalUrl(currentUrl);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconst msg = e instanceof SsrfError ? e.message : \"SSRF validation failed\";\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Plugin \"${pluginId}\": blocked fetch to \"${new URL(currentUrl).hostname}\": ${msg}`,\n\t\t\t\t\t\t{ cause: e },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t\t\t...currentInit,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t});\n\n\t\t\t\t// Not a redirect -- return directly\n\t\t\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Extract redirect target\n\t\t\t\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Resolve relative redirects; strip credentials on cross-origin hops\n\t\t\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new Error(`Plugin \"${pluginId}\": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);\n\t\t},\n\t};\n}\n\n/**\n * Create blocked HTTP access (for plugins without network:request capability)\n */\nexport function createBlockedHttpAccess(pluginId: string): HttpAccess {\n\treturn {\n\t\tasync fetch(): Promise<never> {\n\t\t\tthrow new Error(\n\t\t\t\t`Plugin \"${pluginId}\" does not have the \"network:request\" capability. ` +\n\t\t\t\t\t`Add \"network:request\" to the plugin's capabilities to enable HTTP requests.`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Log Access\n// =============================================================================\n\n/**\n * Create logger for a plugin\n */\nexport function createLogAccess(pluginId: string): LogAccess {\n\tconst prefix = `[plugin:${pluginId}]`;\n\n\treturn {\n\t\tdebug(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.debug(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.debug(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\tinfo(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.info(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.info(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\twarn(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.warn(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.warn(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\terror(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.error(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.error(prefix, message);\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Site Info\n// =============================================================================\n\nconst TRAILING_SLASH_RE = /\\/$/;\n\n/**\n * Options for creating site info\n */\nexport interface SiteInfoOptions {\n\t/** Site name from options table */\n\tsiteName?: string;\n\t/** Site URL from options table or Astro config */\n\tsiteUrl?: string;\n\t/** Site locale from options table */\n\tlocale?: string;\n\t/** Astro's `trailingSlash` config (from `virtual:emdash/config`). */\n\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n}\n\n/**\n * Create site info from config and settings.\n *\n * Resolution order for URL:\n * 1. options table (emdash:site_url)\n * 2. Astro `site` config\n * 3. fallback to empty string\n */\nexport function createSiteInfo(options: SiteInfoOptions): SiteInfo {\n\treturn {\n\t\tname: options.siteName ?? \"\",\n\t\turl: (options.siteUrl ?? \"\").replace(TRAILING_SLASH_RE, \"\"), // strip trailing slash\n\t\tlocale: options.locale ?? \"en\",\n\t\ttrailingSlash: options.trailingSlash ?? \"ignore\", // Astro's default\n\t};\n}\n\n/**\n * Create a URL helper that generates absolute URLs from relative paths.\n * Validates that path starts with \"/\" and rejects protocol-relative paths (\"//\").\n */\nexport function createUrlHelper(siteUrl: string): (path: string) => string {\n\tconst base = siteUrl.replace(TRAILING_SLASH_RE, \"\"); // strip trailing slash\n\n\treturn (path: string): string => {\n\t\tif (!path.startsWith(\"/\")) {\n\t\t\tthrow new Error(`URL path must start with \"/\", got: \"${path}\"`);\n\t\t}\n\t\tif (path.startsWith(\"//\")) {\n\t\t\tthrow new Error(`URL path must not be protocol-relative, got: \"${path}\"`);\n\t\t}\n\t\treturn `${base}${path}`;\n\t};\n}\n\n// =============================================================================\n// User Access\n// =============================================================================\n\n/**\n * Convert a UserRepository user to the plugin-facing UserInfo shape.\n * Strips sensitive fields (avatarUrl, emailVerified, data).\n */\nfunction toUserInfo(user: {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\trole: number;\n\tcreatedAt: string;\n}): UserInfo {\n\treturn {\n\t\tid: user.id,\n\t\temail: user.email,\n\t\tname: user.name,\n\t\trole: user.role,\n\t\tcreatedAt: user.createdAt,\n\t};\n}\n\n/**\n * Create read-only user access for plugins.\n * Excludes sensitive fields (password hashes, sessions, passkeys, avatar URL, data).\n */\nexport function createUserAccess(db: Kysely<Database>): UserAccess {\n\tconst userRepo = new UserRepository(db);\n\n\treturn {\n\t\tasync get(id: string): Promise<UserInfo | null> {\n\t\t\tconst user = await userRepo.findById(id);\n\t\t\tif (!user) return null;\n\t\t\treturn toUserInfo(user);\n\t\t},\n\n\t\tasync getByEmail(email: string): Promise<UserInfo | null> {\n\t\t\tconst user = await userRepo.findByEmail(email);\n\t\t\tif (!user) return null;\n\t\t\treturn toUserInfo(user);\n\t\t},\n\n\t\tasync list(opts?: {\n\t\t\trole?: number;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t}): Promise<{ items: UserInfo[]; nextCursor?: string }> {\n\t\t\tconst result = await userRepo.findMany({\n\t\t\t\trole: opts?.role as 10 | 20 | 30 | 40 | 50 | undefined,\n\t\t\t\tcursor: opts?.cursor,\n\t\t\t\tlimit: opts?.limit,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map(toUserInfo),\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Plugin Context Factory\n// =============================================================================\n\nexport interface PluginContextFactoryOptions {\n\tdb: Kysely<Database>;\n\t/**\n\t * Resolver for the database connection, preferred over `db` when present.\n\t * Called per `createContext()` so connection-backed adapters (e.g. Postgres\n\t * over Hyperdrive) get the current request/event-scoped connection from ALS\n\t * rather than a snapshot of the per-isolate singleton — reusing the\n\t * singleton's socket from a later event trips workerd's cross-request I/O\n\t * guard. When omitted, `db` is used directly (correct for stateless\n\t * adapters like D1 and Node SQLite). `db` remains required as the fallback.\n\t */\n\tgetDb?: () => Kysely<Database>;\n\t/**\n\t * Storage backend for direct media uploads.\n\t * If not provided, upload() will throw.\n\t */\n\tstorage?: Storage;\n\t/**\n\t * Explicit provider for `ctx.media.getUploadUrl()`. Optional: when omitted\n\t * but `storage` is configured, the factory derives a working `getUploadUrl()`\n\t * (and `upload()`) from storage. Only when neither `getUploadUrl` nor\n\t * `storage` is present do media write operations become unavailable.\n\t */\n\tgetUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\t/**\n\t * Site information for ctx.site and ctx.url().\n\t * If not provided, site info will have empty defaults.\n\t */\n\tsiteInfo?: SiteInfoOptions;\n\t/**\n\t * Callback to notify the cron scheduler that the next due time may have changed.\n\t * If not provided, ctx.cron will not be available.\n\t */\n\tcronReschedule?: () => void;\n\t/**\n\t * Email pipeline instance for ctx.email.\n\t * If not provided (or no provider configured), ctx.email will be undefined.\n\t */\n\temailPipeline?: EmailPipeline;\n\t/**\n\t * Pre-resolved list of trusted proxy header names (from the runtime\n\t * `EmDashConfig.trustedProxyHeaders` or the env var). Plugin route\n\t * handlers pass this to `extractRequestMeta` so plugins see the same\n\t * client IP the core auth path does.\n\t */\n\ttrustedProxyHeaders?: string[];\n}\n\n/**\n * Factory for creating plugin contexts\n */\nexport class PluginContextFactory {\n\tprivate resolveDb: () => Kysely<Database>;\n\tprivate storage?: Storage;\n\tprivate getUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\tprivate site: SiteInfo;\n\tprivate urlHelper: (path: string) => string;\n\tprivate cronReschedule?: () => void;\n\tprivate emailPipeline?: EmailPipeline;\n\t/**\n\t * Plugin IDs already warned about a missing media-write backend, so the\n\t * warning fires once per factory instead of on every hook/route context\n\t * creation (which would spam logs for hook-participating plugins).\n\t */\n\tprivate warnedMissingMediaBackend = new Set<string>();\n\n\tconstructor(options: PluginContextFactoryOptions) {\n\t\tconst fixedDb = options.db;\n\t\tthis.resolveDb = options.getDb ?? (() => fixedDb);\n\t\tthis.storage = options.storage;\n\t\tthis.getUploadUrl = options.getUploadUrl;\n\t\tthis.site = createSiteInfo(options.siteInfo ?? {});\n\t\tthis.urlHelper = createUrlHelper(this.site.url);\n\t\tthis.cronReschedule = options.cronReschedule;\n\t\tthis.emailPipeline = options.emailPipeline;\n\t}\n\n\t/**\n\t * Create the unified plugin context\n\t */\n\tcreateContext(plugin: ResolvedPlugin): PluginContext {\n\t\tconst capabilities = new Set(plugin.capabilities);\n\n\t\t// Resolve the connection once per context. For stateless adapters this\n\t\t// is the singleton; for connection-backed adapters it's the current\n\t\t// request/event-scoped connection from ALS. All repos below are built\n\t\t// from this local `db` so a hook never queries a stale singleton socket.\n\t\tconst db = this.resolveDb();\n\t\tconst optionsRepo = new OptionsRepository(db);\n\n\t\t// Always available\n\t\tconst kv = createKVAccess(optionsRepo, plugin.id);\n\t\tconst log = createLogAccess(plugin.id);\n\t\tconst storage = createStorageAccess(db, plugin.id, plugin.storage);\n\n\t\t// Capability-gated: content\n\t\t// Note: capabilities reach this point already normalized to the\n\t\t// canonical names by definePlugin / adaptSandboxEntry. Deprecated\n\t\t// names (\"read:content\", \"write:content\") never appear here.\n\t\tlet content: ContentAccess | ContentAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"content:write\")) {\n\t\t\tcontent = createContentAccessWithWrite(db);\n\t\t} else if (capabilities.has(\"content:read\")) {\n\t\t\tcontent = createContentAccess(db);\n\t\t}\n\n\t\t// Capability-gated: taxonomies (read-only)\n\t\tlet taxonomies: TaxonomyAccess | undefined;\n\t\tif (capabilities.has(\"taxonomies:read\")) {\n\t\t\ttaxonomies = createTaxonomyAccess(db);\n\t\t}\n\n\t\t// Capability-gated: media\n\t\t// `upload()` only needs `storage`; `getUploadUrl()` is derived from\n\t\t// storage when no explicit provider is wired. Granting write access on\n\t\t// either avoids silently degrading media:write to read-only — the bug\n\t\t// where the runtime threads `storage` but not `getUploadUrl`.\n\t\tlet media: MediaAccess | MediaAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"media:write\")) {\n\t\t\tif (this.getUploadUrl || this.storage) {\n\t\t\t\tmedia = createMediaAccessWithWrite(db, this.getUploadUrl, this.storage);\n\t\t\t} else {\n\t\t\t\tif (!this.warnedMissingMediaBackend.has(plugin.id)) {\n\t\t\t\t\tthis.warnedMissingMediaBackend.add(plugin.id);\n\t\t\t\t\tlog.warn(\n\t\t\t\t\t\t\"declares the media:write capability but no storage backend is configured; upload() is unavailable.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (capabilities.has(\"media:read\")) {\n\t\t\t\t\tmedia = createMediaAccess(db);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (capabilities.has(\"media:read\")) {\n\t\t\tmedia = createMediaAccess(db);\n\t\t}\n\n\t\t// Capability-gated: http\n\t\tlet http: HttpAccess | undefined;\n\t\tif (capabilities.has(\"network:request:unrestricted\")) {\n\t\t\thttp = createUnrestrictedHttpAccess(plugin.id);\n\t\t} else if (capabilities.has(\"network:request\")) {\n\t\t\thttp = createHttpAccess(plugin.id, plugin.allowedHosts);\n\t\t}\n\n\t\t// Capability-gated: users\n\t\tlet users: UserAccess | undefined;\n\t\tif (capabilities.has(\"users:read\")) {\n\t\t\tusers = createUserAccess(db);\n\t\t}\n\n\t\t// Cron access — always available (scoped to plugin), but only if\n\t\t// the runtime provided a reschedule callback (i.e. cron is wired up).\n\t\tlet cron: CronAccess | undefined;\n\t\tif (this.cronReschedule) {\n\t\t\tcron = new CronAccessImpl(db, plugin.id, this.cronReschedule);\n\t\t}\n\n\t\t// Email access — requires email:send capability AND a configured provider\n\t\tlet email: EmailAccess | undefined;\n\t\tif (capabilities.has(\"email:send\") && this.emailPipeline?.isAvailable()) {\n\t\t\tconst pipeline = this.emailPipeline;\n\t\t\tconst pluginId = plugin.id;\n\t\t\temail = {\n\t\t\t\tsend: (message) => pipeline.send(message, pluginId),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tplugin: {\n\t\t\t\tid: plugin.id,\n\t\t\t\tversion: plugin.version,\n\t\t\t},\n\t\t\tstorage,\n\t\t\tkv,\n\t\t\tcontent,\n\t\t\ttaxonomies,\n\t\t\tmedia,\n\t\t\thttp,\n\t\t\tlog,\n\t\t\tsite: this.site,\n\t\t\turl: this.urlHelper,\n\t\t\tusers,\n\t\t\tcron,\n\t\t\temail,\n\t\t};\n\t}\n}\n\n/**\n * Create a plugin context for a resolved plugin\n */\nexport function createPluginContext(\n\toptions: PluginContextFactoryOptions,\n\tplugin: ResolvedPlugin,\n): PluginContext {\n\tconst factory = new PluginContextFactory(options);\n\treturn factory.createContext(plugin);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgBA,IAAa,oBAAb,cAAuC,MAAM;CAC5C,YACC,SACA,AAAO,OACP,AAAO,YACN;AACD,QAAM,QAAQ;EAHP;EACA;AAGP,OAAK,OAAO;;;;;;AAOd,SAAgB,cAAc,OAAyC;AACtE,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAO,QAAQ,SAAS,SAAS,SAAS,QAAQ,SAAS,SAAS;;;;;AAMrE,SAAgB,WAAW,OAAsC;AAChE,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAO,QAAQ,SAAS,MAAM,QAAQ,MAAM,GAAG;;;;;AAMhD,SAAgB,mBAAmB,OAA8C;AAChF,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAO,gBAAgB,SAAS,OAAO,MAAM,eAAe;;;;;;;AAQ7D,SAAgB,kBAAkB,OAAuB;AACxD,QAAO,MAAM,WAAW,MAAM,OAAO,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,KAAK,MAAM;;;;;AAMpF,SAAgB,iBAAiB,SAAgD;CAChF,MAAM,yBAAS,IAAI,KAAa;AAChC,MAAK,MAAM,SAAS,QACnB,KAAI,MAAM,QAAQ,MAAM,CACvB,MAAK,MAAM,SAAS,MACnB,QAAO,IAAI,MAAM;KAGlB,QAAO,IAAI,MAAM;AAGnB,QAAO;;;;;AAMR,SAAgB,oBACf,OACA,eACA,UACA,YACO;AACP,MAAK,MAAM,SAAS,OAAO,KAAK,MAAM,CACrC,KAAI,CAAC,cAAc,IAAI,MAAM,CAC5B,OAAM,IAAI,kBACT,sCAAsC,MAAM,KAC5C,OACA,QAAQ,MAAM,eAAe,WAAW,sBAAsB,SAAS,yBACvE;;;;;AAQJ,SAAgB,sBACf,SACA,eACA,UACA,YACO;AACP,MAAK,MAAM,SAAS,OAAO,KAAK,QAAQ,CACvC,KAAI,CAAC,cAAc,IAAI,MAAM,CAC5B,OAAM,IAAI,kBACT,sCAAsC,MAAM,KAC5C,OACA,QAAQ,MAAM,eAAe,WAAW,sBAAsB,SAAS,qCACvE;;;;;;;;;;;;AAgBJ,SAAgB,YACf,IACA,OACA,SACS;AACT,QAAO,sBAAsB,IAAI,OAAO,QAAQ;;;;;;;;;AAWjD,SAAgB,iBAAiB,IAAiB,OAAuB;AACxE,QAAO,oBAAoB,IAAI,MAAM;;;;;AAOtC,SAAgB,eACf,IACA,OACA,OACqC;CAMrC,MAAM,cAAc,YAA6B,YAAY,IAAI,OAAO,EAAE,SAAS,CAAC;AAEpF,KAAI,UAAU,KACb,QAAO;EAAE,KAAK,GAAG,WAAW,MAAM,CAAC;EAAW,QAAQ,EAAE;EAAE;AAG3D,KAAI,OAAO,UAAU,SACpB,QAAO;EAAE,KAAK,GAAG,WAAW,KAAK,CAAC;EAAO,QAAQ,CAAC,MAAM;EAAE;AAG3D,KAAI,OAAO,UAAU,SACpB,QAAO;EAAE,KAAK,GAAG,WAAW,MAAM,CAAC;EAAO,QAAQ,CAAC,MAAM;EAAE;AAG5D,KAAI,OAAO,UAAU,UAEpB,QAAO;EAAE,KAAK,GAAG,WAAW,MAAM,CAAC;EAAO,QAAQ,CAAC,MAAM;EAAE;AAG5D,KAAI,WAAW,MAAM,EAAE;EACtB,MAAM,UAAU,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,OAAO,MAAM,OAAO,MAAM,SAAS;EACnF,MAAM,eAAe,MAAM,GAAG,UAAU,IAAI,CAAC,KAAK,KAAK;AACvD,SAAO;GACN,KAAK,GAAG,WAAW,QAAQ,CAAC,OAAO,aAAa;GAChD,QAAQ,MAAM;GACd;;AAGF,KAAI,mBAAmB,MAAM,CAG5B,QAAO;EACN,KAAK,GAAG,WAAW,MAAM,CAAC;EAC1B,QAAQ,CAAC,GAAG,kBAAkB,MAAM,WAAW,CAAC,GAAG;EACnD;AAGF,KAAI,cAAc,MAAM,EAAE;EACzB,MAAM,aAAuB,EAAE;EAC/B,MAAM,SAAoB,EAAE;EAI5B,MAAM,aAAa,IAAY,UAAiC;AAC/D,cAAW,KAAK,GAAG,WAAW,OAAO,UAAU,SAAS,CAAC,GAAG,GAAG,IAAI;AACnE,UAAO,KAAK,MAAM;;AAGnB,MAAI,MAAM,OAAO,OAAW,WAAU,KAAK,MAAM,GAAG;AACpD,MAAI,MAAM,QAAQ,OAAW,WAAU,MAAM,MAAM,IAAI;AACvD,MAAI,MAAM,OAAO,OAAW,WAAU,KAAK,MAAM,GAAG;AACpD,MAAI,MAAM,QAAQ,OAAW,WAAU,MAAM,MAAM,IAAI;AAEvD,SAAO;GACN,KAAK,WAAW,KAAK,QAAQ;GAC7B;GACA;;AAGF,OAAM,IAAI,kBAAkB,kCAAkC,MAAM,GAAG;;;;;AAOxE,SAAgB,iBACf,IACA,OAIC;CACD,MAAM,aAAuB,EAAE;CAC/B,MAAM,SAAoB,EAAE;AAE5B,MAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,MAAM,EAAE;EACnD,MAAM,YAAY,eAAe,IAAI,OAAO,MAAM;AAClD,aAAW,KAAK,UAAU,IAAI;AAC9B,SAAO,KAAK,GAAG,UAAU,OAAO;;AAGjC,KAAI,WAAW,WAAW,EACzB,QAAO;EAAE,KAAK;EAAI,QAAQ,EAAE;EAAE;AAG/B,QAAO;EACN,KAAK,WAAW,KAAK,QAAQ;EAC7B;EACA;;;;;;;;;;ACzNF,SAAS,aAAa,SAAiB,QAAwC;CAC9E,MAAM,QAAkC,EAAE;CAC1C,IAAI,aAAa;CACjB,MAAM,WAAW,QAAQ,MAAM,IAAI;AACnC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACzC,MAAI,IAAI,EACP,OAAM,KAAK,GAAG,GAAG,OAAO,gBAAgB;AAEzC,MAAI,SAAS,GACZ,OAAM,KAAK,IAAI,IAAI,SAAS,GAAG,CAAC;;AAGlC,QAAO,GAAY,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;;;;;;;AAQrD,IAAa,0BAAb,MAAkF;CACjF,AAAQ;CAER,YACC,AAAQ,IACR,AAAQ,UACR,AAAQ,YACR,SACC;EAJO;EACA;EACA;AAGR,OAAK,gBAAgB,iBAAiB,QAAQ;;;;;CAM/C,MAAM,IAAI,IAA+B;EACxC,MAAM,MAAM,MAAM,KAAK,GACrB,WAAW,kBAAkB,CAC7B,OAAO,OAAO,CACd,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW,CACzC,MAAM,MAAM,KAAK,GAAG,CACpB,kBAAkB;AAEpB,MAAI,CAAC,IAAK,QAAO;AAEjB,SAAO,KAAK,MAAM,IAAI,KAAK;;;;;CAM5B,MAAM,IAAI,IAAY,MAAwB;EAC7C,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EACpC,MAAM,WAAW,KAAK,UAAU,KAAK;AAErC,QAAM,KAAK,GACT,WAAW,kBAAkB,CAC7B,OAAO;GACP,WAAW,KAAK;GAChB,YAAY,KAAK;GACjB;GACA,MAAM;GACN,YAAY;GACZ,YAAY;GACZ,CAAC,CACD,YAAY,OACZ,GAAG,QAAQ;GAAC;GAAa;GAAc;GAAK,CAAC,CAAC,YAAY;GACzD,MAAM;GACN,YAAY;GACZ,CAAC,CACF,CACA,SAAS;;;;;CAMZ,MAAM,OAAO,IAA8B;AAQ1C,WAPe,MAAM,KAAK,GACxB,WAAW,kBAAkB,CAC7B,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW,CACzC,MAAM,MAAM,KAAK,GAAG,CACpB,kBAAkB,EAEL,kBAAkB,KAAK;;;;;CAMvC,MAAM,OAAO,IAA8B;AAS1C,SAAO,CAAC,CARI,MAAM,KAAK,GACrB,WAAW,kBAAkB,CAC7B,OAAO,KAAK,CACZ,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW,CACzC,MAAM,MAAM,KAAK,GAAG,CACpB,kBAAkB;;;;;CAQrB,MAAM,QAAQ,KAAwC;AACrD,MAAI,IAAI,WAAW,EAAG,wBAAO,IAAI,KAAK;EAEtC,MAAM,OAAO,MAAM,KAAK,GACtB,WAAW,kBAAkB,CAC7B,OAAO,CAAC,MAAM,OAAO,CAAC,CACtB,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW,CACzC,MAAM,MAAM,MAAM,IAAI,CACtB,SAAS;EAEX,MAAM,yBAAS,IAAI,KAAgB;AACnC,OAAK,MAAM,OAAO,KAEjB,QAAO,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,CAAM;AAE9C,SAAO;;;;;CAMR,MAAM,QAAQ,OAAsD;AACnE,MAAI,MAAM,WAAW,EAAG;EAExB,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;AAIpC,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;AAC7C,QAAK,MAAM,QAAQ,OAAO;IACzB,MAAM,WAAW,KAAK,UAAU,KAAK,KAAK;AAC1C,UAAM,IACJ,WAAW,kBAAkB,CAC7B,OAAO;KACP,WAAW,KAAK;KAChB,YAAY,KAAK;KACjB,IAAI,KAAK;KACT,MAAM;KACN,YAAY;KACZ,YAAY;KACZ,CAAC,CACD,YAAY,OACZ,GAAG,QAAQ;KAAC;KAAa;KAAc;KAAK,CAAC,CAAC,YAAY;KACzD,MAAM;KACN,YAAY;KACZ,CAAC,CACF,CACA,SAAS;;IAEX;;;;;CAMH,MAAM,WAAW,KAAgC;AAChD,MAAI,IAAI,WAAW,EAAG,QAAO;EAE7B,MAAM,SAAS,MAAM,KAAK,GACxB,WAAW,kBAAkB,CAC7B,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW,CACzC,MAAM,MAAM,MAAM,IAAI,CACtB,kBAAkB;AAEpB,SAAO,OAAO,OAAO,kBAAkB,EAAE;;;;;CAM1C,MAAM,MAAM,UAAwB,EAAE,EAAqD;EAC1F,MAAM,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,WAAW;EAC7C,MAAM,QAAQ,KAAK,IAAI,QAAQ,SAAS,IAAI,IAAI;AAGhD,sBAAoB,OAAO,KAAK,eAAe,KAAK,UAAU,KAAK,WAAW;AAC9E,MAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,EACjC,uBAAsB,SAAS,KAAK,eAAe,KAAK,UAAU,KAAK,WAAW;EAInF,IAAI,QAAQ,KAAK,GACf,WAAW,kBAAkB,CAC7B,OAAO;GAAC;GAAM;GAAQ;GAAa,CAAC,CACpC,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW;EAG3C,MAAM,cAAc,iBAAiB,KAAK,IAAI,MAAM;AACpD,MAAI,YAAY,IACf,SAAQ,MAAM,MAAM,aAAa,YAAY,KAAK,YAAY,OAAO,CAAC;AAIvE,MAAI,QAAQ;GACX,MAAM,UAAU,aAAa,OAAO;AACpC,WAAQ,MAAM,OAAO,EAAE,SACtB,GAAG,GAAG,oBAAoB,KAAK,GAAG,IAAI,QAAQ,WAAW,IAAI,QAAQ,GAAG,GAAG,CAC3E;;AAIF,MAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,EACjC,MAAK,MAAM,CAAC,OAAO,cAAc,OAAO,QAAQ,QAAQ,EAAE;GAGzD,MAAM,UAAU,iBAAiB,KAAK,IAAI,MAAM;GAChD,MAAM,YACL,cAAc,SAAS,GAAG,GAAG,IAAI,IAAI,QAAQ,CAAC,SAAS,GAAG,GAAG,IAAI,IAAI,QAAQ,CAAC;AAC/E,WAAQ,MAAM,QAAQ,UAAU;;MAIjC,SAAQ,MAAM,QAAQ,cAAc,MAAM,CAAC,QAAQ,MAAM,MAAM;AAIhE,UAAQ,MAAM,MAAM,QAAQ,EAAE;EAE9B,MAAM,OAAO,MAAM,MAAM,SAAS;EAElC,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,QAAQ,KAAK,MAAM,GAAG,MAAM,CAAC,KAAK,SAAS;GAChD,IAAI,IAAI;GAER,MAAM,KAAK,MAAM,IAAI,KAAK;GAC1B,EAAE;EAGH,IAAI;AACJ,MAAI,SAAS;GACZ,MAAM,WAAW,KAAK,QAAQ;AAC9B,OAAI,SACH,cAAa,aAAa,SAAS,YAAY,SAAS,GAAG;;AAI7D,SAAO;GAAE;GAAO,QAAQ;GAAY;GAAS;;;;;CAM9C,MAAM,MAAM,OAAsC;AACjD,MAAI,SAAS,OAAO,KAAK,MAAM,CAAC,SAAS,EACxC,qBAAoB,OAAO,KAAK,eAAe,KAAK,UAAU,KAAK,WAAW;EAG/E,IAAI,QAAQ,KAAK,GACf,WAAW,kBAAkB,CAC7B,OAAO,GAAW,WAAW,GAAG,QAAQ,CAAC,CACzC,MAAM,aAAa,KAAK,KAAK,SAAS,CACtC,MAAM,cAAc,KAAK,KAAK,WAAW;AAG3C,MAAI,SAAS,OAAO,KAAK,MAAM,CAAC,SAAS,GAAG;GAC3C,MAAM,cAAc,iBAAiB,KAAK,IAAI,MAAM;AACpD,OAAI,YAAY,IACf,SAAQ,MAAM,MAAM,aAAa,YAAY,KAAK,YAAY,OAAO,CAAC;;EAIxE,MAAM,SAAS,MAAM,MAAM,kBAAkB;AAE7C,SAAO,OAAO,QAAQ,SAAS,EAAE;;;;;;;;;;AC9OnC,SAAgB,eAAe,aAAgC,UAA4B;CAC1F,MAAM,SAAS,UAAU,SAAS;AAElC,QAAO;EACN,MAAM,IAAO,KAAgC;AAC5C,UAAO,YAAY,IAAO,GAAG,SAAS,MAAM;;EAG7C,MAAM,IAAI,KAAa,OAA+B;AACrD,SAAM,YAAY,IAAI,GAAG,SAAS,OAAO,MAAM;;EAGhD,MAAM,OAAO,KAA+B;AAC3C,UAAO,YAAY,OAAO,GAAG,SAAS,MAAM;;EAG7C,MAAM,KAAK,WAAqE;GAC/E,MAAM,aAAa,GAAG,SAAS,aAAa;GAC5C,MAAM,aAAa,MAAM,YAAY,YAAY,WAAW;GAC5D,MAAM,SAAiD,EAAE;AACzD,QAAK,MAAM,CAAC,SAAS,UAAU,WAC9B,QAAO,KAAK;IACX,KAAK,QAAQ,MAAM,OAAO,OAAO;IACjC;IACA,CAAC;AAEH,UAAO;;EAER;;;;;;AAWF,SAAS,wBACR,IACA,UACA,gBACA,SACuB;CACvB,MAAM,OAAO,IAAI,wBAA2B,IAAI,UAAU,gBAAgB,QAAQ;AAElF,QAAO;EACN,MAAM,OAAO,KAAK,IAAI,GAAG;EACzB,MAAM,IAAI,SAAS,KAAK,IAAI,IAAI,KAAK;EACrC,SAAS,OAAO,KAAK,OAAO,GAAG;EAC/B,SAAS,OAAO,KAAK,OAAO,GAAG;EAC/B,UAAU,QAAQ,KAAK,QAAQ,IAAI;EACnC,UAAU,UAAU,KAAK,QAAQ,MAAM;EACvC,aAAa,QAAQ,KAAK,WAAW,IAAI;EACzC,QAAQ,UAAU,KAAK,MAAM,MAAM;EAGnC,MAAM,MAAM,SAA2E;GACtF,MAAM,SAAS,MAAM,KAAK,MAAM;IAC/B,OAAO,SAAS;IAChB,SAAS,SAAS;IAClB,OAAO,SAAS;IAChB,QAAQ,SAAS;IACjB,CAAC;AAEF,UAAO;IACN,OAAO,OAAO;IACd,QAAQ,OAAO;IACf,SAAS,OAAO;IAChB;;EAEF;;;;;AAMF,SAAgB,oBACf,IACA,UACA,eACoC;CACpC,MAAM,UAA6C,EAAE;AAErD,MAAK,MAAM,CAAC,gBAAgB,WAAW,OAAO,QAAQ,cAAc,CAEnE,SAAQ,kBAAkB,wBAAwB,IAAI,UAAU,gBAD7C,CAAC,GAAG,OAAO,SAAS,GAAI,OAAO,iBAAiB,EAAE,CAAE,CACoB;AAG5F,QAAO;;;;;;AAWR,SAAS,kBAAkB,OAGzB;CACD,MAAM,EAAE,KAAK,GAAG,WAAW;AAE3B,KAAI,QAAQ,WAAc,QAAQ,QAAQ,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,EACtF,OAAM,IAAI,MAAM,gCAAgC;AAEjD,QAAO;EAAE;EAAQ;EAAK;;;;;;AAOvB,eAAe,iBACd,SACA,YACA,KACmB;CACnB,MAAM,SAAS,MAAM,QAAQ,UAAU,WAAW;AAClD,KAAI,QAAQ,UAAa,CAAC,OACzB,OAAM,IAAI,MACT,eAAe,WAAW,qFAE1B;AAEF,QAAO;;;;;;;AAQR,SAAS,uBAAuB,OAAgC;AAC/D,KAAI,CAAC,MAAO,QAAO,EAAE;AACrB,KAAI;EACH,MAAM,SAAkB,KAAK,MAAM,MAAM;AACzC,SAAO,MAAM,QAAQ,OAAO,GACzB,OAAO,QAAQ,SAAyB,OAAO,SAAS,SAAS,GACjE,EAAE;SACE;AACP,SAAO,EAAE;;;;AAKX,SAAS,mBAAmB,MAAkC;AAC7D,QAAO;EACN,IAAI,KAAK;EACT,UAAU,KAAK;EACf,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,UAAU,KAAK;EACf,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,kBAAkB,KAAK;EACvB;;;;;AAMF,SAAgB,oBAAoB,IAAqC;CACxE,MAAM,cAAc,IAAI,kBAAkB,GAAG;CAC7C,MAAM,UAAU,IAAI,cAAc,GAAG;AAErC,QAAO;EACN,MAAM,IAAI,YAAoB,IAAyC;GACtE,MAAM,OAAO,MAAM,YAAY,SAAS,YAAY,GAAG;AACvD,OAAI,CAAC,KAAM,QAAO;GAElB,MAAM,SAAsB;IAC3B,IAAI,KAAK;IACT,MAAM,KAAK;IACX,MAAM,KAAK;IACX,QAAQ,KAAK;IACb,MAAM,KAAK;IACX,WAAW,KAAK;IAChB,WAAW,KAAK;IAChB,QAAQ,KAAK;IACb,aAAa,KAAK;IAClB,aAAa,KAAK;IAClB;AAED,OAAI,MAAM,QAAQ,UAAU,WAAW,CACtC,QAAO,MAAM,MAAM,QAAQ,IAAI,YAAY,KAAK,GAAG;AAGpD,UAAO;;EAGR,MAAM,KACL,YACA,SACwC;GAExC,IAAI;AACJ,OAAI,SAAS,SAAS;IAErB,MAAM,QADU,OAAO,QAAQ,QAAQ,QAAQ,CACzB;AACtB,QAAI,MACH,WAAU;KAAE,OAAO,MAAM;KAAI,WAAW,MAAM;KAAI;;GAIpD,MAAM,SAAS,MAAM,YAAY,SAAS,YAAY;IACrD,OAAO,SAAS,SAAS;IACzB,QAAQ,SAAS;IACjB;IACA,OAAO,SAAS;IAChB,CAAC;GAEF,MAAM,QAAuB,OAAO,MAAM,KAAK,UAAU;IACxD,IAAI,KAAK;IACT,MAAM,KAAK;IACX,MAAM,KAAK;IACX,QAAQ,KAAK;IACb,MAAM,KAAK;IACX,WAAW,KAAK;IAChB,WAAW,KAAK;IAChB,QAAQ,KAAK;IACb,aAAa,KAAK;IAClB,aAAa,KAAK;IAClB,EAAE;AAEH,OAAI,MAAM,SAAS,KAAM,MAAM,QAAQ,UAAU,WAAW,EAAG;IAC9D,MAAM,SAAS,MAAM,QAAQ,QAC5B,YACA,MAAM,KAAK,MAAM,EAAE,GAAG,CACtB;AACD,SAAK,MAAM,QAAQ,OAAO;KACzB,MAAM,MAAM,OAAO,IAAI,KAAK,GAAG;AAC/B,SAAI,IAAK,MAAK,MAAM;;;AAItB,UAAO;IACN;IACA,QAAQ,OAAO;IACf,SAAS,CAAC,CAAC,OAAO;IAClB;;EAEF;;;;;AAMF,SAAgB,qBAAqB,IAAsC;CAC1E,MAAM,eAAe,IAAI,mBAAmB,GAAG;AAE/C,QAAO;EACN,MAAM,OAAO,SAA2D;GACvE,IAAI,QAAQ,GAAG,WAAW,wBAAwB,CAAC,WAAW;AAC9D,OAAI,SAAS,WAAW,OAAW,SAAQ,MAAM,MAAM,UAAU,KAAK,QAAQ,OAAO;AAErF,WADa,MAAM,MAAM,QAAQ,QAAQ,MAAM,CAAC,SAAS,EAC7C,KAAK,SAAS;IACzB,MAAM,IAAI;IACV,OAAO,IAAI;IACX,eAAe,IAAI;IACnB,cAAc,IAAI,iBAAiB;IACnC,aAAa,uBAAuB,IAAI,YAAY;IACpD,QAAQ,IAAI;IACZ,EAAE;;EAGJ,MAAM,SAAS,UAAkB,SAA4D;AAE5F,WADc,MAAM,aAAa,WAAW,UAAU,EAAE,QAAQ,SAAS,QAAQ,CAAC,EACrE,IAAI,mBAAmB;;EAGrC,MAAM,cACL,YACA,SACA,SAC8B;AAO9B,WANc,MAAM,aAAa,iBAChC,YACA,SACA,SAAS,UACT,SAAS,OACT,EACY,IAAI,mBAAmB;;EAErC;;;;;;;;;;;AAYF,SAAgB,6BAA6B,IAA8C;AAG1F,QAAO;EACN,GAHkB,oBAAoB,GAAG;EAKzC,MAAM,OAAO,YAAoB,MAA+C;GAC/E,MAAM,EAAE,QAAQ,QAAQ,kBAAkB,KAAK;GAC/C,IAAI,iBAAiB;AAErB,OAAI;IACH,MAAM,UAAU,MAAM,gBAAgB,IAAI,OAAO,QAAQ;KACxD,MAAM,iBAAiB,IAAI,kBAAkB,IAAI;KACjD,MAAM,aAAa,IAAI,cAAc,IAAI;KAEzC,MAAM,SAAS,MAAM,iBAAiB,YAAY,YAAY,IAAI;KAElE,MAAM,OAAO,MAAM,eAAe,OAAO;MACxC,MAAM;MACN,MAAM;MACN,CAAC;AACF,sBAAiB;KAEjB,MAAM,SAAsB;MAC3B,IAAI,KAAK;MACT,MAAM,KAAK;MACX,MAAM,KAAK;MACX,QAAQ,KAAK;MACb,MAAM,KAAK;MACX,WAAW,KAAK;MAChB,WAAW,KAAK;MAChB,QAAQ,KAAK;MACb,aAAa,KAAK;MAClB,aAAa,KAAK;MAClB;AAED,SAAI,OACH,QAAO,MACN,QAAQ,SACL,MAAM,WAAW,OAAO,YAAY,KAAK,IAAI,IAAI,GACjD,MAAM,WAAW,IAAI,YAAY,KAAK,GAAG;AAG9C,YAAO;MACN;AACF,UAAM,2CAA2C,IAAI,YAAY,sBAAsB;AACvF,WAAO;YACC,OAAO;AACf,QAAI,eACH,OAAM,2CAA2C,IAAI,YAAY,sBAAsB;AAExF,UAAM;;;EAIR,MAAM,OAAO,YAAoB,IAAY,MAA+C;GAC3F,MAAM,EAAE,QAAQ,QAAQ,kBAAkB,KAAK;GAC/C,MAAM,kBAAkB,OAAO,KAAK,OAAO,CAAC,SAAS;GACrD,IAAI,iBAAiB;AAErB,OAAI;IACH,MAAM,UAAU,MAAM,gBAAgB,IAAI,OAAO,QAAQ;KACxD,MAAM,iBAAiB,IAAI,kBAAkB,IAAI;KACjD,MAAM,aAAa,IAAI,cAAc,IAAI;KAEzC,MAAM,SAAS,MAAM,iBAAiB,YAAY,YAAY,IAAI;KAOlE,MAAM,OAAO,kBACV,MAAM,eAAe,OAAO,YAAY,IAAI,EAAE,MAAM,QAAQ,CAAC,GAC7D,OAAO,YAAY;MACnB,MAAM,WAAW,MAAM,eAAe,SAAS,YAAY,GAAG;AAC9D,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,oBAAoB;AACnD,aAAO;SACJ;AACN,SAAI,gBAAiB,kBAAiB;KAEtC,MAAM,SAAsB;MAC3B,IAAI,KAAK;MACT,MAAM,KAAK;MACX,MAAM,KAAK;MACX,QAAQ,KAAK;MACb,MAAM,KAAK;MACX,WAAW,KAAK;MAChB,WAAW,KAAK;MAChB,QAAQ,KAAK;MACb,aAAa,KAAK;MAClB,aAAa,KAAK;MAClB;AAED,SAAI,OACH,QAAO,MACN,QAAQ,SACL,MAAM,WAAW,OAAO,YAAY,KAAK,IAAI,IAAI,GACjD,MAAM,WAAW,IAAI,YAAY,KAAK,GAAG;AAG9C,YAAO;MACN;AACF,QAAI,gBACH,OAAM,2CAA2C,IAAI,YAAY,sBAAsB;AAExF,WAAO;YACC,OAAO;AACf,QAAI,eACH,OAAM,2CAA2C,IAAI,YAAY,sBAAsB;AAExF,UAAM;;;EAIR,MAAM,OAAO,YAAoB,IAA8B;GAE9D,MAAM,UAAU,MADI,IAAI,kBAAkB,GAAG,CACX,OAAO,YAAY,GAAG;AACxD,OAAI,QACH,OAAM,2CAA2C,IAAI,YAAY,sBAAsB;AAExF,UAAO;;EAER;;;;;AAUF,SAAgB,kBAAkB,IAAmC;CACpE,MAAM,YAAY,IAAI,gBAAgB,GAAG;AAEzC,QAAO;EACN,MAAM,IAAI,IAAuC;GAChD,MAAM,OAAO,MAAM,UAAU,SAAS,GAAG;AACzC,OAAI,CAAC,KAAM,QAAO;AAElB,UAAO;IACN,IAAI,KAAK;IACT,UAAU,KAAK;IACf,UAAU,KAAK;IACf,MAAM,KAAK;IAEX,KAAK,UAAU,KAAK,GAAG,GAAG,KAAK;IAC/B,WAAW,KAAK;IAChB;;EAGF,MAAM,KAAK,SAAiE;GAC3E,MAAM,SAAS,MAAM,UAAU,SAAS;IACvC,OAAO,SAAS,SAAS;IACzB,QAAQ,SAAS;IACjB,UAAU,SAAS;IACnB,CAAC;AAEF,UAAO;IACN,OAAO,OAAO,MAAM,KAAK,UAAU;KAClC,IAAI,KAAK;KACT,UAAU,KAAK;KACf,UAAU,KAAK;KACf,MAAM,KAAK;KACX,KAAK,UAAU,KAAK,GAAG,GAAG,KAAK;KAC/B,WAAW,KAAK;KAChB,EAAE;IACH,QAAQ,OAAO;IACf,SAAS,CAAC,CAAC,OAAO;IAClB;;EAEF;;;;;;;;;;AAWF,SAAgB,2BACf,IACA,gBAGA,SACuB;CACvB,MAAM,YAAY,IAAI,gBAAgB,GAAG;CACzC,MAAM,aAAa,kBAAkB,GAAG;CAExC,MAAM,eACL,mBACC,OAAO,UAAkB,gBAAwB;AACjD,MAAI,CAAC,QACJ,OAAM,IAAI,MACT,qGACA;EAGF,MAAM,WAAW,SAAS,MAAM,IAAI,CAAC,KAAK,IAAI;EAC9C,MAAM,SAAS,SAAS,YAAY,IAAI;EACxC,MAAM,MAAM,SAAS,IAAI,SAAS,MAAM,OAAO,CAAC,aAAa,GAAG;EAChE,MAAM,aAAa,GAAG,MAAM,GAAG;EAE/B,MAAM,QAAQ,MAAM,UAAU,cAAc;GAC3C,UAAU;GACV,UAAU;GACV;GACA,CAAC;AAQF,SAAO;GAAE,YANM,MAAM,QAAQ,mBAAmB;IAC/C,KAAK;IACL;IACA,WAAW;IACX,CAAC,EAEyB;GAAK,SAAS,MAAM;GAAI;;AAGrD,QAAO;EACN,GAAG;EAEH;EAEA,MAAM,OACL,UACA,aACA,OACgE;AAChE,OAAI,CAAC,QACJ,OAAM,IAAI,MACT,+FACA;GAIF,MAAM,YAAY,MAAM;GAExB,MAAM,WAAW,SAAS,MAAM,IAAI,CAAC,KAAK,IAAI;GAC9C,MAAM,SAAS,SAAS,YAAY,IAAI;GAExC,MAAM,aAAa,GAAG,YADV,SAAS,IAAI,SAAS,MAAM,OAAO,CAAC,aAAa,GAAG;AAIhE,SAAM,QAAQ,OAAO;IACpB,KAAK;IACL,MAAM,IAAI,WAAW,MAAM;IAC3B;IACA,CAAC;GAGF,MAAM,WAAW,MAAM,oBAAoB,IAAI,WAAW,MAAM,EAAE,YAAY;GAG9E,IAAI;AACJ,OAAI;AACH,YAAQ,MAAM,UAAU,OAAO;KAC9B,UAAU;KACV,UAAU;KACV,MAAM,MAAM;KACZ;KACA,QAAQ;KACR,OAAO,SAAS;KAChB,QAAQ,SAAS;KACjB,UAAU,SAAS;KACnB,eAAe,SAAS;KACxB,CAAC;YACM,OAAO;AACf,QAAI;AACH,WAAM,QAAQ,OAAO,WAAW;YACzB;AAGR,UAAM;;AAGP,UAAO;IACN,SAAS,MAAM;IACf;IACA,KAAK,2BAA2B;IAChC;;EAGF,MAAM,OAAO,IAA8B;GAC1C,MAAM,UAAU,MAAM,UAAU,OAAO,GAAG;AAM1C,OAAI,QACH,8BAA6B;AAE9B,UAAO;;EAER;;;AAQF,MAAM,uBAAuB;;;;;;AAO7B,SAAS,cAAc,MAAc,cAAiC;AACrE,QAAO,aAAa,MAAM,YAAY;AACrC,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,QAAQ,WAAW,KAAK,EAAE;GAC7B,MAAM,SAAS,QAAQ,MAAM,EAAE;AAE/B,UAAO,KAAK,SAAS,OAAO,IAAI,SAAS,QAAQ,MAAM,EAAE;;AAE1D,SAAO,SAAS;GACf;;;;;;;;AASH,SAAgB,iBAAiB,UAAkB,cAAoC;AACtF,QAAO,EACN,MAAM,MAAM,KAAa,MAAuC;AAE/D,MAAI,aAAa,WAAW,EAC3B,OAAM,IAAI,MACT,WAAW,SAAS,0GAEpB;EAGF,IAAI,aAAa;EACjB,IAAI,cAAc;AAElB,OAAK,IAAI,IAAI,GAAG,KAAK,sBAAsB,KAAK;GAC/C,MAAM,WAAW,IAAI,IAAI,WAAW,CAAC;AACrC,OAAI,CAAC,cAAc,UAAU,aAAa,CACzC,OAAM,IAAI,MACT,WAAW,SAAS,uCAAuC,SAAS,oBACjD,aAAa,KAAK,KAAK,GAC1C;GAGF,MAAM,WAAW,MAAM,WAAW,MAAM,YAAY;IACnD,GAAG;IACH,UAAU;IACV,CAAC;AAGF,OAAI,SAAS,SAAS,OAAO,SAAS,UAAU,IAC/C,QAAO;GAIR,MAAM,WAAW,SAAS,QAAQ,IAAI,WAAW;AACjD,OAAI,CAAC,SACJ,QAAO;GAIR,MAAM,iBAAiB,IAAI,IAAI,WAAW,CAAC;AAC3C,gBAAa,IAAI,IAAI,UAAU,WAAW,CAAC;AAG3C,OAAI,mBAFe,IAAI,IAAI,WAAW,CAAC,UAEF,YACpC,eAAc,uBAAuB,YAAY;;AAInD,QAAM,IAAI,MAAM,WAAW,SAAS,6BAA6B,qBAAqB,GAAG;IAE1F;;;;;;;AAQF,SAAgB,6BAA6B,UAA8B;AAC1E,QAAO,EACN,MAAM,MAAM,KAAa,MAAuC;EAC/D,IAAI,aAAa;EACjB,IAAI,cAAc;AAElB,OAAK,IAAI,IAAI,GAAG,KAAK,sBAAsB,KAAK;AAG/C,OAAI;AACH,UAAM,8BAA8B,WAAW;YACvC,GAAG;IACX,MAAM,MAAM,aAAa,YAAY,EAAE,UAAU;AACjD,UAAM,IAAI,MACT,WAAW,SAAS,uBAAuB,IAAI,IAAI,WAAW,CAAC,SAAS,KAAK,OAC7E,EAAE,OAAO,GAAG,CACZ;;GAGF,MAAM,WAAW,MAAM,WAAW,MAAM,YAAY;IACnD,GAAG;IACH,UAAU;IACV,CAAC;AAGF,OAAI,SAAS,SAAS,OAAO,SAAS,UAAU,IAC/C,QAAO;GAIR,MAAM,WAAW,SAAS,QAAQ,IAAI,WAAW;AACjD,OAAI,CAAC,SACJ,QAAO;GAIR,MAAM,iBAAiB,IAAI,IAAI,WAAW,CAAC;AAC3C,gBAAa,IAAI,IAAI,UAAU,WAAW,CAAC;AAG3C,OAAI,mBAFe,IAAI,IAAI,WAAW,CAAC,UAEF,YACpC,eAAc,uBAAuB,YAAY;;AAInD,QAAM,IAAI,MAAM,WAAW,SAAS,6BAA6B,qBAAqB,GAAG;IAE1F;;;;;AAwBF,SAAgB,gBAAgB,UAA6B;CAC5D,MAAM,SAAS,WAAW,SAAS;AAEnC,QAAO;EACN,MAAM,SAAiB,MAAsB;AAC5C,OAAI,SAAS,OACZ,SAAQ,MAAM,QAAQ,SAAS,KAAK;OAEpC,SAAQ,MAAM,QAAQ,QAAQ;;EAIhC,KAAK,SAAiB,MAAsB;AAC3C,OAAI,SAAS,OACZ,SAAQ,KAAK,QAAQ,SAAS,KAAK;OAEnC,SAAQ,KAAK,QAAQ,QAAQ;;EAI/B,KAAK,SAAiB,MAAsB;AAC3C,OAAI,SAAS,OACZ,SAAQ,KAAK,QAAQ,SAAS,KAAK;OAEnC,SAAQ,KAAK,QAAQ,QAAQ;;EAI/B,MAAM,SAAiB,MAAsB;AAC5C,OAAI,SAAS,OACZ,SAAQ,MAAM,QAAQ,SAAS,KAAK;OAEpC,SAAQ,MAAM,QAAQ,QAAQ;;EAGhC;;AAOF,MAAM,oBAAoB;;;;;;;;;AAwB1B,SAAgB,eAAe,SAAoC;AAClE,QAAO;EACN,MAAM,QAAQ,YAAY;EAC1B,MAAM,QAAQ,WAAW,IAAI,QAAQ,mBAAmB,GAAG;EAC3D,QAAQ,QAAQ,UAAU;EAC1B,eAAe,QAAQ,iBAAiB;EACxC;;;;;;AAOF,SAAgB,gBAAgB,SAA2C;CAC1E,MAAM,OAAO,QAAQ,QAAQ,mBAAmB,GAAG;AAEnD,SAAQ,SAAyB;AAChC,MAAI,CAAC,KAAK,WAAW,IAAI,CACxB,OAAM,IAAI,MAAM,uCAAuC,KAAK,GAAG;AAEhE,MAAI,KAAK,WAAW,KAAK,CACxB,OAAM,IAAI,MAAM,iDAAiD,KAAK,GAAG;AAE1E,SAAO,GAAG,OAAO;;;;;;;AAYnB,SAAS,WAAW,MAMP;AACZ,QAAO;EACN,IAAI,KAAK;EACT,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,MAAM,KAAK;EACX,WAAW,KAAK;EAChB;;;;;;AAOF,SAAgB,iBAAiB,IAAkC;CAClE,MAAM,WAAW,IAAI,eAAe,GAAG;AAEvC,QAAO;EACN,MAAM,IAAI,IAAsC;GAC/C,MAAM,OAAO,MAAM,SAAS,SAAS,GAAG;AACxC,OAAI,CAAC,KAAM,QAAO;AAClB,UAAO,WAAW,KAAK;;EAGxB,MAAM,WAAW,OAAyC;GACzD,MAAM,OAAO,MAAM,SAAS,YAAY,MAAM;AAC9C,OAAI,CAAC,KAAM,QAAO;AAClB,UAAO,WAAW,KAAK;;EAGxB,MAAM,KAAK,MAI6C;GACvD,MAAM,SAAS,MAAM,SAAS,SAAS;IACtC,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,OAAO,MAAM;IACb,CAAC;AAEF,UAAO;IACN,OAAO,OAAO,MAAM,IAAI,WAAW;IACnC,YAAY,OAAO;IACnB;;EAEF;;;;;AA6DF,IAAa,uBAAb,MAAkC;CACjC,AAAQ;CACR,AAAQ;CACR,AAAQ;CAIR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;;;;;;CAMR,AAAQ,4CAA4B,IAAI,KAAa;CAErD,YAAY,SAAsC;EACjD,MAAM,UAAU,QAAQ;AACxB,OAAK,YAAY,QAAQ,gBAAgB;AACzC,OAAK,UAAU,QAAQ;AACvB,OAAK,eAAe,QAAQ;AAC5B,OAAK,OAAO,eAAe,QAAQ,YAAY,EAAE,CAAC;AAClD,OAAK,YAAY,gBAAgB,KAAK,KAAK,IAAI;AAC/C,OAAK,iBAAiB,QAAQ;AAC9B,OAAK,gBAAgB,QAAQ;;;;;CAM9B,cAAc,QAAuC;EACpD,MAAM,eAAe,IAAI,IAAI,OAAO,aAAa;EAMjD,MAAM,KAAK,KAAK,WAAW;EAI3B,MAAM,KAAK,eAHS,IAAI,kBAAkB,GAAG,EAGN,OAAO,GAAG;EACjD,MAAM,MAAM,gBAAgB,OAAO,GAAG;EACtC,MAAM,UAAU,oBAAoB,IAAI,OAAO,IAAI,OAAO,QAAQ;EAMlE,IAAI;AACJ,MAAI,aAAa,IAAI,gBAAgB,CACpC,WAAU,6BAA6B,GAAG;WAChC,aAAa,IAAI,eAAe,CAC1C,WAAU,oBAAoB,GAAG;EAIlC,IAAI;AACJ,MAAI,aAAa,IAAI,kBAAkB,CACtC,cAAa,qBAAqB,GAAG;EAQtC,IAAI;AACJ,MAAI,aAAa,IAAI,cAAc,CAClC,KAAI,KAAK,gBAAgB,KAAK,QAC7B,SAAQ,2BAA2B,IAAI,KAAK,cAAc,KAAK,QAAQ;OACjE;AACN,OAAI,CAAC,KAAK,0BAA0B,IAAI,OAAO,GAAG,EAAE;AACnD,SAAK,0BAA0B,IAAI,OAAO,GAAG;AAC7C,QAAI,KACH,qGACA;;AAEF,OAAI,aAAa,IAAI,aAAa,CACjC,SAAQ,kBAAkB,GAAG;;WAGrB,aAAa,IAAI,aAAa,CACxC,SAAQ,kBAAkB,GAAG;EAI9B,IAAI;AACJ,MAAI,aAAa,IAAI,+BAA+B,CACnD,QAAO,6BAA6B,OAAO,GAAG;WACpC,aAAa,IAAI,kBAAkB,CAC7C,QAAO,iBAAiB,OAAO,IAAI,OAAO,aAAa;EAIxD,IAAI;AACJ,MAAI,aAAa,IAAI,aAAa,CACjC,SAAQ,iBAAiB,GAAG;EAK7B,IAAI;AACJ,MAAI,KAAK,eACR,QAAO,IAAI,eAAe,IAAI,OAAO,IAAI,KAAK,eAAe;EAI9D,IAAI;AACJ,MAAI,aAAa,IAAI,aAAa,IAAI,KAAK,eAAe,aAAa,EAAE;GACxE,MAAM,WAAW,KAAK;GACtB,MAAM,WAAW,OAAO;AACxB,WAAQ,EACP,OAAO,YAAY,SAAS,KAAK,SAAS,SAAS,EACnD;;AAGF,SAAO;GACN,QAAQ;IACP,IAAI,OAAO;IACX,SAAS,OAAO;IAChB;GACD;GACA;GACA;GACA;GACA;GACA;GACA;GACA,MAAM,KAAK;GACX,KAAK,KAAK;GACV;GACA;GACA;GACA"}
|