emdash 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activation-Dim4YMM7.mjs → activation-CGlyigmg.mjs} +3 -3
- package/dist/{activation-Dim4YMM7.mjs.map → activation-CGlyigmg.mjs.map} +1 -1
- package/dist/{adapters-F_xorvWv.d.mts → adapters-D9a1PfqH.d.mts} +1 -1
- package/dist/{adapters-F_xorvWv.d.mts.map → adapters-D9a1PfqH.d.mts.map} +1 -1
- package/dist/{after-PcEvMY_6.mjs → after-32yNRvd2.mjs} +5 -2
- package/dist/{after-PcEvMY_6.mjs.map → after-32yNRvd2.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +4 -4
- package/dist/api/route-utils.mjs +50 -49
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +7 -7
- package/dist/{api-D-7BNHoz.mjs → api--PYoyeTo.mjs} +121 -48
- package/dist/api--PYoyeTo.mjs.map +1 -0
- package/dist/api-tokens-DqSlkDVI.mjs +9 -0
- package/dist/api-tokens-DqSlkDVI.mjs.map +1 -0
- package/dist/{api-tokens-Bx38ZCQp.mjs → api-tokens-GNCsIegW.mjs} +3 -3
- package/dist/{api-tokens-Bx38ZCQp.mjs.map → api-tokens-GNCsIegW.mjs.map} +1 -1
- package/dist/{apply-T7ATgzCh.mjs → apply-DVnupNeS.mjs} +149 -91
- package/dist/apply-DVnupNeS.mjs.map +1 -0
- package/dist/{approvals-D-m6wvGl.mjs → approvals-DTtYSB27.mjs} +2 -2
- package/dist/{approvals-D-m6wvGl.mjs.map → approvals-DTtYSB27.mjs.map} +1 -1
- package/dist/{artifacts-htGyfCSt.mjs → artifacts-Dvdp2KK-.mjs} +29 -13
- package/dist/artifacts-Dvdp2KK-.mjs.map +1 -0
- package/dist/astro/image-endpoint.mjs +12 -2
- package/dist/astro/image-endpoint.mjs.map +1 -1
- package/dist/astro/index.d.mts +15 -14
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +59 -8
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +13 -12
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +10 -8
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/media-usage-write-fence.mjs +5 -5
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +129 -49
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +18 -9
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware.d.mts +13 -12
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +111 -101
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +11 -11
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +11 -11
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +14 -14
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +14 -14
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +14 -14
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +25 -25
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +25 -25
- package/dist/astro/routes/api/admin/bylines/index.mjs +25 -25
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +17 -17
- package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/bulk.mjs +16 -16
- package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/index.mjs +16 -16
- package/dist/astro/routes/api/admin/core-update.mjs +6 -6
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
- package/dist/astro/routes/api/admin/media-usage/activation.mjs +18 -18
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +19 -19
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +19 -19
- package/dist/astro/routes/api/admin/media-usage/progress.mjs +22 -21
- package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +26 -25
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +19 -19
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +19 -19
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +7 -7
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +7 -7
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +66 -65
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +66 -65
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +66 -65
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +73 -72
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +66 -65
- 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 +66 -65
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +73 -72
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +66 -65
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +66 -65
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +74 -73
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +68 -67
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +67 -66
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +66 -65
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +66 -65
- 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 +66 -65
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/_...path_.mjs +3 -3
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +33 -32
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +33 -32
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/approvals/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/capabilities.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/capabilities.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +36 -35
- package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +36 -35
- package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +36 -35
- package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/exports/index.mjs +37 -36
- package/dist/astro/routes/api/admin/transfer/exports/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +37 -36
- package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +37 -36
- package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +37 -36
- package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +35 -34
- package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/imports/index.mjs +37 -36
- package/dist/astro/routes/api/admin/transfer/imports/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +12 -12
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +11 -11
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/handover.mjs +5 -5
- package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/complete.mjs +15 -15
- package/dist/astro/routes/api/auth/invite/index.mjs +12 -12
- package/dist/astro/routes/api/auth/invite/register-options.mjs +13 -13
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +14 -14
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +13 -13
- package/dist/astro/routes/api/auth/me.mjs +12 -12
- package/dist/astro/routes/api/auth/mode.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +8 -8
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +11 -11
- package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/options.mjs +15 -15
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +13 -13
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -13
- package/dist/astro/routes/api/auth/passkey/verify.mjs +15 -15
- package/dist/astro/routes/api/auth/signup/complete.mjs +15 -15
- package/dist/astro/routes/api/auth/signup/request.mjs +13 -13
- package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
- package/dist/astro/routes/api/calendar.mjs +12 -12
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +12 -12
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +29 -28
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +34 -33
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +34 -33
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +15 -15
- 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 +35 -27
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +8 -4
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/index.mjs +12 -12
- package/dist/astro/routes/api/content/_collection_/trash.mjs +12 -12
- package/dist/astro/routes/api/dashboard.mjs +22 -16
- package/dist/astro/routes/api/dashboard.mjs.map +1 -1
- package/dist/astro/routes/api/dev/emails.mjs +3 -3
- package/dist/astro/routes/api/dev/scheduled-tasks.mjs +97 -95
- package/dist/astro/routes/api/dev/scheduled-tasks.mjs.map +1 -1
- package/dist/astro/routes/api/health.mjs +4 -4
- package/dist/astro/routes/api/import/probe.d.mts +4 -3
- package/dist/astro/routes/api/import/probe.d.mts.map +1 -1
- package/dist/astro/routes/api/import/probe.mjs +27 -25
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.d.mts +4 -3
- package/dist/astro/routes/api/import/wordpress/analyze.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +7 -29
- package/dist/astro/routes/api/import/wordpress/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +14 -13
- package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +29 -29
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -12
- package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +14 -14
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +19 -19
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +14 -13
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +25 -24
- 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 +27 -25
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +14 -13
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +39 -38
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +4 -4
- package/dist/astro/routes/api/mcp.mjs +99 -83
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +16 -16
- package/dist/astro/routes/api/media/_id_/replace.mjs +12 -12
- package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
- package/dist/astro/routes/api/media/_id_/usage.mjs +28 -27
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +26 -25
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +5 -5
- package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +62 -8
- package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
- package/dist/astro/routes/api/media/folders/_id_.mjs +13 -13
- package/dist/astro/routes/api/media/folders/index.mjs +13 -13
- 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 +15 -15
- package/dist/astro/routes/api/media.mjs +30 -29
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +17 -15
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +17 -15
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +17 -15
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +17 -15
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +17 -15
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +17 -15
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +2 -2
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
- package/dist/astro/routes/api/oauth/register.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.d.mts.map +1 -1
- package/dist/astro/routes/api/oauth/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/token.mjs.map +1 -1
- package/dist/astro/routes/api/openapi.json.mjs +12 -12
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +9 -9
- package/dist/astro/routes/api/redirects/404s/index.mjs +20 -19
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +20 -19
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +21 -20
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +21 -20
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +34 -33
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +34 -33
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +15 -15
- package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +66 -65
- package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +66 -65
- package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -1
- package/dist/astro/routes/api/schema/block-types/index.mjs +66 -65
- package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +66 -65
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +66 -65
- 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 +66 -65
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +66 -65
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +66 -65
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +66 -65
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +17 -16
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +66 -65
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +66 -65
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +20 -18
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +19 -17
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +20 -18
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +13 -11
- package/dist/astro/routes/api/search/stats.mjs.map +1 -1
- package/dist/astro/routes/api/search/suggest.mjs +19 -17
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +19 -17
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +19 -17
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
- package/dist/astro/routes/api/settings/domain-notify.mjs +7 -7
- package/dist/astro/routes/api/settings/domain.mjs +19 -17
- package/dist/astro/routes/api/settings/domain.mjs.map +1 -1
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +25 -23
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +18 -18
- package/dist/astro/routes/api/setup/admin.mjs +14 -14
- package/dist/astro/routes/api/setup/dev-bypass.mjs +45 -44
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.mjs +48 -47
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +4 -4
- package/dist/astro/routes/api/site/domain-proof.mjs +17 -15
- package/dist/astro/routes/api/site/domain-proof.mjs.map +1 -1
- package/dist/astro/routes/api/snapshot.mjs +9 -9
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +35 -34
- package/dist/astro/routes/api/taxonomies/bulk-tag.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +25 -24
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/visual-editing/action-token.mjs +5 -5
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +17 -17
- package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +3 -3
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +24 -13
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +22 -17
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +21 -17
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +14 -10
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +21 -17
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +3 -3
- package/dist/astro/routes/robots.txt.mjs +16 -14
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +18 -16
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +16 -14
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +22 -20
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{audit-_gieLVGU.mjs → audit-B8XE-0UV.mjs} +2 -2
- package/dist/{audit-_gieLVGU.mjs.map → audit-B8XE-0UV.mjs.map} +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/auth/providers/microsoft.d.mts +1 -1
- package/dist/{authorize-DPFBGbE6.mjs → authorize-NJgp8kLI.mjs} +2 -2
- package/dist/{authorize-DPFBGbE6.mjs.map → authorize-NJgp8kLI.mjs.map} +1 -1
- package/dist/{backup-3FgVxiDJ.mjs → backup-8rPSJ28a.mjs} +4 -4
- package/dist/{backup-3FgVxiDJ.mjs.map → backup-8rPSJ28a.mjs.map} +1 -1
- package/dist/{block-types-CYcG56JB.mjs → block-types-BbOrfnWs.mjs} +5 -5
- package/dist/{block-types-CYcG56JB.mjs.map → block-types-BbOrfnWs.mjs.map} +1 -1
- package/dist/{block-values-gCX4HBSl.mjs → block-values-Dg0-8PPS.mjs} +4 -4
- package/dist/{block-values-gCX4HBSl.mjs.map → block-values-Dg0-8PPS.mjs.map} +1 -1
- package/dist/{byline-BR1qhyiw.mjs → byline-DHJS-GGb.mjs} +123 -38
- package/dist/byline-DHJS-GGb.mjs.map +1 -0
- package/dist/{byline-fields-BLXZnX7N.mjs → byline-fields-CiR8FvvF.mjs} +3 -3
- package/dist/{byline-fields-BLXZnX7N.mjs.map → byline-fields-CiR8FvvF.mjs.map} +1 -1
- package/dist/{byline-fields-4ClDC88c.mjs → byline-fields-D6KYsEa7.mjs} +2 -2
- package/dist/{byline-fields-4ClDC88c.mjs.map → byline-fields-D6KYsEa7.mjs.map} +1 -1
- package/dist/{byline-registry-dvt4N3xV.mjs → byline-registry-BhzpO41z.mjs} +3 -3
- package/dist/{byline-registry-dvt4N3xV.mjs.map → byline-registry-BhzpO41z.mjs.map} +1 -1
- package/dist/{bylines-CVOvRUL7.mjs → bylines-Bsg5cH4S.mjs} +7 -7
- package/dist/bylines-Bsg5cH4S.mjs.map +1 -0
- package/dist/{bylines-DxOs9B7z.mjs → bylines-f_ZEc6Sn.mjs} +8 -8
- package/dist/{bylines-DxOs9B7z.mjs.map → bylines-f_ZEc6Sn.mjs.map} +1 -1
- package/dist/{cache-CEU9wo_h.mjs → cache-5DHbpEWu.mjs} +4 -4
- package/dist/{cache-CEU9wo_h.mjs.map → cache-5DHbpEWu.mjs.map} +1 -1
- package/dist/{capture-triggers-CT6dK9zd.mjs → capture-triggers-BezAvWcB.mjs} +2 -2
- package/dist/{capture-triggers-CT6dK9zd.mjs.map → capture-triggers-BezAvWcB.mjs.map} +1 -1
- package/dist/{challenge-store-C17BYx2-.mjs → challenge-store-B1C0V4b3.mjs} +1 -1
- package/dist/{challenge-store-C17BYx2-.mjs.map → challenge-store-B1C0V4b3.mjs.map} +1 -1
- package/dist/{chunks-xC4UD5-S.mjs → chunks-DiW8VMwz.mjs} +2 -2
- package/dist/{chunks-xC4UD5-S.mjs.map → chunks-DiW8VMwz.mjs.map} +1 -1
- package/dist/cli/index.mjs +53 -52
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +61 -61
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +3 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/{collection-slugs-cache-e09QeJQt.mjs → collection-slugs-cache-DIrLjqH-.mjs} +20 -14
- package/dist/collection-slugs-cache-DIrLjqH-.mjs.map +1 -0
- package/dist/{comment-UvIWDKcW.mjs → comment-DB74Zpa4.mjs} +18 -8
- package/dist/comment-DB74Zpa4.mjs.map +1 -0
- package/dist/{comment-reaction-dGp6_FRo.mjs → comment-reaction-BScxIWUn.mjs} +2 -2
- package/dist/{comment-reaction-dGp6_FRo.mjs.map → comment-reaction-BScxIWUn.mjs.map} +1 -1
- package/dist/{comments-pej-_BUO.mjs → comments-Cor_zKml.mjs} +3 -3
- package/dist/{comments-pej-_BUO.mjs.map → comments-Cor_zKml.mjs.map} +1 -1
- package/dist/config-Cs_AwWXI.mjs +106 -0
- package/dist/config-Cs_AwWXI.mjs.map +1 -0
- package/dist/{config-CFMojMFK.d.mts → config-D44_8G9J.d.mts} +3 -1
- package/dist/config-D44_8G9J.d.mts.map +1 -0
- package/dist/{config-Bsm_u_oE.d.mts → config-sDhZvkLR.d.mts} +56 -20
- package/dist/config-sDhZvkLR.d.mts.map +1 -0
- package/dist/{content-DQSRZTVw.mjs → content-BKHUVsE5.mjs} +67 -30
- package/dist/content-BKHUVsE5.mjs.map +1 -0
- package/dist/{content-list-query-CYKcY6Vi.d.mts → content-list-query-ByP4jRpZ.d.mts} +1 -1
- package/dist/{content-list-query-CYKcY6Vi.d.mts.map → content-list-query-ByP4jRpZ.d.mts.map} +1 -1
- package/dist/{content-policy-Dm32n9C6.mjs → content-policy-BmE0MPZY.mjs} +1 -1
- package/dist/{content-policy-Dm32n9C6.mjs.map → content-policy-BmE0MPZY.mjs.map} +1 -1
- package/dist/{content-repair-DNh5--x5.mjs → content-repair-Ukdxoq-R.mjs} +4 -4
- package/dist/{content-repair-DNh5--x5.mjs.map → content-repair-Ukdxoq-R.mjs.map} +1 -1
- package/dist/{context-BYptmCN0.mjs → context-CzjejcsE.mjs} +32 -33
- package/dist/context-CzjejcsE.mjs.map +1 -0
- package/dist/{credits-B9iORdWB.mjs → credits-BKREepht.mjs} +3 -3
- package/dist/{credits-B9iORdWB.mjs.map → credits-BKREepht.mjs.map} +1 -1
- package/dist/{dashboard-D0jcgBD7.mjs → dashboard-ynr1qJWM.mjs} +10 -9
- package/dist/dashboard-ynr1qJWM.mjs.map +1 -0
- package/dist/database/migration-lock.mjs +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +3 -3
- package/dist/db/libsql-migrations.d.mts +1 -1
- package/dist/db/libsql-migrations.mjs +5 -5
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +1 -1
- package/dist/db/postgres-migrations.mjs +5 -5
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite-migrations.d.mts +1 -1
- package/dist/db/sqlite-migrations.mjs +5 -5
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-CBD5yMro.mjs → db-errors-unnO6yiZ.mjs} +12 -2
- package/dist/db-errors-unnO6yiZ.mjs.map +1 -0
- package/dist/deferred-tasks-wlMFJL95.d.mts +10 -0
- package/dist/deferred-tasks-wlMFJL95.d.mts.map +1 -0
- package/dist/{destination-r2zIAZ2S.mjs → destination-DR3IHjmy.mjs} +1 -1
- package/dist/{destination-r2zIAZ2S.mjs.map → destination-DR3IHjmy.mjs.map} +1 -1
- package/dist/{device-flow-BkFKBoAn.mjs → device-flow-D1U392GC.mjs} +3 -5
- package/dist/device-flow-D1U392GC.mjs.map +1 -0
- package/dist/{dialect-helpers-7_IqPdh9.mjs → dialect-helpers-B1XN2HGB.mjs} +10 -3
- package/dist/dialect-helpers-B1XN2HGB.mjs.map +1 -0
- package/dist/{direct-executor-Cbxzg4qe.mjs → direct-executor-Bs2g0ake.mjs} +4 -4
- package/dist/{direct-executor-Cbxzg4qe.mjs.map → direct-executor-Bs2g0ake.mjs.map} +1 -1
- package/dist/{dist-Cfu8mMBs.mjs → dist-BUmWMTbN.mjs} +1 -1
- package/dist/{dist-Cfu8mMBs.mjs.map → dist-BUmWMTbN.mjs.map} +1 -1
- package/dist/{email-locale-BJ8DjTzZ.mjs → email-locale-Bs3HFdnq.mjs} +1 -1
- package/dist/{email-locale-BJ8DjTzZ.mjs.map → email-locale-Bs3HFdnq.mjs.map} +1 -1
- package/dist/{emdash-runtime-DVi7vtXC.mjs → emdash-runtime-C8T-pZpS.mjs} +108 -62
- package/dist/emdash-runtime-C8T-pZpS.mjs.map +1 -0
- package/dist/{enrich-Duu4sV3a.mjs → enrich-DFlfnd3u.mjs} +3 -3
- package/dist/{enrich-Duu4sV3a.mjs.map → enrich-DFlfnd3u.mjs.map} +1 -1
- package/dist/{entry-lock-C7VX0NGK.d.mts → entry-lock-BulbgPBX.d.mts} +23 -23
- package/dist/{entry-lock-C7VX0NGK.d.mts.map → entry-lock-BulbgPBX.d.mts.map} +1 -1
- package/dist/{entry-lock-Df6-XhIu.mjs → entry-lock-CRVmruM0.mjs} +4 -4
- package/dist/{entry-lock-Df6-XhIu.mjs.map → entry-lock-CRVmruM0.mjs.map} +1 -1
- package/dist/{entry-lock-DLxCffFW.mjs → entry-lock-D-zJQ4q6.mjs} +3 -3
- package/dist/{entry-lock-DLxCffFW.mjs.map → entry-lock-D-zJQ4q6.mjs.map} +1 -1
- package/dist/{entry-locks-DJAqVweV.mjs → entry-locks-CJ9uhqg1.mjs} +2 -2
- package/dist/{entry-locks-DJAqVweV.mjs.map → entry-locks-CJ9uhqg1.mjs.map} +1 -1
- package/dist/{env-types-C3FCVBWe.mjs → env-types-BE3bCvfP.mjs} +5 -5
- package/dist/{env-types-C3FCVBWe.mjs.map → env-types-BE3bCvfP.mjs.map} +1 -1
- package/dist/{error-CVWeIOku.mjs → error-BCMgPxdn.mjs} +3 -3
- package/dist/{error-CVWeIOku.mjs.map → error-BCMgPxdn.mjs.map} +1 -1
- package/dist/{errors-BOYNdIo3.mjs → errors-8gJqv-XV.mjs} +15 -2
- package/dist/{errors-BOYNdIo3.mjs.map → errors-8gJqv-XV.mjs.map} +1 -1
- package/dist/{fence-C3MUitUN.mjs → fence-Dagicj14.mjs} +4 -4
- package/dist/{fence-C3MUitUN.mjs.map → fence-Dagicj14.mjs.map} +1 -1
- package/dist/{field-map-Bft9LKak.mjs → field-map-BSXdNGt5.mjs} +11 -10
- package/dist/field-map-BSXdNGt5.mjs.map +1 -0
- package/dist/{focal-point-JX8kcOTQ.mjs → focal-point-CgCcr1Ca.mjs} +1 -1
- package/dist/{focal-point-JX8kcOTQ.mjs.map → focal-point-CgCcr1Ca.mjs.map} +1 -1
- package/dist/{fts-manager-Beg3e4bI.mjs → fts-manager-6861qcwk.mjs} +68 -22
- package/dist/fts-manager-6861qcwk.mjs.map +1 -0
- package/dist/{gc-ByyAYiYE.mjs → gc-CNkXB3mE.mjs} +8 -8
- package/dist/{gc-ByyAYiYE.mjs.map → gc-CNkXB3mE.mjs.map} +1 -1
- package/dist/{hook-result-BrdLyOdI.d.mts → hook-result-f8t7hIuu.d.mts} +1 -1
- package/dist/{hook-result-BrdLyOdI.d.mts.map → hook-result-f8t7hIuu.d.mts.map} +1 -1
- package/dist/{hreflang-CY_F5OjF.d.mts → hreflang-8dUOJAPJ.d.mts} +2 -2
- package/dist/{hreflang-CY_F5OjF.d.mts.map → hreflang-8dUOJAPJ.d.mts.map} +1 -1
- package/dist/{hreflang-C8Jb-hrf.mjs → hreflang-BW5A4FLK.mjs} +8 -8
- package/dist/{hreflang-C8Jb-hrf.mjs.map → hreflang-BW5A4FLK.mjs.map} +1 -1
- package/dist/{http-route-dispatch-CyeW6iPv.mjs → http-route-dispatch-rXKvTNm9.mjs} +5 -5
- package/dist/{http-route-dispatch-CyeW6iPv.mjs.map → http-route-dispatch-rXKvTNm9.mjs.map} +1 -1
- package/dist/{identity-Cael5uTT.mjs → identity-CPRRSovU.mjs} +3 -3
- package/dist/{identity-Cael5uTT.mjs.map → identity-CPRRSovU.mjs.map} +1 -1
- package/dist/{import-BUXjdZUn.mjs → import-yVbfPyfd.mjs} +5 -5
- package/dist/{import-BUXjdZUn.mjs.map → import-yVbfPyfd.mjs.map} +1 -1
- package/dist/index-VM7Fzxwe.d.mts +260 -0
- package/dist/index-VM7Fzxwe.d.mts.map +1 -0
- package/dist/index.d.mts +25 -24
- package/dist/index.mjs +89 -88
- package/dist/{install-finalization-3JX5qQEA.mjs → install-finalization-RU6VrHOX.mjs} +1 -1
- package/dist/{install-finalization-3JX5qQEA.mjs.map → install-finalization-RU6VrHOX.mjs.map} +1 -1
- package/dist/{json-recordset-BUjxhr77.mjs → json-recordset-D0WhN2UA.mjs} +2 -2
- package/dist/{json-recordset-BUjxhr77.mjs.map → json-recordset-D0WhN2UA.mjs.map} +1 -1
- package/dist/{lifecycle-7vUJeFRe.mjs → lifecycle-B8d7Rdqx.mjs} +1 -1
- package/dist/{lifecycle-7vUJeFRe.mjs.map → lifecycle-B8d7Rdqx.mjs.map} +1 -1
- package/dist/{load-CdAG9wz-.mjs → load-D4xtzm6h.mjs} +2 -2
- package/dist/{load-CdAG9wz-.mjs.map → load-D4xtzm6h.mjs.map} +1 -1
- package/dist/{loader-CswroUbe.mjs → loader-DhnedqY4.mjs} +199 -101
- package/dist/loader-DhnedqY4.mjs.map +1 -0
- package/dist/{loops-uA4iTOzd.mjs → loops-B2YS-Zzr.mjs} +2 -2
- package/dist/{loops-uA4iTOzd.mjs.map → loops-B2YS-Zzr.mjs.map} +1 -1
- package/dist/{manifest-builder-B8zjuH3f.mjs → manifest-builder-g1FLviel.mjs} +10 -3
- package/dist/manifest-builder-g1FLviel.mjs.map +1 -0
- package/dist/media/image-endpoint.d.mts +3 -2
- package/dist/media/image-endpoint.d.mts.map +1 -1
- package/dist/media/image-endpoint.mjs +17 -8
- package/dist/media/image-endpoint.mjs.map +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +7 -6
- package/dist/media/index.mjs.map +1 -1
- package/dist/media/local-runtime.d.mts +15 -14
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +20 -15
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-1WC65OCB.mjs → media-BR4_t-Wd.mjs} +3 -3
- package/dist/{media-1WC65OCB.mjs.map → media-BR4_t-Wd.mjs.map} +1 -1
- package/dist/{media--dJRU6i2.mjs → media-CikS-q6E.mjs} +4 -4
- package/dist/{media--dJRU6i2.mjs.map → media-CikS-q6E.mjs.map} +1 -1
- package/dist/{media-Bws9f18T.mjs → media-DepNLhPq.mjs} +14 -4
- package/dist/media-DepNLhPq.mjs.map +1 -0
- package/dist/{media-allowlist-BJB7MFx6.mjs → media-allowlist-BMBLqyWh.mjs} +2 -2
- package/dist/{media-allowlist-BJB7MFx6.mjs.map → media-allowlist-BMBLqyWh.mjs.map} +1 -1
- package/dist/{media-folders-Dn3C16B8.mjs → media-folders-CI0Wydfo.mjs} +2 -2
- package/dist/{media-folders-Dn3C16B8.mjs.map → media-folders-CI0Wydfo.mjs.map} +1 -1
- package/dist/{media-usage-CF0WVcLO.mjs → media-usage-X1_20MY-.mjs} +8 -8
- package/dist/{media-usage-CF0WVcLO.mjs.map → media-usage-X1_20MY-.mjs.map} +1 -1
- package/dist/{media-usage-activation-C9HwVw5w.mjs → media-usage-activation-bWusy3M2.mjs} +3 -3
- package/dist/{media-usage-activation-C9HwVw5w.mjs.map → media-usage-activation-bWusy3M2.mjs.map} +1 -1
- package/dist/{media-usage-work-m3AWTmmC.mjs → media-usage-work-B2RaoJIa.mjs} +4 -4
- package/dist/{media-usage-work-m3AWTmmC.mjs.map → media-usage-work-B2RaoJIa.mjs.map} +1 -1
- package/dist/{media-usage-work-CkLa5-nc.mjs → media-usage-work-BCK2J-v_.mjs} +7 -7
- package/dist/{media-usage-work-CkLa5-nc.mjs.map → media-usage-work-BCK2J-v_.mjs.map} +1 -1
- package/dist/{menus-BCVSSp1z.mjs → menus-B0Qqv_cS.mjs} +176 -92
- package/dist/menus-B0Qqv_cS.mjs.map +1 -0
- package/dist/{menus-Cl4kwdt7.mjs → menus-oypxT1pI.mjs} +134 -53
- package/dist/menus-oypxT1pI.mjs.map +1 -0
- package/dist/{menus-BSzwObhV.mjs → menus-uEAxWAm1.mjs} +3 -3
- package/dist/{menus-BSzwObhV.mjs.map → menus-uEAxWAm1.mjs.map} +1 -1
- package/dist/migrations/index.d.mts +1 -1
- package/dist/migrations/index.mjs +5 -5
- package/dist/{mime-B5M9R53H.mjs → mime-D1We9jUx.mjs} +1 -1
- package/dist/{mime-B5M9R53H.mjs.map → mime-D1We9jUx.mjs.map} +1 -1
- package/dist/{normalize-BuWa0lvd.mjs → normalize-Cx-bNwhP.mjs} +41 -12
- package/dist/normalize-Cx-bNwhP.mjs.map +1 -0
- package/dist/{oauth-authorization-TpEJrYB7.mjs → oauth-authorization-CPLgBrdo.mjs} +19 -6
- package/dist/oauth-authorization-CPLgBrdo.mjs.map +1 -0
- package/dist/{oauth-state-store-CHMn_mwc.mjs → oauth-state-store-BndCfRum.mjs} +1 -1
- package/dist/{oauth-state-store-CHMn_mwc.mjs.map → oauth-state-store-BndCfRum.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{object-cache-Bo6DGaC9.mjs → object-cache-DmKr09La.mjs} +21 -4
- package/dist/{object-cache-Bo6DGaC9.mjs.map → object-cache-DmKr09La.mjs.map} +1 -1
- package/dist/{options-DJWaq3Ie.d.mts → options-BUUgroHg.d.mts} +3 -3
- package/dist/{options-DJWaq3Ie.d.mts.map → options-BUUgroHg.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/page/index.mjs +16 -14
- package/dist/page/index.mjs.map +1 -1
- package/dist/{parse-DgQ2NABT.mjs → parse-B_b6gZxG.mjs} +2 -2
- package/dist/{parse-DgQ2NABT.mjs.map → parse-B_b6gZxG.mjs.map} +1 -1
- package/dist/{parse-CWHOFc6z.d.mts → parse-Dndx1QmN.d.mts} +3 -3
- package/dist/{parse-CWHOFc6z.d.mts.map → parse-Dndx1QmN.d.mts.map} +1 -1
- package/dist/{passkey-config-D0kdzEao.mjs → passkey-config-D2Lua5__.mjs} +1 -1
- package/dist/{passkey-config-D0kdzEao.mjs.map → passkey-config-D2Lua5__.mjs.map} +1 -1
- package/dist/{patterns-CMNnYCgf.mjs → patterns-CryGz_EV.mjs} +1 -1
- package/dist/{patterns-CMNnYCgf.mjs.map → patterns-CryGz_EV.mjs.map} +1 -1
- package/dist/{placeholder-D41vk11_.d.mts → placeholder-CSKu5OkD.d.mts} +9 -1
- package/dist/placeholder-CSKu5OkD.d.mts.map +1 -0
- package/dist/plugin-test-runtime.d.mts +17 -15
- package/dist/plugin-test-runtime.d.mts.map +1 -1
- package/dist/plugin-test-runtime.mjs +99 -98
- package/dist/plugin-types.d.mts +2 -2
- package/dist/plugin-utils.d.mts +13 -12
- package/dist/plugin-utils.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +13 -12
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/host.d.mts +5 -5
- package/dist/plugins/host.mjs +48 -47
- package/dist/{policy-DLQ--Hnm.mjs → policy-BPugqmeV.mjs} +13 -5
- package/dist/policy-BPugqmeV.mjs.map +1 -0
- package/dist/{protocol-79kUO7jP.d.mts → protocol-PoAfD_9W.d.mts} +2 -2
- package/dist/{protocol-79kUO7jP.d.mts.map → protocol-PoAfD_9W.d.mts.map} +1 -1
- package/dist/{public-plugin-api-routes-BuAMJ8nN.mjs → public-plugin-api-routes-CMljrTby.mjs} +9 -9
- package/dist/{public-plugin-api-routes-BuAMJ8nN.mjs.map → public-plugin-api-routes-CMljrTby.mjs.map} +1 -1
- package/dist/{public-submission-Di3TsgzX.mjs → public-submission-ayqoyee4.mjs} +10 -10
- package/dist/{public-submission-Di3TsgzX.mjs.map → public-submission-ayqoyee4.mjs.map} +1 -1
- package/dist/query-BFHjCzSh.mjs +37 -0
- package/dist/query-BFHjCzSh.mjs.map +1 -0
- package/dist/{query-CpnRlBh8.mjs → query-D62NntCZ.mjs} +55 -65
- package/dist/query-D62NntCZ.mjs.map +1 -0
- package/dist/{query-haKvLAWl.mjs → query-LmPY2y0C.mjs} +5 -5
- package/dist/{query-haKvLAWl.mjs.map → query-LmPY2y0C.mjs.map} +1 -1
- package/dist/{rate-limit-Xp4d0NBY.mjs → rate-limit-C04Llsoc.mjs} +3 -3
- package/dist/{rate-limit-Xp4d0NBY.mjs.map → rate-limit-C04Llsoc.mjs.map} +1 -1
- package/dist/{redirect-DiS_3mA4.mjs → redirect-DjLdw427.mjs} +6 -6
- package/dist/{redirect-DiS_3mA4.mjs.map → redirect-DjLdw427.mjs.map} +1 -1
- package/dist/{redirect-paths-CNYq7w80.mjs → redirect-paths-DOaVBp6E.mjs} +1 -1
- package/dist/{redirect-paths-CNYq7w80.mjs.map → redirect-paths-DOaVBp6E.mjs.map} +1 -1
- package/dist/{redirects-BHpy-VsZ.mjs → redirects-Cpq1catN.mjs} +6 -6
- package/dist/{redirects-BHpy-VsZ.mjs.map → redirects-Cpq1catN.mjs.map} +1 -1
- package/dist/{redirects-BEVSRMa2.mjs → redirects-CyvvZrCJ.mjs} +2 -2
- package/dist/{redirects-BEVSRMa2.mjs.map → redirects-CyvvZrCJ.mjs.map} +1 -1
- package/dist/{registry-TUVaid9Z.mjs → registry-B0-O575m.mjs} +25 -20
- package/dist/registry-B0-O575m.mjs.map +1 -0
- package/dist/{relations-CA_QuMTU.mjs → relations-WIOojTL4.mjs} +24 -11
- package/dist/relations-WIOojTL4.mjs.map +1 -0
- package/dist/{request-cache-C4CIo5ad.mjs → request-cache-5z2WzWmh.mjs} +1 -1
- package/dist/{request-cache-C4CIo5ad.mjs.map → request-cache-5z2WzWmh.mjs.map} +1 -1
- package/dist/request-context.d.mts +12 -1
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{resolve-DwNM-CXl.mjs → resolve-BNnm8EkB.mjs} +14 -13
- package/dist/{resolve-DwNM-CXl.mjs.map → resolve-BNnm8EkB.mjs.map} +1 -1
- package/dist/{resolve-D-kqyKns.mjs → resolve-DdNXBJSr.mjs} +4 -4
- package/dist/resolve-DdNXBJSr.mjs.map +1 -0
- package/dist/{revision-BVp8S0e_.mjs → revision-Dx7Uth1X.mjs} +5 -5
- package/dist/{revision-BVp8S0e_.mjs.map → revision-Dx7Uth1X.mjs.map} +1 -1
- package/dist/{routes-BerZFrvV.mjs → routes-avqgc-8r.mjs} +3 -3
- package/dist/{routes-BerZFrvV.mjs.map → routes-avqgc-8r.mjs.map} +1 -1
- package/dist/{runner-C5NeAkBQ.mjs → runner-Bkkfk4O8.mjs} +78 -4
- package/dist/runner-Bkkfk4O8.mjs.map +1 -0
- package/dist/{runner-BObHz5FC.d.mts → runner-Dfgp6-S_.d.mts} +7 -2
- package/dist/runner-Dfgp6-S_.d.mts.map +1 -0
- package/dist/runtime.d.mts +14 -13
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +8 -6
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema/project-env-types.mjs +43 -42
- package/dist/schema/project-env-types.mjs.map +1 -1
- package/dist/{schema-DJRfvM_o.mjs → schema-B-3rntlX.mjs} +11 -11
- package/dist/{schema-DJRfvM_o.mjs.map → schema-B-3rntlX.mjs.map} +1 -1
- package/dist/{schema-C4OBuv6Z.mjs → schema-BqBlUZtb.mjs} +4 -4
- package/dist/{schema-C4OBuv6Z.mjs.map → schema-BqBlUZtb.mjs.map} +1 -1
- package/dist/{scopes-BU03K7Uo.mjs → scopes-DNaY7Ey0.mjs} +3 -3
- package/dist/{scopes-BU03K7Uo.mjs.map → scopes-DNaY7Ey0.mjs.map} +1 -1
- package/dist/{search-DfVzks89.mjs → search-BPWTnpnD.mjs} +32 -32
- package/dist/search-BPWTnpnD.mjs.map +1 -0
- package/dist/{secrets-Cc_O3Kp-.mjs → secrets-Dswzrz3k.mjs} +2 -2
- package/dist/{secrets-Cc_O3Kp-.mjs.map → secrets-Dswzrz3k.mjs.map} +1 -1
- package/dist/{sections-BMy4JVUz.mjs → sections-rSao6rBG.mjs} +3 -3
- package/dist/{sections-BMy4JVUz.mjs.map → sections-rSao6rBG.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +42 -41
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +30 -28
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-VZYOEy8f.mjs → seo-B_H_pqOd.mjs} +3 -3
- package/dist/{seo-VZYOEy8f.mjs.map → seo-B_H_pqOd.mjs.map} +1 -1
- package/dist/{service-Bo_usgSk.mjs → service-DpiUQVpp.mjs} +2 -2
- package/dist/{service-Bo_usgSk.mjs.map → service-DpiUQVpp.mjs.map} +1 -1
- package/dist/{session-user-DlpPdf8L.mjs → session-user-DiE4JKPP.mjs} +4 -4
- package/dist/{session-user-DlpPdf8L.mjs.map → session-user-DiE4JKPP.mjs.map} +1 -1
- package/dist/{settings-DS914jjE.mjs → settings--XlUdUaI.mjs} +3 -3
- package/dist/{settings-DS914jjE.mjs.map → settings--XlUdUaI.mjs.map} +1 -1
- package/dist/{settings-CWb84sDm.mjs → settings-D932YP2-.mjs} +111 -44
- package/dist/settings-D932YP2-.mjs.map +1 -0
- package/dist/{setup-complete-BuNxAUiN.mjs → setup-complete-qsJYWutC.mjs} +2 -2
- package/dist/{setup-complete-BuNxAUiN.mjs.map → setup-complete-qsJYWutC.mjs.map} +1 -1
- package/dist/{setup-nonce-BnEAX0iy.mjs → setup-nonce-CBFgUFXw.mjs} +1 -1
- package/dist/{setup-nonce-BnEAX0iy.mjs.map → setup-nonce-CBFgUFXw.mjs.map} +1 -1
- package/dist/{site-domain-Bq5j5bVg.mjs → site-domain-oRGS_JfA.mjs} +2 -2
- package/dist/{site-domain-Bq5j5bVg.mjs.map → site-domain-oRGS_JfA.mjs.map} +1 -1
- package/dist/{sitemap-name-BTg2NJJv.mjs → sitemap-name-B8QP8wud.mjs} +1 -1
- package/dist/{sitemap-name-BTg2NJJv.mjs.map → sitemap-name-B8QP8wud.mjs.map} +1 -1
- package/dist/{slugify-bu2-jKia.mjs → slugify-BDEG9pLk.mjs} +1 -1
- package/dist/{slugify-bu2-jKia.mjs.map → slugify-BDEG9pLk.mjs.map} +1 -1
- package/dist/{snapshot-DYsG4uOq.mjs → snapshot-BjaXDZQ5.mjs} +2 -2
- package/dist/{snapshot-DYsG4uOq.mjs.map → snapshot-BjaXDZQ5.mjs.map} +1 -1
- package/dist/{stage-D8DWgPcO.mjs → stage-CZQnXM_G.mjs} +3 -3
- package/dist/{stage-D8DWgPcO.mjs.map → stage-CZQnXM_G.mjs.map} +1 -1
- package/dist/{staged-CD_imitH.mjs → staged-ByBmPFtX.mjs} +3 -3
- package/dist/{staged-CD_imitH.mjs.map → staged-ByBmPFtX.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/{storage-query-DRyu3bSU.d.mts → storage-query-BFp06Y0s.d.mts} +22 -10
- package/dist/storage-query-BFp06Y0s.d.mts.map +1 -0
- package/dist/{taxonomies-Dd9JJv_a.mjs → taxonomies-5SR76j11.mjs} +15 -17
- package/dist/taxonomies-5SR76j11.mjs.map +1 -0
- package/dist/{taxonomies-ChnU7Mc8.mjs → taxonomies-B__VmCKQ.mjs} +64 -58
- package/dist/taxonomies-B__VmCKQ.mjs.map +1 -0
- package/dist/{taxonomy-C_C_LBAU.mjs → taxonomy-CMI5fczd.mjs} +30 -17
- package/dist/taxonomy-CMI5fczd.mjs.map +1 -0
- package/dist/{transaction-BVYIo_yu.mjs → transaction-CkoQ5wqQ.mjs} +1 -1
- package/dist/{transaction-BVYIo_yu.mjs.map → transaction-CkoQ5wqQ.mjs.map} +1 -1
- package/dist/{transfer-WmWiR3xf.mjs → transfer-BbKfufJ4.mjs} +2 -2
- package/dist/{transfer-WmWiR3xf.mjs.map → transfer-BbKfufJ4.mjs.map} +1 -1
- package/dist/{transfer-ClH6w6BV.mjs → transfer-DTGblAj2.mjs} +71 -34
- package/dist/{transfer-ClH6w6BV.mjs.map → transfer-DTGblAj2.mjs.map} +1 -1
- package/dist/{transfer-download--ofRj9Cw.mjs → transfer-download-D9LQEDX0.mjs} +1 -1
- package/dist/{transfer-download--ofRj9Cw.mjs.map → transfer-download-D9LQEDX0.mjs.map} +1 -1
- package/dist/{transport-DxiXnbXk.d.mts → transport-Dm5QzpT_.d.mts} +1 -1
- package/dist/{transport-DxiXnbXk.d.mts.map → transport-Dm5QzpT_.d.mts.map} +1 -1
- package/dist/{transport-BjbqNiPI.mjs → transport-Dzpn5b3i.mjs} +4 -3
- package/dist/transport-Dzpn5b3i.mjs.map +1 -0
- package/dist/{turnstile-CJwvsOqw.mjs → turnstile-Cs2wfkJG.mjs} +1 -1
- package/dist/{turnstile-CJwvsOqw.mjs.map → turnstile-Cs2wfkJG.mjs.map} +1 -1
- package/dist/{types-BA3ey2QH.d.mts → types-BAsCk5KU.d.mts} +1 -1
- package/dist/{types-BA3ey2QH.d.mts.map → types-BAsCk5KU.d.mts.map} +1 -1
- package/dist/{types-RLgeIpnF.d.mts → types-BuvSwT9Y.d.mts} +8 -7
- package/dist/{types-RLgeIpnF.d.mts.map → types-BuvSwT9Y.d.mts.map} +1 -1
- package/dist/{types-CNB-dj6F.d.mts → types-C4KT3Md2.d.mts} +10 -6
- package/dist/types-C4KT3Md2.d.mts.map +1 -0
- package/dist/{types-DvqVxZXa.d.mts → types-CVXHwIBB.d.mts} +1 -1
- package/dist/{types-DvqVxZXa.d.mts.map → types-CVXHwIBB.d.mts.map} +1 -1
- package/dist/{types-BGmQ0uXs.mjs → types-C_asjp1J.mjs} +1 -1
- package/dist/types-C_asjp1J.mjs.map +1 -0
- package/dist/{types-DHO7Pd25.d.mts → types-CadaL_k8.d.mts} +1 -1
- package/dist/{types-DHO7Pd25.d.mts.map → types-CadaL_k8.d.mts.map} +1 -1
- package/dist/{types-QoPHm-2w.d.mts → types-CjftrmZR.d.mts} +3 -3
- package/dist/{types-QoPHm-2w.d.mts.map → types-CjftrmZR.d.mts.map} +1 -1
- package/dist/{types-DCFavOii.d.mts → types-DKwRUA-x.d.mts} +1 -1
- package/dist/{types-DCFavOii.d.mts.map → types-DKwRUA-x.d.mts.map} +1 -1
- package/dist/{types-D2NEXtgB.mjs → types-DwhEvIVb.mjs} +8 -2
- package/dist/types-DwhEvIVb.mjs.map +1 -0
- package/dist/{types-Bwpzb5o6.d.mts → types-GwAyS5SH.d.mts} +1 -1
- package/dist/{types-Bwpzb5o6.d.mts.map → types-GwAyS5SH.d.mts.map} +1 -1
- package/dist/{types-slSqujQL.d.mts → types-uU2YNYSt.d.mts} +6 -1
- package/dist/types-uU2YNYSt.d.mts.map +1 -0
- package/dist/{update-check-BxNg9T6N.mjs → update-check-ABBJgg6M.mjs} +4 -4
- package/dist/{update-check-BxNg9T6N.mjs.map → update-check-ABBJgg6M.mjs.map} +1 -1
- package/dist/{url-Dn3IStq8.mjs → url-DTfGDJhI.mjs} +1 -1
- package/dist/{url-Dn3IStq8.mjs.map → url-DTfGDJhI.mjs.map} +1 -1
- package/dist/{user-BlCTKVAm.mjs → user-By8SzQxS.mjs} +3 -3
- package/dist/{user-BlCTKVAm.mjs.map → user-By8SzQxS.mjs.map} +1 -1
- package/dist/{utils-TPt7w2cS.mjs → utils-D5uOi6WS.mjs} +13 -5
- package/dist/utils-D5uOi6WS.mjs.map +1 -0
- package/dist/{index-BB9sVisO.d.mts → utils-Szp4iBQB.d.mts} +9 -256
- package/dist/utils-Szp4iBQB.d.mts.map +1 -0
- package/dist/{validate-emKxGC5F.d.mts → validate-DDvxy6qG.d.mts} +8 -8
- package/dist/{validate-emKxGC5F.d.mts.map → validate-DDvxy6qG.d.mts.map} +1 -1
- package/dist/{validate-DxYhJQA-.mjs → validate-Dz-TIOFB.mjs} +7 -7
- package/dist/{validate-DxYhJQA-.mjs.map → validate-Dz-TIOFB.mjs.map} +1 -1
- package/dist/{validation-Ckq4vsVg.mjs → validation-Fc9ZSUS_.mjs} +17 -16
- package/dist/{validation-Ckq4vsVg.mjs.map → validation-Fc9ZSUS_.mjs.map} +1 -1
- package/dist/version-Bu6Nlbb5.mjs +7 -0
- package/dist/{version-DHmGpcch.mjs.map → version-Bu6Nlbb5.mjs.map} +1 -1
- package/dist/version-DqB7LIO7.mjs +49 -0
- package/dist/version-DqB7LIO7.mjs.map +1 -0
- package/dist/{widgets-DA5XmvVC.mjs → widgets-BUx_Mf2q.mjs} +18 -5
- package/dist/widgets-BUx_Mf2q.mjs.map +1 -0
- package/dist/{wxr-taxonomies-RgRG0d5e.mjs → wxr-taxonomies-DbJtWrWM.mjs} +7 -6
- package/dist/wxr-taxonomies-DbJtWrWM.mjs.map +1 -0
- package/dist/{wxr-taxonomies-CrMonejI.d.mts → wxr-taxonomies-Dy2i2ON3.d.mts} +1 -1
- package/dist/wxr-taxonomies-Dy2i2ON3.d.mts.map +1 -0
- package/dist/{zod-generator-D0JKy94C.mjs → zod-generator--nlM0930.mjs} +5 -4
- package/dist/zod-generator--nlM0930.mjs.map +1 -0
- package/package.json +5 -5
- package/src/api/handlers/bulk-tag.ts +11 -3
- package/src/api/handlers/bylines.ts +6 -2
- package/src/api/handlers/content.ts +154 -23
- package/src/api/handlers/dashboard.ts +13 -6
- package/src/api/handlers/device-flow.ts +1 -3
- package/src/api/handlers/menus.ts +27 -13
- package/src/api/handlers/oauth-authorization.ts +1 -3
- package/src/api/handlers/relations.ts +15 -1
- package/src/api/handlers/taxonomies.ts +27 -17
- package/src/astro/image-endpoint.ts +23 -2
- package/src/astro/integration/admin-nav.ts +52 -0
- package/src/astro/integration/index.ts +7 -0
- package/src/astro/integration/runtime.ts +14 -0
- package/src/astro/integration/shims/use-sync-external-store-with-selector.js +2 -0
- package/src/astro/integration/shims/use-sync-external-store.js +4 -1
- package/src/astro/integration/vite-config.ts +16 -8
- package/src/astro/middleware/auth.ts +9 -2
- package/src/astro/middleware/redirect.ts +158 -63
- package/src/astro/middleware/request-context.ts +1 -0
- package/src/astro/middleware.ts +17 -4
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +4 -1
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +14 -1
- package/src/astro/routes/api/dashboard.ts +2 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +5 -29
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -3
- package/src/astro/routes/api/media/file/[...key].ts +31 -7
- package/src/astro/routes/api/oauth/token.ts +5 -1
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +7 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +3 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +2 -1
- package/src/astro/routes/api/widget-areas/[name].ts +2 -1
- package/src/astro/routes/api/widget-areas/index.ts +2 -1
- package/src/astro/types.ts +4 -3
- package/src/auth/api-tokens.ts +3 -0
- package/src/auth/types.ts +6 -0
- package/src/cleanup.ts +44 -0
- package/src/cli/commands/export-seed.ts +6 -4
- package/src/cli/commands/login.ts +2 -4
- package/src/client/index.ts +1 -0
- package/src/client/transport.ts +11 -7
- package/src/components/EmDashImage.astro +14 -2
- package/src/components/InlinePortableTextEditor.tsx +4 -1
- package/src/components/inline-bubble-menu.tsx +109 -95
- package/src/components/inline-image.tsx +54 -48
- package/src/database/dialect-helpers.ts +10 -3
- package/src/database/migrations/content-sized.ts +82 -0
- package/src/database/migrations/policy.ts +28 -2
- package/src/database/migrations/runner.ts +48 -0
- package/src/database/repositories/byline.ts +95 -31
- package/src/database/repositories/comment.ts +17 -7
- package/src/database/repositories/content.ts +96 -23
- package/src/database/repositories/media.ts +17 -0
- package/src/database/repositories/menu.ts +236 -139
- package/src/database/repositories/taxonomy-def.ts +9 -2
- package/src/database/repositories/taxonomy.ts +47 -14
- package/src/database/repositories/types.ts +14 -3
- package/src/database/types.ts +1 -1
- package/src/emdash-runtime.ts +60 -17
- package/src/fields/image.ts +1 -0
- package/src/i18n/config.ts +58 -10
- package/src/i18n/normalize.ts +16 -2
- package/src/i18n/query.ts +47 -0
- package/src/i18n/resolve.ts +8 -3
- package/src/import/utils.ts +15 -2
- package/src/import/wxr-taxonomies.ts +3 -2
- package/src/index.ts +1 -0
- package/src/loader.ts +183 -114
- package/src/mcp/server.ts +28 -1
- package/src/media/caption.ts +30 -0
- package/src/media/content-disposition.ts +56 -0
- package/src/media/image-endpoint.ts +13 -6
- package/src/media/local-runtime.ts +3 -0
- package/src/media/normalize.ts +17 -8
- package/src/media/responsive.ts +12 -2
- package/src/media/types.ts +16 -3
- package/src/media/usage/content-fields.ts +2 -1
- package/src/menus/index.ts +158 -36
- package/src/migrations/manifest.ts +1 -0
- package/src/object-cache/index.ts +22 -0
- package/src/plugin-test-runtime.ts +1 -0
- package/src/plugins/byline-access.ts +3 -1
- package/src/plugins/context.ts +1 -2
- package/src/plugins/storage-indexes.ts +7 -3
- package/src/query.ts +67 -53
- package/src/redirects/artifacts.ts +39 -5
- package/src/references/field-map.ts +6 -4
- package/src/request-context.ts +8 -0
- package/src/schema/collection-slugs-cache.ts +31 -17
- package/src/schema/registry.ts +9 -0
- package/src/schema/types.ts +7 -6
- package/src/schema/zod-generator.ts +2 -1
- package/src/search/fts-manager.ts +103 -31
- package/src/search/query.ts +31 -64
- package/src/seed/apply.ts +221 -109
- package/src/seed/types.ts +6 -4
- package/src/settings/index.ts +135 -35
- package/src/settings/version.ts +54 -0
- package/src/taxonomies/index.ts +99 -85
- package/src/taxonomies/term-counts.ts +2 -1
- package/src/transfer/import/advance.ts +12 -1
- package/src/transfer/import/scaffold.ts +7 -1
- package/src/transfer/import/stages.ts +53 -2
- package/src/utils/db-errors.ts +15 -0
- package/src/utils/revalidating-cache.ts +85 -0
- package/src/virtual-modules.d.ts +1 -0
- package/src/visual-editing/toolbar.ts +7 -2
- package/src/widgets/index.ts +27 -13
- package/dist/api-D-7BNHoz.mjs.map +0 -1
- package/dist/api-tokens-CdP49zVF.mjs +0 -3
- package/dist/apply-T7ATgzCh.mjs.map +0 -1
- package/dist/artifacts-htGyfCSt.mjs.map +0 -1
- package/dist/byline-BR1qhyiw.mjs.map +0 -1
- package/dist/bylines-CVOvRUL7.mjs.map +0 -1
- package/dist/collection-slugs-cache-e09QeJQt.mjs.map +0 -1
- package/dist/comment-UvIWDKcW.mjs.map +0 -1
- package/dist/config-Bsm_u_oE.d.mts.map +0 -1
- package/dist/config-CFMojMFK.d.mts.map +0 -1
- package/dist/config-CvmjJcVw.mjs +0 -73
- package/dist/config-CvmjJcVw.mjs.map +0 -1
- package/dist/content-DQSRZTVw.mjs.map +0 -1
- package/dist/context-BYptmCN0.mjs.map +0 -1
- package/dist/dashboard-D0jcgBD7.mjs.map +0 -1
- package/dist/db-errors-CBD5yMro.mjs.map +0 -1
- package/dist/deferred-tasks-ZvUECSzI.d.mts +0 -9
- package/dist/deferred-tasks-ZvUECSzI.d.mts.map +0 -1
- package/dist/device-flow-BkFKBoAn.mjs.map +0 -1
- package/dist/dialect-helpers-7_IqPdh9.mjs.map +0 -1
- package/dist/emdash-runtime-DVi7vtXC.mjs.map +0 -1
- package/dist/field-defs-cache-Dez5iWJJ.mjs +0 -77
- package/dist/field-defs-cache-Dez5iWJJ.mjs.map +0 -1
- package/dist/field-map-Bft9LKak.mjs.map +0 -1
- package/dist/fts-manager-Beg3e4bI.mjs.map +0 -1
- package/dist/index-BB9sVisO.d.mts.map +0 -1
- package/dist/loader-CswroUbe.mjs.map +0 -1
- package/dist/manifest-builder-B8zjuH3f.mjs.map +0 -1
- package/dist/media-Bws9f18T.mjs.map +0 -1
- package/dist/menus-BCVSSp1z.mjs.map +0 -1
- package/dist/menus-Cl4kwdt7.mjs.map +0 -1
- package/dist/normalize-BuWa0lvd.mjs.map +0 -1
- package/dist/oauth-authorization-TpEJrYB7.mjs.map +0 -1
- package/dist/placeholder-D41vk11_.d.mts.map +0 -1
- package/dist/policy-DLQ--Hnm.mjs.map +0 -1
- package/dist/query-CpnRlBh8.mjs.map +0 -1
- package/dist/registry-TUVaid9Z.mjs.map +0 -1
- package/dist/relations-CA_QuMTU.mjs.map +0 -1
- package/dist/resolve-D-kqyKns.mjs.map +0 -1
- package/dist/runner-BObHz5FC.d.mts.map +0 -1
- package/dist/runner-C5NeAkBQ.mjs.map +0 -1
- package/dist/search-DfVzks89.mjs.map +0 -1
- package/dist/settings-CWb84sDm.mjs.map +0 -1
- package/dist/storage-query-DRyu3bSU.d.mts.map +0 -1
- package/dist/taxonomies-ChnU7Mc8.mjs.map +0 -1
- package/dist/taxonomies-Dd9JJv_a.mjs.map +0 -1
- package/dist/taxonomy-C_C_LBAU.mjs.map +0 -1
- package/dist/transport-BjbqNiPI.mjs.map +0 -1
- package/dist/types-BGmQ0uXs.mjs.map +0 -1
- package/dist/types-CNB-dj6F.d.mts.map +0 -1
- package/dist/types-D2NEXtgB.mjs.map +0 -1
- package/dist/types-slSqujQL.d.mts.map +0 -1
- package/dist/utils-TPt7w2cS.mjs.map +0 -1
- package/dist/version-DHmGpcch.mjs +0 -7
- package/dist/widgets-DA5XmvVC.mjs.map +0 -1
- package/dist/wxr-taxonomies-CrMonejI.d.mts.map +0 -1
- package/dist/wxr-taxonomies-RgRG0d5e.mjs.map +0 -1
- package/dist/zod-generator-D0JKy94C.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-BYptmCN0.mjs","names":["isRecord"],"sources":["../src/plugins/storage-query.ts","../src/plugins/storage-update.ts","../src/database/repositories/plugin-storage.ts","../src/plugins/byline-access.ts","../src/plugins/content-access.ts","../src/plugins/media.ts","../src/plugins/route-error.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 * Error thrown when a guarded `updateIf` loses a concurrent race by aborting\n * rather than resolving to `{ applied: false }`.\n *\n * `updateIf`'s `{ applied: false }` contract (row absent OR guard failed)\n * assumes READ COMMITTED — the default. There, a losing concurrent writer\n * re-evaluates the guard against the winner's freshly committed row and\n * cleanly resolves to `{ applied: false }`. Two aborts escape that contract:\n *\n * - `40001` (serialization_failure), only above READ COMMITTED, where the\n * loser cannot re-read against a newer snapshot.\n * - `40P01` (deadlock_detected), at any isolation level, when transactions\n * take row locks in opposite order.\n *\n * This error surfaces either abort so the caller can retry the write.\n *\n * The no-oversell SAFETY invariant holds either way: a losing writer NEVER\n * applies its update — it either sees `{ applied: false }` or throws here.\n */\nexport class StorageSerializationError extends Error {\n\treadonly code = \"STORAGE_SERIALIZATION_FAILURE\";\n\treadonly retryable = true;\n\t/** The Postgres SQLSTATE that triggered this error (`40001` / `40P01`). */\n\treadonly sqlState?: string;\n\n\tconstructor(message: string, options?: { cause?: unknown; sqlState?: string }) {\n\t\tsuper(message, { cause: options?.cause });\n\t\tthis.name = \"StorageSerializationError\";\n\t\tthis.sqlState = options?.sqlState;\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\t// A filter with no defined bound contributes no SQL. Returning it would\n\t\t// widen the caller's predicate to \"match everything\" — survivable in a\n\t\t// read, but it strips the guard off a conditional write.\n\t\tif (conditions.length === 0) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Range filter for field '${field}' has no defined bound`,\n\t\t\t\tfield,\n\t\t\t\t\"Provide at least one of gt, gte, lt, or lte, or omit the field.\",\n\t\t\t);\n\t\t}\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\t// An empty slot in the join would emit `<cond> AND ` and fail to parse.\n\t\tif (!condition.sql) continue;\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","import { validateJsonFieldName } from \"../database/validate.js\";\nimport type { RangeFilter, WhereClause, WhereValue } from \"./types.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction requireRecord(value: unknown, label: string): Record<string, unknown> {\n\tif (!isRecord(value)) {\n\t\tthrow new TypeError(`${label} must be an object`);\n\t}\n\tconst prototype: unknown = Object.getPrototypeOf(value);\n\tif (prototype !== Object.prototype && prototype !== null) {\n\t\tthrow new TypeError(`${label} must be a plain object`);\n\t}\n\treturn value;\n}\n\nfunction operand(value: unknown): string | number {\n\tif (typeof value === \"string\" || (typeof value === \"number\" && Number.isFinite(value))) {\n\t\treturn value;\n\t}\n\tthrow new TypeError(\"Storage guard operands must be strings or finite numbers\");\n}\n\nfunction normalizeFilter(value: unknown): WhereValue {\n\tif (value === null || typeof value === \"boolean\") return value;\n\tif (typeof value === \"string\" || typeof value === \"number\") return operand(value);\n\tconst filter = requireRecord(value, \"Storage guard filter\");\n\tconst values = filter.in;\n\tif (Array.isArray(values)) {\n\t\treturn { in: Array.from(values, operand) };\n\t}\n\tconst prefix = filter.startsWith;\n\tif (typeof prefix === \"string\") {\n\t\treturn { startsWith: prefix };\n\t}\n\tconst range: RangeFilter = {};\n\tfor (const key of [\"gt\", \"gte\", \"lt\", \"lte\"] as const) {\n\t\tif (key in filter) {\n\t\t\tconst bound = filter[key];\n\t\t\trange[key] = bound === undefined ? undefined : operand(bound);\n\t\t}\n\t}\n\treturn range;\n}\n\nexport function parseStorageUpdate(value: unknown): {\n\twhere: WhereClause;\n\tsetEntries: Array<[string, unknown]>;\n\tdeltaEntries: Array<[string, number]>;\n} {\n\tconst args = requireRecord(value, \"Storage update arguments\");\n\tif (Object.keys(args).some((key) => key !== \"where\" && key !== \"set\" && key !== \"delta\")) {\n\t\tthrow new TypeError(\"Unknown storage update argument\");\n\t}\n\tconst where: WhereClause = {};\n\tfor (const [field, filter] of Object.entries(requireRecord(args.where, \"Storage where guard\"))) {\n\t\tvalidateJsonFieldName(field);\n\t\twhere[field] = normalizeFilter(filter);\n\t}\n\tconst set = args.set === undefined ? {} : requireRecord(args.set, \"Storage set\");\n\tconst delta = args.delta === undefined ? {} : requireRecord(args.delta, \"Storage delta\");\n\tconst setEntries: Array<[string, unknown]> = [];\n\tfor (const [field, entry] of Object.entries(set)) {\n\t\tif (entry === undefined) continue;\n\t\tvalidateJsonFieldName(field);\n\t\tconst serialized = JSON.stringify(entry);\n\t\tif (serialized === undefined)\n\t\t\tthrow new TypeError(\"Storage set values must be JSON serializable\");\n\t\tconst normalized: unknown = JSON.parse(serialized);\n\t\tsetEntries.push([field, normalized]);\n\t}\n\tconst setFields = new Set(setEntries.map(([field]) => field));\n\tconst deltaEntries: Array<[string, number]> = [];\n\tfor (const [field, entry] of Object.entries(delta)) {\n\t\tif (entry === undefined) continue;\n\t\tvalidateJsonFieldName(field);\n\t\tif (setFields.has(field)) throw new TypeError(\"Storage field appears in both set and delta\");\n\t\tconst spec = requireRecord(entry, \"Storage numeric delta\");\n\t\tconst keys = Object.keys(spec);\n\t\tconst key = keys[0];\n\t\tif (keys.length !== 1 || (key !== \"inc\" && key !== \"dec\")) {\n\t\t\tthrow new TypeError(\"Storage delta must contain exactly one of inc or dec\");\n\t\t}\n\t\tconst amount = spec[key];\n\t\tif (typeof amount !== \"number\" || !Number.isSafeInteger(amount)) {\n\t\t\tthrow new TypeError(\"Storage delta must be a safe integer\");\n\t\t}\n\t\tdeltaEntries.push([field, key === \"dec\" ? -amount : amount]);\n\t}\n\tif (setEntries.length + deltaEntries.length === 0) {\n\t\tthrow new TypeError(\"Storage update requires at least one of set or delta\");\n\t}\n\treturn { where, setEntries, deltaEntries };\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\tassertStorageKey,\n\tassertStorageRevision,\n\tserializeConditionalValue,\n} from \"../../plugins/conditional-storage.js\";\nimport {\n\tbuildWhereClause,\n\tvalidateWhereClause,\n\tvalidateOrderByClause,\n\tgetIndexedFields,\n\tjsonOrderExtract,\n\tStorageQueryError,\n\tisInFilter,\n\tStorageSerializationError,\n} from \"../../plugins/storage-query.js\";\nimport { parseStorageUpdate } from \"../../plugins/storage-update.js\";\nimport type {\n\tStorageCollection,\n\tQueryOptions,\n\tPaginatedResult,\n\tWhereClause,\n\tUpdateIfResult,\n\tVersionedValue,\n\tConditionalWriteResult,\n\tConditionalDeleteResult,\n} from \"../../plugins/types.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport { isPostgres, pluginDataWriteExpr, pluginDataUpdateGuard } from \"../dialect-helpers.js\";\nimport { withTransaction } from \"../transaction.js\";\nimport type { Database } from \"../types.js\";\nimport {\n\tencodeCursor,\n\tdecodeCursor,\n\tInvalidCursorError,\n\tMAX_CURSOR_LENGTH,\n\ttype SortFieldBoundary,\n} from \"./types.js\";\n\n/**\n * SQLSTATEs a losing concurrent `updateIf` writer can abort with.\n *\n * `40001` (serialization_failure) is raised only above READ COMMITTED, where\n * the loser cannot re-evaluate its guard against a newer snapshot. `40P01`\n * (deadlock_detected) is not tied to isolation level: it needs only two\n * transactions taking row locks in opposite order, which a caller reaches at\n * READ COMMITTED by wrapping several `updateIf` calls in one transaction.\n */\nconst SERIALIZATION_SQLSTATES = new Set([\"40001\", \"40P01\"]);\n\nfunction errSqlState(err: unknown): string | undefined {\n\tif (typeof err !== \"object\" || err === null) return undefined;\n\tconst code = (err as { code?: unknown }).code;\n\tif (typeof code === \"string\") return code;\n\tconst cause = (err as { cause?: unknown }).cause;\n\tif (typeof cause === \"object\" && cause !== null) {\n\t\tconst causeCode = (cause as { code?: unknown }).code;\n\t\tif (typeof causeCode === \"string\") return causeCode;\n\t}\n\treturn undefined;\n}\n\nexport function mapSerializationFailure(err: unknown): unknown {\n\tconst sqlState = errSqlState(err);\n\tif (sqlState !== undefined && SERIALIZATION_SQLSTATES.has(sqlState)) {\n\t\treturn new StorageSerializationError(\n\t\t\t\"Storage write must be retried. Restart the transaction before retrying when using an explicit transaction.\",\n\t\t\t{ cause: err, sqlState },\n\t\t);\n\t}\n\treturn err;\n}\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 */\n/**\n * Rank a sort expression so NULLs get a deterministic position.\n *\n * A missing key in a schemaless document extracts as NULL, which sorts\n * differently per dialect (SQLite first, Postgres last) and makes every\n * comparison against it UNKNOWN. Sorting on this rank ahead of the value puts\n * NULLs in the same place on both dialects and keeps the seek operands\n * non-NULL.\n */\nfunction nullRank(expr: RawBuilder<unknown>): RawBuilder<number> {\n\treturn sql`(case when ${expr} is null then 1 else 0 end)`;\n}\n\n/**\n * Comparison value for an orderBy cursor, matching the type of the ordered\n * JSON expression.\n *\n * On Postgres the order expression is jsonb, so non-missing cursor values are\n * cast to jsonb. This is important for explicit JSON `null`: the bound value\n * must be the jsonb `null` scalar, not SQL NULL, because Postgres\n * `data::jsonb->'field'` returns jsonb `null` for an explicit null and SQL NULL\n * for a missing key. SQLite extracts SQL NULL for both cases, so a missing\n * boundary and an explicit null both bind SQL NULL.\n */\nfunction cursorFieldValue(db: Kysely<Database>, boundary: SortFieldBoundary): RawBuilder<unknown> {\n\tif (boundary.missing) {\n\t\treturn sql`null`;\n\t}\n\tif (isPostgres(db)) {\n\t\treturn sql`${sql.val(JSON.stringify(boundary.value))}::jsonb`;\n\t}\n\treturn sql.val(boundary.value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction sortBoundaries(data: unknown, orderFields: string[]): SortFieldBoundary[] {\n\tconst record = isRecord(data) ? data : undefined;\n\treturn orderFields.map((field) => {\n\t\tif (record !== undefined && Object.hasOwn(record, field)) {\n\t\t\treturn { value: record[field], missing: false };\n\t\t}\n\t\treturn { missing: true };\n\t});\n}\n\nfunction isScalarSortBoundary(boundary: SortFieldBoundary): boolean {\n\tif (boundary.missing) {\n\t\treturn true;\n\t}\n\tconst value = boundary.value;\n\treturn (\n\t\tvalue === null ||\n\t\ttypeof value === \"string\" ||\n\t\ttypeof value === \"number\" ||\n\t\ttypeof value === \"boolean\"\n\t);\n}\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\t\tconst revision = crypto.randomUUID();\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\trevision,\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\trevision,\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\tasync getVersioned(id: string): Promise<VersionedValue<T> | null> {\n\t\tassertStorageKey(id);\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"data\", \"revision\"])\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\t\tif (!row) return null;\n\t\treturn { value: JSON.parse(row.data), revision: row.revision };\n\t}\n\n\tasync compareAndSet(\n\t\tid: string,\n\t\texpectedRevision: string | null,\n\t\tdata: T,\n\t): Promise<ConditionalWriteResult> {\n\t\tassertStorageKey(id);\n\t\tif (expectedRevision !== null) assertStorageRevision(expectedRevision);\n\t\tconst jsonData = serializeConditionalValue(data);\n\t\tconst revision = crypto.randomUUID();\n\t\tconst now = new Date().toISOString();\n\t\tconst row =\n\t\t\texpectedRevision === null\n\t\t\t\t? await this.db\n\t\t\t\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t\t\t\t.values({\n\t\t\t\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\t\t\t\tcollection: this.collection,\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\t\trevision,\n\t\t\t\t\t\t\tcreated_at: now,\n\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.onConflict((oc) => oc.columns([\"plugin_id\", \"collection\", \"id\"]).doNothing())\n\t\t\t\t\t\t.returning(\"revision\")\n\t\t\t\t\t\t.executeTakeFirst()\n\t\t\t\t: await this.db\n\t\t\t\t\t\t.updateTable(\"_plugin_storage\")\n\t\t\t\t\t\t.set({ data: jsonData, revision, updated_at: now })\n\t\t\t\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t\t\t\t.where(\"id\", \"=\", id)\n\t\t\t\t\t\t.where(\"revision\", \"=\", expectedRevision)\n\t\t\t\t\t\t.returning(\"revision\")\n\t\t\t\t\t\t.executeTakeFirst();\n\t\treturn row ? { applied: true, revision: row.revision } : { applied: false };\n\t}\n\n\tasync compareAndDelete(id: string, expectedRevision: string): Promise<ConditionalDeleteResult> {\n\t\tassertStorageKey(id);\n\t\tassertStorageRevision(expectedRevision);\n\t\tconst row = 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.where(\"revision\", \"=\", expectedRevision)\n\t\t\t.returning(\"id\")\n\t\t\t.executeTakeFirst();\n\t\treturn { applied: row !== undefined };\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 result = new Map<string, T>();\n\t\tfor (const batch of chunks(ids, SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await this.db\n\t\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t\t.select([\"id\", \"data\"])\n\t\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t\t.where(\"id\", \"in\", batch)\n\t\t\t\t.execute();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\t\tresult.set(row.id, JSON.parse(row.data) as T);\n\t\t\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\tconst revision = crypto.randomUUID();\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\trevision,\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\trevision,\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\treturn withTransaction(this.db, async (trx) => {\n\t\t\tlet deleted = 0;\n\t\t\tfor (const batch of chunks(ids, SQL_BATCH_SIZE)) {\n\t\t\t\tconst result = await trx\n\t\t\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t\t\t.where(\"id\", \"in\", batch)\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tdeleted += Number(result.numDeletedRows ?? 0);\n\t\t\t}\n\t\t\treturn deleted;\n\t\t});\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.max(1, 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\tconst orderEntries = Object.entries(orderBy);\n\t\tconst orderDirections = new Set(orderEntries.map(([, direction]) => direction));\n\t\tconst descending = orderEntries.length > 0 && orderEntries[0][1] === \"desc\";\n\n\t\t// A missing key in a schemaless document extracts as NULL, and NULL sorts\n\t\t// differently per dialect (SQLite first, Postgres last) and makes any\n\t\t// comparison against it UNKNOWN. Rank nulls explicitly so both the sort and\n\t\t// the seek agree on where they sit, on either dialect, and so no comparison\n\t\t// operand is ever NULL.\n\t\tconst sortExpr = (field: string): RawBuilder<unknown> =>\n\t\t\tsql`${sql.raw(jsonOrderExtract(this.db, field))}`;\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\tif (orderEntries.length === 0) {\n\t\t\t\tquery = query.where(({ eb }) =>\n\t\t\t\t\teb(sql`(created_at, id)`, \">\", sql`(${decoded.orderValue}, ${decoded.id})`),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (orderDirections.size > 1) {\n\t\t\t\t\tthrow new StorageQueryError(\n\t\t\t\t\t\t\"Cursor pagination requires every orderBy field to share one direction.\",\n\t\t\t\t\t\tObject.keys(orderBy).join(\", \"),\n\t\t\t\t\t\t\"Sort every field the same way, or page without a cursor.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst op = descending ? sql`<` : sql`>`;\n\t\t\t\tconst cursorSortValues = decoded.sortValues;\n\n\t\t\t\t// New cursors carry the ordered field values so paging survives deletion\n\t\t\t\t// of the anchor row. Legacy cursors fall back to re-reading the anchor;\n\t\t\t\t// if it has been deleted we can no longer compute the seek boundary, so\n\t\t\t\t// we surface a clear error instead of returning a wrong page.\n\t\t\t\t//\n\t\t\t\t// SQLite's json_extract returns scalar values, so cursors that encode\n\t\t\t\t// objects or arrays cannot be bound as query parameters there. On any\n\t\t\t\t// dialect we also skip cursor-bound values when they would make the\n\t\t\t\t// cursor exceed the length cap, and fall back to the anchor lookup.\n\t\t\t\tconst useCursorValues =\n\t\t\t\t\tcursorSortValues !== undefined &&\n\t\t\t\t\tcursorSortValues.length === orderEntries.length &&\n\t\t\t\t\t(isPostgres(this.db) || cursorSortValues.every(isScalarSortBoundary));\n\n\t\t\t\tconst cursorExpr = (field: string): RawBuilder<unknown> =>\n\t\t\t\t\tsql`(select ${sql.raw(jsonOrderExtract(this.db, field))} from _plugin_storage where plugin_id = ${this.pluginId} and collection = ${this.collection} and id = ${decoded.id})`;\n\n\t\t\t\tif (!useCursorValues) {\n\t\t\t\t\tconst anchorExists = await this.db\n\t\t\t\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t\t\t\t.select(sql`1`.as(\"one\"))\n\t\t\t\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t\t\t\t.where(\"id\", \"=\", decoded.id)\n\t\t\t\t\t\t.executeTakeFirst();\n\t\t\t\t\tif (!anchorExists) {\n\t\t\t\t\t\tthrow new InvalidCursorError(cursor);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst fields = orderEntries.map(([field], index) => {\n\t\t\t\t\tconst own = sortExpr(field);\n\t\t\t\t\tconst theirs = useCursorValues\n\t\t\t\t\t\t? cursorFieldValue(this.db, cursorSortValues[index])\n\t\t\t\t\t\t: cursorExpr(field);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tequal: sql`(${nullRank(own)} = ${nullRank(theirs)} and (${nullRank(own)} = 1 or ${own} = ${theirs}))`,\n\t\t\t\t\t\tafter: sql`(${nullRank(own)} ${op} ${nullRank(theirs)} or (${nullRank(own)} = ${nullRank(theirs)} and ${nullRank(own)} = 0 and ${own} ${op} ${theirs}))`,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Lexicographic seek: ties on every earlier field, then strictly after\n\t\t\t\t// on this one; finally all fields tied and strictly after on id.\n\t\t\t\tconst terms = fields.map(\n\t\t\t\t\t(_, k) =>\n\t\t\t\t\t\tsql`(${sql.join([...fields.slice(0, k).map((f) => f.equal), fields[k].after], sql` and `)})`,\n\t\t\t\t);\n\t\t\t\tterms.push(\n\t\t\t\t\tsql`(${sql.join([...fields.map((f) => f.equal), sql`id ${op} ${decoded.id}`], sql` and `)})`,\n\t\t\t\t);\n\t\t\t\tquery = query.where(sql<boolean>`(${sql.join(terms, sql` or `)})`);\n\t\t\t}\n\t\t}\n\n\t\t// Build ORDER BY using sql template\n\t\tif (orderEntries.length > 0) {\n\t\t\tfor (const [field, direction] of orderEntries) {\n\t\t\t\tconst dir = direction === \"desc\" ? sql`desc` : sql`asc`;\n\t\t\t\tconst expr = sortExpr(field);\n\t\t\t\t// Null rank first so NULL placement is identical on both dialects.\n\t\t\t\tquery = query.orderBy(sql`${nullRank(expr)} ${dir}`);\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\tquery = query.orderBy(sql`${expr} ${dir}`);\n\t\t\t}\n\t\t\t// Total order, so a page boundary can never fall inside a group of ties.\n\t\t\tquery = query.orderBy(\"id\", descending ? \"desc\" : \"asc\");\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\tlet sortValues: SortFieldBoundary[] | undefined;\n\t\t\t\tif (orderEntries.length > 0) {\n\t\t\t\t\tconst parsed: unknown = JSON.parse(lastItem.data);\n\t\t\t\t\tconst candidate = sortBoundaries(\n\t\t\t\t\t\tparsed,\n\t\t\t\t\t\torderEntries.map(([field]) => field),\n\t\t\t\t\t);\n\t\t\t\t\t// Only encode scalar boundaries. Non-scalar values cannot be bound\n\t\t\t\t\t// on SQLite, and large values can blow past the cursor length cap.\n\t\t\t\t\t// In both cases we fall back to the legacy anchor-row lookup.\n\t\t\t\t\tif (candidate.every(isScalarSortBoundary)) {\n\t\t\t\t\t\tsortValues = candidate;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tnextCursor = encodeCursor(lastItem.created_at, lastItem.id, sortValues);\n\t\t\t\t// If the scalar values still produced an oversized cursor, drop them\n\t\t\t\t// and rely on the anchor row instead.\n\t\t\t\tif (nextCursor.length > MAX_CURSOR_LENGTH) {\n\t\t\t\t\tnextCursor = encodeCursor(lastItem.created_at, lastItem.id, undefined);\n\t\t\t\t}\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\tasync updateIf(id: string, args: unknown): Promise<UpdateIfResult<T>> {\n\t\tif (typeof id !== \"string\") throw new TypeError(\"Storage ID must be a string\");\n\t\tconst { where, setEntries, deltaEntries } = parseStorageUpdate(args);\n\n\t\t// Defensive empty-`in` guard: an empty `in: []` matches nothing. The shared\n\t\t// where-translation would emit invalid `IN ()`; short-circuit to a no-op\n\t\t// (matches nothing → applied:false) BEFORE building any SQL.\n\t\tfor (const value of Object.values(where)) {\n\t\t\tif (isInFilter(value) && value.in.length === 0) {\n\t\t\t\treturn { applied: false };\n\t\t\t}\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\t\tconst dataExpr = pluginDataWriteExpr(this.db, setEntries, deltaEntries);\n\n\t\tlet query = this.db\n\t\t\t.updateTable(\"_plugin_storage\")\n\t\t\t.set({ data: dataExpr, updated_at: now })\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.where(pluginDataUpdateGuard(this.db, deltaEntries));\n\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\tlet row: { data: string } | undefined;\n\t\ttry {\n\t\t\trow = await query.returning(\"data\").executeTakeFirst();\n\t\t} catch (err) {\n\t\t\tthrow mapSerializationFailure(err);\n\t\t}\n\t\tif (!row) return { applied: false };\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\tconst data = JSON.parse(row.data) as T;\n\t\treturn { applied: true, data };\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","import { sql, type Kysely } from \"kysely\";\n\nimport { resolveBylineCredits, type BylineEntry } from \"../bylines/credits.js\";\nimport {\n\tdecodeCursor,\n\tencodeCursor,\n\ttype ContentBylineCredit,\n} from \"../database/repositories/types.js\";\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport type { BylineAccess, BylineCreditInfo, BylineInfo } from \"./types.js\";\n\nconst MAX_BYLINE_ENTRY_IDS = 100;\n\nconst DEFAULT_LIST_LIMIT = 50;\nconst MAX_LIST_LIMIT = 100;\n\ninterface PublicBylineFields {\n\tid: string;\n\tslug: string;\n\tdisplayName: string;\n\tbio: string | null;\n\twebsiteUrl: string | null;\n\tavatarMediaId: string | null;\n\tlocale: string;\n\ttranslationGroup: string | null;\n}\n\nexport function toBylineInfo(byline: PublicBylineFields): BylineInfo {\n\treturn {\n\t\tid: byline.id,\n\t\tslug: byline.slug,\n\t\tdisplayName: byline.displayName,\n\t\tbio: byline.bio,\n\t\twebsiteUrl: byline.websiteUrl,\n\t\tavatarMediaId: byline.avatarMediaId,\n\t\tlocale: byline.locale,\n\t\ttranslationGroup: byline.translationGroup ?? byline.id,\n\t};\n}\n\nfunction toCreditInfo(credit: ContentBylineCredit): BylineCreditInfo {\n\treturn {\n\t\tbyline: toBylineInfo(credit.byline),\n\t\tsortOrder: credit.sortOrder,\n\t\troleLabel: credit.roleLabel,\n\t\tsource: credit.source ?? \"explicit\",\n\t};\n}\n\nfunction clampListLimit(limit: number | undefined): number {\n\tif (limit === undefined || !Number.isFinite(limit)) return DEFAULT_LIST_LIMIT;\n\treturn Math.min(Math.max(Math.trunc(limit), 1), MAX_LIST_LIMIT);\n}\n\nfunction selectPublicBylines(db: Kysely<Database>) {\n\treturn db\n\t\t.selectFrom(\"_emdash_bylines\")\n\t\t.select([\n\t\t\t\"id\",\n\t\t\t\"slug\",\n\t\t\t\"display_name as displayName\",\n\t\t\t\"bio\",\n\t\t\t\"website_url as websiteUrl\",\n\t\t\t\"avatar_media_id as avatarMediaId\",\n\t\t\t\"locale\",\n\t\t\t\"translation_group as translationGroup\",\n\t\t\t\"created_at as createdAt\",\n\t\t]);\n}\n\nasync function loadEntryRefs(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryIds: string[],\n): Promise<BylineEntry[]> {\n\tconst tableName = `ec_${collection}`;\n\tconst refs: BylineEntry[] = [];\n\tfor (const chunk of chunks(entryIds, SQL_BATCH_SIZE)) {\n\t\tconst result = await sql<{\n\t\t\tid: string;\n\t\t\tauthor_id: string | null;\n\t\t\tprimary_byline_id: string | null;\n\t\t\tlocale: string | null;\n\t\t}>`\n\t\t\tSELECT id, author_id, primary_byline_id, locale FROM ${sql.ref(tableName)}\n\t\t\tWHERE id IN (${sql.join(chunk)})\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(db);\n\t\tfor (const row of result.rows) {\n\t\t\trefs.push({\n\t\t\t\tid: row.id,\n\t\t\t\tauthorId: row.author_id,\n\t\t\t\tprimaryBylineId: row.primary_byline_id,\n\t\t\t\tlocale: row.locale,\n\t\t\t});\n\t\t}\n\t}\n\treturn refs;\n}\n\n/**\n * Create read-only byline access (gated on `bylines:read`).\n */\nexport function createBylineAccess(db: Kysely<Database>): BylineAccess {\n\treturn {\n\t\tasync get(id) {\n\t\t\tconst row = await selectPublicBylines(db).where(\"id\", \"=\", id).executeTakeFirst();\n\t\t\treturn row ? toBylineInfo(row) : null;\n\t\t},\n\n\t\tasync list(options) {\n\t\t\tconst limit = clampListLimit(options?.limit);\n\t\t\tlet query = selectPublicBylines(db)\n\t\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t\t.limit(limit + 1);\n\t\t\tif (options?.locale !== undefined) query = query.where(\"locale\", \"=\", options.locale);\n\t\t\tif (options?.cursor) {\n\t\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(\"created_at\", \"<\", decoded.orderValue),\n\t\t\t\t\t\teb.and([eb(\"created_at\", \"=\", decoded.orderValue), eb(\"id\", \"<\", decoded.id)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst rows = await query.execute();\n\t\t\tconst pageRows = rows.slice(0, limit);\n\t\t\tconst last = pageRows.at(-1);\n\t\t\treturn {\n\t\t\t\titems: pageRows.map(toBylineInfo),\n\t\t\t\thasMore: rows.length > limit,\n\t\t\t\t...(rows.length > limit && last ? { cursor: encodeCursor(last.createdAt, last.id) } : {}),\n\t\t\t};\n\t\t},\n\n\t\tasync getEntriesBylines(collection, entryIds) {\n\t\t\tvalidateIdentifier(collection, \"collection\");\n\t\t\tconst uniqueIds = [...new Set(entryIds)];\n\t\t\tif (uniqueIds.length > MAX_BYLINE_ENTRY_IDS) {\n\t\t\t\tthrow new Error(`Byline lookups accept at most ${MAX_BYLINE_ENTRY_IDS} entry IDs`);\n\t\t\t}\n\t\t\tif (uniqueIds.length === 0) return [];\n\n\t\t\tconst refs = await loadEntryRefs(db, collection, uniqueIds);\n\t\t\tconst credits = await resolveBylineCredits(db, collection, refs, {\n\t\t\t\thydrateCustomFields: false,\n\t\t\t});\n\t\t\treturn uniqueIds.map((entryId) => ({\n\t\t\t\tentryId,\n\t\t\t\tbylines: (credits.get(entryId) ?? []).map(toCreditInfo),\n\t\t\t}));\n\t\t},\n\t};\n}\n","import type { Kysely } from \"kysely\";\n\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport { normalizeRevisionLimit, RevisionRepository } from \"../database/repositories/revision.js\";\nimport { SeoRepository } from \"../database/repositories/seo.js\";\nimport type { Database } from \"../database/types.js\";\nimport { resolveLocalizedContentRoutePath } from \"../i18n/resolve.js\";\nimport { SchemaRegistry } from \"../schema/registry.js\";\nimport type {\n\tContentAccess,\n\tContentItem,\n\tContentListOptions,\n\tPaginatedResult,\n\tSiteInfo,\n} from \"./types.js\";\n\nexport function createContentAccess(\n\tdb: Kysely<Database>,\n\taccessOptions?: { site?: SiteInfo; revisions?: boolean },\n): 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\tauthorId: item.authorId,\n\t\t\t\ttranslationGroup: item.translationGroup,\n\t\t\t\tliveRevisionId: item.liveRevisionId,\n\t\t\t\tdraftRevisionId: item.draftRevisionId,\n\t\t\t\tversion: item.version,\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\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) orderBy = { field: first[0], direction: first[1] };\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\tauthorId: item.authorId,\n\t\t\t\ttranslationGroup: item.translationGroup,\n\t\t\t\tliveRevisionId: item.liveRevisionId,\n\t\t\t\tdraftRevisionId: item.draftRevisionId,\n\t\t\t\tversion: item.version,\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((item) => item.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 { items, cursor: result.nextCursor, hasMore: !!result.nextCursor };\n\t\t},\n\n\t\tasync getTranslations(collection, id) {\n\t\t\tconst item = await contentRepo.findById(collection, id);\n\t\t\tif (!item) throw new Error(`Content not found: ${collection}/${id}`);\n\t\t\tconst translationGroup = item.translationGroup || item.id;\n\t\t\tconst translations = item.translationGroup\n\t\t\t\t? await contentRepo.findTranslations(collection, item.translationGroup)\n\t\t\t\t: [item];\n\t\t\treturn {\n\t\t\t\ttranslationGroup,\n\t\t\t\ttranslations: translations.map((translation) => ({\n\t\t\t\t\tid: translation.id,\n\t\t\t\t\tlocale: translation.locale,\n\t\t\t\t\tslug: translation.slug,\n\t\t\t\t\tstatus: translation.status,\n\t\t\t\t\tupdatedAt: translation.updatedAt,\n\t\t\t\t})),\n\t\t\t};\n\t\t},\n\n\t\tasync getPublicUrl(collection, id) {\n\t\t\tconst site = accessOptions?.site;\n\t\t\tif (!site?.url) return null;\n\t\t\tconst [item, collectionInfo] = await Promise.all([\n\t\t\t\tcontentRepo.findById(collection, id),\n\t\t\t\tnew SchemaRegistry(db).getCollection(collection),\n\t\t\t]);\n\t\t\tif (!item || item.status !== \"published\" || !item.slug || !collectionInfo?.routable) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst path = await resolveLocalizedContentRoutePath({\n\t\t\t\tpattern: collectionInfo.urlPattern ?? null,\n\t\t\t\tcollection,\n\t\t\t\tslug: item.slug,\n\t\t\t\tid: item.id,\n\t\t\t\tdate: item.publishedAt,\n\t\t\t\tlocale: item.locale || site.locale,\n\t\t\t\ttrailingSlash: site.trailingSlash,\n\t\t\t});\n\t\t\treturn path === null ? null : `${site.url}${path}`;\n\t\t},\n\n\t\t...(accessOptions?.revisions\n\t\t\t? {\n\t\t\t\t\tasync listRevisions(\n\t\t\t\t\t\tcollection: string,\n\t\t\t\t\t\tid: string,\n\t\t\t\t\t\trevisionOptions?: { limit?: number },\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst revisions = await new RevisionRepository(db).findVisibleByEntry(collection, id, {\n\t\t\t\t\t\t\tlimit: normalizeRevisionLimit(revisionOptions?.limit),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn revisions.map(({ authorId: _authorId, ...revision }) => revision);\n\t\t\t\t\t},\n\t\t\t\t\tasync getRevision(collection: string, id: string, revisionId: string) {\n\t\t\t\t\t\tconst revision = await new RevisionRepository(db).findVisibleById(\n\t\t\t\t\t\t\tcollection,\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\trevisionId,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (!revision) return null;\n\t\t\t\t\t\tconst { authorId: _authorId, ...safeRevision } = revision;\n\t\t\t\t\t\treturn safeRevision;\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n","import type { Kysely } from \"kysely\";\n\nimport {\n\tMediaRepository,\n\ttype MediaItem as RepositoryMediaItem,\n} from \"../database/repositories/media.js\";\nimport type { Database } from \"../database/types.js\";\nimport { isValidFocalPointUpdate } from \"../media/focal-point.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport type { MediaBytes, MediaItem, MediaMetadataPatch } from \"./types.js\";\n\nexport const DEFAULT_PLUGIN_MEDIA_READ_BYTES = 10 * 1024 * 1024;\nexport const MAX_PLUGIN_MEDIA_READ_BYTES = 16 * 1024 * 1024;\n\nfunction mediaUrl(item: RepositoryMediaItem): string {\n\treturn `/_emdash/api/media/asset/${encodeURIComponent(item.id)}/${encodeURIComponent(item.filename)}`;\n}\n\nexport function toPluginMediaItem(item: RepositoryMediaItem): MediaItem {\n\treturn {\n\t\tid: item.id,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\tsize: item.size,\n\t\turl: mediaUrl(item),\n\t\tcreatedAt: item.createdAt,\n\t\twidth: item.width,\n\t\theight: item.height,\n\t\talt: item.alt,\n\t\tcaption: item.caption,\n\t\tfocalX: item.focalX,\n\t\tfocalY: item.focalY,\n\t\tblurhash: item.blurhash,\n\t\tdominantColor: item.dominantColor,\n\t\tfolderId: item.folderId ?? null,\n\t\tstatus: \"ready\",\n\t};\n}\n\nfunction resolveByteLimit(maxBytes: number | undefined): number {\n\tconst limit = maxBytes ?? DEFAULT_PLUGIN_MEDIA_READ_BYTES;\n\tif (!Number.isSafeInteger(limit) || limit <= 0 || limit > MAX_PLUGIN_MEDIA_READ_BYTES) {\n\t\tthrow new RangeError(\n\t\t\t`media.readBytes maxBytes must be a positive integer no greater than ${MAX_PLUGIN_MEDIA_READ_BYTES}`,\n\t\t);\n\t}\n\treturn limit;\n}\n\nasync function readStreamWithLimit(\n\tbody: ReadableStream<Uint8Array>,\n\tmaxBytes: number,\n): Promise<Uint8Array> {\n\tconst reader = body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet size = 0;\n\ttry {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tsize += value.byteLength;\n\t\t\tif (size > maxBytes) {\n\t\t\t\ttry {\n\t\t\t\t\tawait reader.cancel();\n\t\t\t\t} catch {}\n\t\t\t\tthrow new PluginMediaByteLimitError(`Media exceeds the requested ${maxBytes}-byte limit`);\n\t\t\t}\n\t\t\tchunks.push(value);\n\t\t}\n\t} finally {\n\t\treader.releaseLock();\n\t}\n\n\tconst bytes = new Uint8Array(size);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tbytes.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\treturn bytes;\n}\n\nclass PluginMediaByteLimitError extends RangeError {}\n\nexport async function readPluginMediaBytes(\n\tdb: Kysely<Database>,\n\tstorage: Pick<Storage, \"download\"> | undefined,\n\tid: string,\n\toptions?: { maxBytes?: number },\n): Promise<MediaBytes> {\n\tif (!storage) throw new Error(\"Media storage is not configured\");\n\tconst limit = resolveByteLimit(options?.maxBytes);\n\tconst item = await new MediaRepository(db).findById(id);\n\tif (!item || item.status !== \"ready\") {\n\t\tthrow new Error(\"Media item is not ready or does not exist\");\n\t}\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tconst download = await storage.download(item.storageKey);\n\t\tbytes = await readStreamWithLimit(download.body, limit);\n\t} catch (error) {\n\t\tif (error instanceof PluginMediaByteLimitError) throw error;\n\t\t// oxlint-disable-next-line preserve-caught-error -- adapter errors may contain private storage keys\n\t\tthrow new Error(\"Failed to read media bytes\");\n\t}\n\treturn {\n\t\tbytes,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\tsize: bytes.byteLength,\n\t\t...(item.contentHash ? { contentHash: item.contentHash } : {}),\n\t};\n}\n\nexport async function updatePluginMediaMetadata(\n\tdb: Kysely<Database>,\n\tid: string,\n\tpatch: MediaMetadataPatch,\n): Promise<MediaItem> {\n\tconst parsed = parsePluginMediaMetadataPatch(patch);\n\tconst item = await new MediaRepository(db).updateReadyMetadata(id, parsed);\n\tif (!item) throw new Error(\"Media item is not ready or does not exist\");\n\treturn toPluginMediaItem(item);\n}\n\nconst MEDIA_METADATA_KEYS = new Set([\"alt\", \"caption\", \"focalX\", \"focalY\"]);\n\nexport function parsePluginMediaMetadataPatch(value: unknown): MediaMetadataPatch {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new TypeError(\"media.updateMetadata patch must be an object\");\n\t}\n\tconst keys = Object.keys(value);\n\tif (keys.length === 0) {\n\t\tthrow new TypeError(\"media.updateMetadata must change at least one metadata field\");\n\t}\n\tfor (const key of keys) {\n\t\tif (!MEDIA_METADATA_KEYS.has(key)) {\n\t\t\tthrow new TypeError(`media.updateMetadata cannot change ${key}`);\n\t\t}\n\t}\n\tconst alt = Reflect.get(value, \"alt\");\n\tconst caption = Reflect.get(value, \"caption\");\n\tconst focalX = Reflect.get(value, \"focalX\");\n\tconst focalY = Reflect.get(value, \"focalY\");\n\tif (alt !== undefined && alt !== null && typeof alt !== \"string\") {\n\t\tthrow new TypeError(\"media.updateMetadata alt must be a string or null\");\n\t}\n\tif (caption !== undefined && caption !== null && typeof caption !== \"string\") {\n\t\tthrow new TypeError(\"media.updateMetadata caption must be a string or null\");\n\t}\n\tif (focalX !== undefined && focalX !== null && typeof focalX !== \"number\") {\n\t\tthrow new TypeError(\"media.updateMetadata focalX must be a number or null\");\n\t}\n\tif (focalY !== undefined && focalY !== null && typeof focalY !== \"number\") {\n\t\tthrow new TypeError(\"media.updateMetadata focalY must be a number or null\");\n\t}\n\tconst patch: MediaMetadataPatch = {};\n\tif (alt !== undefined) patch.alt = alt;\n\tif (caption !== undefined) patch.caption = caption;\n\tif (focalX !== undefined) patch.focalX = focalX;\n\tif (focalY !== undefined) patch.focalY = focalY;\n\tif (!isValidFocalPointUpdate(patch)) {\n\t\tthrow new TypeError(\"focalX and focalY must both be valid numbers or both be null\");\n\t}\n\treturn patch;\n}\n","/**\n * Error class for plugin routes\n * Allows plugins to return structured errors with specific HTTP status codes\n */\nexport class PluginRouteError extends Error {\n\tconstructor(\n\t\tpublic code: string,\n\t\tmessage: string,\n\t\tpublic status: number = 400,\n\t\tpublic details?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"PluginRouteError\";\n\t}\n\n\t/**\n\t * Create a bad request error (400)\n\t */\n\tstatic badRequest(message: string, details?: unknown): PluginRouteError {\n\t\treturn new PluginRouteError(\"BAD_REQUEST\", message, 400, details);\n\t}\n\n\t/**\n\t * Create an unauthorized error (401)\n\t */\n\tstatic unauthorized(message: string = \"Unauthorized\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"UNAUTHORIZED\", message, 401);\n\t}\n\n\t/**\n\t * Create a forbidden error (403)\n\t */\n\tstatic forbidden(message: string = \"Forbidden\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"FORBIDDEN\", message, 403);\n\t}\n\n\t/**\n\t * Create a not found error (404)\n\t */\n\tstatic notFound(message: string = \"Not found\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"NOT_FOUND\", message, 404);\n\t}\n\n\t/**\n\t * Create a conflict error (409)\n\t */\n\tstatic conflict(message: string, details?: unknown): PluginRouteError {\n\t\treturn new PluginRouteError(\"CONFLICT\", message, 409, details);\n\t}\n\n\t/**\n\t * Create an internal error (500)\n\t */\n\tstatic internal(message: string = \"Internal error\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"INTERNAL_ERROR\", message, 500);\n\t}\n}\n\n/**\n * Check whether a thrown value is a PluginRouteError.\n *\n * Also matches on shape, not just `instanceof`: under `astro dev`, Vite can load a\n * plugin's `emdash` import as a separate module instance, so the error the plugin\n * throws comes from a different copy of this class.\n */\nexport function isPluginRouteError(error: unknown): error is PluginRouteError {\n\tif (error instanceof PluginRouteError) return true;\n\tif (!(error instanceof Error) || error.name !== \"PluginRouteError\") return false;\n\tconst { code, status } = error as Partial<PluginRouteError>;\n\treturn typeof code === \"string\" && typeof status === \"number\";\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 mime from \"mime/lite\";\nimport { ulid } from \"ulidx\";\n\nimport { GLOBAL_UPLOAD_ALLOWLIST } from \"../api/handlers/media-allowlist.js\";\nimport { handleMediaDelete } from \"../api/handlers/media.js\";\nimport {\n\thandleRedirectCreate,\n\thandleRedirectDelete,\n\thandleRedirectList,\n\thandleRedirectUpdate,\n} from \"../api/handlers/redirects.js\";\nimport { handleTermCreate } from \"../api/handlers/taxonomies.js\";\nimport { CONTENT_TYPE_RE } from \"../api/schemas/media.js\";\nimport { createRedirectBody, updateRedirectBody } from \"../api/schemas/redirects.js\";\nimport { CommentRepository, type Comment } from \"../database/repositories/comment.js\";\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport { EntryLockRepository } from \"../database/repositories/entry-locks.js\";\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport { PluginStorageRepository } from \"../database/repositories/plugin-storage.js\";\nimport {\n\tRedirectRepository,\n\ttype Redirect,\n\ttype VersionedRedirectRecord,\n} from \"../database/repositories/redirect.js\";\nimport { SeoRepository } from \"../database/repositories/seo.js\";\nimport {\n\tfindTaxonomyStructure,\n\tselectTaxonomyDefs,\n} from \"../database/repositories/taxonomy-def.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 { getI18nConfig, resolveContentCreateLocale } from \"../i18n/config.js\";\nimport {\n\tresolveAndValidateExternalUrl,\n\tSsrfError,\n\tstripCredentialHeaders,\n} from \"../import/ssrf.js\";\nimport { enrichImageMetadata } from \"../media/enrich.js\";\nimport { matchesMimeAllowlist, normalizeMime } from \"../media/mime.js\";\nimport { markContentMediaUsageCollectionStaleSafely } from \"../media/usage/content-refresh.js\";\nimport { SchemaRegistry } from \"../schema/registry.js\";\nimport { invalidateSiteSettingsCache } from \"../settings/index.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport { createBylineAccess } from \"./byline-access.js\";\nimport { assertStorageKey } from \"./conditional-storage.js\";\nimport { createContentAccess } from \"./content-access.js\";\nimport { CronAccessImpl } from \"./cron.js\";\nimport type { EmailPipeline } from \"./email.js\";\nimport {\n\tbufferPluginHttpRequest,\n\tpluginHttpRedirectAction,\n\tpluginHttpResponseFromWire,\n\tpluginHttpResponseToWire,\n\trewritePluginHttpRedirect,\n} from \"./http-wire.js\";\nimport { readPluginMediaBytes, toPluginMediaItem, updatePluginMediaMetadata } from \"./media.js\";\nimport { PluginRouteError } from \"./route-error.js\";\nimport {\n\tcreatePluginSecretRedactor,\n\tcreateSettingsAccess,\n\ttype PluginSecretRedactor,\n} from \"./settings.js\";\nimport type {\n\tResolvedPlugin,\n\tPluginContext,\n\tPluginStorageConfig,\n\tStorageCollection,\n\tKVAccess,\n\tSettingsAccess,\n\tCronAccess,\n\tEmailAccess,\n\tContentAccess,\n\tContentAccessWithWrite,\n\tVersionedContentItem,\n\tMediaAccess,\n\tMediaAccessWithWrite,\n\tHttpAccess,\n\tLogAccess,\n\tSiteInfo,\n\tUserAccess,\n\tUserInfo,\n\tContentItem,\n\tContentCreateOptions,\n\tContentItemSeoInput,\n\tContentWriteInput,\n\tMediaItem,\n\tPaginatedResult,\n\tQueryOptions,\n\tMediaListOptions,\n\tTaxonomyAccess,\n\tTaxonomyAccessWithWrite,\n\tTaxonomyDefInfo,\n\tTaxonomyTermInfo,\n\tTaxonomyTermCreateInput,\n\tTaxonomyReadOptions,\n\tCommentAccess,\n\tCommentListOptions,\n\tPluginComment,\n\tPluginCommentStatus,\n\tRedirectAccess,\n\tRedirectAccessWithWrite,\n\tRedirectCreateInput,\n\tRedirectInfo,\n\tRedirectListOptions,\n\tRedirectStatus,\n\tRedirectUpdateInput,\n\tVersionedRedirect,\n\tSchemaAccess,\n\tCollectionSchemaInfo,\n\tPluginContentCreateCallback,\n} from \"./types.js\";\n\nexport { createContentAccess } from \"./content-access.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(\n\toptionsRepo: OptionsRepository,\n\tpluginId: string,\n\tsettings: SettingsAccess = createSettingsAccess(optionsRepo, pluginId),\n): KVAccess {\n\tconst prefix = `plugin:${pluginId}:`;\n\n\treturn {\n\t\tasync get<T>(key: string): Promise<T | null> {\n\t\t\tif (key.startsWith(\"settings:\")) return settings.get<T>(key.slice(\"settings:\".length));\n\t\t\treturn optionsRepo.get<T>(`${prefix}${key}`);\n\t\t},\n\t\tasync getVersioned<T>(key: string) {\n\t\t\tassertStorageKey(key);\n\t\t\tif (key.startsWith(\"settings:\")) {\n\t\t\t\treturn settings.getVersioned<T>(key.slice(\"settings:\".length));\n\t\t\t}\n\t\t\treturn optionsRepo.getVersioned<T>(`${prefix}${key}`);\n\t\t},\n\t\tasync compareAndSet(key, expectedRevision, value) {\n\t\t\tassertStorageKey(key);\n\t\t\tif (key.startsWith(\"settings:\")) {\n\t\t\t\treturn settings.compareAndSet(key.slice(\"settings:\".length), expectedRevision, value);\n\t\t\t}\n\t\t\treturn optionsRepo.compareAndSet(`${prefix}${key}`, expectedRevision, value);\n\t\t},\n\t\tasync compareAndDelete(key, expectedRevision) {\n\t\t\tassertStorageKey(key);\n\t\t\tif (key.startsWith(\"settings:\")) {\n\t\t\t\treturn settings.compareAndDelete(key.slice(\"settings:\".length), expectedRevision);\n\t\t\t}\n\t\t\treturn optionsRepo.compareAndDelete(`${prefix}${key}`, expectedRevision);\n\t\t},\n\n\t\tasync set(key: string, value: unknown): Promise<void> {\n\t\t\tif (key.startsWith(\"settings:\")) {\n\t\t\t\tawait settings.set(key.slice(\"settings:\".length), value);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait optionsRepo.set(`${prefix}${key}`, value);\n\t\t},\n\n\t\tasync delete(key: string): Promise<boolean> {\n\t\t\tif (key.startsWith(\"settings:\")) return settings.delete(key.slice(\"settings:\".length));\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 requestedPrefix = keyPrefix ?? \"\";\n\t\t\tconst includesSettings =\n\t\t\t\t\"settings:\".startsWith(requestedPrefix) || requestedPrefix.startsWith(\"settings:\");\n\t\t\tconst fullPrefix = `${prefix}${requestedPrefix}`;\n\t\t\tconst entriesMap = requestedPrefix.startsWith(\"settings:\")\n\t\t\t\t? new Map<string, unknown>()\n\t\t\t\t: 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\tif (includesSettings && fullKey.startsWith(`${prefix}settings:`)) continue;\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\tif (includesSettings) {\n\t\t\t\tconst settingPrefix = requestedPrefix.startsWith(\"settings:\")\n\t\t\t\t\t? requestedPrefix.slice(\"settings:\".length)\n\t\t\t\t\t: \"\";\n\t\t\t\tfor (const entry of await settings.list(settingPrefix)) {\n\t\t\t\t\tconst key = `settings:${entry.key}`;\n\t\t\t\t\tif (key.startsWith(requestedPrefix)) result.push({ key, value: entry.value });\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\tgetVersioned: (id) => repo.getVersioned(id),\n\t\tcompareAndSet: (id, expectedRevision, data) => repo.compareAndSet(id, expectedRevision, data),\n\t\tcompareAndDelete: (id, expectedRevision) => repo.compareAndDelete(id, expectedRevision),\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\t\tupdateIf: (id, updateArgs) => repo.updateIf(id, updateArgs),\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\nfunction collectionToSchemaInfo(\n\tcollection: Awaited<ReturnType<SchemaRegistry[\"getCollectionWithFields\"]>>,\n): CollectionSchemaInfo | null {\n\tif (!collection) return null;\n\treturn {\n\t\tslug: collection.slug,\n\t\tlabel: collection.label,\n\t\tlabelSingular: collection.labelSingular ?? null,\n\t\tdescription: collection.description ?? null,\n\t\tsupports: collection.supports,\n\t\thasSeo: collection.hasSeo,\n\t\ttitleField: collection.titleField ?? null,\n\t\tdateField: collection.dateField ?? null,\n\t\turlPattern: collection.urlPattern ?? null,\n\t\troutable: collection.routable !== false,\n\t\thidden: collection.hidden,\n\t\tfields: collection.fields.map((field) => ({\n\t\t\tslug: field.slug,\n\t\t\tlabel: field.label,\n\t\t\ttype: field.type,\n\t\t\trequired: field.required,\n\t\t\tunique: field.unique,\n\t\t\t...(field.defaultValue === undefined ? {} : { default: field.defaultValue }),\n\t\t\t...(field.validation === undefined ? {} : { validation: field.validation }),\n\t\t\t...(field.widget === undefined ? {} : { widget: field.widget }),\n\t\t\t...(field.options === undefined ? {} : { options: field.options }),\n\t\t\tsearchable: field.searchable,\n\t\t\tindexed: field.indexed,\n\t\t\ttranslatable: field.translatable,\n\t\t\tsortOrder: field.sortOrder,\n\t\t})),\n\t};\n}\n\nexport function createSchemaAccess(db: Kysely<Database>): SchemaAccess {\n\tconst registry = new SchemaRegistry(db);\n\treturn {\n\t\tasync listCollections() {\n\t\t\tconst collections: CollectionSchemaInfo[] = [];\n\t\t\tfor (const collection of await registry.listCollectionsWithFields()) {\n\t\t\t\tconst info = collectionToSchemaInfo(collection);\n\t\t\t\tif (info) collections.push(info);\n\t\t\t}\n\t\t\treturn collections;\n\t\t},\n\t\tasync getCollection(slug) {\n\t\t\treturn collectionToSchemaInfo(await registry.getCollectionWithFields(slug));\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 = selectTaxonomyDefs(db);\n\t\t\tif (options?.locale !== undefined) query = query.where(\"d.locale\", \"=\", options.locale);\n\t\t\tconst rows = await query.orderBy(\"d.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\nfunction toPluginComment(comment: Comment): PluginComment {\n\tif (comment.status === \"trash\") throw new Error(\"Trashed comments are not plugin-readable\");\n\treturn {\n\t\tid: comment.id,\n\t\tcollection: comment.collection,\n\t\tcontentId: comment.contentId,\n\t\tparentId: comment.parentId,\n\t\tauthorName: comment.authorName,\n\t\tauthorEmail: comment.authorEmail,\n\t\tbody: comment.body,\n\t\tstatus: comment.status,\n\t\tipHash: comment.ipHash,\n\t\tuserAgent: comment.userAgent,\n\t\tmoderationMetadata: comment.moderationMetadata,\n\t\tcreatedAt: comment.createdAt,\n\t\tupdatedAt: comment.updatedAt,\n\t};\n}\n\nexport function createCommentAccess(\n\tdb: Kysely<Database>,\n\tmoderate?: (\n\t\tid: string,\n\t\tstatus: PluginCommentStatus,\n\t\texpectedStatus: PluginCommentStatus,\n\t) => Promise<PluginComment>,\n): CommentAccess {\n\tconst repo = new CommentRepository(db);\n\treturn {\n\t\tasync get(id) {\n\t\t\tconst comment = await repo.findById(id);\n\t\t\treturn !comment || comment.status === \"trash\" ? null : toPluginComment(comment);\n\t\t},\n\t\tasync list(options: CommentListOptions = {}) {\n\t\t\tconst result = await repo.findForPlugin(options);\n\t\t\treturn {\n\t\t\t\titems: result.items.map(toPluginComment),\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: result.nextCursor !== undefined,\n\t\t\t};\n\t\t},\n\t\tcount: (options) => repo.countForPlugin(options),\n\t\t...(moderate\n\t\t\t? {\n\t\t\t\t\tsetStatus: (id, status, options) => moderate(id, status, options.expectedStatus),\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\nexport class RedirectAccessError extends Error {\n\toverride readonly name = \"RedirectAccessError\";\n\n\tconstructor(\n\t\treadonly code: string,\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t}\n}\n\nconst REDIRECT_REVISION_PREFIX = \"r1.\";\nconst BASE64_PADDING_RE = /=+$/;\n\nfunction encodeRedirectRevision(id: string, revision: string): string {\n\tconst payload = `${id}\\0${revision}`;\n\treturn `${REDIRECT_REVISION_PREFIX}${btoa(payload)\n\t\t.replaceAll(\"+\", \"-\")\n\t\t.replaceAll(\"/\", \"_\")\n\t\t.replace(BASE64_PADDING_RE, \"\")}`;\n}\n\nfunction decodeRedirectRevision(id: string, revision: string): string {\n\tif (typeof revision !== \"string\" || !revision.startsWith(REDIRECT_REVISION_PREFIX)) {\n\t\tthrow new RedirectAccessError(\"INVALID_PRECONDITION\", \"Invalid redirect revision\");\n\t}\n\ttry {\n\t\tconst encoded = revision\n\t\t\t.slice(REDIRECT_REVISION_PREFIX.length)\n\t\t\t.replaceAll(\"-\", \"+\")\n\t\t\t.replaceAll(\"_\", \"/\");\n\t\tconst padded = encoded.padEnd(Math.ceil(encoded.length / 4) * 4, \"=\");\n\t\tconst [revisionId, updatedAt, extra] = atob(padded).split(\"\\0\");\n\t\tif (revisionId !== id || !updatedAt || extra !== undefined) throw new Error(\"invalid\");\n\t\treturn updatedAt;\n\t} catch (error) {\n\t\tif (error instanceof RedirectAccessError) throw error;\n\t\tthrow new RedirectAccessError(\"INVALID_PRECONDITION\", \"Invalid redirect revision\");\n\t}\n}\n\nfunction toRedirectInfo(redirect: Redirect): RedirectInfo {\n\treturn {\n\t\t...redirect,\n\t\ttype: redirect.type as RedirectStatus,\n\t};\n}\n\nfunction toVersionedRedirect(record: VersionedRedirectRecord): VersionedRedirect {\n\treturn {\n\t\tredirect: toRedirectInfo(record.redirect),\n\t\t_rev: encodeRedirectRevision(record.redirect.id, record.configRevision),\n\t};\n}\n\nasync function readVersionedRedirect(\n\trepo: RedirectRepository,\n\tid: string,\n): Promise<VersionedRedirect | null> {\n\tconst record = await repo.findVersionedById(id);\n\treturn record ? toVersionedRedirect(record) : null;\n}\n\nfunction throwRedirectResult(error: { code: string; message: string }): never {\n\tthrow new RedirectAccessError(error.code, error.message);\n}\n\nfunction assertNoAutomaticRedirectMarker(input: object): void {\n\tif (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n\t\tthrow new RedirectAccessError(\"VALIDATION_ERROR\", \"Redirect input must be an object\");\n\t}\n\tif (Object.hasOwn(input, \"auto\")) {\n\t\tthrow new RedirectAccessError(\n\t\t\t\"VALIDATION_ERROR\",\n\t\t\t\"The automatic redirect marker is managed by EmDash\",\n\t\t);\n\t}\n}\n\nexport function createRedirectAccess(db: Kysely<Database>): RedirectAccess;\nexport function createRedirectAccess(db: Kysely<Database>, writable: true): RedirectAccessWithWrite;\nexport function createRedirectAccess(\n\tdb: Kysely<Database>,\n\twritable = false,\n): RedirectAccess | RedirectAccessWithWrite {\n\tconst repo = new RedirectRepository(db);\n\tconst readAccess: RedirectAccess = {\n\t\tasync list(options: RedirectListOptions = {}) {\n\t\t\tconst result = await handleRedirectList(db, options);\n\t\t\tif (!result.success) return throwRedirectResult(result.error);\n\t\t\treturn {\n\t\t\t\titems: result.data.items.map(toRedirectInfo),\n\t\t\t\tcursor: result.data.nextCursor,\n\t\t\t\thasMore: result.data.nextCursor !== undefined,\n\t\t\t};\n\t\t},\n\t\tget: (id: string) => readVersionedRedirect(repo, id),\n\t};\n\tif (!writable) return readAccess;\n\n\treturn {\n\t\t...readAccess,\n\t\tasync create(input: RedirectCreateInput) {\n\t\t\tassertNoAutomaticRedirectMarker(input);\n\t\t\tconst parsed = createRedirectBody.safeParse(input);\n\t\t\tif (!parsed.success) {\n\t\t\t\tthrow new RedirectAccessError(\n\t\t\t\t\t\"VALIDATION_ERROR\",\n\t\t\t\t\tparsed.error.issues[0]?.message ?? \"Invalid redirect\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst result = await handleRedirectCreate(db, parsed.data);\n\t\t\tif (!result.success) return throwRedirectResult(result.error);\n\t\t\tconst current = await readVersionedRedirect(repo, result.data.id);\n\t\t\tif (!current) throw new RedirectAccessError(\"NOT_FOUND\", \"Created redirect not found\");\n\t\t\treturn current;\n\t\t},\n\t\tasync update(id: string, input: RedirectUpdateInput & { _rev: string }) {\n\t\t\tassertNoAutomaticRedirectMarker(input);\n\t\t\tconst { _rev, ...patch } = input;\n\t\t\tconst expectedRevision = decodeRedirectRevision(id, _rev);\n\t\t\tconst parsed = updateRedirectBody.safeParse(patch);\n\t\t\tif (!parsed.success) {\n\t\t\t\tthrow new RedirectAccessError(\n\t\t\t\t\t\"VALIDATION_ERROR\",\n\t\t\t\t\tparsed.error.issues[0]?.message ?? \"Invalid redirect\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst result = await handleRedirectUpdate(db, id, parsed.data, { expectedRevision });\n\t\t\tif (!result.success) return throwRedirectResult(result.error);\n\t\t\tconst current = await readVersionedRedirect(repo, result.data.id);\n\t\t\tif (!current) throw new RedirectAccessError(\"NOT_FOUND\", \"Updated redirect not found\");\n\t\t\treturn current;\n\t\t},\n\t\tasync delete(id: string, options: { _rev: string }) {\n\t\t\tif (typeof options !== \"object\" || options === null) {\n\t\t\t\tthrow new RedirectAccessError(\"INVALID_PRECONDITION\", \"Invalid redirect revision\");\n\t\t\t}\n\t\t\tconst expectedRevision = decodeRedirectRevision(id, options._rev);\n\t\t\tconst result = await handleRedirectDelete(db, id, { expectedRevision });\n\t\t\tif (!result.success) return throwRedirectResult(result.error);\n\t\t\treturn result.data.deleted;\n\t\t},\n\t};\n}\n\nconst MAX_TAXONOMY_DELTA_TERMS = 64;\n\nfunction taxonomyAccessError(code: string, message: string): Error {\n\treturn Object.assign(new Error(message), { code });\n}\n\nasync function resolveTaxonomyDelta(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\ttaxonomy: string,\n\ttermIds: string[],\n): Promise<{ repo: TaxonomyRepository; groups: string[]; locale: string }> {\n\tif (termIds.length > MAX_TAXONOMY_DELTA_TERMS) {\n\t\tthrow taxonomyAccessError(\n\t\t\t\"VALIDATION_ERROR\",\n\t\t\t`A taxonomy assignment delta can contain at most ${MAX_TAXONOMY_DELTA_TERMS} term IDs`,\n\t\t);\n\t}\n\tif (termIds.some((id) => typeof id !== \"string\" || id.length === 0)) {\n\t\tthrow taxonomyAccessError(\"VALIDATION_ERROR\", \"Taxonomy term IDs must be non-empty strings\");\n\t}\n\n\tconst structure = await findTaxonomyStructure(db, taxonomy);\n\tif (!structure) {\n\t\tthrow taxonomyAccessError(\"NOT_FOUND\", `Taxonomy '${taxonomy}' not found`);\n\t}\n\tconst attached = structure.collections.includes(collection);\n\tif (!attached) {\n\t\tthrow taxonomyAccessError(\n\t\t\t\"VALIDATION_ERROR\",\n\t\t\t`Taxonomy '${taxonomy}' is not attached to collection '${collection}'`,\n\t\t);\n\t}\n\n\tconst entry = await new ContentRepository(db).findById(collection, entryId);\n\tif (!entry) {\n\t\tthrow taxonomyAccessError(\n\t\t\t\"NOT_FOUND\",\n\t\t\t`Content entry '${entryId}' not found in '${collection}'`,\n\t\t);\n\t}\n\n\tconst repo = new TaxonomyRepository(db);\n\tconst groups: string[] = [];\n\tfor (const id of new Set(termIds)) {\n\t\tconst term = await repo.findByIdOrTranslationGroup(id);\n\t\tif (!term) throw taxonomyAccessError(\"NOT_FOUND\", `Taxonomy term '${id}' not found`);\n\t\tif (term.name !== taxonomy) {\n\t\t\tthrow taxonomyAccessError(\n\t\t\t\t\"VALIDATION_ERROR\",\n\t\t\t\t`Taxonomy term '${id}' belongs to '${term.name}', not '${taxonomy}'`,\n\t\t\t);\n\t\t}\n\t\tgroups.push(term.translationGroup ?? term.id);\n\t}\n\n\treturn { repo, groups, locale: entry.locale ?? getI18nConfig()?.defaultLocale ?? \"en\" };\n}\n\nasync function readResolvedEntryTerms(\n\trepo: TaxonomyRepository,\n\tcollection: string,\n\tentryId: string,\n\ttaxonomy: string,\n\tlocale: string,\n): Promise<TaxonomyTermInfo[]> {\n\tconst defaultLocale = getI18nConfig()?.defaultLocale ?? locale;\n\tconst assignments = await repo.getTermAssignmentsForEntry(\n\t\tcollection,\n\t\tentryId,\n\t\ttaxonomy,\n\t\tlocale,\n\t\tdefaultLocale,\n\t);\n\treturn assignments.flatMap(({ term }) => (term ? [taxonomyToTermInfo(term)] : []));\n}\n\nexport function createTaxonomyAccessWithWrite(db: Kysely<Database>): TaxonomyAccessWithWrite {\n\treturn {\n\t\t...createTaxonomyAccess(db),\n\t\tasync createTerm(taxonomy: string, input: TaxonomyTermCreateInput) {\n\t\t\tconst result = await handleTermCreate(db, taxonomy, input);\n\t\t\tif (!result.success) throw taxonomyAccessError(result.error.code, result.error.message);\n\t\t\tconst { term } = result.data;\n\t\t\treturn {\n\t\t\t\tid: term.id,\n\t\t\t\ttaxonomy: term.name,\n\t\t\t\tslug: term.slug,\n\t\t\t\tlabel: term.label,\n\t\t\t\tparentId: term.parentId,\n\t\t\t\tdata: term.description ? { description: term.description } : null,\n\t\t\t\tlocale: term.locale,\n\t\t\t\ttranslationGroup: term.translationGroup,\n\t\t\t};\n\t\t},\n\t\tasync addEntryTerms(collection, entryId, taxonomy, termIds) {\n\t\t\tconst { repo, groups, locale } = await resolveTaxonomyDelta(\n\t\t\t\tdb,\n\t\t\t\tcollection,\n\t\t\t\tentryId,\n\t\t\t\ttaxonomy,\n\t\t\t\ttermIds,\n\t\t\t);\n\t\t\tawait repo.attachGroupsToEntry(collection, entryId, groups);\n\t\t\treturn readResolvedEntryTerms(repo, collection, entryId, taxonomy, locale);\n\t\t},\n\t\tasync removeEntryTerms(collection, entryId, taxonomy, termIds) {\n\t\t\tconst { repo, groups, locale } = await resolveTaxonomyDelta(\n\t\t\t\tdb,\n\t\t\t\tcollection,\n\t\t\t\tentryId,\n\t\t\t\ttaxonomy,\n\t\t\t\ttermIds,\n\t\t\t);\n\t\t\tawait repo.detachGroupsFromEntry(collection, entryId, groups);\n\t\t\treturn readResolvedEntryTerms(repo, collection, entryId, taxonomy, locale);\n\t\t},\n\t};\n}\n\n/**\n * Called immediately before a plugin content write; it throws to refuse the\n * write. When it returns a function, that function is called once the write\n * has succeeded.\n */\nexport type ContentWriteGuard = () => Promise<void | (() => Promise<void>)>;\n\nasync function afterContentWrite(recordWrite: void | (() => Promise<void>)): Promise<void> {\n\tif (typeof recordWrite === \"function\") await recordWrite();\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(\n\tdb: Kysely<Database>,\n\tbeforeContentWrite?: ContentWriteGuard,\n\taccessOptions?: { site?: SiteInfo; revisions?: boolean },\n\tcontentCreate?: (data: {\n\t\tcollection: string;\n\t\tinput: ContentWriteInput;\n\t\toptions?: ContentCreateOptions;\n\t}) => Promise<ContentItem>,\n): ContentAccessWithWrite {\n\tconst readAccess = createContentAccess(db, accessOptions);\n\n\treturn {\n\t\t...readAccess,\n\n\t\tasync create(\n\t\t\tcollection: string,\n\t\t\tdata: ContentWriteInput,\n\t\t\toptions?: ContentCreateOptions,\n\t\t): Promise<ContentItem> {\n\t\t\tconst locale = resolveContentCreateLocale(options?.locale);\n\t\t\tconst recordWrite = await beforeContentWrite?.();\n\t\t\tif (contentCreate) {\n\t\t\t\tconst created = await contentCreate({\n\t\t\t\t\tcollection,\n\t\t\t\t\tinput: data,\n\t\t\t\t\toptions: { ...options, locale },\n\t\t\t\t});\n\t\t\t\tawait afterContentWrite(recordWrite);\n\t\t\t\treturn created;\n\t\t\t}\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\tlocale,\n\t\t\t\t\t\ttranslationOf: options?.translationOf,\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\tawait afterContentWrite(recordWrite);\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 recordWrite = await beforeContentWrite?.();\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.updateDraftAware 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. updateDraftAware delegates no-op writes to\n\t\t\t\t\t// ContentRepository.update.\n\t\t\t\t\tconst item = hasFieldUpdates\n\t\t\t\t\t\t? await trxContentRepo.updateDraftAware(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\tawait afterContentWrite(recordWrite);\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 recordWrite = await beforeContentWrite?.();\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\t// A trashed entry can no longer be opened, so its holder can never\n\t\t\t\t// release the lease itself. Mirrors handleContentDelete.\n\t\t\t\tawait new EntryLockRepository(db).releaseEntry(collection, id);\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\tawait afterContentWrite(recordWrite);\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\treturn item?.status === \"ready\" ? toPluginMediaItem(item) : null;\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(toPluginMediaItem),\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\nfunction mediaReadDenied(): never {\n\tthrow new Error(\"Missing capability: media:read\");\n}\n\nfunction createBlockedMediaReadAccess(): MediaAccess {\n\treturn {\n\t\tget: async () => mediaReadDenied(),\n\t\tlist: async () => mediaReadDenied(),\n\t};\n}\n\nfunction allowedUploadType(contentType: string): string {\n\tif (!CONTENT_TYPE_RE.test(contentType)) {\n\t\tthrow PluginRouteError.badRequest(\"Invalid content type\");\n\t}\n\tconst mimeType = normalizeMime(contentType);\n\tif (!matchesMimeAllowlist(mimeType, GLOBAL_UPLOAD_ALLOWLIST)) {\n\t\tthrow new PluginRouteError(\"UNSUPPORTED_MEDIA_TYPE\", \"File type not allowed\", 415);\n\t}\n\treturn mimeType;\n}\n\nfunction uploadStorageKey(\n\tfilename: string,\n\tmimeType: string,\n): { basename: string; storageKey: string } {\n\tconst keyPrefix = ulid();\n\tconst basename = filename.split(\"/\").pop() ?? filename;\n\tconst dotIdx = basename.lastIndexOf(\".\");\n\tconst nameExt = dotIdx > 0 ? basename.slice(dotIdx + 1).toLowerCase() : \"\";\n\t// Local storage serves files by their key's extension, so it must map to an allowed type.\n\tconst nameType = mime.getType(nameExt);\n\tconst nameExtAllowed =\n\t\tnameType !== null && matchesMimeAllowlist(nameType, GLOBAL_UPLOAD_ALLOWLIST);\n\tconst ext =\n\t\tnameType === mimeType\n\t\t\t? nameExt\n\t\t\t: (mime.getExtension(mimeType) ?? (nameExtAllowed ? nameExt : null));\n\treturn { basename, storageKey: ext ? `${keyPrefix}.${ext}` : keyPrefix };\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 = getUploadUrlFn\n\t\t? async (filename: string, contentType: string) =>\n\t\t\t\tgetUploadUrlFn(filename, allowedUploadType(contentType))\n\t\t: async (filename: string, contentType: string) => {\n\t\t\t\tif (!storage) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"Media getUploadUrl() requires a storage backend. Configure storage in PluginContextFactoryOptions.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst mimeType = allowedUploadType(contentType);\n\t\t\t\tconst { basename, storageKey } = uploadStorageKey(filename, mimeType);\n\n\t\t\t\tconst media = await mediaRepo.createPending({\n\t\t\t\t\tfilename: basename,\n\t\t\t\t\tmimeType,\n\t\t\t\t\tstorageKey,\n\t\t\t\t});\n\n\t\t\t\tconst signed = await storage.getSignedUploadUrl({\n\t\t\t\t\tkey: storageKey,\n\t\t\t\t\tcontentType: mimeType,\n\t\t\t\t\texpiresIn: 3600,\n\t\t\t\t});\n\n\t\t\t\treturn { uploadUrl: signed.url, mediaId: media.id };\n\t\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\tconst mimeType = allowedUploadType(contentType);\n\t\t\tconst { basename, storageKey } = uploadStorageKey(filename, mimeType);\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: mimeType,\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), mimeType);\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,\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 result = await handleMediaDelete(db, id, storage);\n\t\t\tif (!result.success) {\n\t\t\t\tif (result.error.code === \"NOT_FOUND\") return false;\n\t\t\t\tthrow new Error(result.error.message);\n\t\t\t}\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\tinvalidateSiteSettingsCache();\n\t\t\treturn true;\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\nfunction stripTrailingDots(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && value.charCodeAt(end - 1) === 46) end--;\n\treturn end === value.length ? value : value.slice(0, end);\n}\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\tconst normalizedHost = stripTrailingDots(host.toLowerCase());\n\treturn allowedHosts.some((pattern) => {\n\t\tconst normalizedPattern = stripTrailingDots(pattern.toLowerCase());\n\t\tif (normalizedPattern === \"*\") return true;\n\t\tif (normalizedPattern.startsWith(\"*.\")) {\n\t\t\tconst suffix = normalizedPattern.slice(1); // \".example.com\"\n\t\t\t// Match subdomains (foo.example.com) and bare domain (example.com)\n\t\t\treturn normalizedHost.endsWith(suffix) || normalizedHost === normalizedPattern.slice(2);\n\t\t}\n\t\treturn normalizedHost === normalizedPattern;\n\t});\n}\n\nfunction tryParsePluginHttpTarget(url: string): URL | null {\n\ttry {\n\t\treturn new URL(url);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function validatePluginHttpTarget(pluginId: string, url: string): Promise<URL> {\n\ttry {\n\t\treturn await resolveAndValidateExternalUrl(url);\n\t} catch (error) {\n\t\tconst message = error instanceof SsrfError ? error.message : \"SSRF validation failed\";\n\t\tconst target = tryParsePluginHttpTarget(url);\n\t\tthrow new Error(\n\t\t\t`Plugin \"${pluginId}\": blocked fetch to \"${target ? target.hostname : \"invalid URL\"}\": ${message}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n}\n\n/**\n * Create HTTP access with host validation and SSRF protection.\n *\n * Uses redirect: \"manual\" to re-validate each redirect target before dispatch.\n */\nexport function createHttpAccess(\n\tpluginId: string,\n\tallowedHosts: string[],\n\tfetchImpl: typeof fetch = globalThis.fetch,\n): 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\t\t\tlet requestBuffered = false;\n\t\t\tlet redirected = false;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\tconst target = tryParsePluginHttpTarget(currentUrl);\n\t\t\t\tif (target && !isHostAllowed(target.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 \"${target.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\t\t\t\tawait validatePluginHttpTarget(pluginId, currentUrl);\n\t\t\t\tif (!requestBuffered) {\n\t\t\t\t\tcurrentInit = await bufferPluginHttpRequest(currentInit);\n\t\t\t\t\trequestBuffered = true;\n\t\t\t\t}\n\n\t\t\t\tconst response = await fetchImpl(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\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (location === null) {\n\t\t\t\t\treturn pluginHttpResponseFromWire(\n\t\t\t\t\t\tawait pluginHttpResponseToWire(response, currentUrl, redirected),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst redirectAction = pluginHttpRedirectAction(response.status, true, currentInit);\n\t\t\t\tif (redirectAction === \"return\") {\n\t\t\t\t\treturn pluginHttpResponseFromWire(\n\t\t\t\t\t\tawait pluginHttpResponseToWire(response, currentUrl, redirected),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait response.body?.cancel();\n\t\t\t\tif (redirectAction === \"error\") {\n\t\t\t\t\tthrow new Error(`Plugin \"${pluginId}\": redirect mode is \"error\"`);\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\tredirected = true;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentInit = rewritePluginHttpRedirect(response.status, currentInit);\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:request:unrestricted 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(\n\tpluginId: string,\n\tfetchImpl: typeof fetch = globalThis.fetch,\n): 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\t\t\tlet requestBuffered = false;\n\t\t\tlet redirected = false;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\tawait validatePluginHttpTarget(pluginId, currentUrl);\n\t\t\t\tif (!requestBuffered) {\n\t\t\t\t\tcurrentInit = await bufferPluginHttpRequest(currentInit);\n\t\t\t\t\trequestBuffered = true;\n\t\t\t\t}\n\n\t\t\t\tconst response = await fetchImpl(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\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (location === null) {\n\t\t\t\t\treturn pluginHttpResponseFromWire(\n\t\t\t\t\t\tawait pluginHttpResponseToWire(response, currentUrl, redirected),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst redirectAction = pluginHttpRedirectAction(response.status, true, currentInit);\n\t\t\t\tif (redirectAction === \"return\") {\n\t\t\t\t\treturn pluginHttpResponseFromWire(\n\t\t\t\t\t\tawait pluginHttpResponseToWire(response, currentUrl, redirected),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait response.body?.cancel();\n\t\t\t\tif (redirectAction === \"error\") {\n\t\t\t\t\tthrow new Error(`Plugin \"${pluginId}\": redirect mode is \"error\"`);\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\tredirected = true;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentInit = rewritePluginHttpRedirect(response.status, currentInit);\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, redactor?: PluginSecretRedactor): LogAccess {\n\tconst prefix = `[plugin:${pluginId}]`;\n\tconst redact = <T>(value: T): T => redactor?.redact(value) ?? value;\n\n\treturn {\n\t\tdebug(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.debug(prefix, redact(message), redact(data));\n\t\t\t} else {\n\t\t\t\tconsole.debug(prefix, redact(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, redact(message), redact(data));\n\t\t\t} else {\n\t\t\t\tconsole.info(prefix, redact(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, redact(message), redact(data));\n\t\t\t} else {\n\t\t\t\tconsole.warn(prefix, redact(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, redact(message), redact(data));\n\t\t\t} else {\n\t\t\t\tconsole.error(prefix, redact(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, resolved by the runtime in the same order as links in emails */\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. A missing URL becomes an\n * 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\tbeforeContentWrite?: ContentWriteGuard;\n\tcontentCreate?: PluginContentCreateCallback;\n\tcontentActions?: ContentActionCallbacks;\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/** Clock used to calculate the first run of recurring plugin tasks. */\n\tnow?: () => Date;\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\tcommentModerate?: (\n\t\tpluginId: string,\n\t\tid: string,\n\t\tstatus: PluginCommentStatus,\n\t\texpectedStatus: PluginCommentStatus,\n\t) => Promise<PluginComment>;\n}\n\nexport interface ContentActionCallbacks {\n\t/** Register a sandbox invocation before it can call a content action. */\n\tbegin?(\n\t\tpluginId: string,\n\t\tinvocationId: string,\n\t\tinvalidateContentCache?: (tags: string[]) => Promise<void>,\n\t): void;\n\t/** Release queued after-hooks; `final: false` keeps late actions self-scheduling after timeout. */\n\tflush(pluginId: string, invocationId?: string, final?: boolean): Promise<void>;\n\tgetVersioned(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t): Promise<VersionedContentItem | null>;\n\tpublish(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions: { _rev: string },\n\t\tinvocationId?: string,\n\t\tinvalidateContentCache?: (tags: string[]) => Promise<void>,\n\t): Promise<VersionedContentItem>;\n\tunpublish(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions: { _rev: string },\n\t\tinvocationId?: string,\n\t\tinvalidateContentCache?: (tags: string[]) => Promise<void>,\n\t): Promise<VersionedContentItem>;\n\tschedule(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions: { scheduledAt: string; _rev: string },\n\t\tinvocationId?: string,\n\t\tinvalidateContentCache?: (tags: string[]) => Promise<void>,\n\t): Promise<VersionedContentItem>;\n\tunschedule(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions: { _rev: string },\n\t\tinvocationId?: string,\n\t\tinvalidateContentCache?: (tags: string[]) => Promise<void>,\n\t): Promise<VersionedContentItem>;\n\tgetTrashedVersioned(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t): Promise<VersionedContentItem | null>;\n\trestore(\n\t\tpluginId: string,\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions: { _rev: string },\n\t\tinvocationId?: string,\n\t\tinvalidateContentCache?: (tags: string[]) => Promise<void>,\n\t): Promise<VersionedContentItem>;\n}\n\n/**\n * Factory for creating plugin contexts\n */\nexport class PluginContextFactory {\n\tprivate resolveDb: () => Kysely<Database>;\n\tprivate beforeContentWrite?: ContentWriteGuard;\n\tprivate contentCreate?: PluginContentCreateCallback;\n\tprivate contentActions?: ContentActionCallbacks;\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 now: () => Date;\n\tprivate emailPipeline?: EmailPipeline;\n\tprivate commentModerate?: PluginContextFactoryOptions[\"commentModerate\"];\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.beforeContentWrite = options.beforeContentWrite;\n\t\tthis.contentCreate = options.contentCreate;\n\t\tthis.contentActions = options.contentActions;\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.now = options.now ?? (() => new Date());\n\t\tthis.emailPipeline = options.emailPipeline;\n\t\tthis.commentModerate = options.commentModerate;\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 secretRedactor = createPluginSecretRedactor();\n\t\tconst settings = createSettingsAccess(\n\t\t\toptionsRepo,\n\t\t\tplugin.id,\n\t\t\tplugin.admin.settingsSchema ?? {},\n\t\t\tundefined,\n\t\t\tsecretRedactor.add,\n\t\t);\n\t\tconst kv = createKVAccess(optionsRepo, plugin.id, settings);\n\t\tconst log = createLogAccess(plugin.id, secretRedactor);\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(\n\t\t\t\tdb,\n\t\t\t\tthis.beforeContentWrite,\n\t\t\t\t{\n\t\t\t\t\tsite: this.site,\n\t\t\t\t\trevisions: capabilities.has(\"content:revisions:read\"),\n\t\t\t\t},\n\t\t\t\tthis.contentCreate\n\t\t\t\t\t? (input) => this.contentCreate!(plugin.id, input.collection, input.input, input.options)\n\t\t\t\t\t: undefined,\n\t\t\t);\n\t\t} else if (capabilities.has(\"content:read\")) {\n\t\t\tcontent = createContentAccess(db, {\n\t\t\t\tsite: this.site,\n\t\t\t\trevisions: capabilities.has(\"content:revisions:read\"),\n\t\t\t});\n\t\t}\n\t\tif (capabilities.has(\"content:publish\") && this.contentActions) {\n\t\t\tcontent = Object.assign(content ?? createContentAccess(db), {\n\t\t\t\tgetVersioned: (collection: string, id: string) =>\n\t\t\t\t\tthis.contentActions!.getVersioned(plugin.id, collection, id),\n\t\t\t\tpublish: (collection: string, id: string, options: { _rev: string }) =>\n\t\t\t\t\tthis.contentActions!.publish(plugin.id, collection, id, options),\n\t\t\t\tunpublish: (collection: string, id: string, options: { _rev: string }) =>\n\t\t\t\t\tthis.contentActions!.unpublish(plugin.id, collection, id, options),\n\t\t\t\tschedule: (\n\t\t\t\t\tcollection: string,\n\t\t\t\t\tid: string,\n\t\t\t\t\toptions: { scheduledAt: string; _rev: string },\n\t\t\t\t) => this.contentActions!.schedule(plugin.id, collection, id, options),\n\t\t\t\tunschedule: (collection: string, id: string, options: { _rev: string }) =>\n\t\t\t\t\tthis.contentActions!.unschedule(plugin.id, collection, id, options),\n\t\t\t});\n\t\t}\n\t\tif (capabilities.has(\"content:restore\") && this.contentActions) {\n\t\t\tcontent = Object.assign(\n\t\t\t\tcontent ?? {\n\t\t\t\t\tget: async () => {\n\t\t\t\t\t\tthrow new Error(\"Missing capability: content:read\");\n\t\t\t\t\t},\n\t\t\t\t\tlist: async () => {\n\t\t\t\t\t\tthrow new Error(\"Missing capability: content:read\");\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tgetTrashedVersioned: (collection: string, id: string) =>\n\t\t\t\t\t\tthis.contentActions!.getTrashedVersioned(plugin.id, collection, id),\n\t\t\t\t\trestore: (collection: string, id: string, options: { _rev: string }) =>\n\t\t\t\t\t\tthis.contentActions!.restore(plugin.id, collection, id, options),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst schema = capabilities.has(\"schema:read\") ? createSchemaAccess(db) : undefined;\n\n\t\t// Capability-gated: taxonomies\n\t\tlet taxonomies: TaxonomyAccess | TaxonomyAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"taxonomies:write\")) {\n\t\t\ttaxonomies = createTaxonomyAccessWithWrite(db);\n\t\t} else if (capabilities.has(\"taxonomies:read\")) {\n\t\t\ttaxonomies = createTaxonomyAccess(db);\n\t\t}\n\n\t\tconst bylines = capabilities.has(\"bylines:read\") ? createBylineAccess(db) : undefined;\n\n\t\tlet redirects: RedirectAccess | RedirectAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"redirects:write\")) {\n\t\t\tredirects = createRedirectAccess(db, true);\n\t\t} else if (capabilities.has(\"redirects:read\")) {\n\t\t\tredirects = createRedirectAccess(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\tconst hasMediaAccess =\n\t\t\tcapabilities.has(\"media:read\") ||\n\t\t\tcapabilities.has(\"media:write\") ||\n\t\t\tcapabilities.has(\"media:bytes:read\") ||\n\t\t\tcapabilities.has(\"media:metadata:write\");\n\t\tif (hasMediaAccess) {\n\t\t\tmedia =\n\t\t\t\tcapabilities.has(\"media:read\") || capabilities.has(\"media:write\")\n\t\t\t\t\t? createMediaAccess(db)\n\t\t\t\t\t: createBlockedMediaReadAccess();\n\t\t}\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\tmedia ??= createMediaAccess(db);\n\t\t\t}\n\t\t}\n\t\tif (capabilities.has(\"media:bytes:read\") && media) {\n\t\t\tmedia.readBytes = (id, options) => readPluginMediaBytes(db, this.storage, id, options);\n\t\t}\n\t\tif (capabilities.has(\"media:metadata:write\") && media) {\n\t\t\tmedia.updateMetadata = (id, patch) => updatePluginMediaMetadata(db, id, patch);\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\tlet comments: CommentAccess | undefined;\n\t\tif (capabilities.has(\"comments:moderate\")) {\n\t\t\tcomments = createCommentAccess(db, (id, status, expectedStatus) => {\n\t\t\t\tif (!this.commentModerate) throw new Error(\"Comment moderation is unavailable\");\n\t\t\t\treturn this.commentModerate(plugin.id, id, status, expectedStatus);\n\t\t\t});\n\t\t} else if (capabilities.has(\"comments:read\")) {\n\t\t\tcomments = createCommentAccess(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, this.now);\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\tsettings,\n\t\t\tcontent,\n\t\t\tschema,\n\t\t\ttaxonomies,\n\t\t\tbylines,\n\t\t\tredirects,\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\tcomments,\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":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-D0jcgBD7.mjs","names":[],"sources":["../src/scheduler-health.ts","../src/api/handlers/dashboard.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"./database/repositories/options.js\";\nimport type { Database } from \"./database/types.js\";\n\nexport const SCHEDULER_HEARTBEAT_OPTION = \"system:scheduler:last_completed_at\";\nexport const SCHEDULER_STALE_AFTER_MS = 5 * 60 * 1000;\n\nexport interface SchedulerHealth {\n\tstatus: \"healthy\" | \"stale\" | \"unknown\";\n\tlastCompletedAt: string | null;\n}\n\nexport async function recordSchedulerHeartbeat(\n\tdb: Kysely<Database>,\n\tcompletedAt = new Date(),\n): Promise<void> {\n\tawait new OptionsRepository(db).set(SCHEDULER_HEARTBEAT_OPTION, completedAt.toISOString());\n}\n\nexport async function recordSchedulerHeartbeatSafely(\n\tdb: Kysely<Database>,\n\tcompletedAt = new Date(),\n): Promise<void> {\n\ttry {\n\t\tawait recordSchedulerHeartbeat(db, completedAt);\n\t} catch (error) {\n\t\tconsole.error(\"[scheduler] Failed to record heartbeat:\", error);\n\t}\n}\n\nexport async function getSchedulerHealth(\n\tdb: Kysely<Database>,\n\tnow = new Date(),\n): Promise<SchedulerHealth> {\n\tconst lastCompletedAt = await new OptionsRepository(db).get<string>(SCHEDULER_HEARTBEAT_OPTION);\n\tconst lastCompletedAtMs = lastCompletedAt ? Date.parse(lastCompletedAt) : Number.NaN;\n\tif (!lastCompletedAt || Number.isNaN(lastCompletedAtMs)) {\n\t\treturn { status: \"unknown\", lastCompletedAt: null };\n\t}\n\n\treturn {\n\t\tstatus: now.getTime() - lastCompletedAtMs > SCHEDULER_STALE_AFTER_MS ? \"stale\" : \"healthy\",\n\t\tlastCompletedAt,\n\t};\n}\n","/**\n * Dashboard stats handler\n *\n * Returns summary data for the admin dashboard in a single request:\n * collection content counts, media count, user count, and recent\n * content across all collections.\n */\n\nimport { sql, type Kysely } from \"kysely\";\n\nimport { ContentRepository } from \"../../database/repositories/content.js\";\nimport { MediaRepository } from \"../../database/repositories/media.js\";\nimport { OptionsRepository } from \"../../database/repositories/options.js\";\nimport { UserRepository } from \"../../database/repositories/user.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport {\n\tSCHEDULED_POLICY_REJECTION_PREFIX,\n\tisScheduledPolicyRejection,\n\ttype ScheduledPolicyRejection,\n\ttype VersionedScheduledPolicyRejection,\n} from \"../../plugins/content-policy.js\";\nimport type { VersionedValue } from \"../../plugins/types.js\";\nimport { getSchedulerHealth, type SchedulerHealth } from \"../../scheduler-health.js\";\nimport type { ApiResult } from \"../types.js\";\n\nexport interface CollectionStats {\n\tslug: string;\n\tlabel: string;\n\ttotal: number;\n\tpublished: number;\n\tdraft: number;\n\tscheduled: number;\n\toverdueScheduled: number;\n}\n\nexport interface RecentItem {\n\tid: string;\n\tcollection: string;\n\tcollectionLabel: string;\n\ttitle: string;\n\tslug: string | null;\n\tstatus: string;\n\tupdatedAt: string;\n\tauthorId: string | null;\n}\n\nexport interface DashboardStats {\n\tcollections: CollectionStats[];\n\tmediaCount: number;\n\tuserCount: number;\n\trecentItems: RecentItem[];\n\tschedulerHealth: SchedulerHealth;\n\tpolicyRejectedScheduled: number;\n\tpolicyRejections: VersionedScheduledPolicyRejection[];\n}\n\nconst POLICY_REJECTION_PREVIEW_LIMIT = 20;\n\n/**\n * Fetch dashboard statistics.\n *\n * Queries are intentionally lightweight — counts use indexed columns,\n * and recent items are capped at 10.\n *\n * Without `includeDrafts` (callers lacking `content:read_drafts`), only\n * published entries are counted or listed, and scheduled-publication policy\n * rejections are omitted.\n */\nexport async function handleDashboardStats(\n\tdb: Kysely<Database>,\n\t{ includeDrafts }: { includeDrafts: boolean },\n\tnow = new Date(),\n): Promise<ApiResult<DashboardStats>> {\n\ttry {\n\t\t// Discover collections from the system table\n\t\tconst collections = await db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\"slug\", \"label\"])\n\t\t\t.orderBy(\"slug\", \"asc\")\n\t\t\t.execute();\n\n\t\t// Gather per-collection counts in parallel\n\t\tconst contentRepo = new ContentRepository(db);\n\t\tconst collectionStats: CollectionStats[] = await Promise.all(\n\t\t\tcollections.map(async (col) => {\n\t\t\t\tconst stats = await contentRepo.getStats(col.slug, now);\n\t\t\t\tif (!includeDrafts) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tslug: col.slug,\n\t\t\t\t\t\tlabel: col.label,\n\t\t\t\t\t\ttotal: stats.published,\n\t\t\t\t\t\tpublished: stats.published,\n\t\t\t\t\t\tdraft: 0,\n\t\t\t\t\t\tscheduled: 0,\n\t\t\t\t\t\toverdueScheduled: 0,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tslug: col.slug,\n\t\t\t\t\tlabel: col.label,\n\t\t\t\t\ttotal: stats.total,\n\t\t\t\t\tpublished: stats.published,\n\t\t\t\t\tdraft: stats.draft,\n\t\t\t\t\tscheduled: stats.scheduled,\n\t\t\t\t\toverdueScheduled: stats.overdueScheduled,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\n\t\t// Media and user counts\n\t\tconst mediaRepo = new MediaRepository(db);\n\t\tconst userRepo = new UserRepository(db);\n\t\tconst optionsRepo = new OptionsRepository(db);\n\t\tconst [\n\t\t\tmediaCount,\n\t\t\tuserCount,\n\t\t\tschedulerHealth,\n\t\t\tpolicyRejectedScheduled,\n\t\t\tversionedPolicyRejectionOptions,\n\t\t] = await Promise.all([\n\t\t\tmediaRepo.count(),\n\t\t\tuserRepo.count(),\n\t\t\tgetSchedulerHealth(db, now),\n\t\t\tincludeDrafts ? optionsRepo.countByPrefix(SCHEDULED_POLICY_REJECTION_PREFIX) : 0,\n\t\t\tincludeDrafts\n\t\t\t\t? optionsRepo.getVersionedByPrefix<ScheduledPolicyRejection>(\n\t\t\t\t\t\tSCHEDULED_POLICY_REJECTION_PREFIX,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlimit: POLICY_REJECTION_PREVIEW_LIMIT,\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t: new Map<string, VersionedValue<ScheduledPolicyRejection>>(),\n\t\t]);\n\t\tconst policyRejections = [...versionedPolicyRejectionOptions.values()]\n\t\t\t.filter(({ value }) => isScheduledPolicyRejection(value))\n\t\t\t.map(({ value, revision }) => ({ ...value, _rev: revision }));\n\n\t\t// Recent items across all collections (last 10 updated)\n\t\tconst recentItems = await fetchRecentItems(db, collections, includeDrafts);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tcollections: collectionStats,\n\t\t\t\tmediaCount,\n\t\t\t\tuserCount,\n\t\t\t\trecentItems,\n\t\t\t\tschedulerHealth,\n\t\t\t\tpolicyRejectedScheduled,\n\t\t\t\tpolicyRejections,\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Dashboard stats error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"DASHBOARD_STATS_ERROR\",\n\t\t\t\tmessage: \"Failed to load dashboard statistics\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** Raw row shape from the UNION ALL query — all snake_case. */\ninterface RecentItemRow {\n\tid: string;\n\tcollection: string;\n\tcollection_label: string;\n\ttitle: string;\n\tslug: string | null;\n\tstatus: string;\n\tupdated_at: string;\n\tauthor_id: string | null;\n}\n\n/**\n * Fetch the 10 most recently updated items across all collections.\n *\n * Uses UNION ALL over each ec_* table. The query is safe because\n * collection slugs come from the system table and are validated.\n *\n * `title` is not a standard column — it's a user-defined field. We query\n * `_emdash_fields` to discover which collections have one and fall back\n * to `slug` (which is always present) otherwise.\n *\n * Without `includeDrafts`, only published entries are considered.\n */\nasync function fetchRecentItems(\n\tdb: Kysely<Database>,\n\tcollections: Array<{ slug: string; label: string }>,\n\tincludeDrafts: boolean,\n): Promise<RecentItem[]> {\n\tif (collections.length === 0) return [];\n\n\t// Discover which collections have a \"title\" column\n\tconst titleFields = await db\n\t\t.selectFrom(\"_emdash_fields as f\")\n\t\t.innerJoin(\"_emdash_collections as c\", \"c.id\", \"f.collection_id\")\n\t\t.select([\"c.slug as collection_slug\"])\n\t\t.where(\"f.slug\", \"=\", \"title\")\n\t\t.execute();\n\n\tconst collectionsWithTitle = new Set(titleFields.map((r) => r.collection_slug));\n\tconst statusFilter = includeDrafts ? sql`` : sql`AND status = 'published'`;\n\n\t// Issue one query per collection in parallel, then merge in JS.\n\t// A single UNION ALL across N collections trips D1's\n\t// SQLITE_LIMIT_COMPOUND_SELECT cap when N is large enough (#895);\n\t// per-collection queries side-step that. Each query fetches at most\n\t// 10 rows, so the merge handles at most N * 10 rows before slicing.\n\tconst perCollection = await Promise.all(\n\t\tcollections.map(async (col) => {\n\t\t\tvalidateIdentifier(col.slug);\n\t\t\tconst table = `ec_${col.slug}`;\n\t\t\tconst hasTitle = collectionsWithTitle.has(col.slug);\n\n\t\t\t// Use title column if it exists, otherwise fall back to slug, id.\n\t\t\t// All output uses snake_case to avoid SQLite quoting issues on D1.\n\t\t\tconst titleExpr = hasTitle ? sql`COALESCE(title, slug, id)` : sql`COALESCE(slug, id)`;\n\n\t\t\tconst result = await sql<RecentItemRow>`\n\t\t\t\tSELECT\n\t\t\t\t\tid,\n\t\t\t\t\t${sql.lit(col.slug)} AS collection,\n\t\t\t\t\t${sql.lit(col.label)} AS collection_label,\n\t\t\t\t\t${titleExpr} AS title,\n\t\t\t\t\tslug,\n\t\t\t\t\tstatus,\n\t\t\t\t\tupdated_at,\n\t\t\t\t\tauthor_id\n\t\t\t\tFROM ${sql.ref(table)}\n\t\t\t\tWHERE deleted_at IS NULL ${statusFilter}\n\t\t\t\tORDER BY updated_at DESC\n\t\t\t\tLIMIT 10\n\t\t\t`.execute(db);\n\t\t\treturn result.rows;\n\t\t}),\n\t);\n\n\t// Merge across collections, sort by updated_at desc, take top 10.\n\tconst merged = perCollection\n\t\t.flat()\n\t\t.toSorted((a, b) => (a.updated_at < b.updated_at ? 1 : a.updated_at > b.updated_at ? -1 : 0))\n\t\t.slice(0, 10);\n\n\t// Map snake_case DB rows to camelCase API shape\n\treturn merged.map((row) => ({\n\t\tid: row.id,\n\t\tcollection: row.collection,\n\t\tcollectionLabel: row.collection_label,\n\t\ttitle: row.title,\n\t\tslug: row.slug,\n\t\tstatus: row.status,\n\t\tupdatedAt: row.updated_at,\n\t\tauthorId: row.author_id,\n\t}));\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"db-errors-CBD5yMro.mjs","names":[],"sources":["../src/utils/db-errors.ts"],"sourcesContent":["/**\n * Shared detection helpers for database-layer error messages.\n *\n * Different SQL dialects phrase \"table or relation does not exist\" differently:\n *\n * - SQLite / D1: \"no such table: foo\"\n * - PostgreSQL: 'relation \"foo\" does not exist'\n * 'table \"foo\" does not exist'\n * - MySQL (future): \"Table 'db.foo' doesn't exist\"\n *\n * Runtime code paths that short-circuit on missing tables (pre-migration\n * probes, optional feature tables, etc.) should use these helpers rather\n * than hand-rolling string matches per call-site.\n */\n\n/**\n * Extract a lowercase error message from any unknown value, safely.\n */\nfunction messageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message.toLowerCase();\n\tif (typeof error === \"string\") return error.toLowerCase();\n\treturn \"\";\n}\n\n/**\n * Returns true when `error` is a \"column does not exist\" error.\n * Used to handle where filters that reference non-existent field names\n * gracefully (return empty results) instead of propagating a SQL error.\n * When `column` is given, only errors naming that column match.\n */\nexport function isMissingColumnError(error: unknown, column?: string): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\tif (column && !message.includes(column.toLowerCase())) return false;\n\n\t// SQLite / D1: \"no such column: foo\"\n\tif (message.includes(\"no such column\")) return true;\n\n\t// PostgreSQL SQLSTATE 42703: 'column \"foo\" does not exist'\n\t// Exclude \"relation\" to avoid false positives on table names containing \"column\"\n\tif (\n\t\tmessage.includes(\"does not exist\") &&\n\t\tmessage.includes(\"column\") &&\n\t\t!message.includes(\"relation\")\n\t)\n\t\treturn true;\n\n\treturn false;\n}\n\n/**\n * Returns true when `error` is a \"table does not exist\" error across the\n * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime\n * probes to treat pre-migration databases as empty without logging a scary\n * warning, while still propagating unrelated errors (permissions, connection\n * loss, syntax issues) to callers.\n */\nexport function isMissingTableError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1\n\tif (message.includes(\"no such table\")) return true;\n\n\t// PostgreSQL (and some MySQL variants): \"relation ... does not exist\" /\n\t// \"table ... does not exist\" / \"doesn't exist\".\n\tif (message.includes(\"does not exist\") || message.includes(\"doesn't exist\")) {\n\t\treturn message.includes(\"relation\") || message.includes(\"table\");\n\t}\n\n\treturn false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,UAAU,OAAwB;AAC1C,KAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,aAAa;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,aAAa;AACzD,QAAO;;;;;;;;AASR,SAAgB,qBAAqB,OAAgB,QAA0B;CAC9E,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,UAAU,CAAC,QAAQ,SAAS,OAAO,aAAa,CAAC,CAAE,QAAO;AAG9D,KAAI,QAAQ,SAAS,iBAAiB,CAAE,QAAO;AAI/C,KACC,QAAQ,SAAS,iBAAiB,IAClC,QAAQ,SAAS,SAAS,IAC1B,CAAC,QAAQ,SAAS,WAAW,CAE7B,QAAO;AAER,QAAO;;;;;;;;;AAUR,SAAgB,oBAAoB,OAAyB;CAC5D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,gBAAgB,CAAE,QAAO;AAI9C,KAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,gBAAgB,CAC1E,QAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,QAAQ;AAGjE,QAAO"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
//#region src/deferred-tasks.d.ts
|
|
2
|
-
interface DeferredTaskTracker {
|
|
3
|
-
settled: Promise<void>;
|
|
4
|
-
track<T>(promise: Promise<T>): Promise<T>;
|
|
5
|
-
settle(): void;
|
|
6
|
-
}
|
|
7
|
-
//#endregion
|
|
8
|
-
export { DeferredTaskTracker as t };
|
|
9
|
-
//# sourceMappingURL=deferred-tasks-ZvUECSzI.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deferred-tasks-ZvUECSzI.d.mts","names":[],"sources":["../src/deferred-tasks.ts"],"mappings":";UAAiB,mBAAA;EAChB,OAAA,EAAS,OAAA;EACT,KAAA,IAAS,OAAA,EAAS,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EACvC,MAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"device-flow-BkFKBoAn.mjs","names":[],"sources":["../src/api/handlers/device-flow.ts"],"sourcesContent":["/**\n * OAuth Device Flow handlers (RFC 8628).\n *\n * EmDash acts as an OAuth 2.0 authorization server. The CLI requests\n * a device code, displays a URL + user code, and polls for a token.\n * The user opens a browser, logs in, enters the code, and the CLI gets\n * an access + refresh token pair.\n *\n * Uses arctic for code generation and @emdash-cms/auth for token utilities.\n */\n\nimport { clampScopes } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely, Selectable } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tisValidScope,\n\tTOKEN_PREFIXES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database, DeviceCodeTable } from \"../../database/types.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Device codes expire after 15 minutes */\nconst DEVICE_CODE_TTL_SECONDS = 15 * 60;\n\n/** Default polling interval in seconds */\nconst DEFAULT_INTERVAL = 5;\n\n/** RFC 8628 §3.5: interval increase on slow_down */\nconst SLOW_DOWN_INCREMENT = 5;\n\n/** Maximum slow_down interval cap (seconds) */\nconst MAX_SLOW_DOWN_INTERVAL = 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n/** Default scopes for CLI login */\nconst DEFAULT_SCOPES = [\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"schema:read\",\n] as const;\n\n/** Pattern to normalize user codes (strip hyphens) */\nconst HYPHEN_PATTERN = /-/g;\n\n/** Characters for user codes (uppercase, no ambiguous chars like 0/O, 1/I) */\nconst USER_CODE_CHARS = \"ABCDEFGHJKLMNPQRSTUVWXYZ23456789\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// RFC 8628 error codes\nexport type DeviceFlowError =\n\t| \"authorization_pending\"\n\t| \"slow_down\"\n\t| \"expired_token\"\n\t| \"access_denied\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Generate a short human-readable user code (XXXX-XXXX) */\nfunction generateUserCode(): string {\n\tconst bytes = new Uint8Array(8);\n\tcrypto.getRandomValues(bytes);\n\tconst chars = Array.from(bytes, (b) => USER_CODE_CHARS[b % USER_CODE_CHARS.length]).join(\"\");\n\treturn `${chars.slice(0, 4)}-${chars.slice(4, 8)}`;\n}\n\n/** Get an ISO datetime string offset from now */\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\n/** Validate and normalize scopes. Returns validated scope list. */\nfunction normalizeScopes(requested?: string[]): string[] {\n\tif (!requested || requested.length === 0) {\n\t\treturn [...DEFAULT_SCOPES];\n\t}\n\treturn [...new Set(requested.filter(isValidScope))];\n}\n\n/**\n * Scopes an approval by a user with `role` would grant:\n * requested_scopes ∩ scopesForRole(role). Approval and the approval-page\n * lookup must agree, so both go through here.\n */\nfunction grantableScopes(requested: string[], role: RoleLevel): string[] {\n\treturn clampScopes(requested, role);\n}\n\n/** Find a pending device code by user code, ignoring hyphens and case. Does not check expiry. */\nasync function findPendingDeviceCode(\n\tdb: Kysely<Database>,\n\tuserCode: string,\n): Promise<Selectable<DeviceCodeTable> | undefined> {\n\tconst chars = userCode.replace(HYPHEN_PATTERN, \"\").toUpperCase();\n\tif (chars.length !== 8) return undefined;\n\n\t// Stored codes always have the generateUserCode() shape.\n\treturn db\n\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t.selectAll()\n\t\t.where(\"user_code\", \"=\", `${chars.slice(0, 4)}-${chars.slice(4)}`)\n\t\t.where(\"status\", \"=\", \"pending\")\n\t\t.executeTakeFirst();\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * POST /oauth/device/code\n *\n * Issue a device code + user code. The CLI displays the user code\n * and tells the user to open the verification URI.\n */\nexport async function handleDeviceCodeRequest(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tclient_id?: string;\n\t\tscope?: string;\n\t},\n\tverificationUri: string,\n): Promise<ApiResult<DeviceCodeResponse>> {\n\ttry {\n\t\t// Note: client_id is accepted but not validated against _emdash_oauth_clients\n\t\t// because the CLI uses a well-known built-in client ID (\"emdash-cli\") that\n\t\t// isn't stored in the DB. Full client_id validation + scope clamping for the\n\t\t// device flow is tracked as a follow-up.\n\n\t\t// Parse and validate scopes\n\t\tconst requestedScopes = input.scope ? input.scope.split(\" \").filter(Boolean) : [];\n\t\tconst scopes = normalizeScopes(requestedScopes);\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\tconst deviceCode = generateCodeVerifier();\n\t\tconst userCode = generateUserCode();\n\t\tconst expires = expiresAt(DEVICE_CODE_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_device_codes\")\n\t\t\t.values({\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tstatus: \"pending\",\n\t\t\t\texpires_at: expires,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tverification_uri: verificationUri,\n\t\t\t\texpires_in: DEVICE_CODE_TTL_SECONDS,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"DEVICE_CODE_ERROR\",\n\t\t\t\tmessage: \"Failed to create device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/token\n *\n * CLI polls this endpoint with the device_code. Returns:\n * - 200 with tokens if authorized\n * - 400 with error \"authorization_pending\" while waiting\n * - 400 with error \"slow_down\" if polling too fast\n * - 400 with error \"expired_token\" if the code expired\n * - 400 with error \"access_denied\" if the user denied\n */\nexport async function handleDeviceTokenExchange(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tdevice_code: string;\n\t\tgrant_type: string;\n\t},\n): Promise<\n\tApiResult<TokenResponse> & { deviceFlowError?: DeviceFlowError; deviceFlowInterval?: number }\n> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (input.grant_type !== \"urn:ietf:params:oauth:grant-type:device_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the device code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date();\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < now) {\n\t\t\t// Clean up expired code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"expired_token\",\n\t\t\t\terror: { code: \"expired_token\", message: \"The device code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check status\n\t\tif (row.status === \"denied\") {\n\t\t\t// Clean up denied code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"access_denied\",\n\t\t\t\terror: { code: \"access_denied\", message: \"The user denied the request\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status === \"pending\") {\n\t\t\t// RFC 8628 §3.5: slow_down enforcement during polling phase.\n\t\t\t// Only applies while waiting for authorization — once authorized,\n\t\t\t// the final exchange proceeds without throttling.\n\t\t\tif (row.last_polled_at) {\n\t\t\t\tconst lastPolled = new Date(row.last_polled_at);\n\t\t\t\tconst elapsedSeconds = (now.getTime() - lastPolled.getTime()) / 1000;\n\n\t\t\t\tif (elapsedSeconds < row.interval) {\n\t\t\t\t\t// Too fast — increase interval by 5s per RFC 8628 §3.5, capped at 60s\n\t\t\t\t\tconst newInterval = Math.min(row.interval + SLOW_DOWN_INCREMENT, MAX_SLOW_DOWN_INTERVAL);\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\tinterval: newInterval,\n\t\t\t\t\t\t\tlast_polled_at: now.toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t\t\t.execute();\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tdeviceFlowError: \"slow_down\",\n\t\t\t\t\t\tdeviceFlowInterval: newInterval,\n\t\t\t\t\t\terror: { code: \"slow_down\", message: \"Polling too fast\" },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update last_polled_at for future slow_down checks\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ last_polled_at: now.toISOString() })\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"authorization_pending\",\n\t\t\t\terror: { code: \"authorization_pending\", message: \"Authorization pending\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status !== \"authorized\" || !row.user_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code state\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate tokens before consuming the device code so that if\n\t\t// generation fails, the code is still available for retry.\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically consume the device code and create tokens in a single\n\t\t// transaction. DELETE...RETURNING prevents TOCTOU: two concurrent\n\t\t// requests race on the DELETE, only one gets a row back. Wrapping\n\t\t// in a transaction ensures the code isn't consumed if token storage fails.\n\t\tconst result = await withTransaction(db, async (trx) => {\n\t\t\tconst consumed = await trx\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.where(\"status\", \"=\", \"authorized\")\n\t\t\t\t.returningAll()\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!consumed) return null;\n\n\t\t\tif (!consumed.user_id) return null;\n\n\t\t\tconst scopes = JSON.parse(consumed.scopes) as string[];\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\treturn { scopes };\n\t\t});\n\n\t\tif (!result) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Device code already consumed\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: result.scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * GET /oauth/device/authorize\n *\n * Returns the scopes a pending device code requested, and the subset an\n * approval by this user would grant, so the approval page can show them\n * before the user approves. Reveals nothing else about the device code.\n */\nexport async function handleDeviceCodeLookup(\n\tdb: Kysely<Database>,\n\tuserRole: RoleLevel,\n\tinput: { user_code: string },\n): Promise<ApiResult<{ requestedScopes: string[]; grantedScopes: string[] }>> {\n\ttry {\n\t\tconst match = await findPendingDeviceCode(db, input.user_code);\n\n\t\tif (!match) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CODE\", message: \"Invalid or expired code\" },\n\t\t\t};\n\t\t}\n\n\t\tif (new Date(match.expires_at) < new Date()) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"EXPIRED_CODE\", message: \"This code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\tconst requestedScopes = JSON.parse(match.scopes) as string[];\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { requestedScopes, grantedScopes: grantableScopes(requestedScopes, userRole) },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"DEVICE_CODE_LOOKUP_ERROR\", message: \"Failed to look up device code\" },\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/authorize\n *\n * The user submits the user_code after logging in via the browser.\n * This authorizes the device code, allowing the CLI to exchange it for tokens.\n *\n * Scopes are clamped to the user's role at this point. The stored scopes\n * are replaced with the intersection of requested scopes and the scopes\n * the user's role permits. This prevents scope escalation.\n */\nexport async function handleDeviceAuthorize(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tinput: {\n\t\tuser_code: string;\n\t\taction?: \"approve\" | \"deny\";\n\t},\n): Promise<ApiResult<{ authorized: boolean }>> {\n\ttry {\n\t\tconst match = await findPendingDeviceCode(db, input.user_code);\n\n\t\tif (!match) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CODE\", message: \"Invalid or expired code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(match.expires_at) < new Date()) {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"EXPIRED_CODE\", message: \"This code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\tconst action = input.action ?? \"approve\";\n\n\t\tif (action === \"deny\") {\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ status: \"denied\" })\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn { success: true, data: { authorized: false } };\n\t\t}\n\n\t\tconst effectiveScopes = grantableScopes(JSON.parse(match.scopes) as string[], userRole);\n\n\t\tif (effectiveScopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INSUFFICIENT_ROLE\",\n\t\t\t\t\tmessage: \"Your role does not permit any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Approve: set user_id, status, and clamped scopes\n\t\tawait db\n\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t.set({\n\t\t\t\tstatus: \"authorized\",\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(effectiveScopes),\n\t\t\t})\n\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t.execute();\n\n\t\treturn { success: true, data: { authorized: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZE_ERROR\",\n\t\t\t\tmessage: \"Failed to authorize device\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/refresh\n *\n * Exchange a refresh token for a new access token.\n * The refresh token itself is not rotated (per spec: optional rotation).\n */\nexport async function handleTokenRefresh(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\trefresh_token: string;\n\t\tgrant_type: string;\n\t},\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\tif (input.grant_type !== \"refresh_token\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\tif (!input.refresh_token.startsWith(TOKEN_PREFIXES.OAUTH_REFRESH)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token format\" },\n\t\t\t};\n\t\t}\n\n\t\tconst refreshHash = hashApiToken(input.refresh_token);\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.selectAll()\n\t\t\t.where(\"token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"refresh\")\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\t// Clean up expired refresh token and its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Refresh token expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-42: Revalidate user role before issuing new access token.\n\t\t// SEC-43: Reject refresh if user is disabled or deleted.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\t// User no longer exists — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\t// User is disabled — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate stored scopes against the user's current role.\n\t\t// A demoted user's refresh token may carry stale elevated scopes.\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// SEC-41: Intersect with the client's registered scopes (if any).\n\t\t// Same check as the approval path — a client registered with limited\n\t\t// scopes should never receive elevated scopes on refresh, even if the\n\t\t// user's role would allow them.\n\t\tif (row.client_id) {\n\t\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\t\tif (client?.scopes?.length) {\n\t\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t\t}\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\t// User's role no longer supports any of the token's scopes — revoke\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_GRANT\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the token's scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Delete old access tokens for this refresh token\n\t\tawait db\n\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"access\")\n\t\t\t.execute();\n\n\t\t// Generate new access token\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t.values({\n\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\ttoken_type: \"access\",\n\t\t\t\tuser_id: row.user_id,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tclient_type: row.client_type,\n\t\t\t\texpires_at: accessExpires,\n\t\t\t\trefresh_token_hash: refreshHash,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: input.refresh_token, // Return same refresh token\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REFRESH_ERROR\",\n\t\t\t\tmessage: \"Failed to refresh token\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/revoke\n *\n * Revoke an access or refresh token. If a refresh token is revoked,\n * also revoke all associated access tokens.\n *\n * Per RFC 7009, this endpoint always returns 200 (even for invalid tokens).\n */\nexport async function handleTokenRevoke(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\ttoken: string;\n\t},\n): Promise<ApiResult<{ revoked: boolean }>> {\n\ttry {\n\t\tconst hash = hashApiToken(input.token);\n\n\t\t// Look up the token\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.select([\"token_hash\", \"token_type\", \"refresh_token_hash\"])\n\t\t\t.where(\"token_hash\", \"=\", hash)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\t// Per RFC 7009: always 200, even for invalid tokens\n\t\t\treturn { success: true, data: { revoked: true } };\n\t\t}\n\n\t\tif (row.token_type === \"refresh\") {\n\t\t\t// Revoke refresh token and all its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"refresh_token_hash\", \"=\", hash).execute();\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t} else {\n\t\t\t// Revoke just the access token\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t}\n\n\t\treturn { success: true, data: { revoked: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REVOKE_ERROR\",\n\t\t\t\tmessage: \"Failed to revoke token\",\n\t\t\t},\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|