emdash 0.37.0 → 0.39.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/action-token-On1sot_c.mjs +26 -0
- package/dist/action-token-On1sot_c.mjs.map +1 -0
- package/dist/activation-CaMfdBt1.mjs +382 -0
- package/dist/activation-CaMfdBt1.mjs.map +1 -0
- package/dist/adapters-Cq10K4_c.d.mts +171 -0
- package/dist/adapters-Cq10K4_c.d.mts.map +1 -0
- package/dist/after-PcEvMY_6.mjs +96 -0
- package/dist/after-PcEvMY_6.mjs.map +1 -0
- package/dist/allowed-origins-1Sa-2wZK.mjs +116 -0
- package/dist/allowed-origins-1Sa-2wZK.mjs.map +1 -0
- package/dist/api/route-utils.d.mts +4 -3
- package/dist/api/route-utils.d.mts.map +1 -1
- package/dist/api/route-utils.mjs +45 -29
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +7 -6
- package/dist/api-DmjKxPCY.mjs +4831 -0
- package/dist/api-DmjKxPCY.mjs.map +1 -0
- package/dist/api-tokens-BIXItFKV.mjs +163 -0
- package/dist/api-tokens-BIXItFKV.mjs.map +1 -0
- package/dist/apply-drpfmqrR.mjs +1005 -0
- package/dist/apply-drpfmqrR.mjs.map +1 -0
- package/dist/artifact-fetch-COiMhVgk.mjs +813 -0
- package/dist/artifact-fetch-COiMhVgk.mjs.map +1 -0
- package/dist/astro/index.d.mts +14 -12
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +123 -13
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +12 -10
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +20 -11
- 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.mjs +11 -9
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +154 -28
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +13 -10
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +122 -3041
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +10 -9
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +10 -9
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +13 -12
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +13 -12
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +13 -12
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +22 -21
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +22 -21
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +22 -21
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +21 -54
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/bulk.mjs +15 -14
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/index.mjs +15 -14
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
- package/dist/astro/routes/api/admin/media-usage/activation.mjs +17 -16
- package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +19 -18
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +19 -18
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/progress.mjs +17 -17
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +21 -20
- 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 -18
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +19 -18
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +9 -66
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +9 -69
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +66 -59
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -6
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +65 -59
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +70 -61
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +87 -63
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +66 -59
- 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 +65 -59
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +85 -63
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +65 -59
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +70 -61
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +90 -66
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +9 -7
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +77 -63
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +67 -61
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +66 -60
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.d.mts +8 -0
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +27 -0
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs.map +1 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +65 -59
- 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 +65 -59
- package/dist/astro/routes/api/admin/themes/marketplace/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 +11 -10
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
- package/dist/astro/routes/api/admin/users/index.mjs +10 -9
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
- package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/complete.mjs +15 -14
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +12 -11
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +14 -13
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/send.mjs +14 -13
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
- package/dist/astro/routes/api/auth/me.mjs +11 -10
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +10 -9
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/options.mjs +16 -15
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +14 -13
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +15 -14
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +15 -14
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +15 -14
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +14 -13
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +15 -211
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +18 -17
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +17 -11
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/lock.d.mts +10 -0
- package/dist/astro/routes/api/content/_collection_/_id_/lock.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +67 -0
- package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs.map +1 -0
- 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_.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.d.mts.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +35 -0
- package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs.map +1 -0
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +16 -15
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts +9 -2
- package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +28 -16
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +27 -23
- 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 +27 -23
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +27 -12
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +23 -20
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +25 -12
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +27 -14
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/index.mjs +18 -12
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +14 -12
- package/dist/astro/routes/api/dashboard.mjs.map +1 -1
- package/dist/astro/routes/api/dev/emails.mjs +4 -4
- package/dist/astro/routes/api/health.d.mts +8 -0
- package/dist/astro/routes/api/health.d.mts.map +1 -0
- package/dist/astro/routes/api/health.mjs +21 -0
- package/dist/astro/routes/api/health.mjs.map +1 -0
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +25 -23
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +13 -11
- package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +20 -19
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -10
- package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +15 -14
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +14 -13
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +18 -17
- 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 +25 -23
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +36 -33
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +17 -9
- package/dist/astro/routes/api/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/mcp.d.mts.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +192 -110
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +15 -14
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/replace.mjs +12 -11
- package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/upload.mjs +8 -8
- package/dist/astro/routes/api/media/_id_/usage.mjs +23 -22
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +22 -21
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/asset/_id_/_filename_.d.mts +8 -0
- package/dist/astro/routes/api/media/asset/_id_/_filename_.d.mts.map +1 -0
- package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +52 -0
- package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs.map +1 -0
- package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
- package/dist/astro/routes/api/media/folders/_id_.mjs +12 -11
- package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/folders/index.mjs +12 -11
- package/dist/astro/routes/api/media/folders/index.mjs.map +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
- package/dist/astro/routes/api/media/providers/index.mjs +4 -4
- package/dist/astro/routes/api/media/upload-url.mjs +14 -13
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +26 -25
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +15 -14
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +15 -14
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -14
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +15 -14
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +15 -14
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +15 -14
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
- package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
- package/dist/astro/routes/api/oauth/register.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
- package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
- package/dist/astro/routes/api/oauth/token.mjs +7 -7
- package/dist/astro/routes/api/openapi.json.mjs +95 -24
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts +2 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +22 -40
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/index.mjs +18 -14
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +18 -14
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +20 -22
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.d.mts.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +19 -19
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +27 -23
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +21 -18
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +27 -23
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +65 -59
- 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 +65 -59
- 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 +65 -59
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +65 -59
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +65 -59
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +65 -59
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +16 -14
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +65 -59
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +65 -59
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +16 -14
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +17 -14
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +16 -14
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +10 -9
- package/dist/astro/routes/api/search/stats.mjs.map +1 -1
- package/dist/astro/routes/api/search/suggest.mjs +15 -13
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +15 -13
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +15 -13
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +9 -8
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +8 -7
- package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/export.mjs +9 -8
- package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/index.mjs +10 -9
- package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/email.mjs +6 -6
- package/dist/astro/routes/api/settings.mjs +22 -20
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +16 -15
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +15 -14
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +42 -38
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
- package/dist/astro/routes/api/setup/index.mjs +43 -38
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +5 -5
- package/dist/astro/routes/api/snapshot.mjs +11 -10
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +23 -21
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +23 -21
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +23 -21
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +23 -21
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +22 -20
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_.mjs +23 -21
- package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +23 -21
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +8 -8
- package/dist/astro/routes/api/typegen.mjs +6 -6
- package/dist/astro/routes/api/visual-editing/action-token.d.mts +8 -0
- package/dist/astro/routes/api/visual-editing/action-token.d.mts.map +1 -0
- package/dist/astro/routes/api/visual-editing/action-token.mjs +25 -0
- package/dist/astro/routes/api/visual-editing/action-token.mjs.map +1 -0
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.d.mts +8 -0
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.d.mts.map +1 -0
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +37 -0
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs.map +1 -0
- package/dist/astro/routes/api/visual-editing/toolbar-labels.d.mts +8 -0
- package/dist/astro/routes/api/visual-editing/toolbar-labels.d.mts.map +1 -0
- package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +20 -0
- package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs.map +1 -0
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +16 -15
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +16 -15
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +10 -10
- package/dist/astro/routes/api/widget-areas/index.mjs +16 -15
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +4 -4
- package/dist/astro/routes/robots.txt.mjs +13 -12
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +24 -19
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +14 -13
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +79 -26
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/authoritative-records-DbqshfKD.mjs +5172 -0
- package/dist/authoritative-records-DbqshfKD.mjs.map +1 -0
- package/dist/authorize-BXpfMpp0.mjs +40 -0
- package/dist/authorize-BXpfMpp0.mjs.map +1 -0
- package/dist/backup-BsPQdwnS.mjs +246 -0
- package/dist/backup-BsPQdwnS.mjs.map +1 -0
- package/dist/base64-DvnNgP7e.mjs +63 -0
- package/dist/base64-DvnNgP7e.mjs.map +1 -0
- package/dist/byline-Doh-wt99.mjs +906 -0
- package/dist/byline-Doh-wt99.mjs.map +1 -0
- package/dist/byline-fields-CsrdvaLV.mjs +123 -0
- package/dist/byline-fields-CsrdvaLV.mjs.map +1 -0
- package/dist/byline-registry-D6lWzXk6.mjs +406 -0
- package/dist/byline-registry-D6lWzXk6.mjs.map +1 -0
- package/dist/bylines-Cop0hkoy.mjs +214 -0
- package/dist/bylines-Cop0hkoy.mjs.map +1 -0
- package/dist/bylines-kpBzHPr8.mjs +232 -0
- package/dist/bylines-kpBzHPr8.mjs.map +1 -0
- package/dist/cache-B0pOlXdM.mjs +108 -0
- package/dist/cache-B0pOlXdM.mjs.map +1 -0
- package/dist/capture-triggers-gxQxnxrJ.mjs +362 -0
- package/dist/capture-triggers-gxQxnxrJ.mjs.map +1 -0
- package/dist/challenge-store-CkRdSdd8.mjs +51 -0
- package/dist/challenge-store-CkRdSdd8.mjs.map +1 -0
- package/dist/checksum-Bsi2D9M1.mjs +162 -0
- package/dist/checksum-Bsi2D9M1.mjs.map +1 -0
- package/dist/chrome-tags-D7LGB91U.mjs +24 -0
- package/dist/chrome-tags-D7LGB91U.mjs.map +1 -0
- package/dist/chunks-BmWyou79.mjs +25 -0
- package/dist/chunks-BmWyou79.mjs.map +1 -0
- package/dist/cli/index.mjs +921 -106
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +17 -7
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +17 -12
- package/dist/client/index.mjs.map +1 -1
- package/dist/collection-deletion-C3Xixmkx.mjs +436 -0
- package/dist/collection-deletion-C3Xixmkx.mjs.map +1 -0
- package/dist/collection-slugs-cache-D9auiKmI.mjs +85 -0
- package/dist/collection-slugs-cache-D9auiKmI.mjs.map +1 -0
- package/dist/comment-DcT8qdPK.mjs +313 -0
- package/dist/comment-DcT8qdPK.mjs.map +1 -0
- package/dist/comment-reaction-DY5-y8Y5.mjs +85 -0
- package/dist/comment-reaction-DY5-y8Y5.mjs.map +1 -0
- package/dist/comments-B67VRNGw.mjs +204 -0
- package/dist/comments-B67VRNGw.mjs.map +1 -0
- package/dist/comments-BVzzt6x5.mjs +99 -0
- package/dist/comments-BVzzt6x5.mjs.map +1 -0
- package/dist/components-DIl9Qzh6.mjs +108 -0
- package/dist/components-DIl9Qzh6.mjs.map +1 -0
- package/dist/config-B2acLeC5.mjs +242 -0
- package/dist/config-B2acLeC5.mjs.map +1 -0
- package/dist/config-CGVeT8Hc.mjs +73 -0
- package/dist/config-CGVeT8Hc.mjs.map +1 -0
- package/dist/config-D3HhLON3.d.mts +39 -0
- package/dist/config-D3HhLON3.d.mts.map +1 -0
- package/dist/config-DS7e4EZ9.d.mts +6387 -0
- package/dist/config-DS7e4EZ9.d.mts.map +1 -0
- package/dist/content-DZ3B5qtA.mjs +2216 -0
- package/dist/content-DZ3B5qtA.mjs.map +1 -0
- package/dist/content-list-query-CbPdzsna.d.mts +26 -0
- package/dist/content-list-query-CbPdzsna.d.mts.map +1 -0
- package/dist/content-policy-CM4skrNi.mjs +46 -0
- package/dist/content-policy-CM4skrNi.mjs.map +1 -0
- package/dist/content-refresh-BHLHJgyt.mjs +3599 -0
- package/dist/content-refresh-BHLHJgyt.mjs.map +1 -0
- package/dist/content-repair-BVll5WNl.mjs +440 -0
- package/dist/content-repair-BVll5WNl.mjs.map +1 -0
- package/dist/context-B195kjqZ.mjs +1835 -0
- package/dist/context-B195kjqZ.mjs.map +1 -0
- package/dist/cron-BcR3RN4K.mjs +282 -0
- package/dist/cron-BcR3RN4K.mjs.map +1 -0
- package/dist/dashboard-CZD4G7k_.mjs +151 -0
- package/dist/dashboard-CZD4G7k_.mjs.map +1 -0
- package/dist/database/migration-lock.d.mts +37 -0
- package/dist/database/migration-lock.d.mts.map +1 -0
- package/dist/database/migration-lock.mjs +76 -0
- package/dist/database/migration-lock.mjs.map +1 -0
- package/dist/database/pg-migration-lock.d.mts +1 -1
- package/dist/database/pg-migration-lock.mjs +1 -1
- package/dist/database/pg-migration-lock.mjs.map +1 -1
- package/dist/datetime-normalization-PeiEdcRY.mjs +187 -0
- package/dist/datetime-normalization-PeiEdcRY.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +4 -3
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql-migrations.d.mts +1 -1
- package/dist/db/libsql-migrations.mjs +7 -6
- package/dist/db/libsql-migrations.mjs.map +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +1 -1
- package/dist/db/postgres-migrations.mjs +7 -6
- package/dist/db/postgres-migrations.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite-migrations.d.mts +1 -1
- package/dist/db/sqlite-migrations.mjs +7 -6
- package/dist/db/sqlite-migrations.mjs.map +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +1 -1
- package/dist/db-errors-CBD5yMro.mjs +55 -0
- package/dist/db-errors-CBD5yMro.mjs.map +1 -0
- package/dist/default-BtlxWgh9.mjs +81 -0
- package/dist/default-BtlxWgh9.mjs.map +1 -0
- package/dist/deferred-tasks-Ba9fphlE.d.mts +9 -0
- package/dist/deferred-tasks-Ba9fphlE.d.mts.map +1 -0
- package/dist/dev-typegen-CZaPLl3V.mjs +29 -0
- package/dist/dev-typegen-CZaPLl3V.mjs.map +1 -0
- package/dist/device-flow-3E5l0fpH.mjs +466 -0
- package/dist/device-flow-3E5l0fpH.mjs.map +1 -0
- package/dist/dialect-helpers-CtYdsX4W.mjs +307 -0
- package/dist/dialect-helpers-CtYdsX4W.mjs.map +1 -0
- package/dist/direct-executor-ZI8E01Yt.mjs +104 -0
- package/dist/direct-executor-ZI8E01Yt.mjs.map +1 -0
- package/dist/dist-2hDLsLeq.mjs +810 -0
- package/dist/dist-2hDLsLeq.mjs.map +1 -0
- package/dist/email-console-gc-E3Eqj.mjs +50 -0
- package/dist/email-console-gc-E3Eqj.mjs.map +1 -0
- package/dist/emdash-runtime-DZGinqTL.mjs +4041 -0
- package/dist/emdash-runtime-DZGinqTL.mjs.map +1 -0
- package/dist/enrich-C2Mk0lwE.mjs +194 -0
- package/dist/enrich-C2Mk0lwE.mjs.map +1 -0
- package/dist/entry-lock-D3C99MXS.mjs +1520 -0
- package/dist/entry-lock-D3C99MXS.mjs.map +1 -0
- package/dist/entry-lock-D9rnj2Lu.mjs +163 -0
- package/dist/entry-lock-D9rnj2Lu.mjs.map +1 -0
- package/dist/entry-lock-DX7QkqEg.d.mts +3503 -0
- package/dist/entry-lock-DX7QkqEg.d.mts.map +1 -0
- package/dist/entry-locks-CO_KaoU5.mjs +167 -0
- package/dist/entry-locks-CO_KaoU5.mjs.map +1 -0
- package/dist/error-CCTCskie.mjs +90 -0
- package/dist/error-CCTCskie.mjs.map +1 -0
- package/dist/errors-W3PA8HqB.mjs +447 -0
- package/dist/errors-W3PA8HqB.mjs.map +1 -0
- package/dist/escape-DN4Qmjsl.mjs +9 -0
- package/dist/escape-DN4Qmjsl.mjs.map +1 -0
- package/dist/field-defs-cache-B337z8UJ.mjs +77 -0
- package/dist/field-defs-cache-B337z8UJ.mjs.map +1 -0
- package/dist/focal-point-Gr34o-4o.mjs +23 -0
- package/dist/focal-point-Gr34o-4o.mjs.map +1 -0
- package/dist/fts-manager-DMb0sbIQ.mjs +413 -0
- package/dist/fts-manager-DMb0sbIQ.mjs.map +1 -0
- package/dist/hash-CLVFzWvx.mjs +37 -0
- package/dist/hash-CLVFzWvx.mjs.map +1 -0
- package/dist/hook-result-KY1jq21o.d.mts +31 -0
- package/dist/hook-result-KY1jq21o.d.mts.map +1 -0
- package/dist/hreflang-BgS2FqhH.d.mts +50 -0
- package/dist/hreflang-BgS2FqhH.d.mts.map +1 -0
- package/dist/hreflang-CrJwqoeb.mjs +105 -0
- package/dist/hreflang-CrJwqoeb.mjs.map +1 -0
- package/dist/http-route-dispatch-DAKlanXb.mjs +339 -0
- package/dist/http-route-dispatch-DAKlanXb.mjs.map +1 -0
- package/dist/identity-DRGSHEEr.mjs +35 -0
- package/dist/identity-DRGSHEEr.mjs.map +1 -0
- package/dist/import-DFr5B_hC.mjs +2020 -0
- package/dist/import-DFr5B_hC.mjs.map +1 -0
- package/dist/index-CDwKDkTq.d.mts +623 -0
- package/dist/index-CDwKDkTq.d.mts.map +1 -0
- package/dist/index.d.mts +27 -22
- package/dist/index.mjs +101 -86
- package/dist/init-lock-4eRRB4Le.mjs +56 -0
- package/dist/init-lock-4eRRB4Le.mjs.map +1 -0
- package/dist/install-finalization-5Os-QZ-2.mjs +58 -0
- package/dist/install-finalization-5Os-QZ-2.mjs.map +1 -0
- package/dist/lifecycle-7e-Lt_kX.mjs +25 -0
- package/dist/lifecycle-7e-Lt_kX.mjs.map +1 -0
- package/dist/load-Cu5bvCZh.mjs +28 -0
- package/dist/load-Cu5bvCZh.mjs.map +1 -0
- package/dist/loader-t4TxaFiQ.mjs +1004 -0
- package/dist/loader-t4TxaFiQ.mjs.map +1 -0
- package/dist/manifest-builder-D31UoJCu.mjs +189 -0
- package/dist/manifest-builder-D31UoJCu.mjs.map +1 -0
- package/dist/manifest-schema-Bkh8SeHd.mjs +491 -0
- package/dist/manifest-schema-Bkh8SeHd.mjs.map +1 -0
- package/dist/media/image-endpoint.mjs +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +4 -4
- package/dist/media/local-runtime.d.mts +14 -12
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +12 -11
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/media-TBlKCwo7.mjs +334 -0
- package/dist/media-TBlKCwo7.mjs.map +1 -0
- package/dist/media-allowlist-s60eAIf6.mjs +44 -0
- package/dist/media-allowlist-s60eAIf6.mjs.map +1 -0
- package/dist/media-folders-ClIRLUQv.mjs +210 -0
- package/dist/media-folders-ClIRLUQv.mjs.map +1 -0
- package/dist/media-url-DY_ijO3B.mjs +43 -0
- package/dist/media-url-DY_ijO3B.mjs.map +1 -0
- package/dist/media-usage-C4NolTpQ.mjs +1833 -0
- package/dist/media-usage-C4NolTpQ.mjs.map +1 -0
- package/dist/media-usage-activation-CAhmb14O.mjs +78 -0
- package/dist/media-usage-activation-CAhmb14O.mjs.map +1 -0
- package/dist/media-usage-work-Cu5DqewW.mjs +586 -0
- package/dist/media-usage-work-Cu5DqewW.mjs.map +1 -0
- package/dist/media-usage-work-DJr3rOEN.mjs +152 -0
- package/dist/media-usage-work-DJr3rOEN.mjs.map +1 -0
- package/dist/media-usage-write-fence-DW5oHT7q.mjs +42 -0
- package/dist/media-usage-write-fence-DW5oHT7q.mjs.map +1 -0
- package/dist/menus-DbgU9z5Q.mjs +3732 -0
- package/dist/menus-DbgU9z5Q.mjs.map +1 -0
- package/dist/menus-Wy8m5NcH.mjs +753 -0
- package/dist/menus-Wy8m5NcH.mjs.map +1 -0
- package/dist/migrations/index.d.mts +2 -2
- package/dist/migrations/index.d.mts.map +1 -1
- package/dist/migrations/index.mjs +6 -5
- package/dist/mime-CCYRC4r1.mjs +36 -0
- package/dist/mime-CCYRC4r1.mjs.map +1 -0
- package/dist/mode-CkmOhFqj.mjs +23 -0
- package/dist/mode-CkmOhFqj.mjs.map +1 -0
- package/dist/node-sqlite-compat-vpVaILnb.mjs +46 -0
- package/dist/node-sqlite-compat-vpVaILnb.mjs.map +1 -0
- package/dist/normalize-dChXQjwS.mjs +191 -0
- package/dist/normalize-dChXQjwS.mjs.map +1 -0
- package/dist/oauth-authorization-DZ-R-hmx.mjs +274 -0
- package/dist/oauth-authorization-DZ-R-hmx.mjs.map +1 -0
- package/dist/oauth-clients-BMPA8vjl.mjs +266 -0
- package/dist/oauth-clients-BMPA8vjl.mjs.map +1 -0
- package/dist/oauth-state-store-0gIdb48-.mjs +50 -0
- package/dist/oauth-state-store-0gIdb48-.mjs.map +1 -0
- package/dist/oauth-user-lookup-ChVW4UWu.mjs +26 -0
- package/dist/oauth-user-lookup-ChVW4UWu.mjs.map +1 -0
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/object-cache-DlSVbn5Y.mjs +534 -0
- package/dist/object-cache-DlSVbn5Y.mjs.map +1 -0
- package/dist/options-D5WcorD5.mjs +200 -0
- package/dist/options-D5WcorD5.mjs.map +1 -0
- package/dist/options-DAknChnw.d.mts +77 -0
- package/dist/options-DAknChnw.d.mts.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs +13 -0
- package/dist/page/index.mjs.map +1 -1
- package/dist/parse-Cr379Av9.mjs +81 -0
- package/dist/parse-Cr379Av9.mjs.map +1 -0
- package/dist/parse-tj9XLNO_.d.mts +165 -0
- package/dist/parse-tj9XLNO_.d.mts.map +1 -0
- package/dist/passkey-config-BD-wMaCf.mjs +46 -0
- package/dist/passkey-config-BD-wMaCf.mjs.map +1 -0
- package/dist/patterns-Do3iqi7N.mjs +145 -0
- package/dist/patterns-Do3iqi7N.mjs.map +1 -0
- package/dist/placeholder-CubPFn_L.d.mts +306 -0
- package/dist/placeholder-CubPFn_L.d.mts.map +1 -0
- package/dist/plugin-test-runtime.d.mts +97 -0
- package/dist/plugin-test-runtime.d.mts.map +1 -0
- package/dist/plugin-test-runtime.mjs +105 -0
- package/dist/plugin-types.d.mts +53 -15
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-types.mjs +18 -1
- package/dist/plugin-types.mjs.map +1 -0
- package/dist/plugin-utils.d.mts +12 -10
- package/dist/plugin-utils.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +12 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +18 -13
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/plugins/host.d.mts +7 -0
- package/dist/plugins/host.mjs +45 -0
- package/dist/plugins/http-wire.d.mts +25 -0
- package/dist/plugins/http-wire.d.mts.map +1 -0
- package/dist/plugins/http-wire.mjs +148 -0
- package/dist/plugins/http-wire.mjs.map +1 -0
- package/dist/plugins/secret-redactor.d.mts +9 -0
- package/dist/plugins/secret-redactor.d.mts.map +1 -0
- package/dist/plugins/secret-redactor.mjs +51 -0
- package/dist/plugins/secret-redactor.mjs.map +1 -0
- package/dist/plugins-DHurtdXQ.mjs +277 -0
- package/dist/plugins-DHurtdXQ.mjs.map +1 -0
- package/dist/policy-lyZnjpwz.mjs +45 -0
- package/dist/policy-lyZnjpwz.mjs.map +1 -0
- package/dist/portable-text-oAwodbPS.mjs +286 -0
- package/dist/portable-text-oAwodbPS.mjs.map +1 -0
- package/dist/preview-Cup2bj8c.mjs +107 -0
- package/dist/preview-Cup2bj8c.mjs.map +1 -0
- package/dist/protocol-C1CNiKX4.d.mts +61 -0
- package/dist/protocol-C1CNiKX4.d.mts.map +1 -0
- package/dist/public-submission-Cs-pFMQK.mjs +245 -0
- package/dist/public-submission-Cs-pFMQK.mjs.map +1 -0
- package/dist/public-url-BWVHsfQv.mjs +101 -0
- package/dist/public-url-BWVHsfQv.mjs.map +1 -0
- package/dist/query-D-lxiOgn.mjs +890 -0
- package/dist/query-D-lxiOgn.mjs.map +1 -0
- package/dist/query-D7kHKOws.mjs +41 -0
- package/dist/query-D7kHKOws.mjs.map +1 -0
- package/dist/rate-limit-CqJn-w4h.mjs +120 -0
- package/dist/rate-limit-CqJn-w4h.mjs.map +1 -0
- package/dist/redirect-BrChFPIu.mjs +17 -0
- package/dist/redirect-BrChFPIu.mjs.map +1 -0
- package/dist/redirect-D2Yo-b6S.mjs +415 -0
- package/dist/redirect-D2Yo-b6S.mjs.map +1 -0
- package/dist/redirects-BCiTaIQ-.mjs +146 -0
- package/dist/redirects-BCiTaIQ-.mjs.map +1 -0
- package/dist/redirects-ByItliuu.mjs +644 -0
- package/dist/redirects-ByItliuu.mjs.map +1 -0
- package/dist/registry/testing.d.mts +14 -0
- package/dist/registry/testing.d.mts.map +1 -0
- package/dist/registry/testing.mjs +255 -0
- package/dist/registry/testing.mjs.map +1 -0
- package/dist/registry-CkuPoZs7.mjs +1222 -0
- package/dist/registry-CkuPoZs7.mjs.map +1 -0
- package/dist/relation-XTAtOpJO.mjs +334 -0
- package/dist/relation-XTAtOpJO.mjs.map +1 -0
- package/dist/relations-B4w-G5U9.mjs +309 -0
- package/dist/relations-B4w-G5U9.mjs.map +1 -0
- package/dist/relations-O63JWBRj.mjs +403 -0
- package/dist/relations-O63JWBRj.mjs.map +1 -0
- package/dist/request-cache-DaMhSQdx.mjs +104 -0
- package/dist/request-cache-DaMhSQdx.mjs.map +1 -0
- package/dist/request-context.d.mts +1 -1
- package/dist/request-meta-C8yl_sIs.mjs +141 -0
- package/dist/request-meta-C8yl_sIs.mjs.map +1 -0
- package/dist/resolve-BJIS8wKr.mjs +199 -0
- package/dist/resolve-BJIS8wKr.mjs.map +1 -0
- package/dist/route-wire-D66L2oaZ.mjs +197 -0
- package/dist/route-wire-D66L2oaZ.mjs.map +1 -0
- package/dist/routes-DYsTe-oR.mjs +321 -0
- package/dist/routes-DYsTe-oR.mjs.map +1 -0
- package/dist/runner-3gfxRsJG.d.mts +68 -0
- package/dist/runner-3gfxRsJG.d.mts.map +1 -0
- package/dist/runner-ClrleR8A.mjs +5502 -0
- package/dist/runner-ClrleR8A.mjs.map +1 -0
- package/dist/runtime.d.mts +13 -11
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +6 -5
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-CPhPM9Do.mjs +420 -0
- package/dist/schema-CPhPM9Do.mjs.map +1 -0
- package/dist/schema-DEERRrKS.mjs +9 -0
- package/dist/schema-DEERRrKS.mjs.map +1 -0
- package/dist/scopes-Bn8_rCSt.mjs +27 -0
- package/dist/scopes-Bn8_rCSt.mjs.map +1 -0
- package/dist/search-lami_Ota.mjs +448 -0
- package/dist/search-lami_Ota.mjs.map +1 -0
- package/dist/secrets-DNgVgz_e.mjs +341 -0
- package/dist/secrets-DNgVgz_e.mjs.map +1 -0
- package/dist/sections-BIpd4Cxf.mjs +346 -0
- package/dist/sections-BIpd4Cxf.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +35 -31
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +27 -26
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo-BtU70b7l.mjs +114 -0
- package/dist/seo-BtU70b7l.mjs.map +1 -0
- package/dist/seo-_4qmazxz.mjs +133 -0
- package/dist/seo-_4qmazxz.mjs.map +1 -0
- package/dist/service-Bpy5iTX0.mjs +150 -0
- package/dist/service-Bpy5iTX0.mjs.map +1 -0
- package/dist/session-user-BQeS2Mrb.mjs +51 -0
- package/dist/session-user-BQeS2Mrb.mjs.map +1 -0
- package/dist/settings-DmroR53o.mjs +420 -0
- package/dist/settings-DmroR53o.mjs.map +1 -0
- package/dist/settings-g2F2a3Ub.mjs +51 -0
- package/dist/settings-g2F2a3Ub.mjs.map +1 -0
- package/dist/setup-complete-BrZaXpVV.mjs +26 -0
- package/dist/setup-complete-BrZaXpVV.mjs.map +1 -0
- package/dist/setup-nonce-Cb9-884O.mjs +25 -0
- package/dist/setup-nonce-Cb9-884O.mjs.map +1 -0
- package/dist/single-flight-cache-B5O2qhO1.mjs +104 -0
- package/dist/single-flight-cache-B5O2qhO1.mjs.map +1 -0
- package/dist/site-url-DjhYCaep.mjs +16 -0
- package/dist/site-url-DjhYCaep.mjs.map +1 -0
- package/dist/slugify-T5GHH42D.mjs +17 -0
- package/dist/slugify-T5GHH42D.mjs.map +1 -0
- package/dist/snapshot-C32Xuv3o.mjs +233 -0
- package/dist/snapshot-C32Xuv3o.mjs.map +1 -0
- package/dist/ssrf-Co1GGFZz.mjs +406 -0
- package/dist/ssrf-Co1GGFZz.mjs.map +1 -0
- package/dist/state-C7gCoiHr.mjs +153 -0
- package/dist/state-C7gCoiHr.mjs.map +1 -0
- package/dist/status-DLoBmcDz.mjs +30 -0
- package/dist/status-DLoBmcDz.mjs.map +1 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/storage-query-CPctdMKX.d.mts +1519 -0
- package/dist/storage-query-CPctdMKX.d.mts.map +1 -0
- package/dist/target-CUKlGrXb.mjs +27 -0
- package/dist/target-CUKlGrXb.mjs.map +1 -0
- package/dist/taxonomies-BpqpXJUE.mjs +707 -0
- package/dist/taxonomies-BpqpXJUE.mjs.map +1 -0
- package/dist/taxonomies-ClCo4IGO.mjs +899 -0
- package/dist/taxonomies-ClCo4IGO.mjs.map +1 -0
- package/dist/taxonomy-Dj1vpw5E.mjs +508 -0
- package/dist/taxonomy-Dj1vpw5E.mjs.map +1 -0
- package/dist/tokens-Ns02GAma.mjs +171 -0
- package/dist/tokens-Ns02GAma.mjs.map +1 -0
- package/dist/transaction-BcJO4Tmc.mjs +29 -0
- package/dist/transaction-BcJO4Tmc.mjs.map +1 -0
- package/dist/transport-B8P1xsqk.mjs +136 -0
- package/dist/transport-B8P1xsqk.mjs.map +1 -0
- package/dist/transport-BLHZZcD_.d.mts +42 -0
- package/dist/transport-BLHZZcD_.d.mts.map +1 -0
- package/dist/trusted-proxy-Cg7SuD15.mjs +51 -0
- package/dist/trusted-proxy-Cg7SuD15.mjs.map +1 -0
- package/dist/turnstile-DWCSh_Qc.mjs +53 -0
- package/dist/turnstile-DWCSh_Qc.mjs.map +1 -0
- package/dist/types-B3RkqvDW.mjs +156 -0
- package/dist/types-B3RkqvDW.mjs.map +1 -0
- package/dist/types-BkUg2yHx.mjs +92 -0
- package/dist/types-BkUg2yHx.mjs.map +1 -0
- package/dist/types-BtuppOTV.mjs +16 -0
- package/dist/types-BtuppOTV.mjs.map +1 -0
- package/dist/types-CCR3ivXE.d.mts +363 -0
- package/dist/types-CCR3ivXE.d.mts.map +1 -0
- package/dist/types-CSHgU_-_.mjs +18 -0
- package/dist/types-CSHgU_-_.mjs.map +1 -0
- package/dist/types-CbwqGOXm.d.mts +766 -0
- package/dist/types-CbwqGOXm.d.mts.map +1 -0
- package/dist/types-D4RGIc2I.d.mts +198 -0
- package/dist/types-D4RGIc2I.d.mts.map +1 -0
- package/dist/types-DEHidMEk.d.mts +1637 -0
- package/dist/types-DEHidMEk.d.mts.map +1 -0
- package/dist/types-DQeU2SR7.d.mts +279 -0
- package/dist/types-DQeU2SR7.d.mts.map +1 -0
- package/dist/types-DxQWoQLA.d.mts +264 -0
- package/dist/types-DxQWoQLA.d.mts.map +1 -0
- package/dist/types-FBBnGZ2Y.d.mts +198 -0
- package/dist/types-FBBnGZ2Y.d.mts.map +1 -0
- package/dist/types-ZCnlropY.mjs +69 -0
- package/dist/types-ZCnlropY.mjs.map +1 -0
- package/dist/types-_Qtyawiy.d.mts +124 -0
- package/dist/types-_Qtyawiy.d.mts.map +1 -0
- package/dist/types-gC6FAoa_.d.mts +75 -0
- package/dist/types-gC6FAoa_.d.mts.map +1 -0
- package/dist/types-k--LneIF.mjs +22 -0
- package/dist/types-k--LneIF.mjs.map +1 -0
- package/dist/upload-attempts-2xdnoKGe.mjs +27 -0
- package/dist/upload-attempts-2xdnoKGe.mjs.map +1 -0
- package/dist/url-pattern-3m3reMzr.mjs +33 -0
- package/dist/url-pattern-3m3reMzr.mjs.map +1 -0
- package/dist/user-DroRntkx.mjs +171 -0
- package/dist/user-DroRntkx.mjs.map +1 -0
- package/dist/utils-CC2oNGV8.mjs +435 -0
- package/dist/utils-CC2oNGV8.mjs.map +1 -0
- package/dist/validate-BCP9HNDn.mjs +384 -0
- package/dist/validate-BCP9HNDn.mjs.map +1 -0
- package/dist/validate-CyQ7I3xF.d.mts +410 -0
- package/dist/validate-CyQ7I3xF.d.mts.map +1 -0
- package/dist/validate-DANX1-Qi.mjs +120 -0
- package/dist/validate-DANX1-Qi.mjs.map +1 -0
- package/dist/validation-DBy6WF94.mjs +216 -0
- package/dist/validation-DBy6WF94.mjs.map +1 -0
- package/dist/version-B57E4Uo7.mjs +7 -0
- package/dist/version-B57E4Uo7.mjs.map +1 -0
- package/dist/widgets-wQ7Xcgq8.mjs +126 -0
- package/dist/widgets-wQ7Xcgq8.mjs.map +1 -0
- package/dist/wxr-taxonomies-DIHfvEDt.mjs +406 -0
- package/dist/wxr-taxonomies-DIHfvEDt.mjs.map +1 -0
- package/dist/wxr-taxonomies-DU2ZSiGc.d.mts +34 -0
- package/dist/wxr-taxonomies-DU2ZSiGc.d.mts.map +1 -0
- package/dist/zod-generator-D_yHIWfs.mjs +329 -0
- package/dist/zod-generator-D_yHIWfs.mjs.map +1 -0
- package/package.json +39 -12
- package/src/api/errors.ts +23 -0
- package/src/api/handlers/backup.ts +11 -5
- package/src/api/handlers/content.ts +226 -85
- package/src/api/handlers/dashboard.ts +31 -1
- package/src/api/handlers/entry-lock.ts +206 -0
- package/src/api/handlers/index.ts +3 -0
- package/src/api/handlers/manifest.ts +5 -6
- package/src/api/handlers/marketplace.ts +126 -34
- package/src/api/handlers/media-upload.ts +31 -4
- package/src/api/handlers/menus.ts +9 -0
- package/src/api/handlers/plugin-settings.ts +35 -3
- package/src/api/handlers/redirects.ts +221 -138
- package/src/api/handlers/registry.ts +75 -29
- package/src/api/handlers/revision.ts +16 -27
- package/src/api/handlers/seo.ts +10 -1
- package/src/api/handlers/snapshot.ts +87 -72
- package/src/api/handlers/taxonomies.ts +79 -5
- package/src/api/handlers/validation.ts +120 -19
- package/src/api/openapi/document.ts +133 -11
- package/src/api/schemas/content.ts +32 -12
- package/src/api/schemas/entry-lock.ts +57 -0
- package/src/api/schemas/index.ts +1 -0
- package/src/api/schemas/schema.ts +10 -1
- package/src/api/schemas/search.ts +4 -0
- package/src/api/types.ts +4 -0
- package/src/astro/integration/index.ts +22 -6
- package/src/astro/integration/routes.ts +89 -0
- package/src/astro/integration/runtime.ts +40 -24
- package/src/astro/integration/virtual-modules.ts +4 -0
- package/src/astro/integration/vite-config.ts +31 -0
- package/src/astro/middleware/auth.ts +25 -4
- package/src/astro/middleware/request-context.ts +28 -10
- package/src/astro/middleware.ts +33 -1
- package/src/astro/prefetch.ts +2 -2
- package/src/astro/routes/api/admin/comments/[id]/status.ts +22 -58
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +2 -15
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +2 -25
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +1 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +23 -4
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +12 -2
- package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +1 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +29 -10
- package/src/astro/routes/api/admin/plugins/registry/artifact.ts +8 -2
- package/src/astro/routes/api/admin/plugins/registry/install.ts +15 -4
- package/src/astro/routes/api/admin/plugins/registry/verify.ts +2 -1
- package/src/astro/routes/api/admin/plugins/updates.ts +6 -1
- package/src/astro/routes/api/admin/scheduled-policy-rejections/[collection]/[id].ts +34 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +4 -259
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +10 -0
- package/src/astro/routes/api/content/[collection]/[id]/lock.ts +133 -0
- package/src/astro/routes/api/content/[collection]/[id]/plugin-extensions/[pluginId]/[kind]/[extensionId].ts +29 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +21 -4
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +30 -1
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +15 -1
- package/src/astro/routes/api/content/[collection]/[id].ts +27 -2
- package/src/astro/routes/api/content/[collection]/index.ts +3 -0
- package/src/astro/routes/api/health.ts +23 -0
- package/src/astro/routes/api/manifest.ts +14 -4
- package/src/astro/routes/api/mcp.ts +2 -1
- package/src/astro/routes/api/media/asset/[id]/[filename].ts +50 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +13 -81
- package/src/astro/routes/api/redirects/[id].ts +0 -3
- package/src/astro/routes/api/redirects/index.ts +0 -2
- package/src/astro/routes/api/search/index.ts +1 -0
- package/src/astro/routes/api/visual-editing/action-token.ts +36 -0
- package/src/astro/routes/api/visual-editing/content/[collection]/[id]/publish.ts +32 -0
- package/src/astro/routes/api/visual-editing/toolbar-labels.ts +16 -0
- package/src/astro/routes/sitemap-[collection].xml.ts +11 -4
- package/src/astro/types.ts +83 -12
- package/src/cli/commands/bundle-utils.ts +19 -19
- package/src/cli/commands/bundle.ts +9 -6
- package/src/cli/commands/content.ts +32 -6
- package/src/cli/commands/doctor.ts +25 -0
- package/src/cli/commands/export-seed.ts +195 -56
- package/src/cli/commands/migrate.ts +71 -23
- package/src/cli/commands/publish.ts +2 -2
- package/src/client/index.ts +34 -7
- package/src/comments/public-submission.ts +246 -0
- package/src/comments/service.ts +59 -12
- package/src/components/EmDashBodyEnd.astro +4 -1
- package/src/components/EmDashBodyStart.astro +4 -1
- package/src/components/EmDashHead.astro +36 -16
- package/src/components/Gallery.astro +18 -4
- package/src/components/InlineEditor.astro +3 -1
- package/src/components/InlinePortableTextEditor.tsx +106 -11
- package/src/components/PortableText.astro +4 -0
- package/src/components/Table.astro +147 -62
- package/src/components/widgets/Archives.astro +6 -5
- package/src/config/errors.ts +13 -0
- package/src/config/secrets.ts +25 -11
- package/src/content/converters/portable-text-to-prosemirror.ts +26 -2
- package/src/content/converters/prosemirror-to-portable-text.ts +35 -6
- package/src/content/converters/types.ts +10 -0
- package/src/content/known-fields.ts +44 -0
- package/src/database/connection.ts +5 -1
- package/src/database/content-datetime.ts +123 -0
- package/src/database/datetime-storage.ts +452 -0
- package/src/database/dialect-helpers.ts +161 -1
- package/src/database/migration-lock.ts +140 -0
- package/src/database/migrations/075_entry_edit_locks.ts +44 -0
- package/src/database/migrations/076_collection_nav_group.ts +22 -0
- package/src/database/migrations/077_plugin_storage_revisions.ts +89 -0
- package/src/database/migrations/078_menu_item_translation_groups.ts +15 -0
- package/src/database/migrations/079_datetime_normalization.ts +13 -0
- package/src/database/migrations/080_content_translation_locale_unique.ts +88 -0
- package/src/database/migrations/081_redirect_write_guards.ts +257 -0
- package/src/database/migrations/082_taxonomy_translation_locale_unique.ts +49 -0
- package/src/database/migrations/runner.ts +63 -15
- package/src/database/pg-migration-lock.ts +2 -2
- package/src/database/repositories/byline.ts +9 -0
- package/src/database/repositories/comment.ts +86 -0
- package/src/database/repositories/content.ts +560 -69
- package/src/database/repositories/entry-locks.ts +257 -0
- package/src/database/repositories/media.ts +35 -4
- package/src/database/repositories/menu.ts +1 -0
- package/src/database/repositories/options.ts +113 -7
- package/src/database/repositories/plugin-storage.ts +159 -0
- package/src/database/repositories/redirect.ts +262 -56
- package/src/database/repositories/revision.ts +116 -17
- package/src/database/repositories/taxonomy.ts +42 -0
- package/src/database/repositories/types.ts +5 -0
- package/src/database/types.ts +26 -1
- package/src/datetime-normalization.ts +311 -0
- package/src/db/node-sqlite-compat.ts +3 -2
- package/src/emdash-runtime.ts +1994 -424
- package/src/fields/image.ts +2 -0
- package/src/i18n/config.ts +6 -6
- package/src/i18n/resolve.ts +98 -15
- package/src/index.ts +101 -0
- package/src/loader.ts +49 -4
- package/src/mcp/server.ts +216 -68
- package/src/media/responsive.ts +23 -0
- package/src/media/usage/content-refresh.ts +2 -1
- package/src/media/usage/extractor.ts +50 -15
- package/src/media/usage/types.ts +1 -1
- package/src/menus/index.ts +64 -35
- package/src/migrations/direct-executor.ts +40 -2
- package/src/migrations/index.ts +1 -0
- package/src/migrations/protocol.ts +11 -1
- package/src/page/seo-contributions.ts +78 -1
- package/src/page/seo-panel.ts +47 -0
- package/src/plugin-test-runtime.ts +17 -0
- package/src/plugin-types.ts +128 -21
- package/src/plugins/adapt-sandbox-entry.ts +34 -27
- package/src/plugins/conditional-storage.ts +30 -0
- package/src/plugins/content-access.ts +170 -0
- package/src/plugins/content-policy.ts +74 -0
- package/src/plugins/context.ts +790 -166
- package/src/plugins/cron.ts +18 -10
- package/src/plugins/define-plugin.ts +55 -54
- package/src/plugins/editor-draft.ts +257 -0
- package/src/plugins/hooks.ts +136 -3
- package/src/plugins/host.ts +29 -0
- package/src/plugins/http-route-dispatch.ts +389 -0
- package/src/plugins/http-wire.ts +180 -0
- package/src/plugins/index.ts +97 -3
- package/src/plugins/install-finalization.ts +77 -0
- package/src/plugins/lifecycle.ts +47 -0
- package/src/plugins/manager.ts +5 -2
- package/src/plugins/manifest-schema.ts +314 -58
- package/src/plugins/media.ts +166 -0
- package/src/plugins/request-meta.ts +12 -2
- package/src/plugins/route-wire.ts +304 -0
- package/src/plugins/routes.ts +38 -5
- package/src/plugins/sandbox/index.ts +4 -0
- package/src/plugins/sandbox/proxy.ts +129 -0
- package/src/plugins/sandbox/types.ts +83 -7
- package/src/plugins/secret-redactor.ts +65 -0
- package/src/plugins/settings.ts +303 -0
- package/src/plugins/state.ts +26 -0
- package/src/plugins/storage-query.ts +45 -0
- package/src/plugins/storage-update.ts +96 -0
- package/src/plugins/types.ts +602 -15
- package/src/query.ts +84 -4
- package/src/registry/authoritative-records.ts +26 -0
- package/src/registry/config.ts +182 -31
- package/src/registry/testing.ts +100 -0
- package/src/registry/types.ts +12 -70
- package/src/scheduled-publish.ts +70 -3
- package/src/schema/collection-slugs-cache.ts +157 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/registry.ts +64 -1
- package/src/schema/types.ts +32 -6
- package/src/schema/zod-generator.ts +5 -13
- package/src/search/fts-manager.ts +3 -3
- package/src/search/query.ts +32 -5
- package/src/search/types.ts +8 -0
- package/src/security/ssrf.ts +167 -141
- package/src/seed/apply.ts +41 -3
- package/src/seed/types.ts +13 -3
- package/src/seed/validate.ts +18 -1
- package/src/seo/hreflang.ts +10 -6
- package/src/seo/index.ts +5 -10
- package/src/seo/media-url.ts +26 -0
- package/src/settings/index.ts +53 -11
- package/src/taxonomies/term-counts.ts +22 -5
- package/src/visual-editing/action-token.ts +31 -0
- package/src/visual-editing/toolbar-bootstrap.ts +31 -6
- package/src/visual-editing/toolbar.ts +103 -12
- package/src/widgets/archives.ts +10 -9
- package/src/widgets/index.ts +11 -1
- package/dist/activation-K0kGBWuJ.mjs +0 -382
- package/dist/activation-K0kGBWuJ.mjs.map +0 -1
- package/dist/adapters-BDQwg236.d.mts +0 -171
- package/dist/adapters-BDQwg236.d.mts.map +0 -1
- package/dist/after-CBU2vkoO.mjs +0 -96
- package/dist/after-CBU2vkoO.mjs.map +0 -1
- package/dist/allowed-origins-BGglilcs.mjs +0 -116
- package/dist/allowed-origins-BGglilcs.mjs.map +0 -1
- package/dist/api-Dz6rgoa3.mjs +0 -10040
- package/dist/api-Dz6rgoa3.mjs.map +0 -1
- package/dist/api-tokens-BSsQACNe.mjs +0 -163
- package/dist/api-tokens-BSsQACNe.mjs.map +0 -1
- package/dist/apply-B4SIOxR1.mjs +0 -980
- package/dist/apply-B4SIOxR1.mjs.map +0 -1
- package/dist/authorize-Bkf_UHhD.mjs +0 -40
- package/dist/authorize-Bkf_UHhD.mjs.map +0 -1
- package/dist/backup-rwVAl9NI.mjs +0 -241
- package/dist/backup-rwVAl9NI.mjs.map +0 -1
- package/dist/base64-CvLhUKQv.mjs +0 -63
- package/dist/base64-CvLhUKQv.mjs.map +0 -1
- package/dist/bundle-C8S2Zl6U.mjs +0 -663
- package/dist/bundle-C8S2Zl6U.mjs.map +0 -1
- package/dist/byline-DuhF7NWl.mjs +0 -902
- package/dist/byline-DuhF7NWl.mjs.map +0 -1
- package/dist/byline-fields-BovY4KYM.mjs +0 -123
- package/dist/byline-fields-BovY4KYM.mjs.map +0 -1
- package/dist/byline-registry-Bpu9-MVs.mjs +0 -406
- package/dist/byline-registry-Bpu9-MVs.mjs.map +0 -1
- package/dist/bylines-CJ5nd6Lw.mjs +0 -232
- package/dist/bylines-CJ5nd6Lw.mjs.map +0 -1
- package/dist/bylines-RwBAeOF1.mjs +0 -214
- package/dist/bylines-RwBAeOF1.mjs.map +0 -1
- package/dist/cache-BF5Kvn59.mjs +0 -108
- package/dist/cache-BF5Kvn59.mjs.map +0 -1
- package/dist/capture-triggers-CNW1q3HK.mjs +0 -362
- package/dist/capture-triggers-CNW1q3HK.mjs.map +0 -1
- package/dist/challenge-store-DQN_Yrck.mjs +0 -51
- package/dist/challenge-store-DQN_Yrck.mjs.map +0 -1
- package/dist/chrome-tags-D8fiZG94.mjs +0 -24
- package/dist/chrome-tags-D8fiZG94.mjs.map +0 -1
- package/dist/chunks-C4szrD8o.mjs +0 -25
- package/dist/chunks-C4szrD8o.mjs.map +0 -1
- package/dist/collection-deletion-Bj07NHbj.mjs +0 -436
- package/dist/collection-deletion-Bj07NHbj.mjs.map +0 -1
- package/dist/comment-reaction-BhrvSHml.mjs +0 -85
- package/dist/comment-reaction-BhrvSHml.mjs.map +0 -1
- package/dist/comment-vxOzWbZN.mjs +0 -255
- package/dist/comment-vxOzWbZN.mjs.map +0 -1
- package/dist/comments-Ct4ensnf.mjs +0 -204
- package/dist/comments-Ct4ensnf.mjs.map +0 -1
- package/dist/components-DJbo1z0S.mjs +0 -108
- package/dist/components-DJbo1z0S.mjs.map +0 -1
- package/dist/config-B9eZ340f.d.mts +0 -34
- package/dist/config-B9eZ340f.d.mts.map +0 -1
- package/dist/config-C3EBstHi.mjs +0 -72
- package/dist/config-C3EBstHi.mjs.map +0 -1
- package/dist/config-it29yDLA.mjs +0 -1163
- package/dist/config-it29yDLA.mjs.map +0 -1
- package/dist/content-DjvM0T92.mjs +0 -1725
- package/dist/content-DjvM0T92.mjs.map +0 -1
- package/dist/content-list-query-Btqd4uV2.d.mts +0 -26
- package/dist/content-list-query-Btqd4uV2.d.mts.map +0 -1
- package/dist/content-refresh-Bwp-8yGy.mjs +0 -3588
- package/dist/content-refresh-Bwp-8yGy.mjs.map +0 -1
- package/dist/content-repair-Czdp7jz5.mjs +0 -440
- package/dist/content-repair-Czdp7jz5.mjs.map +0 -1
- package/dist/context-CX6gxOLW.mjs +0 -1068
- package/dist/context-CX6gxOLW.mjs.map +0 -1
- package/dist/cron-C6o7VppJ.mjs +0 -277
- package/dist/cron-C6o7VppJ.mjs.map +0 -1
- package/dist/dashboard-C2kTeeRI.mjs +0 -140
- package/dist/dashboard-C2kTeeRI.mjs.map +0 -1
- package/dist/db-errors-Cl-sKYTm.mjs +0 -55
- package/dist/db-errors-Cl-sKYTm.mjs.map +0 -1
- package/dist/default-Clra32mw.mjs +0 -81
- package/dist/default-Clra32mw.mjs.map +0 -1
- package/dist/deferred-tasks-CvDETTJO.d.mts +0 -9
- package/dist/deferred-tasks-CvDETTJO.d.mts.map +0 -1
- package/dist/dev-typegen-DzOPfsNj.mjs +0 -29
- package/dist/dev-typegen-DzOPfsNj.mjs.map +0 -1
- package/dist/device-flow-D2oCWvLJ.mjs +0 -466
- package/dist/device-flow-D2oCWvLJ.mjs.map +0 -1
- package/dist/dialect-helpers-BSbfaO3F.mjs +0 -202
- package/dist/dialect-helpers-BSbfaO3F.mjs.map +0 -1
- package/dist/direct-executor-Bfa5_ToE.mjs +0 -85
- package/dist/direct-executor-Bfa5_ToE.mjs.map +0 -1
- package/dist/dist-89xCrGqi.mjs +0 -810
- package/dist/dist-89xCrGqi.mjs.map +0 -1
- package/dist/email-console-Dzb6uGYa.mjs +0 -50
- package/dist/email-console-Dzb6uGYa.mjs.map +0 -1
- package/dist/enrich-BX0IDp4z.mjs +0 -194
- package/dist/enrich-BX0IDp4z.mjs.map +0 -1
- package/dist/error-DFrQjppn.mjs +0 -90
- package/dist/error-DFrQjppn.mjs.map +0 -1
- package/dist/errors-DsVoDUjJ.mjs +0 -427
- package/dist/errors-DsVoDUjJ.mjs.map +0 -1
- package/dist/escape-HmHTC1Is.mjs +0 -9
- package/dist/escape-HmHTC1Is.mjs.map +0 -1
- package/dist/field-defs-cache-BM4SIRqH.mjs +0 -77
- package/dist/field-defs-cache-BM4SIRqH.mjs.map +0 -1
- package/dist/focal-point-BeWFo6Xx.mjs +0 -23
- package/dist/focal-point-BeWFo6Xx.mjs.map +0 -1
- package/dist/fts-manager-Do1BlW2r.mjs +0 -413
- package/dist/fts-manager-Do1BlW2r.mjs.map +0 -1
- package/dist/hash-3JJPlHF2.mjs +0 -37
- package/dist/hash-3JJPlHF2.mjs.map +0 -1
- package/dist/hook-result-DCh2WUpe.d.mts +0 -31
- package/dist/hook-result-DCh2WUpe.d.mts.map +0 -1
- package/dist/hreflang-B6hbAuuO.d.mts +0 -48
- package/dist/hreflang-B6hbAuuO.d.mts.map +0 -1
- package/dist/hreflang-DQbPZliA.mjs +0 -102
- package/dist/hreflang-DQbPZliA.mjs.map +0 -1
- package/dist/identity-DksvEazf.mjs +0 -35
- package/dist/identity-DksvEazf.mjs.map +0 -1
- package/dist/import-CJpaBBBT.mjs +0 -2020
- package/dist/import-CJpaBBBT.mjs.map +0 -1
- package/dist/index-BJNMCWmu.d.mts +0 -6562
- package/dist/index-BJNMCWmu.d.mts.map +0 -1
- package/dist/index-Du8K7bWa.d.mts +0 -278
- package/dist/index-Du8K7bWa.d.mts.map +0 -1
- package/dist/init-lock-DJkX6Hto.mjs +0 -56
- package/dist/init-lock-DJkX6Hto.mjs.map +0 -1
- package/dist/load-CawHjTOH.mjs +0 -28
- package/dist/load-CawHjTOH.mjs.map +0 -1
- package/dist/loader-Z8vl7zTq.mjs +0 -966
- package/dist/loader-Z8vl7zTq.mjs.map +0 -1
- package/dist/manifest-builder-COtzuca2.mjs +0 -189
- package/dist/manifest-builder-COtzuca2.mjs.map +0 -1
- package/dist/manifest-schema-BNWwR8Vd.mjs +0 -301
- package/dist/manifest-schema-BNWwR8Vd.mjs.map +0 -1
- package/dist/media-BBm8Wg3f.mjs +0 -318
- package/dist/media-BBm8Wg3f.mjs.map +0 -1
- package/dist/media-BHjus86Y.mjs +0 -464
- package/dist/media-BHjus86Y.mjs.map +0 -1
- package/dist/media-allowlist-CoTBWoDp.mjs +0 -44
- package/dist/media-allowlist-CoTBWoDp.mjs.map +0 -1
- package/dist/media-folders-BGkATs8n.mjs +0 -210
- package/dist/media-folders-BGkATs8n.mjs.map +0 -1
- package/dist/media-upload-BCidR-EY.mjs +0 -147
- package/dist/media-upload-BCidR-EY.mjs.map +0 -1
- package/dist/media-url-DHoPr0hq.mjs +0 -26
- package/dist/media-url-DHoPr0hq.mjs.map +0 -1
- package/dist/media-usage-CdY5jhSR.mjs +0 -1833
- package/dist/media-usage-CdY5jhSR.mjs.map +0 -1
- package/dist/media-usage-activation-BgeMtK5C.mjs +0 -78
- package/dist/media-usage-activation-BgeMtK5C.mjs.map +0 -1
- package/dist/media-usage-work-CnGCdHxx.mjs +0 -152
- package/dist/media-usage-work-CnGCdHxx.mjs.map +0 -1
- package/dist/media-usage-work-D6D9sMCA.mjs +0 -586
- package/dist/media-usage-work-D6D9sMCA.mjs.map +0 -1
- package/dist/media-usage-write-fence-hjeD4u7s.mjs +0 -42
- package/dist/media-usage-write-fence-hjeD4u7s.mjs.map +0 -1
- package/dist/menus-6qSwTNxG.mjs +0 -4010
- package/dist/menus-6qSwTNxG.mjs.map +0 -1
- package/dist/menus-uvPLXMZf.mjs +0 -744
- package/dist/menus-uvPLXMZf.mjs.map +0 -1
- package/dist/mime-CUIG6x84.mjs +0 -36
- package/dist/mime-CUIG6x84.mjs.map +0 -1
- package/dist/mode-DN_WneRx.mjs +0 -23
- package/dist/mode-DN_WneRx.mjs.map +0 -1
- package/dist/node-sqlite-compat-BuJXIeMz.mjs +0 -46
- package/dist/node-sqlite-compat-BuJXIeMz.mjs.map +0 -1
- package/dist/normalize-C2xNjTSG.mjs +0 -191
- package/dist/normalize-C2xNjTSG.mjs.map +0 -1
- package/dist/oauth-authorization-Dj6Kwc21.mjs +0 -274
- package/dist/oauth-authorization-Dj6Kwc21.mjs.map +0 -1
- package/dist/oauth-clients-FD7I0J8m.mjs +0 -266
- package/dist/oauth-clients-FD7I0J8m.mjs.map +0 -1
- package/dist/oauth-state-store-DkmDOmHs.mjs +0 -50
- package/dist/oauth-state-store-DkmDOmHs.mjs.map +0 -1
- package/dist/oauth-user-lookup-CvIwGpYd.mjs +0 -26
- package/dist/oauth-user-lookup-CvIwGpYd.mjs.map +0 -1
- package/dist/object-cache-DVKnyOfy.mjs +0 -534
- package/dist/object-cache-DVKnyOfy.mjs.map +0 -1
- package/dist/options-BHPeIbBd.mjs +0 -117
- package/dist/options-BHPeIbBd.mjs.map +0 -1
- package/dist/options-CYb4y7hM.d.mts +0 -221
- package/dist/options-CYb4y7hM.d.mts.map +0 -1
- package/dist/parse-Ckpg-Qfp.mjs +0 -81
- package/dist/parse-Ckpg-Qfp.mjs.map +0 -1
- package/dist/passkey-config-O_lj3y0k.mjs +0 -46
- package/dist/passkey-config-O_lj3y0k.mjs.map +0 -1
- package/dist/patterns-D9iDO3j8.mjs +0 -145
- package/dist/patterns-D9iDO3j8.mjs.map +0 -1
- package/dist/placeholder-4X9BrRrp.d.mts +0 -306
- package/dist/placeholder-4X9BrRrp.d.mts.map +0 -1
- package/dist/policy-Dhtwntrt.mjs +0 -45
- package/dist/policy-Dhtwntrt.mjs.map +0 -1
- package/dist/portable-text-U85hOafW.mjs +0 -286
- package/dist/portable-text-U85hOafW.mjs.map +0 -1
- package/dist/preview-B_vJLvf8.mjs +0 -107
- package/dist/preview-B_vJLvf8.mjs.map +0 -1
- package/dist/protocol-JdD2YhY9.d.mts +0 -52
- package/dist/protocol-JdD2YhY9.d.mts.map +0 -1
- package/dist/public-url-CarhZJgf.mjs +0 -101
- package/dist/public-url-CarhZJgf.mjs.map +0 -1
- package/dist/query-BZKZcG5b.mjs +0 -881
- package/dist/query-BZKZcG5b.mjs.map +0 -1
- package/dist/query-BndaFxxp.mjs +0 -41
- package/dist/query-BndaFxxp.mjs.map +0 -1
- package/dist/rate-limit-BZmATC6W.mjs +0 -120
- package/dist/rate-limit-BZmATC6W.mjs.map +0 -1
- package/dist/redirect-CZkbozu9.mjs +0 -313
- package/dist/redirect-CZkbozu9.mjs.map +0 -1
- package/dist/redirect-CzqnLxK-.mjs +0 -17
- package/dist/redirect-CzqnLxK-.mjs.map +0 -1
- package/dist/redirects-BLNOWQTn.mjs +0 -1238
- package/dist/redirects-BLNOWQTn.mjs.map +0 -1
- package/dist/redirects-CqzNM0KM.mjs +0 -579
- package/dist/redirects-CqzNM0KM.mjs.map +0 -1
- package/dist/registry-AZAsRsor.mjs +0 -1179
- package/dist/registry-AZAsRsor.mjs.map +0 -1
- package/dist/relation-04DfKRTO.mjs +0 -334
- package/dist/relation-04DfKRTO.mjs.map +0 -1
- package/dist/relations-C4htk5sb.mjs +0 -309
- package/dist/relations-C4htk5sb.mjs.map +0 -1
- package/dist/relations-CcFwDJdJ.mjs +0 -403
- package/dist/relations-CcFwDJdJ.mjs.map +0 -1
- package/dist/relations-DnRK4YIT.d.mts +0 -3354
- package/dist/relations-DnRK4YIT.d.mts.map +0 -1
- package/dist/request-cache-DH9zdCOD.mjs +0 -104
- package/dist/request-cache-DH9zdCOD.mjs.map +0 -1
- package/dist/request-meta-DYxLlnmz.mjs +0 -140
- package/dist/request-meta-DYxLlnmz.mjs.map +0 -1
- package/dist/resolve-prZ3mP7l.mjs +0 -143
- package/dist/resolve-prZ3mP7l.mjs.map +0 -1
- package/dist/runner-BLIDKpmA.mjs +0 -4676
- package/dist/runner-BLIDKpmA.mjs.map +0 -1
- package/dist/runner-YKfIMnaF.d.mts +0 -67
- package/dist/runner-YKfIMnaF.d.mts.map +0 -1
- package/dist/schema-CGnBeLiW.mjs +0 -9
- package/dist/schema-CGnBeLiW.mjs.map +0 -1
- package/dist/schema-DYr-Vd-L.mjs +0 -420
- package/dist/schema-DYr-Vd-L.mjs.map +0 -1
- package/dist/scopes-BhWvBavj.mjs +0 -27
- package/dist/scopes-BhWvBavj.mjs.map +0 -1
- package/dist/search-C-qDk4kx.mjs +0 -439
- package/dist/search-C-qDk4kx.mjs.map +0 -1
- package/dist/secrets-rEtHt-6z.mjs +0 -330
- package/dist/secrets-rEtHt-6z.mjs.map +0 -1
- package/dist/sections-B_nYvlwn.mjs +0 -346
- package/dist/sections-B_nYvlwn.mjs.map +0 -1
- package/dist/seo-926NRIgt.mjs +0 -133
- package/dist/seo-926NRIgt.mjs.map +0 -1
- package/dist/seo-CVWkdv6x.mjs +0 -113
- package/dist/seo-CVWkdv6x.mjs.map +0 -1
- package/dist/service-CJl3ZAYs.mjs +0 -195
- package/dist/service-CJl3ZAYs.mjs.map +0 -1
- package/dist/session-user-BlMgqCHS.mjs +0 -51
- package/dist/session-user-BlMgqCHS.mjs.map +0 -1
- package/dist/settings--wobrkkC.mjs +0 -258
- package/dist/settings--wobrkkC.mjs.map +0 -1
- package/dist/settings-Cwm6o2jY.mjs +0 -51
- package/dist/settings-Cwm6o2jY.mjs.map +0 -1
- package/dist/setup-complete-DQbClIN2.mjs +0 -26
- package/dist/setup-complete-DQbClIN2.mjs.map +0 -1
- package/dist/setup-nonce-DZCFS2sn.mjs +0 -25
- package/dist/setup-nonce-DZCFS2sn.mjs.map +0 -1
- package/dist/single-flight-cache-BPuGPdcN.mjs +0 -104
- package/dist/single-flight-cache-BPuGPdcN.mjs.map +0 -1
- package/dist/site-url-BrxA_E9H.mjs +0 -16
- package/dist/site-url-BrxA_E9H.mjs.map +0 -1
- package/dist/slugify-B994zkXC.mjs +0 -17
- package/dist/slugify-B994zkXC.mjs.map +0 -1
- package/dist/snapshot--OCDTqdb.mjs +0 -216
- package/dist/snapshot--OCDTqdb.mjs.map +0 -1
- package/dist/ssrf-5sIM5jzl.mjs +0 -349
- package/dist/ssrf-5sIM5jzl.mjs.map +0 -1
- package/dist/state-BpFVsJZB.mjs +0 -136
- package/dist/state-BpFVsJZB.mjs.map +0 -1
- package/dist/status-B777-dXz.mjs +0 -30
- package/dist/status-B777-dXz.mjs.map +0 -1
- package/dist/target-BmR0iTwz.mjs +0 -27
- package/dist/target-BmR0iTwz.mjs.map +0 -1
- package/dist/taxonomies-DkAkMupr.mjs +0 -837
- package/dist/taxonomies-DkAkMupr.mjs.map +0 -1
- package/dist/taxonomies-Wfy_Lg12.mjs +0 -698
- package/dist/taxonomies-Wfy_Lg12.mjs.map +0 -1
- package/dist/taxonomy-tJepplLL.mjs +0 -484
- package/dist/taxonomy-tJepplLL.mjs.map +0 -1
- package/dist/tokens-BCO1oruF.mjs +0 -171
- package/dist/tokens-BCO1oruF.mjs.map +0 -1
- package/dist/transaction-0KOpOyMu.mjs +0 -29
- package/dist/transaction-0KOpOyMu.mjs.map +0 -1
- package/dist/transport-Dr3qtwXC.mjs +0 -136
- package/dist/transport-Dr3qtwXC.mjs.map +0 -1
- package/dist/transport-xh6YKW2G.d.mts +0 -42
- package/dist/transport-xh6YKW2G.d.mts.map +0 -1
- package/dist/trusted-proxy-BR5F1Otp.mjs +0 -51
- package/dist/trusted-proxy-BR5F1Otp.mjs.map +0 -1
- package/dist/types-3qNFPlci.mjs +0 -16
- package/dist/types-3qNFPlci.mjs.map +0 -1
- package/dist/types-75KIqzL9.mjs +0 -142
- package/dist/types-75KIqzL9.mjs.map +0 -1
- package/dist/types-B6GrPic4.d.mts +0 -1335
- package/dist/types-B6GrPic4.d.mts.map +0 -1
- package/dist/types-BuHW4xDE.d.mts +0 -259
- package/dist/types-BuHW4xDE.d.mts.map +0 -1
- package/dist/types-C00oyCZp.d.mts +0 -198
- package/dist/types-C00oyCZp.d.mts.map +0 -1
- package/dist/types-CDtkNSFQ.d.mts +0 -75
- package/dist/types-CDtkNSFQ.d.mts.map +0 -1
- package/dist/types-CPeXo1wF.d.mts +0 -124
- package/dist/types-CPeXo1wF.d.mts.map +0 -1
- package/dist/types-CQPWhPxE.mjs +0 -92
- package/dist/types-CQPWhPxE.mjs.map +0 -1
- package/dist/types-CXFkwEGC.d.mts +0 -198
- package/dist/types-CXFkwEGC.d.mts.map +0 -1
- package/dist/types-DM3vioBY.mjs +0 -18
- package/dist/types-DM3vioBY.mjs.map +0 -1
- package/dist/types-DczELMop.d.mts +0 -363
- package/dist/types-DczELMop.d.mts.map +0 -1
- package/dist/types-DfuTR6dL.mjs +0 -3
- package/dist/types-FV23hEoW.d.mts +0 -743
- package/dist/types-FV23hEoW.d.mts.map +0 -1
- package/dist/types-xDMtUFnO.d.mts +0 -257
- package/dist/types-xDMtUFnO.d.mts.map +0 -1
- package/dist/upload-attempts-BsqoQ1fn.mjs +0 -27
- package/dist/upload-attempts-BsqoQ1fn.mjs.map +0 -1
- package/dist/url-pattern-D4c2czpJ.mjs +0 -33
- package/dist/url-pattern-D4c2czpJ.mjs.map +0 -1
- package/dist/user-BTZ40aq6.mjs +0 -171
- package/dist/user-BTZ40aq6.mjs.map +0 -1
- package/dist/utils-BKvNjjhY.mjs +0 -435
- package/dist/utils-BKvNjjhY.mjs.map +0 -1
- package/dist/validate-BpwoDOgt.mjs +0 -120
- package/dist/validate-BpwoDOgt.mjs.map +0 -1
- package/dist/validate-DE_LRwo9.mjs +0 -379
- package/dist/validate-DE_LRwo9.mjs.map +0 -1
- package/dist/validate-DUP27OYD.d.mts +0 -400
- package/dist/validate-DUP27OYD.d.mts.map +0 -1
- package/dist/validation-BMImFJ11.mjs +0 -153
- package/dist/validation-BMImFJ11.mjs.map +0 -1
- package/dist/version-Bii3suaO.mjs +0 -7
- package/dist/version-Bii3suaO.mjs.map +0 -1
- package/dist/widgets-D3gJ10xU.mjs +0 -122
- package/dist/widgets-D3gJ10xU.mjs.map +0 -1
- package/dist/wxr-taxonomies-B2Og_6rU.d.mts +0 -34
- package/dist/wxr-taxonomies-B2Og_6rU.d.mts.map +0 -1
- package/dist/wxr-taxonomies-Cxngsjwo.mjs +0 -406
- package/dist/wxr-taxonomies-Cxngsjwo.mjs.map +0 -1
- package/dist/zod-generator-BuYSQJB7.mjs +0 -331
- package/dist/zod-generator-BuYSQJB7.mjs.map +0 -1
- /package/dist/{api-tokens-BfYts-sq.mjs → api-tokens-DS1VzN7_.mjs} +0 -0
- /package/dist/{ssrf-Dg3cIT-b.mjs → ssrf-BRpTDMV6.mjs} +0 -0
package/dist/runner-BLIDKpmA.mjs
DELETED
|
@@ -1,4676 +0,0 @@
|
|
|
1
|
-
import { MIGRATION_LOCK_BUSY_MESSAGE } from "./database/pg-migration-lock.mjs";
|
|
2
|
-
import { t as validateIdentifier } from "./validate-BpwoDOgt.mjs";
|
|
3
|
-
import { a as currentTimestamp, c as isSqlite, o as currentTimestampValue, p as tableExists, r as columnExists, s as isPostgres, t as binaryType, u as listTablesLike } from "./dialect-helpers-BSbfaO3F.mjs";
|
|
4
|
-
import { r as getI18nConfig } from "./config-C3EBstHi.mjs";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import { sql } from "kysely";
|
|
7
|
-
import { Migrator } from "kysely/migration";
|
|
8
|
-
|
|
9
|
-
//#region \0rolldown/runtime.js
|
|
10
|
-
var __defProp = Object.defineProperty;
|
|
11
|
-
var __exportAll = (all, no_symbols) => {
|
|
12
|
-
let target = {};
|
|
13
|
-
for (var name in all) {
|
|
14
|
-
__defProp(target, name, {
|
|
15
|
-
get: all[name],
|
|
16
|
-
enumerable: true
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
if (!no_symbols) {
|
|
20
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
21
|
-
}
|
|
22
|
-
return target;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/database/migrations/001_initial.ts
|
|
27
|
-
var _001_initial_exports = /* @__PURE__ */ __exportAll({
|
|
28
|
-
down: () => down$72,
|
|
29
|
-
up: () => up$72
|
|
30
|
-
});
|
|
31
|
-
/**
|
|
32
|
-
* Initial schema migration
|
|
33
|
-
*
|
|
34
|
-
* Note: Content tables (ec_posts, ec_pages, etc.) are created dynamically
|
|
35
|
-
* by the SchemaRegistry when collections are added via the admin UI.
|
|
36
|
-
* This migration only creates system tables.
|
|
37
|
-
*/
|
|
38
|
-
async function up$72(db) {
|
|
39
|
-
await db.schema.createTable("revisions").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("collection", "text", (col) => col.notNull()).addColumn("entry_id", "text", (col) => col.notNull()).addColumn("data", "text", (col) => col.notNull()).addColumn("author_id", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
40
|
-
await db.schema.createIndex("idx_revisions_entry").ifNotExists().on("revisions").columns(["collection", "entry_id"]).execute();
|
|
41
|
-
await db.schema.createTable("taxonomies").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull()).addColumn("slug", "text", (col) => col.notNull()).addColumn("label", "text", (col) => col.notNull()).addColumn("parent_id", "text").addColumn("data", "text").addUniqueConstraint("taxonomies_name_slug_unique", ["name", "slug"]).addForeignKeyConstraint("taxonomies_parent_fk", ["parent_id"], "taxonomies", ["id"], (cb) => cb.onDelete("set null")).execute();
|
|
42
|
-
await db.schema.createIndex("idx_taxonomies_name").ifNotExists().on("taxonomies").column("name").execute();
|
|
43
|
-
await db.schema.createTable("content_taxonomies").ifNotExists().addColumn("collection", "text", (col) => col.notNull()).addColumn("entry_id", "text", (col) => col.notNull()).addColumn("taxonomy_id", "text", (col) => col.notNull()).addPrimaryKeyConstraint("content_taxonomies_pk", [
|
|
44
|
-
"collection",
|
|
45
|
-
"entry_id",
|
|
46
|
-
"taxonomy_id"
|
|
47
|
-
]).addForeignKeyConstraint("content_taxonomies_taxonomy_fk", ["taxonomy_id"], "taxonomies", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
48
|
-
await db.schema.createTable("media").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("filename", "text", (col) => col.notNull()).addColumn("mime_type", "text", (col) => col.notNull()).addColumn("size", "integer").addColumn("width", "integer").addColumn("height", "integer").addColumn("alt", "text").addColumn("caption", "text").addColumn("storage_key", "text", (col) => col.notNull()).addColumn("content_hash", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("author_id", "text").execute();
|
|
49
|
-
await db.schema.createIndex("idx_media_content_hash").ifNotExists().on("media").column("content_hash").execute();
|
|
50
|
-
await db.schema.createTable("users").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("password_hash", "text", (col) => col.notNull()).addColumn("name", "text").addColumn("role", "text", (col) => col.defaultTo("subscriber")).addColumn("avatar_id", "text").addColumn("data", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
51
|
-
await db.schema.createIndex("idx_users_email").ifNotExists().on("users").column("email").execute();
|
|
52
|
-
await db.schema.createTable("options").ifNotExists().addColumn("name", "text", (col) => col.primaryKey()).addColumn("value", "text", (col) => col.notNull()).execute();
|
|
53
|
-
await db.schema.createTable("audit_logs").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("timestamp", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("actor_id", "text").addColumn("actor_ip", "text").addColumn("action", "text", (col) => col.notNull()).addColumn("resource_type", "text").addColumn("resource_id", "text").addColumn("details", "text").addColumn("status", "text").execute();
|
|
54
|
-
await db.schema.createIndex("idx_audit_actor").ifNotExists().on("audit_logs").column("actor_id").execute();
|
|
55
|
-
await db.schema.createIndex("idx_audit_action").ifNotExists().on("audit_logs").column("action").execute();
|
|
56
|
-
await db.schema.createIndex("idx_audit_timestamp").ifNotExists().on("audit_logs").column("timestamp").execute();
|
|
57
|
-
}
|
|
58
|
-
async function down$72(db) {
|
|
59
|
-
await db.schema.dropTable("audit_logs").execute();
|
|
60
|
-
await db.schema.dropTable("options").execute();
|
|
61
|
-
await db.schema.dropTable("users").execute();
|
|
62
|
-
await db.schema.dropTable("media").execute();
|
|
63
|
-
await db.schema.dropTable("content_taxonomies").execute();
|
|
64
|
-
await db.schema.dropTable("taxonomies").execute();
|
|
65
|
-
await db.schema.dropTable("revisions").execute();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
//#region src/database/migrations/002_media_status.ts
|
|
70
|
-
var _002_media_status_exports = /* @__PURE__ */ __exportAll({
|
|
71
|
-
down: () => down$71,
|
|
72
|
-
up: () => up$71
|
|
73
|
-
});
|
|
74
|
-
/**
|
|
75
|
-
* Add status column to media table for tracking upload state.
|
|
76
|
-
* Status values: 'pending' | 'ready' | 'failed'
|
|
77
|
-
*/
|
|
78
|
-
async function up$71(db) {
|
|
79
|
-
await db.schema.alterTable("media").addColumn("status", "text", (col) => col.notNull().defaultTo("ready")).execute();
|
|
80
|
-
await db.schema.createIndex("idx_media_status").on("media").column("status").execute();
|
|
81
|
-
}
|
|
82
|
-
async function down$71(db) {
|
|
83
|
-
await db.schema.dropIndex("idx_media_status").execute();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
//#endregion
|
|
87
|
-
//#region src/database/migrations/003_schema_registry.ts
|
|
88
|
-
var _003_schema_registry_exports = /* @__PURE__ */ __exportAll({
|
|
89
|
-
down: () => down$70,
|
|
90
|
-
up: () => up$70
|
|
91
|
-
});
|
|
92
|
-
/**
|
|
93
|
-
* Migration: Schema Registry Tables
|
|
94
|
-
*
|
|
95
|
-
* Creates the schema registry tables that store collection and field definitions.
|
|
96
|
-
* This enables dynamic schema management where D1 is the source of truth.
|
|
97
|
-
*/
|
|
98
|
-
async function up$70(db) {
|
|
99
|
-
await db.schema.createTable("_emdash_collections").addColumn("id", "text", (col) => col.primaryKey()).addColumn("slug", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("label_singular", "text").addColumn("description", "text").addColumn("icon", "text").addColumn("supports", "text").addColumn("source", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
100
|
-
await db.schema.createTable("_emdash_fields").addColumn("id", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("slug", "text", (col) => col.notNull()).addColumn("label", "text", (col) => col.notNull()).addColumn("type", "text", (col) => col.notNull()).addColumn("column_type", "text", (col) => col.notNull()).addColumn("required", "integer", (col) => col.defaultTo(0)).addColumn("unique", "integer", (col) => col.defaultTo(0)).addColumn("default_value", "text").addColumn("validation", "text").addColumn("widget", "text").addColumn("options", "text").addColumn("sort_order", "integer", (col) => col.defaultTo(0)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("fields_collection_fk", ["collection_id"], "_emdash_collections", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
101
|
-
await db.schema.createIndex("idx_fields_collection_slug").on("_emdash_fields").columns(["collection_id", "slug"]).unique().execute();
|
|
102
|
-
await db.schema.createIndex("idx_fields_collection").on("_emdash_fields").column("collection_id").execute();
|
|
103
|
-
await db.schema.createIndex("idx_fields_sort").on("_emdash_fields").columns(["collection_id", "sort_order"]).execute();
|
|
104
|
-
}
|
|
105
|
-
async function down$70(db) {
|
|
106
|
-
await db.schema.dropTable("_emdash_fields").execute();
|
|
107
|
-
await db.schema.dropTable("_emdash_collections").execute();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
//#endregion
|
|
111
|
-
//#region src/database/migrations/004_plugins.ts
|
|
112
|
-
var _004_plugins_exports = /* @__PURE__ */ __exportAll({
|
|
113
|
-
down: () => down$69,
|
|
114
|
-
up: () => up$69
|
|
115
|
-
});
|
|
116
|
-
/**
|
|
117
|
-
* Migration: Plugin System Tables
|
|
118
|
-
*
|
|
119
|
-
* Creates the plugin storage table and plugin state tracking.
|
|
120
|
-
* Plugin storage uses a document store with declared indexes.
|
|
121
|
-
*
|
|
122
|
-
* @see PLUGIN-SYSTEM.md § Plugin Storage
|
|
123
|
-
*/
|
|
124
|
-
async function up$69(db) {
|
|
125
|
-
await db.schema.createTable("_plugin_storage").addColumn("plugin_id", "text", (col) => col.notNull()).addColumn("collection", "text", (col) => col.notNull()).addColumn("id", "text", (col) => col.notNull()).addColumn("data", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("pk_plugin_storage", [
|
|
126
|
-
"plugin_id",
|
|
127
|
-
"collection",
|
|
128
|
-
"id"
|
|
129
|
-
]).execute();
|
|
130
|
-
await db.schema.createIndex("idx_plugin_storage_list").on("_plugin_storage").columns([
|
|
131
|
-
"plugin_id",
|
|
132
|
-
"collection",
|
|
133
|
-
"created_at"
|
|
134
|
-
]).execute();
|
|
135
|
-
await db.schema.createTable("_plugin_state").addColumn("plugin_id", "text", (col) => col.primaryKey()).addColumn("version", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("installed")).addColumn("installed_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("activated_at", "text").addColumn("deactivated_at", "text").addColumn("data", "text").execute();
|
|
136
|
-
await db.schema.createTable("_plugin_indexes").addColumn("plugin_id", "text", (col) => col.notNull()).addColumn("collection", "text", (col) => col.notNull()).addColumn("index_name", "text", (col) => col.notNull()).addColumn("fields", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("pk_plugin_indexes", [
|
|
137
|
-
"plugin_id",
|
|
138
|
-
"collection",
|
|
139
|
-
"index_name"
|
|
140
|
-
]).execute();
|
|
141
|
-
}
|
|
142
|
-
async function down$69(db) {
|
|
143
|
-
await db.schema.dropTable("_plugin_indexes").execute();
|
|
144
|
-
await db.schema.dropTable("_plugin_state").execute();
|
|
145
|
-
await db.schema.dropTable("_plugin_storage").execute();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/database/migrations/005_menus.ts
|
|
150
|
-
var _005_menus_exports = /* @__PURE__ */ __exportAll({
|
|
151
|
-
down: () => down$68,
|
|
152
|
-
up: () => up$68
|
|
153
|
-
});
|
|
154
|
-
/**
|
|
155
|
-
* Navigation Menus migration
|
|
156
|
-
*
|
|
157
|
-
* Creates tables for admin-editable navigation menus.
|
|
158
|
-
* Menu items can reference content entries, taxonomy terms, or custom URLs.
|
|
159
|
-
*/
|
|
160
|
-
async function up$68(db) {
|
|
161
|
-
await db.schema.createTable("_emdash_menus").addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
162
|
-
await db.schema.createTable("_emdash_menu_items").addColumn("id", "text", (col) => col.primaryKey()).addColumn("menu_id", "text", (col) => col.notNull()).addColumn("parent_id", "text").addColumn("sort_order", "integer", (col) => col.notNull().defaultTo(0)).addColumn("type", "text", (col) => col.notNull()).addColumn("reference_collection", "text").addColumn("reference_id", "text").addColumn("custom_url", "text").addColumn("label", "text", (col) => col.notNull()).addColumn("title_attr", "text").addColumn("target", "text").addColumn("css_classes", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("menu_items_menu_fk", ["menu_id"], "_emdash_menus", ["id"], (cb) => cb.onDelete("cascade")).addForeignKeyConstraint("menu_items_parent_fk", ["parent_id"], "_emdash_menu_items", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
163
|
-
await db.schema.createIndex("idx_menu_items_menu").on("_emdash_menu_items").columns(["menu_id", "sort_order"]).execute();
|
|
164
|
-
await db.schema.createIndex("idx_menu_items_parent").on("_emdash_menu_items").column("parent_id").execute();
|
|
165
|
-
}
|
|
166
|
-
async function down$68(db) {
|
|
167
|
-
await db.schema.dropTable("_emdash_menu_items").execute();
|
|
168
|
-
await db.schema.dropTable("_emdash_menus").execute();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
//#endregion
|
|
172
|
-
//#region src/database/migrations/006_taxonomy_defs.ts
|
|
173
|
-
var _006_taxonomy_defs_exports = /* @__PURE__ */ __exportAll({
|
|
174
|
-
down: () => down$67,
|
|
175
|
-
up: () => up$67
|
|
176
|
-
});
|
|
177
|
-
/**
|
|
178
|
-
* Taxonomy definitions migration
|
|
179
|
-
*
|
|
180
|
-
* Adds _emdash_taxonomy_defs table to store taxonomy definitions (category, tag, custom)
|
|
181
|
-
* and seeds default category and tag taxonomies.
|
|
182
|
-
*/
|
|
183
|
-
async function up$67(db) {
|
|
184
|
-
await db.schema.createTable("_emdash_taxonomy_defs").addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("label_singular", "text").addColumn("hierarchical", "integer", (col) => col.defaultTo(0)).addColumn("collections", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
185
|
-
await db.insertInto("_emdash_taxonomy_defs").values([{
|
|
186
|
-
id: "taxdef_category",
|
|
187
|
-
name: "category",
|
|
188
|
-
label: "Categories",
|
|
189
|
-
label_singular: "Category",
|
|
190
|
-
hierarchical: 1,
|
|
191
|
-
collections: JSON.stringify(["posts"])
|
|
192
|
-
}, {
|
|
193
|
-
id: "taxdef_tag",
|
|
194
|
-
name: "tag",
|
|
195
|
-
label: "Tags",
|
|
196
|
-
label_singular: "Tag",
|
|
197
|
-
hierarchical: 0,
|
|
198
|
-
collections: JSON.stringify(["posts"])
|
|
199
|
-
}]).execute();
|
|
200
|
-
}
|
|
201
|
-
async function down$67(db) {
|
|
202
|
-
await db.schema.dropTable("_emdash_taxonomy_defs").execute();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
//#endregion
|
|
206
|
-
//#region src/database/migrations/007_widgets.ts
|
|
207
|
-
var _007_widgets_exports = /* @__PURE__ */ __exportAll({
|
|
208
|
-
down: () => down$66,
|
|
209
|
-
up: () => up$66
|
|
210
|
-
});
|
|
211
|
-
async function up$66(db) {
|
|
212
|
-
await db.schema.createTable("_emdash_widget_areas").addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("description", "text").addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
|
|
213
|
-
await db.schema.createTable("_emdash_widgets").addColumn("id", "text", (col) => col.primaryKey()).addColumn("area_id", "text", (col) => col.notNull().references("_emdash_widget_areas.id").onDelete("cascade")).addColumn("sort_order", "integer", (col) => col.notNull().defaultTo(0)).addColumn("type", "text", (col) => col.notNull()).addColumn("title", "text").addColumn("content", "text").addColumn("menu_name", "text").addColumn("component_id", "text").addColumn("component_props", "text").addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
|
|
214
|
-
await db.schema.createIndex("idx_widgets_area").on("_emdash_widgets").columns(["area_id", "sort_order"]).execute();
|
|
215
|
-
}
|
|
216
|
-
async function down$66(db) {
|
|
217
|
-
await db.schema.dropTable("_emdash_widgets").execute();
|
|
218
|
-
await db.schema.dropTable("_emdash_widget_areas").execute();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
//#endregion
|
|
222
|
-
//#region src/database/migrations/008_auth.ts
|
|
223
|
-
var _008_auth_exports = /* @__PURE__ */ __exportAll({
|
|
224
|
-
down: () => down$65,
|
|
225
|
-
up: () => up$65
|
|
226
|
-
});
|
|
227
|
-
/**
|
|
228
|
-
* Auth migration - passkey-first authentication
|
|
229
|
-
*
|
|
230
|
-
* Changes:
|
|
231
|
-
* - Removes password_hash from users (no passwords)
|
|
232
|
-
* - Adds role as integer (RBAC levels)
|
|
233
|
-
* - Adds email_verified, avatar_url, updated_at to users
|
|
234
|
-
* - Creates credentials table (passkeys)
|
|
235
|
-
* - Creates auth_tokens table (magic links, invites)
|
|
236
|
-
* - Creates oauth_accounts table (external provider links)
|
|
237
|
-
* - Creates allowed_domains table (self-signup)
|
|
238
|
-
*/
|
|
239
|
-
async function up$65(db) {
|
|
240
|
-
await db.schema.createTable("users_new").addColumn("id", "text", (col) => col.primaryKey()).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("name", "text").addColumn("avatar_url", "text").addColumn("role", "integer", (col) => col.notNull().defaultTo(10)).addColumn("email_verified", "integer", (col) => col.notNull().defaultTo(0)).addColumn("data", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
241
|
-
await sql`
|
|
242
|
-
INSERT INTO users_new (id, email, name, role, data, created_at, updated_at)
|
|
243
|
-
SELECT
|
|
244
|
-
id,
|
|
245
|
-
email,
|
|
246
|
-
name,
|
|
247
|
-
CASE role
|
|
248
|
-
WHEN 'admin' THEN 50
|
|
249
|
-
WHEN 'editor' THEN 40
|
|
250
|
-
WHEN 'author' THEN 30
|
|
251
|
-
WHEN 'contributor' THEN 20
|
|
252
|
-
ELSE 10
|
|
253
|
-
END,
|
|
254
|
-
data,
|
|
255
|
-
created_at,
|
|
256
|
-
${currentTimestampValue(db)}
|
|
257
|
-
FROM users
|
|
258
|
-
`.execute(db);
|
|
259
|
-
await db.schema.dropTable("users").execute();
|
|
260
|
-
await sql`ALTER TABLE users_new RENAME TO users`.execute(db);
|
|
261
|
-
await db.schema.createIndex("idx_users_email").on("users").column("email").execute();
|
|
262
|
-
await db.schema.createTable("credentials").addColumn("id", "text", (col) => col.primaryKey()).addColumn("user_id", "text", (col) => col.notNull()).addColumn("public_key", binaryType(db), (col) => col.notNull()).addColumn("counter", "integer", (col) => col.notNull().defaultTo(0)).addColumn("device_type", "text", (col) => col.notNull()).addColumn("backed_up", "integer", (col) => col.notNull().defaultTo(0)).addColumn("transports", "text").addColumn("name", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("last_used_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("credentials_user_fk", ["user_id"], "users", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
263
|
-
await db.schema.createIndex("idx_credentials_user").on("credentials").column("user_id").execute();
|
|
264
|
-
await db.schema.createTable("auth_tokens").addColumn("hash", "text", (col) => col.primaryKey()).addColumn("user_id", "text").addColumn("email", "text").addColumn("type", "text", (col) => col.notNull()).addColumn("role", "integer").addColumn("invited_by", "text").addColumn("expires_at", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("auth_tokens_user_fk", ["user_id"], "users", ["id"], (cb) => cb.onDelete("cascade")).addForeignKeyConstraint("auth_tokens_invited_by_fk", ["invited_by"], "users", ["id"], (cb) => cb.onDelete("set null")).execute();
|
|
265
|
-
await db.schema.createIndex("idx_auth_tokens_email").on("auth_tokens").column("email").execute();
|
|
266
|
-
await db.schema.createTable("oauth_accounts").addColumn("provider", "text", (col) => col.notNull()).addColumn("provider_account_id", "text", (col) => col.notNull()).addColumn("user_id", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("oauth_accounts_pk", ["provider", "provider_account_id"]).addForeignKeyConstraint("oauth_accounts_user_fk", ["user_id"], "users", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
267
|
-
await db.schema.createIndex("idx_oauth_accounts_user").on("oauth_accounts").column("user_id").execute();
|
|
268
|
-
await db.schema.createTable("allowed_domains").addColumn("domain", "text", (col) => col.primaryKey()).addColumn("default_role", "integer", (col) => col.notNull().defaultTo(20)).addColumn("enabled", "integer", (col) => col.notNull().defaultTo(1)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
269
|
-
await db.schema.createTable("auth_challenges").addColumn("challenge", "text", (col) => col.primaryKey()).addColumn("type", "text", (col) => col.notNull()).addColumn("user_id", "text").addColumn("data", "text").addColumn("expires_at", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
270
|
-
await db.schema.createIndex("idx_auth_challenges_expires").on("auth_challenges").column("expires_at").execute();
|
|
271
|
-
}
|
|
272
|
-
async function down$65(db) {
|
|
273
|
-
await db.schema.dropTable("auth_challenges").execute();
|
|
274
|
-
await db.schema.dropTable("allowed_domains").execute();
|
|
275
|
-
await db.schema.dropTable("oauth_accounts").execute();
|
|
276
|
-
await db.schema.dropTable("auth_tokens").execute();
|
|
277
|
-
await db.schema.dropTable("credentials").execute();
|
|
278
|
-
await db.schema.createTable("users_old").addColumn("id", "text", (col) => col.primaryKey()).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("password_hash", "text", (col) => col.notNull()).addColumn("name", "text").addColumn("role", "text", (col) => col.defaultTo("subscriber")).addColumn("avatar_id", "text").addColumn("data", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
279
|
-
await sql`
|
|
280
|
-
INSERT INTO users_old (id, email, password_hash, name, role, data, created_at)
|
|
281
|
-
SELECT
|
|
282
|
-
id,
|
|
283
|
-
email,
|
|
284
|
-
'', -- No way to restore password
|
|
285
|
-
name,
|
|
286
|
-
CASE role
|
|
287
|
-
WHEN 50 THEN 'admin'
|
|
288
|
-
WHEN 40 THEN 'editor'
|
|
289
|
-
WHEN 30 THEN 'author'
|
|
290
|
-
WHEN 20 THEN 'contributor'
|
|
291
|
-
ELSE 'subscriber'
|
|
292
|
-
END,
|
|
293
|
-
data,
|
|
294
|
-
created_at
|
|
295
|
-
FROM users
|
|
296
|
-
`.execute(db);
|
|
297
|
-
await db.schema.dropTable("users").execute();
|
|
298
|
-
await sql`ALTER TABLE users_old RENAME TO users`.execute(db);
|
|
299
|
-
await db.schema.createIndex("idx_users_email").on("users").column("email").execute();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
//#endregion
|
|
303
|
-
//#region src/database/migrations/009_user_disabled.ts
|
|
304
|
-
var _009_user_disabled_exports = /* @__PURE__ */ __exportAll({
|
|
305
|
-
down: () => down$64,
|
|
306
|
-
up: () => up$64
|
|
307
|
-
});
|
|
308
|
-
/**
|
|
309
|
-
* User disabled column - for soft-disabling users
|
|
310
|
-
*
|
|
311
|
-
* Changes:
|
|
312
|
-
* - Adds disabled column to users table (INTEGER, default 0)
|
|
313
|
-
* - Disabled users cannot log in
|
|
314
|
-
*/
|
|
315
|
-
async function up$64(db) {
|
|
316
|
-
await sql`ALTER TABLE users ADD COLUMN disabled INTEGER NOT NULL DEFAULT 0`.execute(db);
|
|
317
|
-
await db.schema.createIndex("idx_users_disabled").on("users").column("disabled").execute();
|
|
318
|
-
}
|
|
319
|
-
async function down$64(db) {
|
|
320
|
-
await db.schema.dropIndex("idx_users_disabled").execute();
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
//#endregion
|
|
324
|
-
//#region src/database/migrations/011_sections.ts
|
|
325
|
-
var _011_sections_exports = /* @__PURE__ */ __exportAll({
|
|
326
|
-
down: () => down$63,
|
|
327
|
-
up: () => up$63
|
|
328
|
-
});
|
|
329
|
-
/**
|
|
330
|
-
* Migration: Add sections tables and performance indexes
|
|
331
|
-
*
|
|
332
|
-
* Sections are reusable content blocks that can be inserted into any Portable Text field.
|
|
333
|
-
* They provide a library of pre-built page sections (heroes, CTAs, testimonials, etc.)
|
|
334
|
-
* that content authors can browse and insert with a single click.
|
|
335
|
-
*/
|
|
336
|
-
async function up$63(db) {
|
|
337
|
-
await db.schema.createTable("_emdash_section_categories").addColumn("id", "text", (col) => col.primaryKey()).addColumn("slug", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("sort_order", "integer", (col) => col.defaultTo(0)).addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
|
|
338
|
-
await db.schema.createTable("_emdash_sections").addColumn("id", "text", (col) => col.primaryKey()).addColumn("slug", "text", (col) => col.notNull().unique()).addColumn("title", "text", (col) => col.notNull()).addColumn("description", "text").addColumn("category_id", "text", (col) => col.references("_emdash_section_categories.id").onDelete("set null")).addColumn("keywords", "text").addColumn("content", "text", (col) => col.notNull()).addColumn("preview_media_id", "text").addColumn("source", "text", (col) => col.notNull().defaultTo("user")).addColumn("theme_id", "text").addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).addColumn("updated_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
|
|
339
|
-
await db.schema.createIndex("idx_sections_category").on("_emdash_sections").columns(["category_id"]).execute();
|
|
340
|
-
await db.schema.createIndex("idx_sections_source").on("_emdash_sections").columns(["source"]).execute();
|
|
341
|
-
}
|
|
342
|
-
async function down$63(db) {
|
|
343
|
-
await db.schema.dropIndex("idx_sections_source").execute();
|
|
344
|
-
await db.schema.dropIndex("idx_sections_category").execute();
|
|
345
|
-
await db.schema.dropTable("_emdash_sections").execute();
|
|
346
|
-
await db.schema.dropTable("_emdash_section_categories").execute();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
//#endregion
|
|
350
|
-
//#region src/database/migrations/012_search.ts
|
|
351
|
-
var _012_search_exports = /* @__PURE__ */ __exportAll({
|
|
352
|
-
down: () => down$62,
|
|
353
|
-
up: () => up$62
|
|
354
|
-
});
|
|
355
|
-
/**
|
|
356
|
-
* Migration: Search Support
|
|
357
|
-
*
|
|
358
|
-
* Adds search configuration to collections and searchable flag to fields.
|
|
359
|
-
* FTS5 tables are created dynamically when search is enabled for a collection.
|
|
360
|
-
*/
|
|
361
|
-
async function up$62(db) {
|
|
362
|
-
await db.schema.alterTable("_emdash_collections").addColumn("search_config", "text").execute();
|
|
363
|
-
await db.schema.alterTable("_emdash_fields").addColumn("searchable", "integer", (col) => col.defaultTo(0)).execute();
|
|
364
|
-
}
|
|
365
|
-
async function down$62(db) {
|
|
366
|
-
await db.schema.alterTable("_emdash_fields").dropColumn("searchable").execute();
|
|
367
|
-
await db.schema.alterTable("_emdash_collections").dropColumn("search_config").execute();
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/database/migrations/013_scheduled_publishing.ts
|
|
372
|
-
var _013_scheduled_publishing_exports = /* @__PURE__ */ __exportAll({
|
|
373
|
-
down: () => down$61,
|
|
374
|
-
up: () => up$61
|
|
375
|
-
});
|
|
376
|
-
/**
|
|
377
|
-
* Migration: Add scheduled publishing support
|
|
378
|
-
*
|
|
379
|
-
* Adds scheduled_at column to all ec_* content tables.
|
|
380
|
-
* When scheduled_at is set and status is 'scheduled', the content
|
|
381
|
-
* will be auto-published when the scheduled time is reached.
|
|
382
|
-
*/
|
|
383
|
-
async function up$61(db) {
|
|
384
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
385
|
-
for (const tableName of tableNames) {
|
|
386
|
-
const table = { name: tableName };
|
|
387
|
-
await sql`
|
|
388
|
-
ALTER TABLE ${sql.ref(table.name)}
|
|
389
|
-
ADD COLUMN scheduled_at TEXT
|
|
390
|
-
`.execute(db);
|
|
391
|
-
await sql`
|
|
392
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_scheduled`)}
|
|
393
|
-
ON ${sql.ref(table.name)} (scheduled_at)
|
|
394
|
-
WHERE scheduled_at IS NOT NULL AND status = 'scheduled'
|
|
395
|
-
`.execute(db);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
async function down$61(db) {
|
|
399
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
400
|
-
for (const tableName of tableNames) {
|
|
401
|
-
const table = { name: tableName };
|
|
402
|
-
await sql`
|
|
403
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_scheduled`)}
|
|
404
|
-
`.execute(db);
|
|
405
|
-
await sql`
|
|
406
|
-
ALTER TABLE ${sql.ref(table.name)}
|
|
407
|
-
DROP COLUMN scheduled_at
|
|
408
|
-
`.execute(db);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
//#endregion
|
|
413
|
-
//#region src/database/migrations/014_draft_revisions.ts
|
|
414
|
-
var _014_draft_revisions_exports = /* @__PURE__ */ __exportAll({
|
|
415
|
-
down: () => down$60,
|
|
416
|
-
up: () => up$60
|
|
417
|
-
});
|
|
418
|
-
async function up$60(db) {
|
|
419
|
-
const tables = await db.selectFrom("_emdash_collections").select("slug").execute();
|
|
420
|
-
for (const row of tables) {
|
|
421
|
-
const tableName = `ec_${row.slug}`;
|
|
422
|
-
await sql`
|
|
423
|
-
ALTER TABLE ${sql.ref(tableName)}
|
|
424
|
-
ADD COLUMN live_revision_id TEXT REFERENCES revisions(id)
|
|
425
|
-
`.execute(db);
|
|
426
|
-
await sql`
|
|
427
|
-
ALTER TABLE ${sql.ref(tableName)}
|
|
428
|
-
ADD COLUMN draft_revision_id TEXT REFERENCES revisions(id)
|
|
429
|
-
`.execute(db);
|
|
430
|
-
await sql`
|
|
431
|
-
CREATE INDEX ${sql.ref(`idx_${row.slug}_live_revision`)}
|
|
432
|
-
ON ${sql.ref(tableName)} (live_revision_id)
|
|
433
|
-
`.execute(db);
|
|
434
|
-
await sql`
|
|
435
|
-
CREATE INDEX ${sql.ref(`idx_${row.slug}_draft_revision`)}
|
|
436
|
-
ON ${sql.ref(tableName)} (draft_revision_id)
|
|
437
|
-
`.execute(db);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
async function down$60(db) {
|
|
441
|
-
const tables = await db.selectFrom("_emdash_collections").select("slug").execute();
|
|
442
|
-
for (const row of tables) {
|
|
443
|
-
const tableName = `ec_${row.slug}`;
|
|
444
|
-
await sql`
|
|
445
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_${row.slug}_draft_revision`)}
|
|
446
|
-
`.execute(db);
|
|
447
|
-
await sql`
|
|
448
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_${row.slug}_live_revision`)}
|
|
449
|
-
`.execute(db);
|
|
450
|
-
await sql`
|
|
451
|
-
ALTER TABLE ${sql.ref(tableName)}
|
|
452
|
-
DROP COLUMN draft_revision_id
|
|
453
|
-
`.execute(db);
|
|
454
|
-
await sql`
|
|
455
|
-
ALTER TABLE ${sql.ref(tableName)}
|
|
456
|
-
DROP COLUMN live_revision_id
|
|
457
|
-
`.execute(db);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
//#endregion
|
|
462
|
-
//#region src/database/migrations/015_indexes.ts
|
|
463
|
-
var _015_indexes_exports = /* @__PURE__ */ __exportAll({
|
|
464
|
-
down: () => down$59,
|
|
465
|
-
up: () => up$59
|
|
466
|
-
});
|
|
467
|
-
/**
|
|
468
|
-
* Add performance indexes for common query patterns.
|
|
469
|
-
*
|
|
470
|
-
* Covers:
|
|
471
|
-
* 1. Media table: mime_type, filename, created_at
|
|
472
|
-
* 2. content_taxonomies: reverse lookup by taxonomy_id
|
|
473
|
-
* 3. taxonomies: parent_id FK
|
|
474
|
-
* 4. audit_logs: compound (resource_type, resource_id)
|
|
475
|
-
* 5. Retroactive author_id + updated_at on existing ec_* content tables
|
|
476
|
-
* (new tables get these from createContentTable() in registry.ts)
|
|
477
|
-
*/
|
|
478
|
-
async function up$59(db) {
|
|
479
|
-
await db.schema.createIndex("idx_media_mime_type").on("media").column("mime_type").execute();
|
|
480
|
-
await db.schema.createIndex("idx_media_filename").on("media").column("filename").execute();
|
|
481
|
-
await db.schema.createIndex("idx_media_created_at").on("media").column("created_at").execute();
|
|
482
|
-
await db.schema.createIndex("idx_content_taxonomies_term").on("content_taxonomies").column("taxonomy_id").execute();
|
|
483
|
-
await db.schema.createIndex("idx_taxonomies_parent").on("taxonomies").column("parent_id").execute();
|
|
484
|
-
await db.schema.createIndex("idx_audit_resource").on("audit_logs").columns(["resource_type", "resource_id"]).execute();
|
|
485
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
486
|
-
for (const tableName of tableNames) {
|
|
487
|
-
const table = { name: tableName };
|
|
488
|
-
await sql`
|
|
489
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_author`)}
|
|
490
|
-
ON ${sql.ref(table.name)} (author_id)
|
|
491
|
-
`.execute(db);
|
|
492
|
-
await sql`
|
|
493
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_updated`)}
|
|
494
|
-
ON ${sql.ref(table.name)} (updated_at)
|
|
495
|
-
`.execute(db);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
async function down$59(db) {
|
|
499
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
500
|
-
for (const tableName of tableNames) {
|
|
501
|
-
const table = { name: tableName };
|
|
502
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_updated`)}`.execute(db);
|
|
503
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_author`)}`.execute(db);
|
|
504
|
-
}
|
|
505
|
-
await db.schema.dropIndex("idx_audit_resource").execute();
|
|
506
|
-
await db.schema.dropIndex("idx_taxonomies_parent").execute();
|
|
507
|
-
await db.schema.dropIndex("idx_content_taxonomies_term").execute();
|
|
508
|
-
await db.schema.dropIndex("idx_media_created_at").execute();
|
|
509
|
-
await db.schema.dropIndex("idx_media_filename").execute();
|
|
510
|
-
await db.schema.dropIndex("idx_media_mime_type").execute();
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region src/database/migrations/016_api_tokens.ts
|
|
515
|
-
var _016_api_tokens_exports = /* @__PURE__ */ __exportAll({
|
|
516
|
-
down: () => down$58,
|
|
517
|
-
up: () => up$58
|
|
518
|
-
});
|
|
519
|
-
/**
|
|
520
|
-
* API token tables for programmatic access.
|
|
521
|
-
*
|
|
522
|
-
* Three tables:
|
|
523
|
-
* 1. _emdash_api_tokens — Personal Access Tokens (ec_pat_...)
|
|
524
|
-
* 2. _emdash_oauth_tokens — OAuth access/refresh tokens (ec_oat_/ec_ort_...)
|
|
525
|
-
* 3. _emdash_device_codes — OAuth Device Flow state (RFC 8628)
|
|
526
|
-
*
|
|
527
|
-
* Every CREATE is guarded with `.ifNotExists()` so the migration is safe to
|
|
528
|
-
* re-run against a partially-applied schema. See #954 for the failure mode:
|
|
529
|
-
* if `up()` crashes mid-way (D1 subrequest limit, isolate cancellation,
|
|
530
|
-
* transient connection error), the migration record never gets inserted
|
|
531
|
-
* into `_emdash_migrations`, and the next request retries `up()` from the
|
|
532
|
-
* top. Without these guards, the retry crashed with `table ... already
|
|
533
|
-
* exists` and blocked every subsequent boot of the Worker.
|
|
534
|
-
*/
|
|
535
|
-
async function up$58(db) {
|
|
536
|
-
await db.schema.createTable("_emdash_api_tokens").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("prefix", "text", (col) => col.notNull()).addColumn("user_id", "text", (col) => col.notNull()).addColumn("scopes", "text", (col) => col.notNull()).addColumn("expires_at", "text").addColumn("last_used_at", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("api_tokens_user_fk", ["user_id"], "users", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
537
|
-
await db.schema.createIndex("idx_api_tokens_token_hash").ifNotExists().on("_emdash_api_tokens").column("token_hash").execute();
|
|
538
|
-
await db.schema.createIndex("idx_api_tokens_user_id").ifNotExists().on("_emdash_api_tokens").column("user_id").execute();
|
|
539
|
-
await db.schema.createTable("_emdash_oauth_tokens").ifNotExists().addColumn("token_hash", "text", (col) => col.primaryKey()).addColumn("token_type", "text", (col) => col.notNull()).addColumn("user_id", "text", (col) => col.notNull()).addColumn("scopes", "text", (col) => col.notNull()).addColumn("client_type", "text", (col) => col.notNull().defaultTo("cli")).addColumn("expires_at", "text", (col) => col.notNull()).addColumn("refresh_token_hash", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("oauth_tokens_user_fk", ["user_id"], "users", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
540
|
-
await db.schema.createIndex("idx_oauth_tokens_user_id").ifNotExists().on("_emdash_oauth_tokens").column("user_id").execute();
|
|
541
|
-
await db.schema.createIndex("idx_oauth_tokens_expires").ifNotExists().on("_emdash_oauth_tokens").column("expires_at").execute();
|
|
542
|
-
await db.schema.createTable("_emdash_device_codes").ifNotExists().addColumn("device_code", "text", (col) => col.primaryKey()).addColumn("user_code", "text", (col) => col.notNull().unique()).addColumn("scopes", "text", (col) => col.notNull()).addColumn("user_id", "text").addColumn("status", "text", (col) => col.notNull().defaultTo("pending")).addColumn("expires_at", "text", (col) => col.notNull()).addColumn("interval", "integer", (col) => col.notNull().defaultTo(5)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
543
|
-
}
|
|
544
|
-
async function down$58(db) {
|
|
545
|
-
await db.schema.dropTable("_emdash_device_codes").ifExists().execute();
|
|
546
|
-
await db.schema.dropTable("_emdash_oauth_tokens").ifExists().execute();
|
|
547
|
-
await db.schema.dropTable("_emdash_api_tokens").ifExists().execute();
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
//#endregion
|
|
551
|
-
//#region src/database/migrations/017_authorization_codes.ts
|
|
552
|
-
var _017_authorization_codes_exports = /* @__PURE__ */ __exportAll({
|
|
553
|
-
down: () => down$57,
|
|
554
|
-
up: () => up$57
|
|
555
|
-
});
|
|
556
|
-
/**
|
|
557
|
-
* Authorization codes for OAuth 2.1 Authorization Code + PKCE flow.
|
|
558
|
-
*
|
|
559
|
-
* Used by MCP clients (Claude Desktop, VS Code, etc.) to authenticate
|
|
560
|
-
* via the standard OAuth authorization code grant.
|
|
561
|
-
*
|
|
562
|
-
* Also adds client_id tracking to oauth_tokens for per-client revocation.
|
|
563
|
-
*
|
|
564
|
-
* Every statement is guarded so the migration can restart after any one of
|
|
565
|
-
* them has committed. D1 commits each DDL statement on its own and the
|
|
566
|
-
* migrator records the migration only once `up` returns, so an interrupted
|
|
567
|
-
* run leaves the table created and the migration pending; an unguarded
|
|
568
|
-
* `CREATE TABLE` then fails on every retry and the database never finishes
|
|
569
|
-
* migrating.
|
|
570
|
-
*/
|
|
571
|
-
async function up$57(db) {
|
|
572
|
-
await db.schema.createTable("_emdash_authorization_codes").ifNotExists().addColumn("code_hash", "text", (col) => col.primaryKey()).addColumn("client_id", "text", (col) => col.notNull()).addColumn("redirect_uri", "text", (col) => col.notNull()).addColumn("user_id", "text", (col) => col.notNull()).addColumn("scopes", "text", (col) => col.notNull()).addColumn("code_challenge", "text", (col) => col.notNull()).addColumn("code_challenge_method", "text", (col) => col.notNull().defaultTo("S256")).addColumn("resource", "text").addColumn("expires_at", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addForeignKeyConstraint("auth_codes_user_fk", ["user_id"], "users", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
573
|
-
await db.schema.createIndex("idx_auth_codes_expires").ifNotExists().on("_emdash_authorization_codes").column("expires_at").execute();
|
|
574
|
-
if (!await columnExists(db, "_emdash_oauth_tokens", "client_id")) await db.schema.alterTable("_emdash_oauth_tokens").addColumn("client_id", "text").execute();
|
|
575
|
-
}
|
|
576
|
-
async function down$57(db) {
|
|
577
|
-
await db.schema.dropTable("_emdash_authorization_codes").ifExists().execute();
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
//#endregion
|
|
581
|
-
//#region src/database/migrations/018_seo.ts
|
|
582
|
-
var _018_seo_exports = /* @__PURE__ */ __exportAll({
|
|
583
|
-
down: () => down$56,
|
|
584
|
-
up: () => up$56
|
|
585
|
-
});
|
|
586
|
-
/**
|
|
587
|
-
* Migration: SEO support
|
|
588
|
-
*
|
|
589
|
-
* Creates:
|
|
590
|
-
* - `_emdash_seo` table: per-content SEO metadata (separate from content tables)
|
|
591
|
-
* - `has_seo` column on `_emdash_collections`: opt-in flag per collection
|
|
592
|
-
*
|
|
593
|
-
* SEO is not a universal concern — only collections representing web pages
|
|
594
|
-
* need it. The `has_seo` flag controls whether the admin shows SEO fields
|
|
595
|
-
* and whether the collection's content appears in sitemaps.
|
|
596
|
-
*/
|
|
597
|
-
async function up$56(db) {
|
|
598
|
-
await db.schema.createTable("_emdash_seo").addColumn("collection", "text", (col) => col.notNull()).addColumn("content_id", "text", (col) => col.notNull()).addColumn("seo_title", "text").addColumn("seo_description", "text").addColumn("seo_image", "text").addColumn("seo_canonical", "text").addColumn("seo_no_index", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "text", (col) => col.notNull().defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.notNull().defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("_emdash_seo_pk", ["collection", "content_id"]).execute();
|
|
599
|
-
await sql`
|
|
600
|
-
CREATE INDEX idx_emdash_seo_collection
|
|
601
|
-
ON _emdash_seo (collection)
|
|
602
|
-
`.execute(db);
|
|
603
|
-
await sql`
|
|
604
|
-
ALTER TABLE _emdash_collections
|
|
605
|
-
ADD COLUMN has_seo INTEGER NOT NULL DEFAULT 0
|
|
606
|
-
`.execute(db);
|
|
607
|
-
}
|
|
608
|
-
async function down$56(db) {
|
|
609
|
-
await sql`DROP TABLE IF EXISTS _emdash_seo`.execute(db);
|
|
610
|
-
await sql`
|
|
611
|
-
ALTER TABLE _emdash_collections
|
|
612
|
-
DROP COLUMN has_seo
|
|
613
|
-
`.execute(db);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
//#endregion
|
|
617
|
-
//#region src/database/migrations/019_i18n.ts
|
|
618
|
-
var _019_i18n_exports = /* @__PURE__ */ __exportAll({
|
|
619
|
-
down: () => down$55,
|
|
620
|
-
up: () => up$55
|
|
621
|
-
});
|
|
622
|
-
/**
|
|
623
|
-
* Quote an identifier for use in raw SQL. Escapes embedded double-quotes
|
|
624
|
-
* per SQL standard (double them). The name should first pass
|
|
625
|
-
* validateIdentifier() or validateTableName() for defense-in-depth.
|
|
626
|
-
*/
|
|
627
|
-
const DOUBLE_QUOTE_RE = /"/g;
|
|
628
|
-
function quoteIdent(name) {
|
|
629
|
-
return `"${name.replace(DOUBLE_QUOTE_RE, "\"\"")}"`;
|
|
630
|
-
}
|
|
631
|
-
/** Suffix added to tmp tables during i18n migration rebuild. */
|
|
632
|
-
const I18N_TMP_SUFFIX = /_i18n_tmp$/;
|
|
633
|
-
/** Table names from sqlite_master are ec_{slug} — validate the pattern. */
|
|
634
|
-
const TABLE_NAME_PATTERN = /^ec_[a-z][a-z0-9_]*$/;
|
|
635
|
-
function validateTableName(name) {
|
|
636
|
-
if (!TABLE_NAME_PATTERN.test(name)) throw new Error(`Invalid content table name: "${name}"`);
|
|
637
|
-
}
|
|
638
|
-
/** SQLite column types produced by EmDash's schema registry. */
|
|
639
|
-
const ALLOWED_COLUMN_TYPES = new Set([
|
|
640
|
-
"TEXT",
|
|
641
|
-
"INTEGER",
|
|
642
|
-
"REAL",
|
|
643
|
-
"BLOB",
|
|
644
|
-
"JSON",
|
|
645
|
-
"NUMERIC",
|
|
646
|
-
""
|
|
647
|
-
]);
|
|
648
|
-
function validateColumnType(type, colName) {
|
|
649
|
-
if (!ALLOWED_COLUMN_TYPES.has(type.toUpperCase())) throw new Error(`Unexpected column type "${type}" for column "${colName}"`);
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Validate that a default value expression from PRAGMA table_info is safe
|
|
653
|
-
* to interpolate into DDL. Allows: string literals, numeric literals,
|
|
654
|
-
* NULL, and known function calls like datetime('now').
|
|
655
|
-
*
|
|
656
|
-
* Note: PRAGMA table_info strips the outer parens from expression defaults,
|
|
657
|
-
* so `DEFAULT (datetime('now'))` is reported as `datetime('now')`.
|
|
658
|
-
* We accept both forms and re-wrap in parens via normalizeDdlDefault().
|
|
659
|
-
*/
|
|
660
|
-
const SAFE_DEFAULT_PATTERN = /^(?:'[^']*'|NULL|-?\d+(?:\.\d+)?|\(?datetime\('now'\)\)?|\(?json\('[^']*'\)\)?|0|1)$/i;
|
|
661
|
-
function validateDefaultValue(value, colName) {
|
|
662
|
-
if (!SAFE_DEFAULT_PATTERN.test(value)) throw new Error(`Unexpected default value "${value}" for column "${colName}"`);
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Normalize a PRAGMA table_info default value for use in DDL.
|
|
666
|
-
* Function-call defaults (e.g. `datetime('now')`) must be wrapped in parens
|
|
667
|
-
* to form valid expression defaults: `DEFAULT (datetime('now'))`.
|
|
668
|
-
* PRAGMA strips the outer parens, so we re-add them here.
|
|
669
|
-
*/
|
|
670
|
-
const FUNCTION_DEFAULT_PATTERN = /^(?:datetime|json)\(/i;
|
|
671
|
-
function normalizeDdlDefault(value) {
|
|
672
|
-
if (value.startsWith("(")) return value;
|
|
673
|
-
if (FUNCTION_DEFAULT_PATTERN.test(value)) return `(${value})`;
|
|
674
|
-
return value;
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Validate that a CREATE INDEX statement from sqlite_master is safe to replay.
|
|
678
|
-
* Must start with CREATE [UNIQUE] INDEX and not contain suspicious patterns.
|
|
679
|
-
*/
|
|
680
|
-
const CREATE_INDEX_PATTERN = /^CREATE\s+(UNIQUE\s+)?INDEX\s+/i;
|
|
681
|
-
function validateCreateIndexSql(sqlStr, idxName) {
|
|
682
|
-
if (!CREATE_INDEX_PATTERN.test(sqlStr)) throw new Error(`Unexpected index SQL for "${idxName}": does not match CREATE INDEX pattern`);
|
|
683
|
-
if (sqlStr.includes(";")) throw new Error(`Unexpected index SQL for "${idxName}": contains semicolon`);
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* PostgreSQL path: ALTER TABLE supports ADD COLUMN and DROP CONSTRAINT directly.
|
|
687
|
-
* No table rebuild needed.
|
|
688
|
-
*/
|
|
689
|
-
async function upPostgres$1(db) {
|
|
690
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
691
|
-
for (const t of tableNames) {
|
|
692
|
-
validateTableName(t);
|
|
693
|
-
if ((await sql`
|
|
694
|
-
SELECT EXISTS(
|
|
695
|
-
SELECT 1 FROM information_schema.columns
|
|
696
|
-
WHERE table_schema = current_schema() AND table_name = ${t} AND column_name = 'locale'
|
|
697
|
-
) as exists
|
|
698
|
-
`.execute(db)).rows[0]?.exists === true) continue;
|
|
699
|
-
await sql`ALTER TABLE ${sql.ref(t)} ADD COLUMN locale TEXT NOT NULL DEFAULT 'en'`.execute(db);
|
|
700
|
-
await sql`ALTER TABLE ${sql.ref(t)} ADD COLUMN translation_group TEXT`.execute(db);
|
|
701
|
-
const constraints = await sql`
|
|
702
|
-
SELECT conname FROM pg_constraint
|
|
703
|
-
WHERE conrelid = ${t}::regclass
|
|
704
|
-
AND contype = 'u'
|
|
705
|
-
AND array_length(conkey, 1) = 1
|
|
706
|
-
AND conkey[1] = (
|
|
707
|
-
SELECT attnum FROM pg_attribute
|
|
708
|
-
WHERE attrelid = ${t}::regclass AND attname = 'slug'
|
|
709
|
-
)
|
|
710
|
-
`.execute(db);
|
|
711
|
-
for (const c of constraints.rows) await sql`ALTER TABLE ${sql.ref(t)} DROP CONSTRAINT ${sql.ref(c.conname)}`.execute(db);
|
|
712
|
-
await sql`
|
|
713
|
-
ALTER TABLE ${sql.ref(t)}
|
|
714
|
-
ADD CONSTRAINT ${sql.ref(`${t}_slug_locale_unique`)} UNIQUE (slug, locale)
|
|
715
|
-
`.execute(db);
|
|
716
|
-
await sql`UPDATE ${sql.ref(t)} SET translation_group = id`.execute(db);
|
|
717
|
-
await sql`CREATE INDEX ${sql.ref(`idx_${t}_locale`)} ON ${sql.ref(t)} (locale)`.execute(db);
|
|
718
|
-
await sql`
|
|
719
|
-
CREATE INDEX ${sql.ref(`idx_${t}_translation_group`)}
|
|
720
|
-
ON ${sql.ref(t)} (translation_group)
|
|
721
|
-
`.execute(db);
|
|
722
|
-
}
|
|
723
|
-
if ((await sql`
|
|
724
|
-
SELECT EXISTS(
|
|
725
|
-
SELECT 1 FROM information_schema.columns
|
|
726
|
-
WHERE table_schema = current_schema() AND table_name = '_emdash_fields' AND column_name = 'translatable'
|
|
727
|
-
) as exists
|
|
728
|
-
`.execute(db)).rows[0]?.exists !== true) await sql`
|
|
729
|
-
ALTER TABLE _emdash_fields
|
|
730
|
-
ADD COLUMN translatable INTEGER NOT NULL DEFAULT 1
|
|
731
|
-
`.execute(db);
|
|
732
|
-
}
|
|
733
|
-
async function up$55(db) {
|
|
734
|
-
if (!isSqlite(db)) return upPostgres$1(db);
|
|
735
|
-
const orphanedTmps = await listTablesLike(db, "ec_%_i18n_tmp");
|
|
736
|
-
for (const tmpName of orphanedTmps) {
|
|
737
|
-
validateTableName(tmpName.replace(I18N_TMP_SUFFIX, ""));
|
|
738
|
-
await sql`DROP TABLE IF EXISTS ${sql.ref(tmpName)}`.execute(db);
|
|
739
|
-
}
|
|
740
|
-
const tables = { rows: (await listTablesLike(db, "ec_%")).map((name) => ({ name })) };
|
|
741
|
-
for (const table of tables.rows) {
|
|
742
|
-
const t = table.name;
|
|
743
|
-
validateTableName(t);
|
|
744
|
-
const tmp = `${t}_i18n_tmp`;
|
|
745
|
-
{
|
|
746
|
-
const trx = db;
|
|
747
|
-
const columns = (await sql`
|
|
748
|
-
PRAGMA table_info(${sql.ref(t)})
|
|
749
|
-
`.execute(trx)).rows;
|
|
750
|
-
if (columns.some((col) => col.name === "locale")) continue;
|
|
751
|
-
const idxResult = await sql`
|
|
752
|
-
PRAGMA index_list(${sql.ref(t)})
|
|
753
|
-
`.execute(trx);
|
|
754
|
-
const indexDefs = [];
|
|
755
|
-
for (const idx of idxResult.rows) {
|
|
756
|
-
if (idx.origin === "pk" || idx.name.startsWith("sqlite_autoindex_")) continue;
|
|
757
|
-
const idxColResult = await sql`
|
|
758
|
-
PRAGMA index_info(${sql.ref(idx.name)})
|
|
759
|
-
`.execute(trx);
|
|
760
|
-
indexDefs.push({
|
|
761
|
-
name: idx.name,
|
|
762
|
-
unique: idx.unique === 1,
|
|
763
|
-
columns: idxColResult.rows.map((c) => c.name),
|
|
764
|
-
partial: idx.partial
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
const partialSqls = /* @__PURE__ */ new Map();
|
|
768
|
-
for (const idx of indexDefs) if (idx.partial) {
|
|
769
|
-
const createResult = await sql`
|
|
770
|
-
SELECT sql FROM sqlite_master
|
|
771
|
-
WHERE type = 'index' AND name = ${idx.name}
|
|
772
|
-
`.execute(trx);
|
|
773
|
-
if (createResult.rows[0]?.sql) partialSqls.set(idx.name, createResult.rows[0].sql);
|
|
774
|
-
}
|
|
775
|
-
for (const col of columns) validateIdentifier(col.name, "column name");
|
|
776
|
-
const colDefs = [];
|
|
777
|
-
const colNames = [];
|
|
778
|
-
for (const col of columns) {
|
|
779
|
-
validateColumnType(col.type || "TEXT", col.name);
|
|
780
|
-
colNames.push(quoteIdent(col.name));
|
|
781
|
-
let def = `${quoteIdent(col.name)} ${col.type || "TEXT"}`;
|
|
782
|
-
if (col.pk) def += " PRIMARY KEY";
|
|
783
|
-
else if (col.name === "slug") {} else if (col.notnull) def += " NOT NULL";
|
|
784
|
-
if (col.dflt_value !== null) {
|
|
785
|
-
validateDefaultValue(col.dflt_value, col.name);
|
|
786
|
-
def += ` DEFAULT ${normalizeDdlDefault(col.dflt_value)}`;
|
|
787
|
-
}
|
|
788
|
-
colDefs.push(def);
|
|
789
|
-
}
|
|
790
|
-
colDefs.push("\"locale\" TEXT NOT NULL DEFAULT 'en'");
|
|
791
|
-
colDefs.push("\"translation_group\" TEXT");
|
|
792
|
-
colDefs.push("UNIQUE(\"slug\", \"locale\")");
|
|
793
|
-
const createColsSql = colDefs.join(",\n ");
|
|
794
|
-
const selectColsSql = colNames.join(", ");
|
|
795
|
-
for (const idx of indexDefs) await sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);
|
|
796
|
-
await sql.raw(`CREATE TABLE ${quoteIdent(tmp)} (\n\t\t\t\t${createColsSql}\n\t\t\t)`).execute(trx);
|
|
797
|
-
await sql.raw(`INSERT INTO ${quoteIdent(tmp)} (${selectColsSql}, "locale", "translation_group")\n\t\t\t SELECT ${selectColsSql}, 'en', "id" FROM ${quoteIdent(t)}`).execute(trx);
|
|
798
|
-
await sql`DROP TABLE ${sql.ref(t)}`.execute(trx);
|
|
799
|
-
await sql.raw(`ALTER TABLE ${quoteIdent(tmp)} RENAME TO ${quoteIdent(t)}`).execute(trx);
|
|
800
|
-
for (const idx of indexDefs) {
|
|
801
|
-
if (idx.name === `idx_${t}_slug`) continue;
|
|
802
|
-
if (idx.partial && partialSqls.has(idx.name)) {
|
|
803
|
-
const idxSql = partialSqls.get(idx.name);
|
|
804
|
-
validateCreateIndexSql(idxSql, idx.name);
|
|
805
|
-
await sql.raw(idxSql).execute(trx);
|
|
806
|
-
} else {
|
|
807
|
-
for (const c of idx.columns) validateIdentifier(c, "index column name");
|
|
808
|
-
const cols = idx.columns.map((c) => quoteIdent(c)).join(", ");
|
|
809
|
-
const unique = idx.unique ? "UNIQUE " : "";
|
|
810
|
-
await sql.raw(`CREATE ${unique}INDEX ${quoteIdent(idx.name)} ON ${quoteIdent(t)} (${cols})`).execute(trx);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
await sql`
|
|
814
|
-
CREATE INDEX ${sql.ref(`idx_${t}_slug`)}
|
|
815
|
-
ON ${sql.ref(t)} (slug)
|
|
816
|
-
`.execute(trx);
|
|
817
|
-
await sql`
|
|
818
|
-
CREATE INDEX ${sql.ref(`idx_${t}_locale`)}
|
|
819
|
-
ON ${sql.ref(t)} (locale)
|
|
820
|
-
`.execute(trx);
|
|
821
|
-
await sql`
|
|
822
|
-
CREATE INDEX ${sql.ref(`idx_${t}_translation_group`)}
|
|
823
|
-
ON ${sql.ref(t)} (translation_group)
|
|
824
|
-
`.execute(trx);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
if (!(await sql`
|
|
828
|
-
PRAGMA table_info(_emdash_fields)
|
|
829
|
-
`.execute(db)).rows.some((col) => col.name === "translatable")) await sql`
|
|
830
|
-
ALTER TABLE _emdash_fields
|
|
831
|
-
ADD COLUMN translatable INTEGER NOT NULL DEFAULT 1
|
|
832
|
-
`.execute(db);
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* PostgreSQL down path: straightforward ALTER TABLE operations.
|
|
836
|
-
*/
|
|
837
|
-
async function downPostgres(db) {
|
|
838
|
-
await sql`ALTER TABLE _emdash_fields DROP COLUMN translatable`.execute(db);
|
|
839
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
840
|
-
for (const t of tableNames) {
|
|
841
|
-
validateTableName(t);
|
|
842
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${t}_locale`)}`.execute(db);
|
|
843
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${t}_translation_group`)}`.execute(db);
|
|
844
|
-
await sql`ALTER TABLE ${sql.ref(t)} DROP CONSTRAINT IF EXISTS ${sql.ref(`${t}_slug_locale_unique`)}`.execute(db);
|
|
845
|
-
await sql`ALTER TABLE ${sql.ref(t)} ADD CONSTRAINT ${sql.ref(`${t}_slug_unique`)} UNIQUE (slug)`.execute(db);
|
|
846
|
-
await sql`ALTER TABLE ${sql.ref(t)} DROP COLUMN locale`.execute(db);
|
|
847
|
-
await sql`ALTER TABLE ${sql.ref(t)} DROP COLUMN translation_group`.execute(db);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
async function down$55(db) {
|
|
851
|
-
if (!isSqlite(db)) return downPostgres(db);
|
|
852
|
-
await sql`
|
|
853
|
-
ALTER TABLE _emdash_fields
|
|
854
|
-
DROP COLUMN translatable
|
|
855
|
-
`.execute(db);
|
|
856
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
857
|
-
for (const tableName of tableNames) {
|
|
858
|
-
const t = tableName;
|
|
859
|
-
validateTableName(t);
|
|
860
|
-
const tmp = `${t}_i18n_tmp`;
|
|
861
|
-
{
|
|
862
|
-
const trx = db;
|
|
863
|
-
const columns = (await sql`
|
|
864
|
-
PRAGMA table_info(${sql.ref(t)})
|
|
865
|
-
`.execute(trx)).rows;
|
|
866
|
-
const idxResult = await sql`
|
|
867
|
-
PRAGMA index_list(${sql.ref(t)})
|
|
868
|
-
`.execute(trx);
|
|
869
|
-
const indexDefs = [];
|
|
870
|
-
for (const idx of idxResult.rows) {
|
|
871
|
-
if (idx.origin === "pk" || idx.name.startsWith("sqlite_autoindex_")) continue;
|
|
872
|
-
const idxColResult = await sql`
|
|
873
|
-
PRAGMA index_info(${sql.ref(idx.name)})
|
|
874
|
-
`.execute(trx);
|
|
875
|
-
indexDefs.push({
|
|
876
|
-
name: idx.name,
|
|
877
|
-
unique: idx.unique === 1,
|
|
878
|
-
columns: idxColResult.rows.map((c) => c.name),
|
|
879
|
-
partial: idx.partial
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
const partialSqls = /* @__PURE__ */ new Map();
|
|
883
|
-
for (const idx of indexDefs) if (idx.partial) {
|
|
884
|
-
const createResult = await sql`
|
|
885
|
-
SELECT sql FROM sqlite_master
|
|
886
|
-
WHERE type = 'index' AND name = ${idx.name}
|
|
887
|
-
`.execute(trx);
|
|
888
|
-
if (createResult.rows[0]?.sql) partialSqls.set(idx.name, createResult.rows[0].sql);
|
|
889
|
-
}
|
|
890
|
-
for (const col of columns) {
|
|
891
|
-
if (col.name === "locale" || col.name === "translation_group") continue;
|
|
892
|
-
validateIdentifier(col.name, "column name");
|
|
893
|
-
}
|
|
894
|
-
const colDefs = [];
|
|
895
|
-
const colNames = [];
|
|
896
|
-
for (const col of columns) {
|
|
897
|
-
if (col.name === "locale" || col.name === "translation_group") continue;
|
|
898
|
-
validateColumnType(col.type || "TEXT", col.name);
|
|
899
|
-
colNames.push(quoteIdent(col.name));
|
|
900
|
-
let def = `${quoteIdent(col.name)} ${col.type || "TEXT"}`;
|
|
901
|
-
if (col.pk) def += " PRIMARY KEY";
|
|
902
|
-
else if (col.name === "slug") def += " UNIQUE";
|
|
903
|
-
else if (col.notnull) def += " NOT NULL";
|
|
904
|
-
if (col.dflt_value !== null) {
|
|
905
|
-
validateDefaultValue(col.dflt_value, col.name);
|
|
906
|
-
def += ` DEFAULT ${normalizeDdlDefault(col.dflt_value)}`;
|
|
907
|
-
}
|
|
908
|
-
colDefs.push(def);
|
|
909
|
-
}
|
|
910
|
-
const createColsSql = colDefs.join(",\n ");
|
|
911
|
-
const selectColsSql = colNames.join(", ");
|
|
912
|
-
for (const idx of indexDefs) await sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);
|
|
913
|
-
await sql.raw(`CREATE TABLE ${quoteIdent(tmp)} (\n\t\t\t\t${createColsSql}\n\t\t\t)`).execute(trx);
|
|
914
|
-
await sql.raw(`INSERT OR IGNORE INTO ${quoteIdent(tmp)} (${selectColsSql})
|
|
915
|
-
SELECT ${selectColsSql} FROM ${quoteIdent(t)}
|
|
916
|
-
WHERE "locale" = 'en'`).execute(trx);
|
|
917
|
-
await sql.raw(`INSERT OR IGNORE INTO ${quoteIdent(tmp)} (${selectColsSql})
|
|
918
|
-
SELECT ${selectColsSql} FROM ${quoteIdent(t)}
|
|
919
|
-
WHERE "id" NOT IN (SELECT "id" FROM ${quoteIdent(tmp)})
|
|
920
|
-
AND "id" IN (
|
|
921
|
-
SELECT "id" FROM ${quoteIdent(t)} AS t2
|
|
922
|
-
WHERE t2."translation_group" IS NOT NULL
|
|
923
|
-
AND t2."locale" = (
|
|
924
|
-
SELECT MIN(t3."locale") FROM ${quoteIdent(t)} AS t3
|
|
925
|
-
WHERE t3."translation_group" = t2."translation_group"
|
|
926
|
-
)
|
|
927
|
-
)`).execute(trx);
|
|
928
|
-
await sql.raw(`INSERT OR IGNORE INTO ${quoteIdent(tmp)} (${selectColsSql})
|
|
929
|
-
SELECT ${selectColsSql} FROM ${quoteIdent(t)}
|
|
930
|
-
WHERE "id" NOT IN (SELECT "id" FROM ${quoteIdent(tmp)})
|
|
931
|
-
AND "translation_group" IS NULL`).execute(trx);
|
|
932
|
-
await sql`DROP TABLE ${sql.ref(t)}`.execute(trx);
|
|
933
|
-
await sql.raw(`ALTER TABLE ${quoteIdent(tmp)} RENAME TO ${quoteIdent(t)}`).execute(trx);
|
|
934
|
-
for (const idx of indexDefs) {
|
|
935
|
-
if (idx.name === `idx_${t}_locale`) continue;
|
|
936
|
-
if (idx.name === `idx_${t}_translation_group`) continue;
|
|
937
|
-
if (idx.partial && partialSqls.has(idx.name)) {
|
|
938
|
-
const idxSql = partialSqls.get(idx.name);
|
|
939
|
-
validateCreateIndexSql(idxSql, idx.name);
|
|
940
|
-
await sql.raw(idxSql).execute(trx);
|
|
941
|
-
} else {
|
|
942
|
-
const cols = idx.columns.filter((c) => c !== "locale" && c !== "translation_group");
|
|
943
|
-
if (cols.length === 0) continue;
|
|
944
|
-
for (const c of cols) validateIdentifier(c, "index column name");
|
|
945
|
-
const colsSql = cols.map((c) => quoteIdent(c)).join(", ");
|
|
946
|
-
const unique = idx.unique ? "UNIQUE " : "";
|
|
947
|
-
await sql.raw(`CREATE ${unique}INDEX ${quoteIdent(idx.name)} ON ${quoteIdent(t)} (${colsSql})`).execute(trx);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
//#endregion
|
|
955
|
-
//#region src/database/migrations/020_collection_url_pattern.ts
|
|
956
|
-
var _020_collection_url_pattern_exports = /* @__PURE__ */ __exportAll({
|
|
957
|
-
down: () => down$54,
|
|
958
|
-
up: () => up$54
|
|
959
|
-
});
|
|
960
|
-
/**
|
|
961
|
-
* Migration: URL pattern for collections
|
|
962
|
-
*
|
|
963
|
-
* Adds `url_pattern` column to `_emdash_collections` so each collection
|
|
964
|
-
* can declare its own URL structure (e.g. "/{slug}" for pages, "/blog/{slug}"
|
|
965
|
-
* for posts). Used for menu URL resolution, sitemaps, and path-based lookups.
|
|
966
|
-
*/
|
|
967
|
-
async function up$54(db) {
|
|
968
|
-
await sql`
|
|
969
|
-
ALTER TABLE _emdash_collections
|
|
970
|
-
ADD COLUMN url_pattern TEXT
|
|
971
|
-
`.execute(db);
|
|
972
|
-
}
|
|
973
|
-
async function down$54(db) {
|
|
974
|
-
await sql`
|
|
975
|
-
ALTER TABLE _emdash_collections
|
|
976
|
-
DROP COLUMN url_pattern
|
|
977
|
-
`.execute(db);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
//#endregion
|
|
981
|
-
//#region src/database/migrations/021_remove_section_categories.ts
|
|
982
|
-
var _021_remove_section_categories_exports = /* @__PURE__ */ __exportAll({
|
|
983
|
-
down: () => down$53,
|
|
984
|
-
up: () => up$53
|
|
985
|
-
});
|
|
986
|
-
/**
|
|
987
|
-
* Migration: Remove section categories
|
|
988
|
-
*
|
|
989
|
-
* Section categories had a complete backend but no UI to create or manage them.
|
|
990
|
-
* Rather than building the missing UI for a feature with very little need at this stage,
|
|
991
|
-
* we're removing the feature entirely.
|
|
992
|
-
*/
|
|
993
|
-
async function up$53(db) {
|
|
994
|
-
await db.schema.dropIndex("idx_sections_category").ifExists().execute();
|
|
995
|
-
await db.schema.alterTable("_emdash_sections").dropColumn("category_id").execute();
|
|
996
|
-
await db.schema.dropTable("_emdash_section_categories").execute();
|
|
997
|
-
}
|
|
998
|
-
async function down$53(db) {
|
|
999
|
-
await db.schema.createTable("_emdash_section_categories").addColumn("id", "text", (col) => col.primaryKey()).addColumn("slug", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("sort_order", "integer", (col) => col.defaultTo(0)).addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`)).execute();
|
|
1000
|
-
await db.schema.alterTable("_emdash_sections").addColumn("category_id", "text", (col) => col.references("_emdash_section_categories.id").onDelete("set null")).execute();
|
|
1001
|
-
await db.schema.createIndex("idx_sections_category").on("_emdash_sections").columns(["category_id"]).execute();
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
//#endregion
|
|
1005
|
-
//#region src/database/migrations/022_marketplace_plugin_state.ts
|
|
1006
|
-
var _022_marketplace_plugin_state_exports = /* @__PURE__ */ __exportAll({
|
|
1007
|
-
down: () => down$52,
|
|
1008
|
-
up: () => up$52
|
|
1009
|
-
});
|
|
1010
|
-
/**
|
|
1011
|
-
* Migration: Add marketplace fields to _plugin_state
|
|
1012
|
-
*
|
|
1013
|
-
* Adds `source` and `marketplace_version` columns to track
|
|
1014
|
-
* whether a plugin was installed from config or marketplace,
|
|
1015
|
-
* and which marketplace version is installed.
|
|
1016
|
-
*/
|
|
1017
|
-
async function up$52(db) {
|
|
1018
|
-
await sql`
|
|
1019
|
-
ALTER TABLE _plugin_state
|
|
1020
|
-
ADD COLUMN source TEXT NOT NULL DEFAULT 'config'
|
|
1021
|
-
`.execute(db);
|
|
1022
|
-
await sql`
|
|
1023
|
-
ALTER TABLE _plugin_state
|
|
1024
|
-
ADD COLUMN marketplace_version TEXT
|
|
1025
|
-
`.execute(db);
|
|
1026
|
-
await sql`
|
|
1027
|
-
CREATE INDEX idx_plugin_state_source
|
|
1028
|
-
ON _plugin_state (source)
|
|
1029
|
-
WHERE source = 'marketplace'
|
|
1030
|
-
`.execute(db);
|
|
1031
|
-
}
|
|
1032
|
-
async function down$52(db) {
|
|
1033
|
-
await sql`
|
|
1034
|
-
DROP INDEX IF EXISTS idx_plugin_state_source
|
|
1035
|
-
`.execute(db);
|
|
1036
|
-
await sql`
|
|
1037
|
-
ALTER TABLE _plugin_state
|
|
1038
|
-
DROP COLUMN marketplace_version
|
|
1039
|
-
`.execute(db);
|
|
1040
|
-
await sql`
|
|
1041
|
-
ALTER TABLE _plugin_state
|
|
1042
|
-
DROP COLUMN source
|
|
1043
|
-
`.execute(db);
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
//#endregion
|
|
1047
|
-
//#region src/database/migrations/023_plugin_metadata.ts
|
|
1048
|
-
var _023_plugin_metadata_exports = /* @__PURE__ */ __exportAll({
|
|
1049
|
-
down: () => down$51,
|
|
1050
|
-
up: () => up$51
|
|
1051
|
-
});
|
|
1052
|
-
/**
|
|
1053
|
-
* Migration: Add display metadata to _plugin_state
|
|
1054
|
-
*
|
|
1055
|
-
* Stores display_name and description for marketplace plugins
|
|
1056
|
-
* so the admin UI can show meaningful info without re-fetching
|
|
1057
|
-
* from the marketplace on every page load.
|
|
1058
|
-
*/
|
|
1059
|
-
async function up$51(db) {
|
|
1060
|
-
await sql`
|
|
1061
|
-
ALTER TABLE _plugin_state
|
|
1062
|
-
ADD COLUMN display_name TEXT
|
|
1063
|
-
`.execute(db);
|
|
1064
|
-
await sql`
|
|
1065
|
-
ALTER TABLE _plugin_state
|
|
1066
|
-
ADD COLUMN description TEXT
|
|
1067
|
-
`.execute(db);
|
|
1068
|
-
}
|
|
1069
|
-
async function down$51(db) {
|
|
1070
|
-
await sql`
|
|
1071
|
-
ALTER TABLE _plugin_state
|
|
1072
|
-
DROP COLUMN description
|
|
1073
|
-
`.execute(db);
|
|
1074
|
-
await sql`
|
|
1075
|
-
ALTER TABLE _plugin_state
|
|
1076
|
-
DROP COLUMN display_name
|
|
1077
|
-
`.execute(db);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
//#endregion
|
|
1081
|
-
//#region src/database/migrations/024_media_placeholders.ts
|
|
1082
|
-
var _024_media_placeholders_exports = /* @__PURE__ */ __exportAll({
|
|
1083
|
-
down: () => down$50,
|
|
1084
|
-
up: () => up$50
|
|
1085
|
-
});
|
|
1086
|
-
/**
|
|
1087
|
-
* Migration: Add placeholder columns to media table
|
|
1088
|
-
*
|
|
1089
|
-
* Stores blurhash and dominant_color for LQIP (Low Quality Image Placeholder)
|
|
1090
|
-
* support. Generated at upload time from image pixel data.
|
|
1091
|
-
*/
|
|
1092
|
-
async function up$50(db) {
|
|
1093
|
-
await sql`
|
|
1094
|
-
ALTER TABLE media
|
|
1095
|
-
ADD COLUMN blurhash TEXT
|
|
1096
|
-
`.execute(db);
|
|
1097
|
-
await sql`
|
|
1098
|
-
ALTER TABLE media
|
|
1099
|
-
ADD COLUMN dominant_color TEXT
|
|
1100
|
-
`.execute(db);
|
|
1101
|
-
}
|
|
1102
|
-
async function down$50(db) {
|
|
1103
|
-
await sql`
|
|
1104
|
-
ALTER TABLE media
|
|
1105
|
-
DROP COLUMN dominant_color
|
|
1106
|
-
`.execute(db);
|
|
1107
|
-
await sql`
|
|
1108
|
-
ALTER TABLE media
|
|
1109
|
-
DROP COLUMN blurhash
|
|
1110
|
-
`.execute(db);
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
//#endregion
|
|
1114
|
-
//#region src/database/migrations/025_oauth_clients.ts
|
|
1115
|
-
var _025_oauth_clients_exports = /* @__PURE__ */ __exportAll({
|
|
1116
|
-
down: () => down$49,
|
|
1117
|
-
up: () => up$49
|
|
1118
|
-
});
|
|
1119
|
-
/**
|
|
1120
|
-
* Migration: Create OAuth clients table
|
|
1121
|
-
*
|
|
1122
|
-
* Implements the oauth_clients registry so that the authorization endpoint
|
|
1123
|
-
* can validate client_id and enforce a per-client redirect URI allowlist.
|
|
1124
|
-
*
|
|
1125
|
-
* Each client has a set of pre-registered redirect URIs (JSON array).
|
|
1126
|
-
* The authorize endpoint rejects any redirect_uri not in the client's list.
|
|
1127
|
-
*/
|
|
1128
|
-
async function up$49(db) {
|
|
1129
|
-
await db.schema.createTable("_emdash_oauth_clients").addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull()).addColumn("redirect_uris", "text", (col) => col.notNull()).addColumn("scopes", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
1130
|
-
}
|
|
1131
|
-
async function down$49(db) {
|
|
1132
|
-
await db.schema.dropTable("_emdash_oauth_clients").execute();
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
//#endregion
|
|
1136
|
-
//#region src/database/migrations/026_cron_tasks.ts
|
|
1137
|
-
var _026_cron_tasks_exports = /* @__PURE__ */ __exportAll({
|
|
1138
|
-
down: () => down$48,
|
|
1139
|
-
up: () => up$48
|
|
1140
|
-
});
|
|
1141
|
-
/**
|
|
1142
|
-
* Migration: Create cron tasks table for plugin scheduled tasks.
|
|
1143
|
-
*
|
|
1144
|
-
* Each plugin can register cron tasks (recurring or one-shot) which are
|
|
1145
|
-
* stored here and executed by the platform-specific scheduler.
|
|
1146
|
-
*
|
|
1147
|
-
* The `next_run_at` + `status` + `enabled` index drives the "find overdue
|
|
1148
|
-
* tasks" query used by CronExecutor.tick().
|
|
1149
|
-
*/
|
|
1150
|
-
async function up$48(db) {
|
|
1151
|
-
await db.schema.createTable("_emdash_cron_tasks").addColumn("id", "text", (col) => col.primaryKey()).addColumn("plugin_id", "text", (col) => col.notNull()).addColumn("task_name", "text", (col) => col.notNull()).addColumn("schedule", "text", (col) => col.notNull()).addColumn("is_oneshot", "integer", (col) => col.notNull().defaultTo(0)).addColumn("data", "text").addColumn("next_run_at", "text", (col) => col.notNull()).addColumn("last_run_at", "text").addColumn("status", "text", (col) => col.notNull().defaultTo("idle")).addColumn("locked_at", "text").addColumn("enabled", "integer", (col) => col.notNull().defaultTo(1)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addUniqueConstraint("uq_cron_tasks_plugin_task", ["plugin_id", "task_name"]).execute();
|
|
1152
|
-
await db.schema.createIndex("idx_cron_tasks_due").on("_emdash_cron_tasks").columns([
|
|
1153
|
-
"enabled",
|
|
1154
|
-
"status",
|
|
1155
|
-
"next_run_at"
|
|
1156
|
-
]).execute();
|
|
1157
|
-
await db.schema.createIndex("idx_cron_tasks_plugin").on("_emdash_cron_tasks").column("plugin_id").execute();
|
|
1158
|
-
}
|
|
1159
|
-
async function down$48(db) {
|
|
1160
|
-
await db.schema.dropTable("_emdash_cron_tasks").execute();
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
//#endregion
|
|
1164
|
-
//#region src/database/migrations/027_comments.ts
|
|
1165
|
-
var _027_comments_exports = /* @__PURE__ */ __exportAll({
|
|
1166
|
-
down: () => down$47,
|
|
1167
|
-
up: () => up$47
|
|
1168
|
-
});
|
|
1169
|
-
async function up$47(db) {
|
|
1170
|
-
await db.schema.createTable("_emdash_comments").addColumn("id", "text", (col) => col.primaryKey()).addColumn("collection", "text", (col) => col.notNull()).addColumn("content_id", "text", (col) => col.notNull()).addColumn("parent_id", "text", (col) => col.references("_emdash_comments.id").onDelete("cascade")).addColumn("author_name", "text", (col) => col.notNull()).addColumn("author_email", "text", (col) => col.notNull()).addColumn("author_url", "text").addColumn("author_user_id", "text", (col) => col.references("users.id").onDelete("set null")).addColumn("body", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("pending")).addColumn("ip_hash", "text").addColumn("user_agent", "text").addColumn("moderation_metadata", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
1171
|
-
await db.schema.createIndex("idx_comments_content").on("_emdash_comments").columns([
|
|
1172
|
-
"collection",
|
|
1173
|
-
"content_id",
|
|
1174
|
-
"status"
|
|
1175
|
-
]).execute();
|
|
1176
|
-
await db.schema.createIndex("idx_comments_parent").on("_emdash_comments").column("parent_id").execute();
|
|
1177
|
-
await db.schema.createIndex("idx_comments_status").on("_emdash_comments").columns(["status", "created_at"]).execute();
|
|
1178
|
-
await db.schema.createIndex("idx_comments_author_email").on("_emdash_comments").column("author_email").execute();
|
|
1179
|
-
await db.schema.createIndex("idx_comments_author_user").on("_emdash_comments").column("author_user_id").execute();
|
|
1180
|
-
await db.schema.alterTable("_emdash_collections").addColumn("comments_enabled", "integer", (col) => col.defaultTo(0)).execute();
|
|
1181
|
-
await db.schema.alterTable("_emdash_collections").addColumn("comments_moderation", "text", (col) => col.defaultTo("first_time")).execute();
|
|
1182
|
-
await db.schema.alterTable("_emdash_collections").addColumn("comments_closed_after_days", "integer", (col) => col.defaultTo(90)).execute();
|
|
1183
|
-
await db.schema.alterTable("_emdash_collections").addColumn("comments_auto_approve_users", "integer", (col) => col.defaultTo(1)).execute();
|
|
1184
|
-
}
|
|
1185
|
-
async function down$47(db) {
|
|
1186
|
-
await db.schema.dropTable("_emdash_comments").execute();
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
//#endregion
|
|
1190
|
-
//#region src/database/migrations/028_drop_author_url.ts
|
|
1191
|
-
var _028_drop_author_url_exports = /* @__PURE__ */ __exportAll({
|
|
1192
|
-
down: () => down$46,
|
|
1193
|
-
up: () => up$46
|
|
1194
|
-
});
|
|
1195
|
-
async function up$46(db) {
|
|
1196
|
-
await sql`ALTER TABLE _emdash_comments DROP COLUMN author_url`.execute(db);
|
|
1197
|
-
}
|
|
1198
|
-
async function down$46(db) {
|
|
1199
|
-
await db.schema.alterTable("_emdash_comments").addColumn("author_url", "text").execute();
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
//#endregion
|
|
1203
|
-
//#region src/database/migrations/029_redirects.ts
|
|
1204
|
-
var _029_redirects_exports = /* @__PURE__ */ __exportAll({
|
|
1205
|
-
down: () => down$45,
|
|
1206
|
-
up: () => up$45
|
|
1207
|
-
});
|
|
1208
|
-
async function up$45(db) {
|
|
1209
|
-
await db.schema.createTable("_emdash_redirects").addColumn("id", "text", (col) => col.primaryKey()).addColumn("source", "text", (col) => col.notNull()).addColumn("destination", "text", (col) => col.notNull()).addColumn("type", "integer", (col) => col.notNull().defaultTo(301)).addColumn("is_pattern", "integer", (col) => col.notNull().defaultTo(0)).addColumn("enabled", "integer", (col) => col.notNull().defaultTo(1)).addColumn("hits", "integer", (col) => col.notNull().defaultTo(0)).addColumn("last_hit_at", "text").addColumn("group_name", "text").addColumn("auto", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
1210
|
-
await db.schema.createIndex("idx_redirects_source").on("_emdash_redirects").column("source").execute();
|
|
1211
|
-
await db.schema.createIndex("idx_redirects_enabled").on("_emdash_redirects").column("enabled").execute();
|
|
1212
|
-
await db.schema.createIndex("idx_redirects_group").on("_emdash_redirects").column("group_name").execute();
|
|
1213
|
-
await db.schema.createTable("_emdash_404_log").addColumn("id", "text", (col) => col.primaryKey()).addColumn("path", "text", (col) => col.notNull()).addColumn("referrer", "text").addColumn("user_agent", "text").addColumn("ip", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
1214
|
-
await db.schema.createIndex("idx_404_log_path").on("_emdash_404_log").column("path").execute();
|
|
1215
|
-
await db.schema.createIndex("idx_404_log_created").on("_emdash_404_log").column("created_at").execute();
|
|
1216
|
-
}
|
|
1217
|
-
async function down$45(db) {
|
|
1218
|
-
await db.schema.dropTable("_emdash_404_log").execute();
|
|
1219
|
-
await db.schema.dropTable("_emdash_redirects").execute();
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
//#endregion
|
|
1223
|
-
//#region src/database/migrations/030_widen_scheduled_index.ts
|
|
1224
|
-
var _030_widen_scheduled_index_exports = /* @__PURE__ */ __exportAll({
|
|
1225
|
-
down: () => down$44,
|
|
1226
|
-
up: () => up$44
|
|
1227
|
-
});
|
|
1228
|
-
/**
|
|
1229
|
-
* Migration: Widen scheduled publishing index
|
|
1230
|
-
*
|
|
1231
|
-
* The original partial index (013) only covered status='scheduled'.
|
|
1232
|
-
* Published posts can now have scheduled draft changes, so widen the
|
|
1233
|
-
* index to cover all rows where scheduled_at IS NOT NULL.
|
|
1234
|
-
*/
|
|
1235
|
-
async function up$44(db) {
|
|
1236
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1237
|
-
for (const tableName of tableNames) {
|
|
1238
|
-
const table = { name: tableName };
|
|
1239
|
-
await sql`
|
|
1240
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_scheduled`)}
|
|
1241
|
-
`.execute(db);
|
|
1242
|
-
await sql`
|
|
1243
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_scheduled`)}
|
|
1244
|
-
ON ${sql.ref(table.name)} (scheduled_at)
|
|
1245
|
-
WHERE scheduled_at IS NOT NULL
|
|
1246
|
-
`.execute(db);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
async function down$44(db) {
|
|
1250
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1251
|
-
for (const tableName of tableNames) {
|
|
1252
|
-
const table = { name: tableName };
|
|
1253
|
-
await sql`
|
|
1254
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_scheduled`)}
|
|
1255
|
-
`.execute(db);
|
|
1256
|
-
await sql`
|
|
1257
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_scheduled`)}
|
|
1258
|
-
ON ${sql.ref(table.name)} (scheduled_at)
|
|
1259
|
-
WHERE scheduled_at IS NOT NULL AND status = 'scheduled'
|
|
1260
|
-
`.execute(db);
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
//#endregion
|
|
1265
|
-
//#region src/database/migrations/031_bylines.ts
|
|
1266
|
-
var _031_bylines_exports = /* @__PURE__ */ __exportAll({
|
|
1267
|
-
down: () => down$43,
|
|
1268
|
-
up: () => up$43
|
|
1269
|
-
});
|
|
1270
|
-
async function up$43(db) {
|
|
1271
|
-
await db.schema.createTable("_emdash_bylines").addColumn("id", "text", (col) => col.primaryKey()).addColumn("slug", "text", (col) => col.notNull().unique()).addColumn("display_name", "text", (col) => col.notNull()).addColumn("bio", "text").addColumn("avatar_media_id", "text", (col) => col.references("media.id").onDelete("set null")).addColumn("website_url", "text").addColumn("user_id", "text", (col) => col.references("users.id").onDelete("set null")).addColumn("is_guest", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
1272
|
-
await sql`
|
|
1273
|
-
CREATE UNIQUE INDEX ${sql.ref("idx_bylines_user_id_unique")}
|
|
1274
|
-
ON ${sql.ref("_emdash_bylines")} (user_id)
|
|
1275
|
-
WHERE user_id IS NOT NULL
|
|
1276
|
-
`.execute(db);
|
|
1277
|
-
await db.schema.createIndex("idx_bylines_slug").on("_emdash_bylines").column("slug").execute();
|
|
1278
|
-
await db.schema.createIndex("idx_bylines_display_name").on("_emdash_bylines").column("display_name").execute();
|
|
1279
|
-
await db.schema.createTable("_emdash_content_bylines").addColumn("id", "text", (col) => col.primaryKey()).addColumn("collection_slug", "text", (col) => col.notNull()).addColumn("content_id", "text", (col) => col.notNull()).addColumn("byline_id", "text", (col) => col.notNull().references("_emdash_bylines.id").onDelete("cascade")).addColumn("sort_order", "integer", (col) => col.notNull().defaultTo(0)).addColumn("role_label", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addUniqueConstraint("content_bylines_unique", [
|
|
1280
|
-
"collection_slug",
|
|
1281
|
-
"content_id",
|
|
1282
|
-
"byline_id"
|
|
1283
|
-
]).execute();
|
|
1284
|
-
await db.schema.createIndex("idx_content_bylines_content").on("_emdash_content_bylines").columns([
|
|
1285
|
-
"collection_slug",
|
|
1286
|
-
"content_id",
|
|
1287
|
-
"sort_order"
|
|
1288
|
-
]).execute();
|
|
1289
|
-
await db.schema.createIndex("idx_content_bylines_byline").on("_emdash_content_bylines").column("byline_id").execute();
|
|
1290
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1291
|
-
for (const tableName of tableNames) {
|
|
1292
|
-
await sql`
|
|
1293
|
-
ALTER TABLE ${sql.ref(tableName)}
|
|
1294
|
-
ADD COLUMN primary_byline_id TEXT
|
|
1295
|
-
`.execute(db);
|
|
1296
|
-
await sql`
|
|
1297
|
-
CREATE INDEX ${sql.ref(`idx_${tableName}_primary_byline`)}
|
|
1298
|
-
ON ${sql.ref(tableName)} (primary_byline_id)
|
|
1299
|
-
`.execute(db);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
async function down$43(db) {
|
|
1303
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1304
|
-
for (const tableName of tableNames) {
|
|
1305
|
-
await sql`
|
|
1306
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_primary_byline`)}
|
|
1307
|
-
`.execute(db);
|
|
1308
|
-
await sql`
|
|
1309
|
-
ALTER TABLE ${sql.ref(tableName)}
|
|
1310
|
-
DROP COLUMN primary_byline_id
|
|
1311
|
-
`.execute(db);
|
|
1312
|
-
}
|
|
1313
|
-
await db.schema.dropTable("_emdash_content_bylines").execute();
|
|
1314
|
-
await db.schema.dropTable("_emdash_bylines").execute();
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
//#endregion
|
|
1318
|
-
//#region src/database/migrations/032_rate_limits.ts
|
|
1319
|
-
var _032_rate_limits_exports = /* @__PURE__ */ __exportAll({
|
|
1320
|
-
down: () => down$42,
|
|
1321
|
-
up: () => up$42
|
|
1322
|
-
});
|
|
1323
|
-
/**
|
|
1324
|
-
* Migration: Rate limits table + device code polling tracking.
|
|
1325
|
-
*
|
|
1326
|
-
* 1. Create _emdash_rate_limits for database-backed rate limiting
|
|
1327
|
-
* of unauthenticated endpoints (device code, magic link, passkey).
|
|
1328
|
-
*
|
|
1329
|
-
* 2. Add last_polled_at column to _emdash_device_codes for
|
|
1330
|
-
* RFC 8628 slow_down enforcement.
|
|
1331
|
-
*/
|
|
1332
|
-
async function up$42(db) {
|
|
1333
|
-
await db.schema.createTable("_emdash_rate_limits").addColumn("key", "text", (col) => col.notNull()).addColumn("window", "text", (col) => col.notNull()).addColumn("count", "integer", (col) => col.notNull().defaultTo(1)).addPrimaryKeyConstraint("pk_rate_limits", ["key", "window"]).execute();
|
|
1334
|
-
await db.schema.createIndex("idx_rate_limits_window").on("_emdash_rate_limits").column("window").execute();
|
|
1335
|
-
await db.schema.alterTable("_emdash_device_codes").addColumn("last_polled_at", "text").execute();
|
|
1336
|
-
}
|
|
1337
|
-
async function down$42(db) {
|
|
1338
|
-
await db.schema.dropTable("_emdash_rate_limits").execute();
|
|
1339
|
-
await db.schema.alterTable("_emdash_device_codes").dropColumn("last_polled_at").execute();
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
//#endregion
|
|
1343
|
-
//#region src/database/migrations/033_optimize_content_indexes.ts
|
|
1344
|
-
var _033_optimize_content_indexes_exports = /* @__PURE__ */ __exportAll({
|
|
1345
|
-
down: () => down$41,
|
|
1346
|
-
up: () => up$41
|
|
1347
|
-
});
|
|
1348
|
-
/**
|
|
1349
|
-
* Migration: Optimize content table indexes for D1 performance
|
|
1350
|
-
*
|
|
1351
|
-
* Addresses GitHub issue #131: Full table scans causing massive D1 row reads.
|
|
1352
|
-
*
|
|
1353
|
-
* Changes:
|
|
1354
|
-
* 1. Replaces single-column indexes with composite indexes on ec_* tables
|
|
1355
|
-
* 2. Adds partial indexes for _emdash_comments status counting
|
|
1356
|
-
*
|
|
1357
|
-
* Impact: Reduces D1 row reads by 90%+ for admin panel operations.
|
|
1358
|
-
*/
|
|
1359
|
-
async function up$41(db) {
|
|
1360
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1361
|
-
for (const tableName of tableNames) {
|
|
1362
|
-
const table = { name: tableName };
|
|
1363
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_status`)}`.execute(db);
|
|
1364
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_created`)}`.execute(db);
|
|
1365
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_deleted`)}`.execute(db);
|
|
1366
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_updated`)}`.execute(db);
|
|
1367
|
-
await sql`
|
|
1368
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${table.name}_deleted_updated_id`)}
|
|
1369
|
-
ON ${sql.ref(table.name)} (deleted_at, updated_at DESC, id DESC)
|
|
1370
|
-
`.execute(db);
|
|
1371
|
-
await sql`
|
|
1372
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${table.name}_deleted_status`)}
|
|
1373
|
-
ON ${sql.ref(table.name)} (deleted_at, status)
|
|
1374
|
-
`.execute(db);
|
|
1375
|
-
await sql`
|
|
1376
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${table.name}_deleted_created_id`)}
|
|
1377
|
-
ON ${sql.ref(table.name)} (deleted_at, created_at DESC, id DESC)
|
|
1378
|
-
`.execute(db);
|
|
1379
|
-
}
|
|
1380
|
-
await sql`
|
|
1381
|
-
CREATE INDEX IF NOT EXISTS idx_comments_pending
|
|
1382
|
-
ON _emdash_comments (id)
|
|
1383
|
-
WHERE status = 'pending'
|
|
1384
|
-
`.execute(db);
|
|
1385
|
-
await sql`
|
|
1386
|
-
CREATE INDEX IF NOT EXISTS idx_comments_approved
|
|
1387
|
-
ON _emdash_comments (id)
|
|
1388
|
-
WHERE status = 'approved'
|
|
1389
|
-
`.execute(db);
|
|
1390
|
-
await sql`
|
|
1391
|
-
CREATE INDEX IF NOT EXISTS idx_comments_spam
|
|
1392
|
-
ON _emdash_comments (id)
|
|
1393
|
-
WHERE status = 'spam'
|
|
1394
|
-
`.execute(db);
|
|
1395
|
-
await sql`
|
|
1396
|
-
CREATE INDEX IF NOT EXISTS idx_comments_trash
|
|
1397
|
-
ON _emdash_comments (id)
|
|
1398
|
-
WHERE status = 'trash'
|
|
1399
|
-
`.execute(db);
|
|
1400
|
-
}
|
|
1401
|
-
async function down$41(db) {
|
|
1402
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1403
|
-
for (const tableName of tableNames) {
|
|
1404
|
-
const table = { name: tableName };
|
|
1405
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_deleted_updated_id`)}`.execute(db);
|
|
1406
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_deleted_status`)}`.execute(db);
|
|
1407
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_deleted_created_id`)}`.execute(db);
|
|
1408
|
-
await sql`
|
|
1409
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_status`)}
|
|
1410
|
-
ON ${sql.ref(table.name)} (status)
|
|
1411
|
-
`.execute(db);
|
|
1412
|
-
await sql`
|
|
1413
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_created`)}
|
|
1414
|
-
ON ${sql.ref(table.name)} (created_at)
|
|
1415
|
-
`.execute(db);
|
|
1416
|
-
await sql`
|
|
1417
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_deleted`)}
|
|
1418
|
-
ON ${sql.ref(table.name)} (deleted_at)
|
|
1419
|
-
`.execute(db);
|
|
1420
|
-
await sql`
|
|
1421
|
-
CREATE INDEX ${sql.ref(`idx_${table.name}_updated`)}
|
|
1422
|
-
ON ${sql.ref(table.name)} (updated_at)
|
|
1423
|
-
`.execute(db);
|
|
1424
|
-
}
|
|
1425
|
-
await sql`DROP INDEX IF EXISTS idx_comments_pending`.execute(db);
|
|
1426
|
-
await sql`DROP INDEX IF EXISTS idx_comments_approved`.execute(db);
|
|
1427
|
-
await sql`DROP INDEX IF EXISTS idx_comments_spam`.execute(db);
|
|
1428
|
-
await sql`DROP INDEX IF EXISTS idx_comments_trash`.execute(db);
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
//#endregion
|
|
1432
|
-
//#region src/database/migrations/034_published_at_index.ts
|
|
1433
|
-
var _034_published_at_index_exports = /* @__PURE__ */ __exportAll({
|
|
1434
|
-
down: () => down$40,
|
|
1435
|
-
up: () => up$40
|
|
1436
|
-
});
|
|
1437
|
-
async function up$40(db) {
|
|
1438
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1439
|
-
for (const tableName of tableNames) {
|
|
1440
|
-
const table = { name: tableName };
|
|
1441
|
-
await sql`
|
|
1442
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${table.name}_deleted_published_id`)}
|
|
1443
|
-
ON ${sql.ref(table.name)} (deleted_at, published_at DESC, id DESC)
|
|
1444
|
-
`.execute(db);
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
async function down$40(db) {
|
|
1448
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
1449
|
-
for (const tableName of tableNames) {
|
|
1450
|
-
const table = { name: tableName };
|
|
1451
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_deleted_published_id`)}`.execute(db);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
//#endregion
|
|
1456
|
-
//#region src/database/migrations/035_bounded_404_log.ts
|
|
1457
|
-
var _035_bounded_404_log_exports = /* @__PURE__ */ __exportAll({
|
|
1458
|
-
down: () => down$39,
|
|
1459
|
-
up: () => up$39
|
|
1460
|
-
});
|
|
1461
|
-
/**
|
|
1462
|
-
* Migration: Bounded 404 logging
|
|
1463
|
-
*
|
|
1464
|
-
* Hardens `_emdash_404_log` against unauthenticated DoS. Previously every 404
|
|
1465
|
-
* inserted a new row, so an attacker could grow the table without bound.
|
|
1466
|
-
*
|
|
1467
|
-
* Changes:
|
|
1468
|
-
* - Adds `hits` (default 1, NOT NULL)
|
|
1469
|
-
* - Adds `last_seen_at` (nullable; SQLite can't add NOT NULL with a
|
|
1470
|
-
* non-constant default to a populated table, so the column is nullable
|
|
1471
|
-
* at the schema level and backfilled from `created_at` for existing rows;
|
|
1472
|
-
* new inserts via `log404` always set it)
|
|
1473
|
-
* - Deduplicates existing rows by path, keeping the most recent row per
|
|
1474
|
-
* path and summing hits
|
|
1475
|
-
* - Adds a UNIQUE index on `path` so upsert semantics work
|
|
1476
|
-
*/
|
|
1477
|
-
async function up$39(db) {
|
|
1478
|
-
const hitsExists = await columnExists(db, "_emdash_404_log", "hits");
|
|
1479
|
-
if (!hitsExists) await db.schema.alterTable("_emdash_404_log").addColumn("hits", "integer", (col) => col.notNull().defaultTo(1)).execute();
|
|
1480
|
-
if (!await columnExists(db, "_emdash_404_log", "last_seen_at")) await db.schema.alterTable("_emdash_404_log").addColumn("last_seen_at", "text").execute();
|
|
1481
|
-
await sql`
|
|
1482
|
-
UPDATE _emdash_404_log
|
|
1483
|
-
SET last_seen_at = created_at
|
|
1484
|
-
WHERE last_seen_at IS NULL
|
|
1485
|
-
`.execute(db);
|
|
1486
|
-
if (!hitsExists) {
|
|
1487
|
-
await sql`
|
|
1488
|
-
WITH ranked AS (
|
|
1489
|
-
SELECT
|
|
1490
|
-
id,
|
|
1491
|
-
path,
|
|
1492
|
-
ROW_NUMBER() OVER (
|
|
1493
|
-
PARTITION BY path
|
|
1494
|
-
ORDER BY created_at DESC, id DESC
|
|
1495
|
-
) AS rn,
|
|
1496
|
-
COUNT(*) OVER (PARTITION BY path) AS path_count,
|
|
1497
|
-
MAX(created_at) OVER (PARTITION BY path) AS latest_created_at
|
|
1498
|
-
FROM _emdash_404_log
|
|
1499
|
-
)
|
|
1500
|
-
UPDATE _emdash_404_log
|
|
1501
|
-
SET
|
|
1502
|
-
hits = (SELECT path_count FROM ranked WHERE ranked.id = _emdash_404_log.id),
|
|
1503
|
-
last_seen_at = (SELECT latest_created_at FROM ranked WHERE ranked.id = _emdash_404_log.id)
|
|
1504
|
-
WHERE id IN (SELECT id FROM ranked WHERE rn = 1)
|
|
1505
|
-
`.execute(db);
|
|
1506
|
-
await sql`
|
|
1507
|
-
DELETE FROM _emdash_404_log
|
|
1508
|
-
WHERE id IN (
|
|
1509
|
-
SELECT id FROM (
|
|
1510
|
-
SELECT
|
|
1511
|
-
id,
|
|
1512
|
-
ROW_NUMBER() OVER (
|
|
1513
|
-
PARTITION BY path
|
|
1514
|
-
ORDER BY created_at DESC, id DESC
|
|
1515
|
-
) AS rn
|
|
1516
|
-
FROM _emdash_404_log
|
|
1517
|
-
) AS ranked
|
|
1518
|
-
WHERE rn > 1
|
|
1519
|
-
)
|
|
1520
|
-
`.execute(db);
|
|
1521
|
-
}
|
|
1522
|
-
await db.schema.createIndex("idx_404_log_path_unique").ifNotExists().on("_emdash_404_log").column("path").unique().execute();
|
|
1523
|
-
await db.schema.dropIndex("idx_404_log_path").ifExists().execute();
|
|
1524
|
-
await db.schema.createIndex("idx_404_log_last_seen").ifNotExists().on("_emdash_404_log").column("last_seen_at").execute();
|
|
1525
|
-
}
|
|
1526
|
-
async function down$39(db) {
|
|
1527
|
-
await db.schema.dropIndex("idx_404_log_last_seen").ifExists().execute();
|
|
1528
|
-
await db.schema.dropIndex("idx_404_log_path_unique").ifExists().execute();
|
|
1529
|
-
await db.schema.createIndex("idx_404_log_path").ifNotExists().on("_emdash_404_log").column("path").execute();
|
|
1530
|
-
await db.schema.alterTable("_emdash_404_log").dropColumn("last_seen_at").execute();
|
|
1531
|
-
await db.schema.alterTable("_emdash_404_log").dropColumn("hits").execute();
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
//#endregion
|
|
1535
|
-
//#region src/database/migrations/036_i18n_menus_and_taxonomies.ts
|
|
1536
|
-
var _036_i18n_menus_and_taxonomies_exports = /* @__PURE__ */ __exportAll({
|
|
1537
|
-
down: () => down$38,
|
|
1538
|
-
up: () => up$38
|
|
1539
|
-
});
|
|
1540
|
-
/**
|
|
1541
|
-
* i18n for menus + taxonomies. Adds `locale` + `translation_group` to system
|
|
1542
|
-
* tables and stores translation_groups (not row ids) in
|
|
1543
|
-
* `_emdash_menu_items.reference_id` and `content_taxonomies.taxonomy_id`.
|
|
1544
|
-
* Backfill locale and column DEFAULTs use the site's configured defaultLocale.
|
|
1545
|
-
*/
|
|
1546
|
-
function getDefaultLocale$1() {
|
|
1547
|
-
return getI18nConfig()?.defaultLocale ?? "en";
|
|
1548
|
-
}
|
|
1549
|
-
async function up$38(db) {
|
|
1550
|
-
const defaultLocale = getDefaultLocale$1();
|
|
1551
|
-
if (isSqlite(db)) {
|
|
1552
|
-
await rebuildContentTaxonomies(db);
|
|
1553
|
-
await rebuildMenuItems(db, defaultLocale);
|
|
1554
|
-
await rebuildMenus(db, defaultLocale);
|
|
1555
|
-
await rebuildTaxonomies(db, defaultLocale);
|
|
1556
|
-
await rebuildTaxonomyDefs(db, defaultLocale);
|
|
1557
|
-
await remapMenuItemRefs(db);
|
|
1558
|
-
return;
|
|
1559
|
-
}
|
|
1560
|
-
await pgWiden(db, "_emdash_menus", ["name"], ["name", "locale"], defaultLocale);
|
|
1561
|
-
await pgWiden(db, "_emdash_menu_items", null, null, defaultLocale);
|
|
1562
|
-
await pgWiden(db, "taxonomies", ["name", "slug"], [
|
|
1563
|
-
"name",
|
|
1564
|
-
"slug",
|
|
1565
|
-
"locale"
|
|
1566
|
-
], defaultLocale);
|
|
1567
|
-
await pgWiden(db, "_emdash_taxonomy_defs", ["name"], ["name", "locale"], defaultLocale);
|
|
1568
|
-
await pgRemapContentTaxonomies(db);
|
|
1569
|
-
await remapMenuItemRefs(db);
|
|
1570
|
-
}
|
|
1571
|
-
async function rebuildMenus(db, defaultLocale) {
|
|
1572
|
-
if (await hasColumn$1(db, "_emdash_menus", "locale")) return;
|
|
1573
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_menus_new"`).execute(db);
|
|
1574
|
-
await db.schema.createTable("_emdash_menus_new").addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull()).addColumn("label", "text", (c) => c.notNull()).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)).addColumn("translation_group", "text").addUniqueConstraint("_emdash_menus_name_locale_unique", ["name", "locale"]).execute();
|
|
1575
|
-
await sql`
|
|
1576
|
-
INSERT INTO _emdash_menus_new (id, name, label, created_at, updated_at, locale, translation_group)
|
|
1577
|
-
SELECT id, name, label, created_at, updated_at, ${defaultLocale}, id FROM _emdash_menus
|
|
1578
|
-
`.execute(db);
|
|
1579
|
-
await db.schema.dropTable("_emdash_menus").execute();
|
|
1580
|
-
await sql`ALTER TABLE _emdash_menus_new RENAME TO _emdash_menus`.execute(db);
|
|
1581
|
-
await db.schema.createIndex("idx__emdash_menus_locale").on("_emdash_menus").column("locale").execute();
|
|
1582
|
-
await db.schema.createIndex("idx__emdash_menus_translation_group").on("_emdash_menus").column("translation_group").execute();
|
|
1583
|
-
}
|
|
1584
|
-
async function rebuildMenuItems(db, defaultLocale) {
|
|
1585
|
-
if (await hasColumn$1(db, "_emdash_menu_items", "locale")) return;
|
|
1586
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_menu_items_new"`).execute(db);
|
|
1587
|
-
await db.schema.createTable("_emdash_menu_items_new").addColumn("id", "text", (c) => c.primaryKey()).addColumn("menu_id", "text", (c) => c.notNull()).addColumn("parent_id", "text").addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0)).addColumn("type", "text", (c) => c.notNull()).addColumn("reference_collection", "text").addColumn("reference_id", "text").addColumn("custom_url", "text").addColumn("label", "text", (c) => c.notNull()).addColumn("title_attr", "text").addColumn("target", "text").addColumn("css_classes", "text").addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)).addColumn("translation_group", "text").execute();
|
|
1588
|
-
await sql`
|
|
1589
|
-
INSERT INTO _emdash_menu_items_new (
|
|
1590
|
-
id, menu_id, parent_id, sort_order, type, reference_collection,
|
|
1591
|
-
reference_id, custom_url, label, title_attr, target, css_classes,
|
|
1592
|
-
created_at, locale, translation_group
|
|
1593
|
-
)
|
|
1594
|
-
SELECT
|
|
1595
|
-
id, menu_id, parent_id, sort_order, type, reference_collection,
|
|
1596
|
-
reference_id, custom_url, label, title_attr, target, css_classes,
|
|
1597
|
-
created_at, ${defaultLocale}, id
|
|
1598
|
-
FROM _emdash_menu_items
|
|
1599
|
-
`.execute(db);
|
|
1600
|
-
await db.schema.dropTable("_emdash_menu_items").execute();
|
|
1601
|
-
await sql`ALTER TABLE _emdash_menu_items_new RENAME TO _emdash_menu_items`.execute(db);
|
|
1602
|
-
await db.schema.createIndex("idx_menu_items_menu").on("_emdash_menu_items").columns(["menu_id", "sort_order"]).execute();
|
|
1603
|
-
await db.schema.createIndex("idx_menu_items_parent").on("_emdash_menu_items").column("parent_id").execute();
|
|
1604
|
-
await db.schema.createIndex("idx__emdash_menu_items_locale").on("_emdash_menu_items").column("locale").execute();
|
|
1605
|
-
await db.schema.createIndex("idx__emdash_menu_items_translation_group").on("_emdash_menu_items").column("translation_group").execute();
|
|
1606
|
-
}
|
|
1607
|
-
async function rebuildTaxonomies(db, defaultLocale) {
|
|
1608
|
-
if (await hasColumn$1(db, "taxonomies", "locale")) return;
|
|
1609
|
-
await sql.raw(`DROP TABLE IF EXISTS "taxonomies_new"`).execute(db);
|
|
1610
|
-
await sql`DROP INDEX IF EXISTS idx_taxonomies_name`.execute(db);
|
|
1611
|
-
await db.schema.createTable("taxonomies_new").addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull()).addColumn("slug", "text", (c) => c.notNull()).addColumn("label", "text", (c) => c.notNull()).addColumn("parent_id", "text").addColumn("data", "text").addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)).addColumn("translation_group", "text").addUniqueConstraint("taxonomies_name_slug_locale_unique", [
|
|
1612
|
-
"name",
|
|
1613
|
-
"slug",
|
|
1614
|
-
"locale"
|
|
1615
|
-
]).addForeignKeyConstraint("taxonomies_parent_fk", ["parent_id"], "taxonomies_new", ["id"], (cb) => cb.onDelete("set null")).execute();
|
|
1616
|
-
await sql`
|
|
1617
|
-
INSERT INTO taxonomies_new (id, name, slug, label, parent_id, data, locale, translation_group)
|
|
1618
|
-
SELECT id, name, slug, label, parent_id, data, ${defaultLocale}, id FROM taxonomies
|
|
1619
|
-
`.execute(db);
|
|
1620
|
-
await db.schema.dropTable("taxonomies").execute();
|
|
1621
|
-
await sql`ALTER TABLE taxonomies_new RENAME TO taxonomies`.execute(db);
|
|
1622
|
-
await db.schema.createIndex("idx_taxonomies_name").on("taxonomies").column("name").execute();
|
|
1623
|
-
await db.schema.createIndex("idx_taxonomies_locale").on("taxonomies").column("locale").execute();
|
|
1624
|
-
await db.schema.createIndex("idx_taxonomies_translation_group").on("taxonomies").column("translation_group").execute();
|
|
1625
|
-
await db.schema.createIndex("idx_taxonomies_parent").ifNotExists().on("taxonomies").column("parent_id").execute();
|
|
1626
|
-
}
|
|
1627
|
-
async function rebuildTaxonomyDefs(db, defaultLocale) {
|
|
1628
|
-
if (await hasColumn$1(db, "_emdash_taxonomy_defs", "locale")) return;
|
|
1629
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_taxonomy_defs_new"`).execute(db);
|
|
1630
|
-
await db.schema.createTable("_emdash_taxonomy_defs_new").addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull()).addColumn("label", "text", (c) => c.notNull()).addColumn("label_singular", "text").addColumn("hierarchical", "integer", (c) => c.defaultTo(0)).addColumn("collections", "text").addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)).addColumn("translation_group", "text").addUniqueConstraint("_emdash_taxonomy_defs_name_locale_unique", ["name", "locale"]).execute();
|
|
1631
|
-
await sql`
|
|
1632
|
-
INSERT INTO _emdash_taxonomy_defs_new
|
|
1633
|
-
(id, name, label, label_singular, hierarchical, collections, created_at, locale, translation_group)
|
|
1634
|
-
SELECT id, name, label, label_singular, hierarchical, collections, created_at, ${defaultLocale}, id
|
|
1635
|
-
FROM _emdash_taxonomy_defs
|
|
1636
|
-
`.execute(db);
|
|
1637
|
-
await db.schema.dropTable("_emdash_taxonomy_defs").execute();
|
|
1638
|
-
await sql`ALTER TABLE _emdash_taxonomy_defs_new RENAME TO _emdash_taxonomy_defs`.execute(db);
|
|
1639
|
-
await db.schema.createIndex("idx__emdash_taxonomy_defs_locale").on("_emdash_taxonomy_defs").column("locale").execute();
|
|
1640
|
-
await db.schema.createIndex("idx__emdash_taxonomy_defs_translation_group").on("_emdash_taxonomy_defs").column("translation_group").execute();
|
|
1641
|
-
}
|
|
1642
|
-
async function rebuildContentTaxonomies(db) {
|
|
1643
|
-
if ((await sql`PRAGMA foreign_key_list(content_taxonomies)`.execute(db)).rows.length > 0) {
|
|
1644
|
-
await sql.raw(`DROP TABLE IF EXISTS "content_taxonomies_new"`).execute(db);
|
|
1645
|
-
await db.schema.createTable("content_taxonomies_new").addColumn("collection", "text", (c) => c.notNull()).addColumn("entry_id", "text", (c) => c.notNull()).addColumn("taxonomy_id", "text", (c) => c.notNull()).addPrimaryKeyConstraint("content_taxonomies_pk", [
|
|
1646
|
-
"collection",
|
|
1647
|
-
"entry_id",
|
|
1648
|
-
"taxonomy_id"
|
|
1649
|
-
]).execute();
|
|
1650
|
-
await sql`
|
|
1651
|
-
INSERT OR IGNORE INTO content_taxonomies_new (collection, entry_id, taxonomy_id)
|
|
1652
|
-
SELECT collection, entry_id, taxonomy_id FROM content_taxonomies
|
|
1653
|
-
`.execute(db);
|
|
1654
|
-
await db.schema.dropTable("content_taxonomies").execute();
|
|
1655
|
-
await sql`ALTER TABLE content_taxonomies_new RENAME TO content_taxonomies`.execute(db);
|
|
1656
|
-
}
|
|
1657
|
-
await sql`CREATE INDEX IF NOT EXISTS idx_content_taxonomies_term ON content_taxonomies(taxonomy_id)`.execute(db);
|
|
1658
|
-
}
|
|
1659
|
-
async function remapMenuItemRefs(db) {
|
|
1660
|
-
const collections = await sql`SELECT slug FROM _emdash_collections`.execute(db);
|
|
1661
|
-
for (const { slug } of collections.rows) {
|
|
1662
|
-
validateIdentifier(slug, "collection slug");
|
|
1663
|
-
const ec = sql.ref(`ec_${slug}`);
|
|
1664
|
-
await sql`
|
|
1665
|
-
UPDATE _emdash_menu_items SET reference_id = (
|
|
1666
|
-
SELECT translation_group FROM ${ec} WHERE ${ec}.id = _emdash_menu_items.reference_id
|
|
1667
|
-
)
|
|
1668
|
-
WHERE reference_collection = ${slug} AND reference_id IS NOT NULL
|
|
1669
|
-
AND EXISTS (SELECT 1 FROM ${ec} WHERE ${ec}.id = _emdash_menu_items.reference_id)
|
|
1670
|
-
`.execute(db);
|
|
1671
|
-
}
|
|
1672
|
-
await sql`
|
|
1673
|
-
UPDATE _emdash_menu_items SET reference_id = (
|
|
1674
|
-
SELECT translation_group FROM taxonomies WHERE taxonomies.id = _emdash_menu_items.reference_id
|
|
1675
|
-
)
|
|
1676
|
-
WHERE type = 'taxonomy' AND reference_id IS NOT NULL
|
|
1677
|
-
AND EXISTS (SELECT 1 FROM taxonomies WHERE taxonomies.id = _emdash_menu_items.reference_id)
|
|
1678
|
-
`.execute(db);
|
|
1679
|
-
}
|
|
1680
|
-
async function pgWiden(db, table, oldCols, newCols, defaultLocale) {
|
|
1681
|
-
validateSystemIdent(table);
|
|
1682
|
-
const ref = sql.ref(table);
|
|
1683
|
-
await sql`ALTER TABLE ${ref} ADD COLUMN IF NOT EXISTS locale TEXT NOT NULL DEFAULT ${sql.lit(defaultLocale)}`.execute(db);
|
|
1684
|
-
await sql`ALTER TABLE ${ref} ADD COLUMN IF NOT EXISTS translation_group TEXT`.execute(db);
|
|
1685
|
-
await sql`UPDATE ${ref} SET translation_group = id WHERE translation_group IS NULL`.execute(db);
|
|
1686
|
-
await sql`CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${table}_locale`)} ON ${ref} (locale)`.execute(db);
|
|
1687
|
-
await sql`
|
|
1688
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${table}_translation_group`)} ON ${ref} (translation_group)
|
|
1689
|
-
`.execute(db);
|
|
1690
|
-
if (!oldCols || !newCols) return;
|
|
1691
|
-
for (const c of [...oldCols, ...newCols]) validateSystemIdent(c);
|
|
1692
|
-
const cons = await sql`
|
|
1693
|
-
SELECT conname FROM pg_constraint c
|
|
1694
|
-
WHERE c.conrelid = ${table}::regclass AND c.contype = 'u'
|
|
1695
|
-
AND array_length(c.conkey, 1) = ${oldCols.length}
|
|
1696
|
-
AND (
|
|
1697
|
-
SELECT array_agg(a.attname ORDER BY pos.ord)
|
|
1698
|
-
FROM unnest(c.conkey) WITH ORDINALITY AS pos(attnum, ord)
|
|
1699
|
-
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = pos.attnum
|
|
1700
|
-
)::text[] = ${oldCols}::text[]
|
|
1701
|
-
`.execute(db);
|
|
1702
|
-
for (const c of cons.rows) await sql`ALTER TABLE ${ref} DROP CONSTRAINT ${sql.ref(c.conname)}`.execute(db);
|
|
1703
|
-
const cols = sql.join(newCols.map((c) => sql.ref(c)), sql`, `);
|
|
1704
|
-
await sql`
|
|
1705
|
-
ALTER TABLE ${ref}
|
|
1706
|
-
ADD CONSTRAINT ${sql.ref(`${table}_${newCols.join("_")}_unique`)} UNIQUE (${cols})
|
|
1707
|
-
`.execute(db);
|
|
1708
|
-
}
|
|
1709
|
-
async function pgRemapContentTaxonomies(db) {
|
|
1710
|
-
const fks = await sql`
|
|
1711
|
-
SELECT conname FROM pg_constraint
|
|
1712
|
-
WHERE conrelid = 'content_taxonomies'::regclass AND contype = 'f'
|
|
1713
|
-
`.execute(db);
|
|
1714
|
-
for (const c of fks.rows) await sql`ALTER TABLE content_taxonomies DROP CONSTRAINT ${sql.ref(c.conname)}`.execute(db);
|
|
1715
|
-
await sql`
|
|
1716
|
-
UPDATE content_taxonomies SET taxonomy_id = t.translation_group
|
|
1717
|
-
FROM taxonomies t WHERE t.id = content_taxonomies.taxonomy_id
|
|
1718
|
-
`.execute(db);
|
|
1719
|
-
}
|
|
1720
|
-
async function hasColumn$1(db, table, column) {
|
|
1721
|
-
return (await sql`PRAGMA table_info(${sql.ref(table)})`.execute(db)).rows.some((r) => r.name === column);
|
|
1722
|
-
}
|
|
1723
|
-
const SYSTEM_IDENT = /^[_a-z][a-z0-9_]*$/;
|
|
1724
|
-
function validateSystemIdent(name) {
|
|
1725
|
-
if (!SYSTEM_IDENT.test(name)) throw new Error(`Invalid identifier: "${name}"`);
|
|
1726
|
-
}
|
|
1727
|
-
/**
|
|
1728
|
-
* down() restores the FK on content_taxonomies. Rows whose taxonomy_id doesn't
|
|
1729
|
-
* resolve to a (translation_group, defaultLocale) pair would fail the rebuild
|
|
1730
|
-
* after other tables are already stripped — leaving the user mid-rollback.
|
|
1731
|
-
* Surface dangling rows up front instead.
|
|
1732
|
-
*/
|
|
1733
|
-
async function assertContentTaxonomiesResolve(db, defaultLocale) {
|
|
1734
|
-
const result = await sql`
|
|
1735
|
-
SELECT COUNT(*) AS count FROM content_taxonomies ct
|
|
1736
|
-
WHERE NOT EXISTS (
|
|
1737
|
-
SELECT 1 FROM taxonomies t
|
|
1738
|
-
WHERE t.translation_group = ct.taxonomy_id AND t.locale = ${defaultLocale}
|
|
1739
|
-
)
|
|
1740
|
-
`.execute(db);
|
|
1741
|
-
const count = Number(result.rows[0]?.count ?? 0);
|
|
1742
|
-
if (count > 0) throw new Error(`Cannot revert migration 036_i18n_menus_and_taxonomies: ${count} row(s) in "content_taxonomies" reference a translation_group with no row in "taxonomies" at locale="${defaultLocale}". Clean up the dangling associations before rolling back.`);
|
|
1743
|
-
}
|
|
1744
|
-
/**
|
|
1745
|
-
* down() is destructive on multi-locale installs (dropping `locale` collapses
|
|
1746
|
-
* translated rows onto an ambiguous unique key). Refuse to run when any row
|
|
1747
|
-
* sits at a locale other than the configured defaultLocale.
|
|
1748
|
-
*/
|
|
1749
|
-
async function assertSingleLocale$1(db, defaultLocale) {
|
|
1750
|
-
for (const table of [
|
|
1751
|
-
"_emdash_menus",
|
|
1752
|
-
"_emdash_menu_items",
|
|
1753
|
-
"taxonomies",
|
|
1754
|
-
"_emdash_taxonomy_defs"
|
|
1755
|
-
]) {
|
|
1756
|
-
validateSystemIdent(table);
|
|
1757
|
-
const result = await sql`
|
|
1758
|
-
SELECT COUNT(*) AS count FROM ${sql.ref(table)} WHERE locale != ${defaultLocale}
|
|
1759
|
-
`.execute(db);
|
|
1760
|
-
const count = Number(result.rows[0]?.count ?? 0);
|
|
1761
|
-
if (count > 0) throw new Error(`Cannot revert migration 036_i18n_menus_and_taxonomies: ${count} row(s) in "${table}" use a non-default locale (defaultLocale="${defaultLocale}"). Reverting would drop them silently. Export translations first (or delete them) and re-run the rollback. See packages/core/src/database/migrations/036_i18n_menus_and_taxonomies.ts.`);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
async function down$38(db) {
|
|
1765
|
-
const defaultLocale = getDefaultLocale$1();
|
|
1766
|
-
await assertSingleLocale$1(db, defaultLocale);
|
|
1767
|
-
await assertContentTaxonomiesResolve(db, defaultLocale);
|
|
1768
|
-
const widenedTables = [
|
|
1769
|
-
"_emdash_menus",
|
|
1770
|
-
"_emdash_menu_items",
|
|
1771
|
-
"taxonomies",
|
|
1772
|
-
"_emdash_taxonomy_defs"
|
|
1773
|
-
];
|
|
1774
|
-
if (isSqlite(db)) {
|
|
1775
|
-
for (const t of widenedTables) {
|
|
1776
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_${t}_locale`).execute(db);
|
|
1777
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_${t}_translation_group`).execute(db);
|
|
1778
|
-
}
|
|
1779
|
-
await remapContentTaxonomiesDown(db, defaultLocale);
|
|
1780
|
-
await rebuildMenusDown(db);
|
|
1781
|
-
await rebuildMenuItemsDown(db);
|
|
1782
|
-
await rebuildTaxonomiesDown(db);
|
|
1783
|
-
await rebuildTaxonomyDefsDown(db);
|
|
1784
|
-
await restoreContentTaxonomiesFk(db);
|
|
1785
|
-
return;
|
|
1786
|
-
}
|
|
1787
|
-
for (const t of widenedTables) {
|
|
1788
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_${t}_locale`).execute(db);
|
|
1789
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_${t}_translation_group`).execute(db);
|
|
1790
|
-
await sql.raw(`ALTER TABLE "${t}" DROP COLUMN IF EXISTS locale`).execute(db);
|
|
1791
|
-
await sql.raw(`ALTER TABLE "${t}" DROP COLUMN IF EXISTS translation_group`).execute(db);
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
async function remapContentTaxonomiesDown(db, defaultLocale) {
|
|
1795
|
-
await sql`
|
|
1796
|
-
UPDATE content_taxonomies
|
|
1797
|
-
SET taxonomy_id = COALESCE(
|
|
1798
|
-
(SELECT t.id FROM taxonomies t
|
|
1799
|
-
WHERE t.translation_group = content_taxonomies.taxonomy_id
|
|
1800
|
-
AND t.locale = ${defaultLocale}),
|
|
1801
|
-
taxonomy_id
|
|
1802
|
-
)
|
|
1803
|
-
`.execute(db);
|
|
1804
|
-
}
|
|
1805
|
-
async function restoreContentTaxonomiesFk(db) {
|
|
1806
|
-
await sql.raw(`DROP TABLE IF EXISTS "content_taxonomies_new"`).execute(db);
|
|
1807
|
-
await db.schema.createTable("content_taxonomies_new").addColumn("collection", "text", (c) => c.notNull()).addColumn("entry_id", "text", (c) => c.notNull()).addColumn("taxonomy_id", "text", (c) => c.notNull()).addPrimaryKeyConstraint("content_taxonomies_pk", [
|
|
1808
|
-
"collection",
|
|
1809
|
-
"entry_id",
|
|
1810
|
-
"taxonomy_id"
|
|
1811
|
-
]).addForeignKeyConstraint("content_taxonomies_taxonomy_fk", ["taxonomy_id"], "taxonomies", ["id"], (cb) => cb.onDelete("cascade")).execute();
|
|
1812
|
-
await sql`
|
|
1813
|
-
INSERT OR IGNORE INTO content_taxonomies_new (collection, entry_id, taxonomy_id)
|
|
1814
|
-
SELECT collection, entry_id, taxonomy_id FROM content_taxonomies
|
|
1815
|
-
`.execute(db);
|
|
1816
|
-
await db.schema.dropTable("content_taxonomies").execute();
|
|
1817
|
-
await sql`ALTER TABLE content_taxonomies_new RENAME TO content_taxonomies`.execute(db);
|
|
1818
|
-
await sql`CREATE INDEX IF NOT EXISTS idx_content_taxonomies_term ON content_taxonomies(taxonomy_id)`.execute(db);
|
|
1819
|
-
}
|
|
1820
|
-
async function rebuildMenusDown(db) {
|
|
1821
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_menus_old"`).execute(db);
|
|
1822
|
-
await db.schema.createTable("_emdash_menus_old").addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull().unique()).addColumn("label", "text", (c) => c.notNull()).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))).execute();
|
|
1823
|
-
await sql`
|
|
1824
|
-
INSERT INTO _emdash_menus_old (id, name, label, created_at, updated_at)
|
|
1825
|
-
SELECT id, name, label, created_at, updated_at FROM _emdash_menus
|
|
1826
|
-
`.execute(db);
|
|
1827
|
-
await db.schema.dropTable("_emdash_menus").execute();
|
|
1828
|
-
await sql`ALTER TABLE _emdash_menus_old RENAME TO _emdash_menus`.execute(db);
|
|
1829
|
-
}
|
|
1830
|
-
async function rebuildMenuItemsDown(db) {
|
|
1831
|
-
await sql.raw(`ALTER TABLE _emdash_menu_items DROP COLUMN locale`).execute(db);
|
|
1832
|
-
await sql.raw(`ALTER TABLE _emdash_menu_items DROP COLUMN translation_group`).execute(db);
|
|
1833
|
-
}
|
|
1834
|
-
async function rebuildTaxonomiesDown(db) {
|
|
1835
|
-
await sql.raw(`DROP TABLE IF EXISTS "taxonomies_old"`).execute(db);
|
|
1836
|
-
await db.schema.createTable("taxonomies_old").addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull()).addColumn("slug", "text", (c) => c.notNull()).addColumn("label", "text", (c) => c.notNull()).addColumn("parent_id", "text").addColumn("data", "text").addUniqueConstraint("taxonomies_name_slug_unique", ["name", "slug"]).addForeignKeyConstraint("taxonomies_parent_fk", ["parent_id"], "taxonomies_old", ["id"], (cb) => cb.onDelete("set null")).execute();
|
|
1837
|
-
await sql`
|
|
1838
|
-
INSERT INTO taxonomies_old (id, name, slug, label, parent_id, data)
|
|
1839
|
-
SELECT id, name, slug, label, parent_id, data FROM taxonomies
|
|
1840
|
-
`.execute(db);
|
|
1841
|
-
await db.schema.dropTable("taxonomies").execute();
|
|
1842
|
-
await sql`ALTER TABLE taxonomies_old RENAME TO taxonomies`.execute(db);
|
|
1843
|
-
await db.schema.createIndex("idx_taxonomies_name").on("taxonomies").column("name").execute();
|
|
1844
|
-
await db.schema.createIndex("idx_taxonomies_parent").ifNotExists().on("taxonomies").column("parent_id").execute();
|
|
1845
|
-
}
|
|
1846
|
-
async function rebuildTaxonomyDefsDown(db) {
|
|
1847
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_taxonomy_defs_old"`).execute(db);
|
|
1848
|
-
await db.schema.createTable("_emdash_taxonomy_defs_old").addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull().unique()).addColumn("label", "text", (c) => c.notNull()).addColumn("label_singular", "text").addColumn("hierarchical", "integer", (c) => c.defaultTo(0)).addColumn("collections", "text").addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).execute();
|
|
1849
|
-
await sql`
|
|
1850
|
-
INSERT INTO _emdash_taxonomy_defs_old
|
|
1851
|
-
(id, name, label, label_singular, hierarchical, collections, created_at)
|
|
1852
|
-
SELECT id, name, label, label_singular, hierarchical, collections, created_at
|
|
1853
|
-
FROM _emdash_taxonomy_defs
|
|
1854
|
-
`.execute(db);
|
|
1855
|
-
await db.schema.dropTable("_emdash_taxonomy_defs").execute();
|
|
1856
|
-
await sql`ALTER TABLE _emdash_taxonomy_defs_old RENAME TO _emdash_taxonomy_defs`.execute(db);
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
//#endregion
|
|
1860
|
-
//#region src/database/migrations/037_credential_algorithm.ts
|
|
1861
|
-
var _037_credential_algorithm_exports = /* @__PURE__ */ __exportAll({
|
|
1862
|
-
down: () => down$37,
|
|
1863
|
-
up: () => up$37
|
|
1864
|
-
});
|
|
1865
|
-
async function up$37(db) {
|
|
1866
|
-
if (!await columnExists(db, "credentials", "algorithm")) await db.schema.alterTable("credentials").addColumn("algorithm", "integer", (col) => col.notNull().defaultTo(-7)).execute();
|
|
1867
|
-
}
|
|
1868
|
-
async function down$37(db) {
|
|
1869
|
-
if (await columnExists(db, "credentials", "algorithm")) await db.schema.alterTable("credentials").dropColumn("algorithm").execute();
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
//#endregion
|
|
1873
|
-
//#region src/database/migrations/038_registry_plugin_state.ts
|
|
1874
|
-
var _038_registry_plugin_state_exports = /* @__PURE__ */ __exportAll({
|
|
1875
|
-
down: () => down$36,
|
|
1876
|
-
up: () => up$36
|
|
1877
|
-
});
|
|
1878
|
-
/**
|
|
1879
|
-
* Migration: Add registry fields to _plugin_state
|
|
1880
|
-
*
|
|
1881
|
-
* Extends the marketplace columns added in 022 to support the
|
|
1882
|
-
* experimental decentralized plugin registry (see RFC #694). Rather
|
|
1883
|
-
* than introducing a separate `_registry_plugin_state` table, we
|
|
1884
|
-
* reuse the same row shape and distinguish registry installs via the
|
|
1885
|
-
* existing `source` column (now `'config' | 'marketplace' | 'registry'`).
|
|
1886
|
-
*
|
|
1887
|
-
* Registry plugins are addressed by `(publisher_did, slug)` in their
|
|
1888
|
-
* lexicon records but stored under a hashed, opaque `plugin_id` for
|
|
1889
|
-
* runtime compatibility -- see `packages/core/src/registry/plugin-id.ts`.
|
|
1890
|
-
* The `(publisher_did, slug)` pair is preserved here for update
|
|
1891
|
-
* resolution against the currently configured aggregator and for admin
|
|
1892
|
-
* UI rendering ("by @example.dev").
|
|
1893
|
-
*
|
|
1894
|
-
* All new columns are nullable; existing marketplace and config rows
|
|
1895
|
-
* keep working unchanged.
|
|
1896
|
-
*
|
|
1897
|
-
* Idempotency: D1 and SQLite don't honor the migration runner's
|
|
1898
|
-
* advisory lock, so a partial re-apply (cold start race between two
|
|
1899
|
-
* isolates, retry after a connection drop) can re-enter this `up`
|
|
1900
|
-
* function with the columns or index already in place. Each step
|
|
1901
|
-
* checks before adding to keep the migration safe under partial
|
|
1902
|
-
* re-application. The same pattern is used in 019_i18n.ts.
|
|
1903
|
-
*/
|
|
1904
|
-
async function up$36(db) {
|
|
1905
|
-
if (isSqlite(db)) await upSqlite(db);
|
|
1906
|
-
else await upPostgres(db);
|
|
1907
|
-
}
|
|
1908
|
-
async function upSqlite(db) {
|
|
1909
|
-
const cols = await sql`PRAGMA table_info(_plugin_state)`.execute(db);
|
|
1910
|
-
const colNames = new Set(cols.rows.map((c) => c.name));
|
|
1911
|
-
if (!colNames.has("registry_publisher_did")) await sql`
|
|
1912
|
-
ALTER TABLE _plugin_state
|
|
1913
|
-
ADD COLUMN registry_publisher_did TEXT
|
|
1914
|
-
`.execute(db);
|
|
1915
|
-
if (!colNames.has("registry_slug")) await sql`
|
|
1916
|
-
ALTER TABLE _plugin_state
|
|
1917
|
-
ADD COLUMN registry_slug TEXT
|
|
1918
|
-
`.execute(db);
|
|
1919
|
-
const indexes = await sql`PRAGMA index_list(_plugin_state)`.execute(db);
|
|
1920
|
-
if (!new Set(indexes.rows.map((i) => i.name)).has("idx_plugin_state_registry")) await sql`
|
|
1921
|
-
CREATE INDEX idx_plugin_state_registry
|
|
1922
|
-
ON _plugin_state (source)
|
|
1923
|
-
WHERE source = 'registry'
|
|
1924
|
-
`.execute(db);
|
|
1925
|
-
}
|
|
1926
|
-
async function upPostgres(db) {
|
|
1927
|
-
const cols = await sql`
|
|
1928
|
-
SELECT column_name FROM information_schema.columns
|
|
1929
|
-
WHERE table_name = '_plugin_state'
|
|
1930
|
-
AND table_schema = current_schema()
|
|
1931
|
-
`.execute(db);
|
|
1932
|
-
const colNames = new Set(cols.rows.map((c) => c.column_name));
|
|
1933
|
-
if (!colNames.has("registry_publisher_did")) await sql`
|
|
1934
|
-
ALTER TABLE _plugin_state
|
|
1935
|
-
ADD COLUMN registry_publisher_did TEXT
|
|
1936
|
-
`.execute(db);
|
|
1937
|
-
if (!colNames.has("registry_slug")) await sql`
|
|
1938
|
-
ALTER TABLE _plugin_state
|
|
1939
|
-
ADD COLUMN registry_slug TEXT
|
|
1940
|
-
`.execute(db);
|
|
1941
|
-
await sql`
|
|
1942
|
-
CREATE INDEX IF NOT EXISTS idx_plugin_state_registry
|
|
1943
|
-
ON _plugin_state (source)
|
|
1944
|
-
WHERE source = 'registry'
|
|
1945
|
-
`.execute(db);
|
|
1946
|
-
}
|
|
1947
|
-
async function down$36(db) {
|
|
1948
|
-
await sql`
|
|
1949
|
-
DROP INDEX IF EXISTS idx_plugin_state_registry
|
|
1950
|
-
`.execute(db);
|
|
1951
|
-
await sql`
|
|
1952
|
-
ALTER TABLE _plugin_state
|
|
1953
|
-
DROP COLUMN registry_slug
|
|
1954
|
-
`.execute(db);
|
|
1955
|
-
await sql`
|
|
1956
|
-
ALTER TABLE _plugin_state
|
|
1957
|
-
DROP COLUMN registry_publisher_did
|
|
1958
|
-
`.execute(db);
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
//#endregion
|
|
1962
|
-
//#region src/database/migrations/039_fix_fts5_triggers.ts
|
|
1963
|
-
var _039_fix_fts5_triggers_exports = /* @__PURE__ */ __exportAll({
|
|
1964
|
-
down: () => down$35,
|
|
1965
|
-
up: () => up$35
|
|
1966
|
-
});
|
|
1967
|
-
async function up$35(db) {
|
|
1968
|
-
if (!isSqlite(db)) return;
|
|
1969
|
-
const collections = await sql`
|
|
1970
|
-
SELECT slug, search_config FROM _emdash_collections
|
|
1971
|
-
WHERE search_config IS NOT NULL
|
|
1972
|
-
`.execute(db);
|
|
1973
|
-
for (const collection of collections.rows) {
|
|
1974
|
-
if (!isSearchEnabled$1(collection.search_config)) continue;
|
|
1975
|
-
try {
|
|
1976
|
-
validateIdentifier(collection.slug, "collection slug");
|
|
1977
|
-
} catch (error) {
|
|
1978
|
-
console.warn(`[migration 039] skipping FTS rebuild for collection "${collection.slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1979
|
-
continue;
|
|
1980
|
-
}
|
|
1981
|
-
const fields = await getSearchableFields$1(db, collection.slug);
|
|
1982
|
-
if (fields.length === 0) {
|
|
1983
|
-
await dropFtsObjects(db, collection.slug);
|
|
1984
|
-
await sql`
|
|
1985
|
-
UPDATE _emdash_collections
|
|
1986
|
-
SET search_config = json_set(search_config, '$.enabled', json('false'))
|
|
1987
|
-
WHERE slug = ${collection.slug}
|
|
1988
|
-
`.execute(db);
|
|
1989
|
-
continue;
|
|
1990
|
-
}
|
|
1991
|
-
await rebuildIndex$1(db, collection.slug, fields);
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
/**
|
|
1995
|
-
* Forward-only migration. Down is a no-op: we cannot meaningfully
|
|
1996
|
-
* "restore the broken triggers" and there is no migration-level state
|
|
1997
|
-
* to roll back. The FTS tables themselves are managed by `FTSManager`
|
|
1998
|
-
* at runtime, not by this migration, so leaving them in their
|
|
1999
|
-
* corruption-safe state on rollback is correct.
|
|
2000
|
-
*/
|
|
2001
|
-
async function down$35(_db) {}
|
|
2002
|
-
function isSearchEnabled$1(searchConfig) {
|
|
2003
|
-
if (!searchConfig) return false;
|
|
2004
|
-
try {
|
|
2005
|
-
const parsed = JSON.parse(searchConfig);
|
|
2006
|
-
return typeof parsed === "object" && parsed !== null && "enabled" in parsed && parsed.enabled === true;
|
|
2007
|
-
} catch {
|
|
2008
|
-
return false;
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
async function getSearchableFields$1(db, collectionSlug) {
|
|
2012
|
-
const rows = await sql`
|
|
2013
|
-
SELECT f.slug FROM _emdash_fields f
|
|
2014
|
-
INNER JOIN _emdash_collections c ON c.id = f.collection_id
|
|
2015
|
-
WHERE c.slug = ${collectionSlug} AND f.searchable = 1
|
|
2016
|
-
`.execute(db);
|
|
2017
|
-
const out = [];
|
|
2018
|
-
for (const row of rows.rows) try {
|
|
2019
|
-
validateIdentifier(row.slug, "searchable field name");
|
|
2020
|
-
out.push(row.slug);
|
|
2021
|
-
} catch {
|
|
2022
|
-
console.warn(`[migration 039] skipping invalid searchable field "${row.slug}" on collection "${collectionSlug}"`);
|
|
2023
|
-
}
|
|
2024
|
-
return out;
|
|
2025
|
-
}
|
|
2026
|
-
async function rebuildIndex$1(db, collectionSlug, fields) {
|
|
2027
|
-
const ftsTable = `_emdash_fts_${collectionSlug}`;
|
|
2028
|
-
const contentTable = `ec_${collectionSlug}`;
|
|
2029
|
-
const columnList = [
|
|
2030
|
-
"id UNINDEXED",
|
|
2031
|
-
"locale UNINDEXED",
|
|
2032
|
-
...fields
|
|
2033
|
-
].join(", ");
|
|
2034
|
-
const fieldList = fields.join(", ");
|
|
2035
|
-
const newFieldList = fields.map((f) => `NEW.${f}`).join(", ");
|
|
2036
|
-
const oldFieldList = fields.map((f) => `OLD.${f}`).join(", ");
|
|
2037
|
-
await dropFtsObjects(db, collectionSlug);
|
|
2038
|
-
await sql.raw(`
|
|
2039
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS "${ftsTable}" USING fts5(
|
|
2040
|
-
${columnList},
|
|
2041
|
-
content='${contentTable}',
|
|
2042
|
-
content_rowid='rowid',
|
|
2043
|
-
tokenize='porter unicode61'
|
|
2044
|
-
)
|
|
2045
|
-
`).execute(db);
|
|
2046
|
-
await sql.raw(`
|
|
2047
|
-
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_insert"
|
|
2048
|
-
AFTER INSERT ON "${contentTable}"
|
|
2049
|
-
WHEN NEW.deleted_at IS NULL
|
|
2050
|
-
BEGIN
|
|
2051
|
-
INSERT INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
2052
|
-
VALUES (NEW.rowid, NEW.id, NEW.locale, ${newFieldList});
|
|
2053
|
-
END
|
|
2054
|
-
`).execute(db);
|
|
2055
|
-
await sql.raw(`
|
|
2056
|
-
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_update"
|
|
2057
|
-
AFTER UPDATE ON "${contentTable}"
|
|
2058
|
-
BEGIN
|
|
2059
|
-
INSERT INTO "${ftsTable}"("${ftsTable}", rowid, id, locale, ${fieldList})
|
|
2060
|
-
SELECT 'delete', OLD.rowid, OLD.id, OLD.locale, ${oldFieldList}
|
|
2061
|
-
WHERE OLD.deleted_at IS NULL;
|
|
2062
|
-
INSERT INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
2063
|
-
SELECT NEW.rowid, NEW.id, NEW.locale, ${newFieldList}
|
|
2064
|
-
WHERE NEW.deleted_at IS NULL;
|
|
2065
|
-
END
|
|
2066
|
-
`).execute(db);
|
|
2067
|
-
await sql.raw(`
|
|
2068
|
-
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_delete"
|
|
2069
|
-
AFTER DELETE ON "${contentTable}"
|
|
2070
|
-
BEGIN
|
|
2071
|
-
INSERT INTO "${ftsTable}"("${ftsTable}", rowid, id, locale, ${fieldList})
|
|
2072
|
-
SELECT 'delete', OLD.rowid, OLD.id, OLD.locale, ${oldFieldList}
|
|
2073
|
-
WHERE OLD.deleted_at IS NULL;
|
|
2074
|
-
END
|
|
2075
|
-
`).execute(db);
|
|
2076
|
-
await sql.raw(`
|
|
2077
|
-
INSERT INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
2078
|
-
SELECT rowid, id, locale, ${fieldList} FROM "${contentTable}"
|
|
2079
|
-
WHERE deleted_at IS NULL
|
|
2080
|
-
`).execute(db);
|
|
2081
|
-
}
|
|
2082
|
-
async function dropFtsObjects(db, collectionSlug) {
|
|
2083
|
-
const ftsTable = `_emdash_fts_${collectionSlug}`;
|
|
2084
|
-
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_insert"`).execute(db);
|
|
2085
|
-
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_update"`).execute(db);
|
|
2086
|
-
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_delete"`).execute(db);
|
|
2087
|
-
await sql.raw(`DROP TABLE IF EXISTS "${ftsTable}"`).execute(db);
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
//#endregion
|
|
2091
|
-
//#region src/database/migrations/040_byline_i18n.ts
|
|
2092
|
-
var _040_byline_i18n_exports = /* @__PURE__ */ __exportAll({
|
|
2093
|
-
down: () => down$34,
|
|
2094
|
-
up: () => up$34
|
|
2095
|
-
});
|
|
2096
|
-
/**
|
|
2097
|
-
* i18n for bylines. Adds `locale` + `translation_group` to `_emdash_bylines`
|
|
2098
|
-
* and stores translation_groups (not row ids) in
|
|
2099
|
-
* `_emdash_content_bylines.byline_id` and `ec_*.primary_byline_id`. Backfill
|
|
2100
|
-
* locale and column DEFAULTs use the site's configured defaultLocale.
|
|
2101
|
-
*
|
|
2102
|
-
* Mirrors the row-per-locale + `translation_group` model PR #916 (migration
|
|
2103
|
-
* 036) applied to menus and taxonomies.
|
|
2104
|
-
*
|
|
2105
|
-
* Key consequences of the model:
|
|
2106
|
-
* - `(slug, locale)` is unique on `_emdash_bylines`; a single slug can repeat
|
|
2107
|
-
* across locales (one row per locale variant of a byline).
|
|
2108
|
-
* - The partial unique on `user_id` widens to `(user_id, locale)` so a CMS
|
|
2109
|
-
* user can have one byline per locale.
|
|
2110
|
-
* - `_emdash_content_bylines.byline_id` no longer FKs to `_emdash_bylines.id`
|
|
2111
|
-
* (it holds a `translation_group`, not a row id). The runtime is
|
|
2112
|
-
* responsible for cascading on byline delete — see `BylineRepository.delete`.
|
|
2113
|
-
*
|
|
2114
|
-
* Hydration is strict per locale (see `BylineRepository.getContentBylines`):
|
|
2115
|
-
* a credit at locale X renders iff a byline row exists at locale X within the
|
|
2116
|
-
* credited translation group. This mirrors `getEntryTerms` and the convention
|
|
2117
|
-
* established by #916. There is no read-time fallback.
|
|
2118
|
-
*/
|
|
2119
|
-
function getDefaultLocale() {
|
|
2120
|
-
return getI18nConfig()?.defaultLocale ?? "en";
|
|
2121
|
-
}
|
|
2122
|
-
async function up$34(db) {
|
|
2123
|
-
const defaultLocale = getDefaultLocale();
|
|
2124
|
-
if (isSqlite(db)) {
|
|
2125
|
-
await rebuildContentBylines(db);
|
|
2126
|
-
await rebuildBylines(db, defaultLocale);
|
|
2127
|
-
await remapPrimaryBylineIds(db);
|
|
2128
|
-
return;
|
|
2129
|
-
}
|
|
2130
|
-
await pgWidenBylines(db, defaultLocale);
|
|
2131
|
-
await pgDropContentBylinesFk(db);
|
|
2132
|
-
await remapPrimaryBylineIds(db);
|
|
2133
|
-
}
|
|
2134
|
-
async function rebuildContentBylines(db) {
|
|
2135
|
-
if ((await sql`PRAGMA foreign_key_list(_emdash_content_bylines)`.execute(db)).rows.length === 0) return;
|
|
2136
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_content_bylines_new"`).execute(db);
|
|
2137
|
-
await db.schema.createTable("_emdash_content_bylines_new").addColumn("id", "text", (c) => c.primaryKey()).addColumn("collection_slug", "text", (c) => c.notNull()).addColumn("content_id", "text", (c) => c.notNull()).addColumn("byline_id", "text", (c) => c.notNull()).addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0)).addColumn("role_label", "text").addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addUniqueConstraint("content_bylines_unique", [
|
|
2138
|
-
"collection_slug",
|
|
2139
|
-
"content_id",
|
|
2140
|
-
"byline_id"
|
|
2141
|
-
]).execute();
|
|
2142
|
-
await sql`
|
|
2143
|
-
INSERT INTO _emdash_content_bylines_new
|
|
2144
|
-
(id, collection_slug, content_id, byline_id, sort_order, role_label, created_at)
|
|
2145
|
-
SELECT id, collection_slug, content_id, byline_id, sort_order, role_label, created_at
|
|
2146
|
-
FROM _emdash_content_bylines
|
|
2147
|
-
`.execute(db);
|
|
2148
|
-
await db.schema.dropTable("_emdash_content_bylines").execute();
|
|
2149
|
-
await sql`ALTER TABLE _emdash_content_bylines_new RENAME TO _emdash_content_bylines`.execute(db);
|
|
2150
|
-
await db.schema.createIndex("idx_content_bylines_content").on("_emdash_content_bylines").columns([
|
|
2151
|
-
"collection_slug",
|
|
2152
|
-
"content_id",
|
|
2153
|
-
"sort_order"
|
|
2154
|
-
]).execute();
|
|
2155
|
-
await db.schema.createIndex("idx_content_bylines_byline").on("_emdash_content_bylines").column("byline_id").execute();
|
|
2156
|
-
}
|
|
2157
|
-
async function rebuildBylines(db, defaultLocale) {
|
|
2158
|
-
if (await hasColumn(db, "_emdash_bylines", "locale")) return;
|
|
2159
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_bylines_new"`).execute(db);
|
|
2160
|
-
await db.schema.createTable("_emdash_bylines_new").addColumn("id", "text", (c) => c.primaryKey()).addColumn("slug", "text", (c) => c.notNull()).addColumn("display_name", "text", (c) => c.notNull()).addColumn("bio", "text").addColumn("avatar_media_id", "text", (c) => c.references("media.id").onDelete("set null")).addColumn("website_url", "text").addColumn("user_id", "text", (c) => c.references("users.id").onDelete("set null")).addColumn("is_guest", "integer", (c) => c.notNull().defaultTo(0)).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)).addColumn("translation_group", "text").addUniqueConstraint("_emdash_bylines_slug_locale_unique", ["slug", "locale"]).execute();
|
|
2161
|
-
await sql`
|
|
2162
|
-
INSERT INTO _emdash_bylines_new (
|
|
2163
|
-
id, slug, display_name, bio, avatar_media_id, website_url,
|
|
2164
|
-
user_id, is_guest, created_at, updated_at, locale, translation_group
|
|
2165
|
-
)
|
|
2166
|
-
SELECT
|
|
2167
|
-
id, slug, display_name, bio, avatar_media_id, website_url,
|
|
2168
|
-
user_id, is_guest, created_at, updated_at, ${defaultLocale}, id
|
|
2169
|
-
FROM _emdash_bylines
|
|
2170
|
-
`.execute(db);
|
|
2171
|
-
await db.schema.dropTable("_emdash_bylines").execute();
|
|
2172
|
-
await sql`ALTER TABLE _emdash_bylines_new RENAME TO _emdash_bylines`.execute(db);
|
|
2173
|
-
await db.schema.createIndex("idx_bylines_slug").on("_emdash_bylines").column("slug").execute();
|
|
2174
|
-
await db.schema.createIndex("idx_bylines_display_name").on("_emdash_bylines").column("display_name").execute();
|
|
2175
|
-
await sql`
|
|
2176
|
-
CREATE UNIQUE INDEX ${sql.ref("idx_bylines_user_id_locale_unique")}
|
|
2177
|
-
ON ${sql.ref("_emdash_bylines")} (user_id, locale)
|
|
2178
|
-
WHERE user_id IS NOT NULL
|
|
2179
|
-
`.execute(db);
|
|
2180
|
-
await db.schema.createIndex("idx__emdash_bylines_locale").on("_emdash_bylines").column("locale").execute();
|
|
2181
|
-
await db.schema.createIndex("idx__emdash_bylines_translation_group").on("_emdash_bylines").column("translation_group").execute();
|
|
2182
|
-
await sql`
|
|
2183
|
-
CREATE UNIQUE INDEX ${sql.ref("idx_bylines_group_locale_unique")}
|
|
2184
|
-
ON ${sql.ref("_emdash_bylines")} (translation_group, locale)
|
|
2185
|
-
WHERE translation_group IS NOT NULL
|
|
2186
|
-
`.execute(db);
|
|
2187
|
-
}
|
|
2188
|
-
async function remapPrimaryBylineIds(db) {
|
|
2189
|
-
const collections = await listTablesLike(db, "ec_%");
|
|
2190
|
-
for (const table of collections) {
|
|
2191
|
-
validateIdentifier(table, "content table");
|
|
2192
|
-
await sql`
|
|
2193
|
-
UPDATE ${sql.ref(table)} SET primary_byline_id = (
|
|
2194
|
-
SELECT translation_group FROM _emdash_bylines
|
|
2195
|
-
WHERE _emdash_bylines.id = ${sql.ref(table)}.primary_byline_id
|
|
2196
|
-
)
|
|
2197
|
-
WHERE primary_byline_id IS NOT NULL
|
|
2198
|
-
AND EXISTS (
|
|
2199
|
-
SELECT 1 FROM _emdash_bylines
|
|
2200
|
-
WHERE _emdash_bylines.id = ${sql.ref(table)}.primary_byline_id
|
|
2201
|
-
)
|
|
2202
|
-
`.execute(db);
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
async function pgWidenBylines(db, defaultLocale) {
|
|
2206
|
-
const ref = sql.ref("_emdash_bylines");
|
|
2207
|
-
await sql`ALTER TABLE ${ref} ADD COLUMN IF NOT EXISTS locale TEXT NOT NULL DEFAULT ${sql.lit(defaultLocale)}`.execute(db);
|
|
2208
|
-
await sql`ALTER TABLE ${ref} ADD COLUMN IF NOT EXISTS translation_group TEXT`.execute(db);
|
|
2209
|
-
await sql`UPDATE ${ref} SET translation_group = id WHERE translation_group IS NULL`.execute(db);
|
|
2210
|
-
await sql`CREATE INDEX IF NOT EXISTS ${sql.ref("idx__emdash_bylines_locale")} ON ${ref} (locale)`.execute(db);
|
|
2211
|
-
await sql`
|
|
2212
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref("idx__emdash_bylines_translation_group")}
|
|
2213
|
-
ON ${ref} (translation_group)
|
|
2214
|
-
`.execute(db);
|
|
2215
|
-
const slugCons = await sql`
|
|
2216
|
-
SELECT conname FROM pg_constraint c
|
|
2217
|
-
WHERE c.conrelid = '_emdash_bylines'::regclass AND c.contype = 'u'
|
|
2218
|
-
AND array_length(c.conkey, 1) = 1
|
|
2219
|
-
AND (
|
|
2220
|
-
SELECT array_agg(a.attname ORDER BY pos.ord)
|
|
2221
|
-
FROM unnest(c.conkey) WITH ORDINALITY AS pos(attnum, ord)
|
|
2222
|
-
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = pos.attnum
|
|
2223
|
-
)::text[] = ARRAY['slug']
|
|
2224
|
-
`.execute(db);
|
|
2225
|
-
for (const c of slugCons.rows) await sql`ALTER TABLE ${ref} DROP CONSTRAINT ${sql.ref(c.conname)}`.execute(db);
|
|
2226
|
-
await sql`
|
|
2227
|
-
ALTER TABLE ${ref}
|
|
2228
|
-
ADD CONSTRAINT _emdash_bylines_slug_locale_unique UNIQUE (slug, locale)
|
|
2229
|
-
`.execute(db);
|
|
2230
|
-
await sql`DROP INDEX IF EXISTS idx_bylines_user_id_unique`.execute(db);
|
|
2231
|
-
await sql`
|
|
2232
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${sql.ref("idx_bylines_user_id_locale_unique")}
|
|
2233
|
-
ON ${ref} (user_id, locale) WHERE user_id IS NOT NULL
|
|
2234
|
-
`.execute(db);
|
|
2235
|
-
await sql`
|
|
2236
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${sql.ref("idx_bylines_group_locale_unique")}
|
|
2237
|
-
ON ${ref} (translation_group, locale) WHERE translation_group IS NOT NULL
|
|
2238
|
-
`.execute(db);
|
|
2239
|
-
}
|
|
2240
|
-
async function pgDropContentBylinesFk(db) {
|
|
2241
|
-
const fks = await sql`
|
|
2242
|
-
SELECT conname FROM pg_constraint
|
|
2243
|
-
WHERE conrelid = '_emdash_content_bylines'::regclass AND contype = 'f'
|
|
2244
|
-
`.execute(db);
|
|
2245
|
-
for (const c of fks.rows) await sql`ALTER TABLE _emdash_content_bylines DROP CONSTRAINT ${sql.ref(c.conname)}`.execute(db);
|
|
2246
|
-
}
|
|
2247
|
-
async function hasColumn(db, table, column) {
|
|
2248
|
-
return (await sql`PRAGMA table_info(${sql.ref(table)})`.execute(db)).rows.some((r) => r.name === column);
|
|
2249
|
-
}
|
|
2250
|
-
/**
|
|
2251
|
-
* down() restores the FK on `_emdash_content_bylines.byline_id`. Rows whose
|
|
2252
|
-
* `byline_id` doesn't resolve to a (translation_group, defaultLocale) row in
|
|
2253
|
-
* `_emdash_bylines` would fail the rebuild after other tables are already
|
|
2254
|
-
* stripped — leaving the user mid-rollback. Surface dangling rows up front.
|
|
2255
|
-
*/
|
|
2256
|
-
async function assertContentBylinesResolve(db, defaultLocale) {
|
|
2257
|
-
const result = await sql`
|
|
2258
|
-
SELECT COUNT(*) AS count FROM _emdash_content_bylines cb
|
|
2259
|
-
WHERE NOT EXISTS (
|
|
2260
|
-
SELECT 1 FROM _emdash_bylines b
|
|
2261
|
-
WHERE b.translation_group = cb.byline_id AND b.locale = ${defaultLocale}
|
|
2262
|
-
)
|
|
2263
|
-
`.execute(db);
|
|
2264
|
-
const count = Number(result.rows[0]?.count ?? 0);
|
|
2265
|
-
if (count > 0) throw new Error(`Cannot revert migration 040_byline_i18n: ${count} row(s) in "_emdash_content_bylines" reference a translation_group with no row in "_emdash_bylines" at locale="${defaultLocale}". Clean up the dangling credits before rolling back.`);
|
|
2266
|
-
}
|
|
2267
|
-
/**
|
|
2268
|
-
* down() is destructive on multi-locale installs (dropping `locale` collapses
|
|
2269
|
-
* translated rows onto an ambiguous unique key). Refuse to run when any row
|
|
2270
|
-
* sits at a locale other than the configured defaultLocale.
|
|
2271
|
-
*/
|
|
2272
|
-
async function assertSingleLocale(db, defaultLocale) {
|
|
2273
|
-
const result = await sql`
|
|
2274
|
-
SELECT COUNT(*) AS count FROM _emdash_bylines WHERE locale != ${defaultLocale}
|
|
2275
|
-
`.execute(db);
|
|
2276
|
-
const count = Number(result.rows[0]?.count ?? 0);
|
|
2277
|
-
if (count > 0) throw new Error(`Cannot revert migration 040_byline_i18n: ${count} row(s) in "_emdash_bylines" use a non-default locale (defaultLocale="${defaultLocale}"). Reverting would drop them silently. Export translations first (or delete them) and re-run the rollback. See packages/core/src/database/migrations/040_byline_i18n.ts.`);
|
|
2278
|
-
}
|
|
2279
|
-
async function down$34(db) {
|
|
2280
|
-
const defaultLocale = getDefaultLocale();
|
|
2281
|
-
await assertSingleLocale(db, defaultLocale);
|
|
2282
|
-
await assertContentBylinesResolve(db, defaultLocale);
|
|
2283
|
-
if (isSqlite(db)) {
|
|
2284
|
-
await sql.raw(`DROP INDEX IF EXISTS idx__emdash_bylines_locale`).execute(db);
|
|
2285
|
-
await sql.raw(`DROP INDEX IF EXISTS idx__emdash_bylines_translation_group`).execute(db);
|
|
2286
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_bylines_user_id_locale_unique`).execute(db);
|
|
2287
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_bylines_group_locale_unique`).execute(db);
|
|
2288
|
-
await remapPrimaryBylineIdsDown(db, defaultLocale);
|
|
2289
|
-
await remapContentBylinesDown(db, defaultLocale);
|
|
2290
|
-
await rebuildBylinesDown(db);
|
|
2291
|
-
await restoreContentBylinesFk(db);
|
|
2292
|
-
return;
|
|
2293
|
-
}
|
|
2294
|
-
await remapPrimaryBylineIdsDown(db, defaultLocale);
|
|
2295
|
-
await sql`
|
|
2296
|
-
UPDATE _emdash_content_bylines
|
|
2297
|
-
SET byline_id = COALESCE(
|
|
2298
|
-
(SELECT b.id FROM _emdash_bylines b
|
|
2299
|
-
WHERE b.translation_group = _emdash_content_bylines.byline_id
|
|
2300
|
-
AND b.locale = ${defaultLocale}),
|
|
2301
|
-
byline_id
|
|
2302
|
-
)
|
|
2303
|
-
`.execute(db);
|
|
2304
|
-
await sql.raw(`DROP INDEX IF EXISTS idx__emdash_bylines_locale`).execute(db);
|
|
2305
|
-
await sql.raw(`DROP INDEX IF EXISTS idx__emdash_bylines_translation_group`).execute(db);
|
|
2306
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_bylines_user_id_locale_unique`).execute(db);
|
|
2307
|
-
await sql.raw(`DROP INDEX IF EXISTS idx_bylines_group_locale_unique`).execute(db);
|
|
2308
|
-
await sql.raw(`ALTER TABLE "_emdash_bylines" DROP CONSTRAINT IF EXISTS _emdash_bylines_slug_locale_unique`).execute(db);
|
|
2309
|
-
await sql.raw(`ALTER TABLE "_emdash_bylines" DROP COLUMN IF EXISTS locale`).execute(db);
|
|
2310
|
-
await sql.raw(`ALTER TABLE "_emdash_bylines" DROP COLUMN IF EXISTS translation_group`).execute(db);
|
|
2311
|
-
await sql.raw(`ALTER TABLE "_emdash_bylines" ADD CONSTRAINT _emdash_bylines_slug_unique UNIQUE (slug)`).execute(db);
|
|
2312
|
-
await sql`
|
|
2313
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${sql.ref("idx_bylines_user_id_unique")}
|
|
2314
|
-
ON _emdash_bylines (user_id) WHERE user_id IS NOT NULL
|
|
2315
|
-
`.execute(db);
|
|
2316
|
-
await sql`
|
|
2317
|
-
ALTER TABLE _emdash_content_bylines
|
|
2318
|
-
ADD CONSTRAINT _emdash_content_bylines_byline_fk
|
|
2319
|
-
FOREIGN KEY (byline_id) REFERENCES _emdash_bylines(id) ON DELETE CASCADE
|
|
2320
|
-
`.execute(db);
|
|
2321
|
-
}
|
|
2322
|
-
async function remapPrimaryBylineIdsDown(db, defaultLocale) {
|
|
2323
|
-
const collections = await listTablesLike(db, "ec_%");
|
|
2324
|
-
for (const table of collections) {
|
|
2325
|
-
validateIdentifier(table, "content table");
|
|
2326
|
-
await sql`
|
|
2327
|
-
UPDATE ${sql.ref(table)}
|
|
2328
|
-
SET primary_byline_id = COALESCE(
|
|
2329
|
-
(SELECT b.id FROM _emdash_bylines b
|
|
2330
|
-
WHERE b.translation_group = ${sql.ref(table)}.primary_byline_id
|
|
2331
|
-
AND b.locale = ${defaultLocale}),
|
|
2332
|
-
primary_byline_id
|
|
2333
|
-
)
|
|
2334
|
-
WHERE primary_byline_id IS NOT NULL
|
|
2335
|
-
`.execute(db);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
async function remapContentBylinesDown(db, defaultLocale) {
|
|
2339
|
-
await sql`
|
|
2340
|
-
UPDATE _emdash_content_bylines
|
|
2341
|
-
SET byline_id = COALESCE(
|
|
2342
|
-
(SELECT b.id FROM _emdash_bylines b
|
|
2343
|
-
WHERE b.translation_group = _emdash_content_bylines.byline_id
|
|
2344
|
-
AND b.locale = ${defaultLocale}),
|
|
2345
|
-
byline_id
|
|
2346
|
-
)
|
|
2347
|
-
`.execute(db);
|
|
2348
|
-
}
|
|
2349
|
-
async function rebuildBylinesDown(db) {
|
|
2350
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_bylines_old"`).execute(db);
|
|
2351
|
-
await db.schema.createTable("_emdash_bylines_old").addColumn("id", "text", (c) => c.primaryKey()).addColumn("slug", "text", (c) => c.notNull().unique()).addColumn("display_name", "text", (c) => c.notNull()).addColumn("bio", "text").addColumn("avatar_media_id", "text", (c) => c.references("media.id").onDelete("set null")).addColumn("website_url", "text").addColumn("user_id", "text", (c) => c.references("users.id").onDelete("set null")).addColumn("is_guest", "integer", (c) => c.notNull().defaultTo(0)).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))).execute();
|
|
2352
|
-
await sql`
|
|
2353
|
-
INSERT INTO _emdash_bylines_old (
|
|
2354
|
-
id, slug, display_name, bio, avatar_media_id, website_url,
|
|
2355
|
-
user_id, is_guest, created_at, updated_at
|
|
2356
|
-
)
|
|
2357
|
-
SELECT
|
|
2358
|
-
id, slug, display_name, bio, avatar_media_id, website_url,
|
|
2359
|
-
user_id, is_guest, created_at, updated_at
|
|
2360
|
-
FROM _emdash_bylines
|
|
2361
|
-
`.execute(db);
|
|
2362
|
-
await db.schema.dropTable("_emdash_bylines").execute();
|
|
2363
|
-
await sql`ALTER TABLE _emdash_bylines_old RENAME TO _emdash_bylines`.execute(db);
|
|
2364
|
-
await db.schema.createIndex("idx_bylines_slug").on("_emdash_bylines").column("slug").execute();
|
|
2365
|
-
await db.schema.createIndex("idx_bylines_display_name").on("_emdash_bylines").column("display_name").execute();
|
|
2366
|
-
await sql`
|
|
2367
|
-
CREATE UNIQUE INDEX ${sql.ref("idx_bylines_user_id_unique")}
|
|
2368
|
-
ON ${sql.ref("_emdash_bylines")} (user_id)
|
|
2369
|
-
WHERE user_id IS NOT NULL
|
|
2370
|
-
`.execute(db);
|
|
2371
|
-
}
|
|
2372
|
-
async function restoreContentBylinesFk(db) {
|
|
2373
|
-
await sql.raw(`DROP TABLE IF EXISTS "_emdash_content_bylines_old"`).execute(db);
|
|
2374
|
-
await db.schema.createTable("_emdash_content_bylines_old").addColumn("id", "text", (c) => c.primaryKey()).addColumn("collection_slug", "text", (c) => c.notNull()).addColumn("content_id", "text", (c) => c.notNull()).addColumn("byline_id", "text", (c) => c.notNull().references("_emdash_bylines.id").onDelete("cascade")).addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0)).addColumn("role_label", "text").addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addUniqueConstraint("content_bylines_unique", [
|
|
2375
|
-
"collection_slug",
|
|
2376
|
-
"content_id",
|
|
2377
|
-
"byline_id"
|
|
2378
|
-
]).execute();
|
|
2379
|
-
await sql`
|
|
2380
|
-
INSERT INTO _emdash_content_bylines_old
|
|
2381
|
-
(id, collection_slug, content_id, byline_id, sort_order, role_label, created_at)
|
|
2382
|
-
SELECT id, collection_slug, content_id, byline_id, sort_order, role_label, created_at
|
|
2383
|
-
FROM _emdash_content_bylines
|
|
2384
|
-
`.execute(db);
|
|
2385
|
-
await db.schema.dropTable("_emdash_content_bylines").execute();
|
|
2386
|
-
await sql`ALTER TABLE _emdash_content_bylines_old RENAME TO _emdash_content_bylines`.execute(db);
|
|
2387
|
-
await db.schema.createIndex("idx_content_bylines_content").on("_emdash_content_bylines").columns([
|
|
2388
|
-
"collection_slug",
|
|
2389
|
-
"content_id",
|
|
2390
|
-
"sort_order"
|
|
2391
|
-
]).execute();
|
|
2392
|
-
await db.schema.createIndex("idx_content_bylines_byline").on("_emdash_content_bylines").column("byline_id").execute();
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
//#endregion
|
|
2396
|
-
//#region src/database/migrations/041_content_locale_list_index.ts
|
|
2397
|
-
var _041_content_locale_list_index_exports = /* @__PURE__ */ __exportAll({
|
|
2398
|
-
down: () => down$33,
|
|
2399
|
-
up: () => up$33
|
|
2400
|
-
});
|
|
2401
|
-
/**
|
|
2402
|
-
* Migration: locale-aware composite indexes for content list queries.
|
|
2403
|
-
*
|
|
2404
|
-
* Addresses GitHub issue #1219. When i18n is enabled the admin content list
|
|
2405
|
-
* filters by `locale` and orders by `updated_at`/`created_at`. The existing
|
|
2406
|
-
* composite indexes (033/034) cover `(deleted_at, updated_at DESC, id DESC)`
|
|
2407
|
-
* etc. but omit `locale`, so a locale-filtered ordered list can't be served
|
|
2408
|
-
* by a single index on large tables. These indexes restore index-only paging
|
|
2409
|
-
* for the locale-scoped case.
|
|
2410
|
-
*
|
|
2411
|
-
* Forward-only and idempotent (`IF NOT EXISTS`).
|
|
2412
|
-
*
|
|
2413
|
-
* Index names use a short `loc_upd`/`loc_crt` suffix rather than spelling out
|
|
2414
|
-
* `deleted_locale_updated_id`: Postgres truncates identifiers to 63 bytes, and
|
|
2415
|
-
* the longer form pushes the `updated`/`created` discriminator past byte 63 for
|
|
2416
|
-
* slugs as short as 40 chars, making both names truncate to the same string.
|
|
2417
|
-
* Keep these identical to the names in `schema/registry.ts`.
|
|
2418
|
-
*/
|
|
2419
|
-
async function up$33(db) {
|
|
2420
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
2421
|
-
for (const tableName of tableNames) {
|
|
2422
|
-
await sql`
|
|
2423
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_loc_upd`)}
|
|
2424
|
-
ON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)
|
|
2425
|
-
`.execute(db);
|
|
2426
|
-
await sql`
|
|
2427
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_loc_crt`)}
|
|
2428
|
-
ON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)
|
|
2429
|
-
`.execute(db);
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
async function down$33(db) {
|
|
2433
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
2434
|
-
for (const tableName of tableNames) {
|
|
2435
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_loc_upd`)}`.execute(db);
|
|
2436
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_loc_crt`)}`.execute(db);
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
//#endregion
|
|
2441
|
-
//#region src/database/migrations/042_byline_fields.ts
|
|
2442
|
-
var _042_byline_fields_exports = /* @__PURE__ */ __exportAll({
|
|
2443
|
-
down: () => down$32,
|
|
2444
|
-
up: () => up$32
|
|
2445
|
-
});
|
|
2446
|
-
/**
|
|
2447
|
-
* Byline custom fields (Discussion #1174). Adds three tables and a
|
|
2448
|
-
* version-counter row in `options`. Purely additive: no change to
|
|
2449
|
-
* `_emdash_bylines` columns landed by migration 040, no change to
|
|
2450
|
-
* `_emdash_content_bylines`.
|
|
2451
|
-
*
|
|
2452
|
-
* Storage model (D11 in the design spec):
|
|
2453
|
-
*
|
|
2454
|
-
* - `_emdash_byline_fields` — definitions. One row per registered field.
|
|
2455
|
-
* - `_emdash_byline_field_values` — translatable values, keyed by
|
|
2456
|
-
* `(byline_id, field_id)`. One value per locale variant of a byline.
|
|
2457
|
-
* - `_emdash_byline_field_group_values` — non-translatable values, keyed
|
|
2458
|
-
* by `(translation_group, field_id)`. One value shared across every
|
|
2459
|
-
* locale variant of the byline's translation group.
|
|
2460
|
-
*
|
|
2461
|
-
* The per-field `translatable` flag (column on `_emdash_byline_fields`)
|
|
2462
|
-
* decides which value table a field writes to. The split is at the
|
|
2463
|
-
* storage level rather than per-row so a single SELECT … IN per locale
|
|
2464
|
-
* bucket suffices for batched hydration (see Phase 3 of the PR plan).
|
|
2465
|
-
*
|
|
2466
|
-
* `options('byline_fields_version', '0')` is the version counter the
|
|
2467
|
-
* field-definitions cache reads (Phase 3). Every mutation on
|
|
2468
|
-
* `_emdash_byline_fields` bumps this row; cached defs invalidate on a
|
|
2469
|
-
* mismatch. Storing it in `options` (the same table `settings/index.ts`
|
|
2470
|
-
* uses) means we get the request-cache + persisted-version pattern for
|
|
2471
|
-
* free — no new infrastructure.
|
|
2472
|
-
*
|
|
2473
|
-
* Idempotency: every `CREATE TABLE` and `CREATE INDEX` uses
|
|
2474
|
-
* `.ifNotExists()`, so a partial prior run (a crash mid-migration, retried
|
|
2475
|
-
* after the runner's race-recovery path or after a manual fix) re-applies
|
|
2476
|
-
* cleanly — including any indexes that landed in the failed pass after the
|
|
2477
|
-
* table itself. A coarse table-level guard would skip the index step if the
|
|
2478
|
-
* table existed but indexes didn't (the realistic crash window between
|
|
2479
|
-
* `CREATE TABLE` and the next `CREATE INDEX`). The runner records applied
|
|
2480
|
-
* migrations only after a successful pass, so a crashed-then-retried `up()`
|
|
2481
|
-
* is the normal recovery path here.
|
|
2482
|
-
*
|
|
2483
|
-
* D1 has no transactions — the schema builder and `INSERT` for the
|
|
2484
|
-
* version row execute one statement at a time. Order matters: parent
|
|
2485
|
-
* tables first (`_emdash_byline_fields`), then the value tables that
|
|
2486
|
-
* reference it. If `down()` runs after a partial `up()`, missing tables
|
|
2487
|
-
* are tolerated (`DROP TABLE IF EXISTS`).
|
|
2488
|
-
*/
|
|
2489
|
-
async function up$32(db) {
|
|
2490
|
-
await db.schema.createTable("_emdash_byline_fields").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("slug", "text", (col) => col.notNull().unique()).addColumn("label", "text", (col) => col.notNull()).addColumn("type", "text", (col) => col.notNull()).addColumn("required", "integer", (col) => col.notNull().defaultTo(0)).addColumn("translatable", "integer", (col) => col.notNull().defaultTo(1)).addColumn("validation", "text").addColumn("sort_order", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
2491
|
-
await db.schema.createIndex("idx__emdash_byline_fields_sort_order").ifNotExists().on("_emdash_byline_fields").column("sort_order").execute();
|
|
2492
|
-
await db.schema.createTable("_emdash_byline_field_values").ifNotExists().addColumn("byline_id", "text", (col) => col.notNull().references("_emdash_bylines.id").onDelete("cascade")).addColumn("field_id", "text", (col) => col.notNull().references("_emdash_byline_fields.id").onDelete("cascade")).addColumn("value", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("_emdash_byline_field_values_pk", ["byline_id", "field_id"]).execute();
|
|
2493
|
-
await db.schema.createIndex("idx__emdash_byline_field_values_byline").ifNotExists().on("_emdash_byline_field_values").column("byline_id").execute();
|
|
2494
|
-
await db.schema.createIndex("idx__emdash_byline_field_values_field").ifNotExists().on("_emdash_byline_field_values").column("field_id").execute();
|
|
2495
|
-
await db.schema.createTable("_emdash_byline_field_group_values").ifNotExists().addColumn("translation_group", "text", (col) => col.notNull()).addColumn("field_id", "text", (col) => col.notNull().references("_emdash_byline_fields.id").onDelete("cascade")).addColumn("value", "text").addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("_emdash_byline_field_group_values_pk", ["translation_group", "field_id"]).execute();
|
|
2496
|
-
await db.schema.createIndex("idx__emdash_byline_field_group_values_group").ifNotExists().on("_emdash_byline_field_group_values").column("translation_group").execute();
|
|
2497
|
-
await db.schema.createIndex("idx__emdash_byline_field_group_values_field").ifNotExists().on("_emdash_byline_field_group_values").column("field_id").execute();
|
|
2498
|
-
await sql`
|
|
2499
|
-
INSERT INTO options (name, value) VALUES ('byline_fields_version', '0')
|
|
2500
|
-
ON CONFLICT (name) DO NOTHING
|
|
2501
|
-
`.execute(db);
|
|
2502
|
-
}
|
|
2503
|
-
/**
|
|
2504
|
-
* Reverses the schema additions and removes the version-counter row.
|
|
2505
|
-
* Used by the test suite and by `rollbackMigration`; the runner itself
|
|
2506
|
-
* never invokes `down()` automatically.
|
|
2507
|
-
*
|
|
2508
|
-
* The drops are unconditional `IF EXISTS` so a `down()` after a partial
|
|
2509
|
-
* `up()` (only one or two tables landed) still settles the database back
|
|
2510
|
-
* to its pre-042 state.
|
|
2511
|
-
*/
|
|
2512
|
-
async function down$32(db) {
|
|
2513
|
-
await db.schema.dropTable("_emdash_byline_field_group_values").ifExists().execute();
|
|
2514
|
-
await db.schema.dropTable("_emdash_byline_field_values").ifExists().execute();
|
|
2515
|
-
await db.schema.dropTable("_emdash_byline_fields").ifExists().execute();
|
|
2516
|
-
await sql`DELETE FROM options WHERE name = 'byline_fields_version'`.execute(db);
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
//#endregion
|
|
2520
|
-
//#region src/database/migrations/043_content_references.ts
|
|
2521
|
-
var _043_content_references_exports = /* @__PURE__ */ __exportAll({
|
|
2522
|
-
down: () => down$31,
|
|
2523
|
-
up: () => up$31
|
|
2524
|
-
});
|
|
2525
|
-
/**
|
|
2526
|
-
* Content references.
|
|
2527
|
-
*
|
|
2528
|
-
* `_emdash_relations` defines relationship types (row-per-locale, mirroring
|
|
2529
|
-
* `_emdash_taxonomy_defs`): which collection is the parent, which is the child
|
|
2530
|
-
* (the side that may multiply), and localized labels for each role.
|
|
2531
|
-
*
|
|
2532
|
-
* `_emdash_content_references` holds directed `parent → child` edges between
|
|
2533
|
-
* content entries. Both endpoints and the relation are referenced by
|
|
2534
|
-
* `translation_group`, so edges are locale-agnostic. As with
|
|
2535
|
-
* `content_taxonomies`, group-linking precludes SQL foreign keys; referential
|
|
2536
|
-
* cleanup is an application-layer concern.
|
|
2537
|
-
*
|
|
2538
|
-
* Idempotency: every `CREATE TABLE` and `CREATE INDEX` uses `.ifNotExists()`,
|
|
2539
|
-
* so a partial prior run (a crash mid-migration, or a retry after the runner's
|
|
2540
|
-
* race-recovery path) re-applies cleanly — including any indexes that landed in
|
|
2541
|
-
* the failed pass after their table.
|
|
2542
|
-
*/
|
|
2543
|
-
async function up$31(db) {
|
|
2544
|
-
const defaultLocale = getI18nConfig()?.defaultLocale ?? "en";
|
|
2545
|
-
await db.schema.createTable("_emdash_relations").ifNotExists().addColumn("id", "text", (c) => c.primaryKey()).addColumn("name", "text", (c) => c.notNull()).addColumn("parent_collection", "text", (c) => c.notNull()).addColumn("child_collection", "text", (c) => c.notNull()).addColumn("parent_label", "text", (c) => c.notNull()).addColumn("child_label", "text", (c) => c.notNull()).addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)).addColumn("translation_group", "text", (c) => c.notNull()).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addUniqueConstraint("_emdash_relations_name_locale_unique", ["name", "locale"]).execute();
|
|
2546
|
-
await db.schema.createIndex("idx__emdash_relations_locale").ifNotExists().on("_emdash_relations").column("locale").execute();
|
|
2547
|
-
await db.schema.createIndex("idx__emdash_relations_translation_group").ifNotExists().on("_emdash_relations").column("translation_group").execute();
|
|
2548
|
-
await db.schema.createIndex("idx__emdash_relations_parent_collection").ifNotExists().on("_emdash_relations").column("parent_collection").execute();
|
|
2549
|
-
await db.schema.createIndex("idx__emdash_relations_child_collection").ifNotExists().on("_emdash_relations").column("child_collection").execute();
|
|
2550
|
-
await db.schema.createIndex("idx__emdash_relations_group_locale_unique").ifNotExists().unique().on("_emdash_relations").columns(["translation_group", "locale"]).execute();
|
|
2551
|
-
await db.schema.createTable("_emdash_content_references").ifNotExists().addColumn("id", "text", (c) => c.primaryKey()).addColumn("relation_group", "text", (c) => c.notNull()).addColumn("parent_group", "text", (c) => c.notNull()).addColumn("child_group", "text", (c) => c.notNull()).addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0)).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addUniqueConstraint("content_references_unique", [
|
|
2552
|
-
"relation_group",
|
|
2553
|
-
"parent_group",
|
|
2554
|
-
"child_group"
|
|
2555
|
-
]).execute();
|
|
2556
|
-
await db.schema.createIndex("idx__emdash_content_references_parent").ifNotExists().on("_emdash_content_references").columns([
|
|
2557
|
-
"parent_group",
|
|
2558
|
-
"relation_group",
|
|
2559
|
-
"sort_order"
|
|
2560
|
-
]).execute();
|
|
2561
|
-
await db.schema.createIndex("idx__emdash_content_references_child").ifNotExists().on("_emdash_content_references").columns(["child_group", "relation_group"]).execute();
|
|
2562
|
-
await db.schema.createIndex("idx__emdash_content_references_relation").ifNotExists().on("_emdash_content_references").column("relation_group").execute();
|
|
2563
|
-
}
|
|
2564
|
-
async function down$31(db) {
|
|
2565
|
-
await db.schema.dropTable("_emdash_content_references").ifExists().execute();
|
|
2566
|
-
await db.schema.dropTable("_emdash_relations").ifExists().execute();
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
//#endregion
|
|
2570
|
-
//#region src/database/migrations/044_comment_reactions.ts
|
|
2571
|
-
var _044_comment_reactions_exports = /* @__PURE__ */ __exportAll({
|
|
2572
|
-
down: () => down$30,
|
|
2573
|
-
up: () => up$30
|
|
2574
|
-
});
|
|
2575
|
-
/**
|
|
2576
|
-
* Comment reactions (Tier 1 of the best-in-class comments RFC).
|
|
2577
|
-
*
|
|
2578
|
-
* One row per (comment, voter, reaction). Toggle semantics are enforced by the
|
|
2579
|
-
* unique index — a voter either has a given reaction on a comment or doesn't.
|
|
2580
|
-
* `voter_hash` is a salted SHA-256 of the visitor's IP (same primitive as
|
|
2581
|
-
* `_emdash_comments.ip_hash`), so no cleartext identity is stored.
|
|
2582
|
-
*
|
|
2583
|
-
* Additive and backward-compatible: a new table, no change to existing schema.
|
|
2584
|
-
*/
|
|
2585
|
-
async function up$30(db) {
|
|
2586
|
-
await db.schema.createTable("_emdash_comment_reactions").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("comment_id", "text", (col) => col.notNull().references("_emdash_comments.id").onDelete("cascade")).addColumn("reaction", "text", (col) => col.notNull().defaultTo("like")).addColumn("voter_hash", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))).execute();
|
|
2587
|
-
await db.schema.createIndex("idx_comment_reactions_unique").ifNotExists().on("_emdash_comment_reactions").columns([
|
|
2588
|
-
"comment_id",
|
|
2589
|
-
"voter_hash",
|
|
2590
|
-
"reaction"
|
|
2591
|
-
]).unique().execute();
|
|
2592
|
-
await db.schema.createIndex("idx_comment_reactions_comment").ifNotExists().on("_emdash_comment_reactions").column("comment_id").execute();
|
|
2593
|
-
await db.schema.createIndex("idx_comment_reactions_voter").ifNotExists().on("_emdash_comment_reactions").columns(["voter_hash", "created_at"]).execute();
|
|
2594
|
-
}
|
|
2595
|
-
async function down$30(db) {
|
|
2596
|
-
await db.schema.dropTable("_emdash_comment_reactions").execute();
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
//#endregion
|
|
2600
|
-
//#region src/database/migrations/045_taxonomy_parent_group.ts
|
|
2601
|
-
var _045_taxonomy_parent_group_exports = /* @__PURE__ */ __exportAll({
|
|
2602
|
-
down: () => down$29,
|
|
2603
|
-
up: () => up$29
|
|
2604
|
-
});
|
|
2605
|
-
/**
|
|
2606
|
-
* Store the parent's `translation_group` in `taxonomies.parent_id` instead of a
|
|
2607
|
-
* locale-bound row id.
|
|
2608
|
-
*
|
|
2609
|
-
* Before this, a term's `parent_id` pointed at one specific parent *row* (one
|
|
2610
|
-
* locale). A child translated in another locale could end up parented to a row
|
|
2611
|
-
* in a different locale, silently flattening that locale's tree (#1347). Every
|
|
2612
|
-
* other taxonomy i18n relationship already keys off `translation_group`
|
|
2613
|
-
* (`content_taxonomies.taxonomy_id`, `_emdash_menu_items.reference_id`), so this
|
|
2614
|
-
* aligns parentage with that model: a child links to the parent *concept* and
|
|
2615
|
-
* stays nested in whichever locale that parent has been translated into.
|
|
2616
|
-
*
|
|
2617
|
-
* Backfill rewrites every existing `parent_id` from the referenced parent row's
|
|
2618
|
-
* id to that parent's `translation_group`. A `translation_group` normally equals
|
|
2619
|
-
* its anchor row's id, so the self-FK on `parent_id` stays valid. We only
|
|
2620
|
-
* rewrite when that anchor row still exists; if a parent's anchor was deleted
|
|
2621
|
-
* but a sibling translation survives, the existing locale-bound id is left as-is
|
|
2622
|
-
* rather than rewritten to a dangling FK value. Such a child then renders as a
|
|
2623
|
-
* root rather than nested — an accepted degradation for an already-inconsistent
|
|
2624
|
-
* dataset (its `translation_group` already points at a deleted row, which breaks
|
|
2625
|
-
* `content_taxonomies` joins too). New deletes can't recreate this state: a
|
|
2626
|
-
* parent with children in any locale is undeletable. The correlated subquery is
|
|
2627
|
-
* a no-op for rows that already hold a group (an anchor row's id resolves to
|
|
2628
|
-
* itself), so the migration is safe to re-run.
|
|
2629
|
-
*
|
|
2630
|
-
* Dialect-independent: the correlated-subquery `UPDATE` runs on SQLite (incl.
|
|
2631
|
-
* D1) and Postgres alike.
|
|
2632
|
-
*/
|
|
2633
|
-
async function up$29(db) {
|
|
2634
|
-
await sql`
|
|
2635
|
-
UPDATE taxonomies
|
|
2636
|
-
SET parent_id = (
|
|
2637
|
-
SELECT p.translation_group FROM taxonomies p WHERE p.id = taxonomies.parent_id
|
|
2638
|
-
)
|
|
2639
|
-
WHERE parent_id IS NOT NULL
|
|
2640
|
-
AND EXISTS (
|
|
2641
|
-
SELECT 1 FROM taxonomies p
|
|
2642
|
-
WHERE p.id = taxonomies.parent_id
|
|
2643
|
-
AND p.translation_group IS NOT NULL
|
|
2644
|
-
-- Only rewrite to a translation_group that is itself a live row
|
|
2645
|
-
-- id, so the self-FK on parent_id can never dangle.
|
|
2646
|
-
AND EXISTS (SELECT 1 FROM taxonomies a WHERE a.id = p.translation_group)
|
|
2647
|
-
)
|
|
2648
|
-
`.execute(db);
|
|
2649
|
-
}
|
|
2650
|
-
/**
|
|
2651
|
-
* Map each `parent_id` group back to the row id of the parent in the same
|
|
2652
|
-
* locale (the pre-migration shape). Rows with no same-locale parent are left as
|
|
2653
|
-
* they are rather than nulled, so a rollback never silently detaches a subtree.
|
|
2654
|
-
*/
|
|
2655
|
-
async function down$29(db) {
|
|
2656
|
-
await sql`
|
|
2657
|
-
UPDATE taxonomies
|
|
2658
|
-
SET parent_id = COALESCE(
|
|
2659
|
-
(
|
|
2660
|
-
SELECT c.id FROM taxonomies c
|
|
2661
|
-
WHERE c.translation_group = taxonomies.parent_id
|
|
2662
|
-
AND c.locale = taxonomies.locale
|
|
2663
|
-
),
|
|
2664
|
-
parent_id
|
|
2665
|
-
)
|
|
2666
|
-
WHERE parent_id IS NOT NULL
|
|
2667
|
-
`.execute(db);
|
|
2668
|
-
}
|
|
2669
|
-
|
|
2670
|
-
//#endregion
|
|
2671
|
-
//#region src/database/migrations/046_media_usage_index.ts
|
|
2672
|
-
var _046_media_usage_index_exports = /* @__PURE__ */ __exportAll({
|
|
2673
|
-
down: () => down$28,
|
|
2674
|
-
up: () => up$28
|
|
2675
|
-
});
|
|
2676
|
-
/**
|
|
2677
|
-
* Internal media usage projection tables.
|
|
2678
|
-
*
|
|
2679
|
-
* This migration is DDL-only by design: no backfill, no runtime media/indexing
|
|
2680
|
-
* imports, and no content-table scans. Production rows are introduced by later
|
|
2681
|
-
* phases once the central snapshot/indexer path is reviewed.
|
|
2682
|
-
*/
|
|
2683
|
-
async function up$28(db) {
|
|
2684
|
-
await db.schema.createTable("_emdash_media_usage_sources").ifNotExists().addColumn("source_key", "text", (c) => c.primaryKey()).addColumn("source_type", "text", (c) => c.notNull()).addColumn("collection_slug", "text").addColumn("content_id", "text").addColumn("source_variant", "text", (c) => c.notNull()).addColumn("locale", "text").addColumn("translation_group", "text").addColumn("content_slug", "text").addColumn("content_title", "text").addColumn("content_status", "text").addColumn("content_scheduled_at", "text").addColumn("content_deleted_at", "text").addColumn("revision_id", "text").addColumn("current_generation", "text", (c) => c.notNull()).addColumn("schema_version", "integer", (c) => c.notNull().defaultTo(1)).addColumn("indexed_at", "text", (c) => c.notNull().defaultTo(currentTimestamp(db))).addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))).execute();
|
|
2685
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_content").ifNotExists().on("_emdash_media_usage_sources").columns([
|
|
2686
|
-
"source_type",
|
|
2687
|
-
"collection_slug",
|
|
2688
|
-
"content_id"
|
|
2689
|
-
]).execute();
|
|
2690
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_variant").ifNotExists().on("_emdash_media_usage_sources").columns(["source_type", "source_variant"]).execute();
|
|
2691
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_locale").ifNotExists().on("_emdash_media_usage_sources").columns(["collection_slug", "locale"]).execute();
|
|
2692
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_deleted").ifNotExists().on("_emdash_media_usage_sources").column("content_deleted_at").execute();
|
|
2693
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_translation_group").ifNotExists().on("_emdash_media_usage_sources").columns(["collection_slug", "translation_group"]).execute();
|
|
2694
|
-
await db.schema.createTable("_emdash_media_usage").ifNotExists().addColumn("id", "text", (c) => c.primaryKey()).addColumn("source_key", "text", (c) => c.notNull()).addColumn("generation", "text", (c) => c.notNull()).addColumn("field_slug", "text", (c) => c.notNull()).addColumn("field_path", "text", (c) => c.notNull()).addColumn("occurrence_index", "integer", (c) => c.notNull().defaultTo(0)).addColumn("reference_type", "text", (c) => c.notNull()).addColumn("media_id", "text").addColumn("provider", "text", (c) => c.notNull().defaultTo("local")).addColumn("provider_asset_id", "text", (c) => c.notNull()).addColumn("media_kind", "text").addColumn("mime_type", "text").addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))).execute();
|
|
2695
|
-
await db.schema.createIndex("idx__emdash_media_usage_unique_occurrence").ifNotExists().unique().on("_emdash_media_usage").columns([
|
|
2696
|
-
"source_key",
|
|
2697
|
-
"generation",
|
|
2698
|
-
"field_path",
|
|
2699
|
-
"occurrence_index"
|
|
2700
|
-
]).execute();
|
|
2701
|
-
await db.schema.createIndex("idx__emdash_media_usage_media_id").ifNotExists().on("_emdash_media_usage").column("media_id").execute();
|
|
2702
|
-
await db.schema.createIndex("idx__emdash_media_usage_provider_asset").ifNotExists().on("_emdash_media_usage").columns(["provider", "provider_asset_id"]).execute();
|
|
2703
|
-
await db.schema.createIndex("idx__emdash_media_usage_source_generation").ifNotExists().on("_emdash_media_usage").columns(["source_key", "generation"]).execute();
|
|
2704
|
-
}
|
|
2705
|
-
async function down$28(db) {
|
|
2706
|
-
await db.schema.dropTable("_emdash_media_usage").ifExists().execute();
|
|
2707
|
-
await db.schema.dropTable("_emdash_media_usage_sources").ifExists().execute();
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
//#endregion
|
|
2711
|
-
//#region src/database/migrations/047_restore_taxonomy_parent_index.ts
|
|
2712
|
-
var _047_restore_taxonomy_parent_index_exports = /* @__PURE__ */ __exportAll({
|
|
2713
|
-
down: () => down$27,
|
|
2714
|
-
up: () => up$27
|
|
2715
|
-
});
|
|
2716
|
-
/**
|
|
2717
|
-
* Restore `idx_taxonomies_parent` on `taxonomies(parent_id)` (#1665).
|
|
2718
|
-
*
|
|
2719
|
-
* Migration 015 created this index, but 036's SQLite table rebuild
|
|
2720
|
-
* (`taxonomies_new` → drop → rename) dropped every index attached to the old
|
|
2721
|
-
* table and only recreated `name`/`locale`/`translation_group`. The parent
|
|
2722
|
-
* index — which backs hierarchical (parent/child) lookups — was silently lost
|
|
2723
|
-
* on any install migrated through 036. The Postgres path in 036 alters the
|
|
2724
|
-
* table in place and keeps its indexes, so it is unaffected; `ifNotExists`
|
|
2725
|
-
* makes this a no-op there.
|
|
2726
|
-
*
|
|
2727
|
-
* Forward-only: 036 has already shipped, so existing installs only recover the
|
|
2728
|
-
* index here. Fresh installs also get it in 036's rebuild for parity with the
|
|
2729
|
-
* other rebuilt tables, making this a no-op for them.
|
|
2730
|
-
*/
|
|
2731
|
-
async function up$27(db) {
|
|
2732
|
-
await db.schema.createIndex("idx_taxonomies_parent").ifNotExists().on("taxonomies").column("parent_id").execute();
|
|
2733
|
-
}
|
|
2734
|
-
async function down$27(db) {
|
|
2735
|
-
await db.schema.dropIndex("idx_taxonomies_parent").ifExists().execute();
|
|
2736
|
-
}
|
|
2737
|
-
|
|
2738
|
-
//#endregion
|
|
2739
|
-
//#region src/database/migrations/048_restore_content_taxonomies_term_index.ts
|
|
2740
|
-
var _048_restore_content_taxonomies_term_index_exports = /* @__PURE__ */ __exportAll({
|
|
2741
|
-
down: () => down$26,
|
|
2742
|
-
up: () => up$26
|
|
2743
|
-
});
|
|
2744
|
-
/**
|
|
2745
|
-
* Restore `idx_content_taxonomies_term` on `content_taxonomies(taxonomy_id)` (#1701).
|
|
2746
|
-
*
|
|
2747
|
-
* Migration 015 created this index. 036's SQLite/D1 table rebuild recreated it,
|
|
2748
|
-
* but the recreate was gated behind an FK-presence early-return: if a first run
|
|
2749
|
-
* committed the rebuild (stripping the FK and dropping the old index) and then
|
|
2750
|
-
* failed before the recreate, the retry saw zero FKs, skipped the whole body,
|
|
2751
|
-
* and never restored the index — while 036 was still recorded as applied. The
|
|
2752
|
-
* result was an install missing only this one index, with no forward migration
|
|
2753
|
-
* to recover it (the sibling `idx_taxonomies_parent` case was fixed by 047).
|
|
2754
|
-
*
|
|
2755
|
-
* 036 has since been fixed to recreate the index unconditionally, so fresh
|
|
2756
|
-
* installs are fine. This recovers the index on installs that hit the trap
|
|
2757
|
-
* before that fix. The Postgres path in 036 alters in place and keeps its
|
|
2758
|
-
* indexes, so it is unaffected; `ifNotExists` makes this a no-op there.
|
|
2759
|
-
*
|
|
2760
|
-
* Forward-only.
|
|
2761
|
-
*/
|
|
2762
|
-
async function up$26(db) {
|
|
2763
|
-
await db.schema.createIndex("idx_content_taxonomies_term").ifNotExists().on("content_taxonomies").column("taxonomy_id").execute();
|
|
2764
|
-
}
|
|
2765
|
-
async function down$26(db) {
|
|
2766
|
-
await db.schema.dropIndex("idx_content_taxonomies_term").ifExists().execute();
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
//#endregion
|
|
2770
|
-
//#region src/database/migrations/049_taxonomies_name_locale_index.ts
|
|
2771
|
-
var _049_taxonomies_name_locale_index_exports = /* @__PURE__ */ __exportAll({
|
|
2772
|
-
down: () => down$25,
|
|
2773
|
-
up: () => up$25
|
|
2774
|
-
});
|
|
2775
|
-
/**
|
|
2776
|
-
* Add composite `idx_taxonomies_name_locale` on `taxonomies(name, locale)` (#1723).
|
|
2777
|
-
*
|
|
2778
|
-
* `TaxonomyRepository.findByName` filters `WHERE name = ? AND locale = ?`, but
|
|
2779
|
-
* the only indexes were the single-column `idx_taxonomies_name(name)` and
|
|
2780
|
-
* `idx_taxonomies_locale(locale)`. On SQLite/D1 with no `sqlite_stat1` the
|
|
2781
|
-
* planner picks `idx_taxonomies_locale` and reads *every* term in the locale,
|
|
2782
|
-
* filtering `name` in memory — once per facet rendered. On a site where one
|
|
2783
|
-
* taxonomy dominates a locale, each small facet still walks the whole locale.
|
|
2784
|
-
*
|
|
2785
|
-
* The composite resolves both equalities, so the planner searches only the one
|
|
2786
|
-
* taxonomy's terms. Its leftmost prefix (`name`) also serves every name-only
|
|
2787
|
-
* lookup, so it supersedes `idx_taxonomies_name`, which we drop. The
|
|
2788
|
-
* single-column `idx_taxonomies_locale` stays for locale-only lookups.
|
|
2789
|
-
*
|
|
2790
|
-
* Strictly additive: no query changes, and the dropped index is fully covered
|
|
2791
|
-
* by the new composite's prefix. Create-before-drop keeps name lookups indexed
|
|
2792
|
-
* at every point. Both statements are idempotent so a partial apply can retry.
|
|
2793
|
-
*/
|
|
2794
|
-
async function up$25(db) {
|
|
2795
|
-
await db.schema.createIndex("idx_taxonomies_name_locale").ifNotExists().on("taxonomies").columns(["name", "locale"]).execute();
|
|
2796
|
-
await db.schema.dropIndex("idx_taxonomies_name").ifExists().execute();
|
|
2797
|
-
}
|
|
2798
|
-
async function down$25(db) {
|
|
2799
|
-
await db.schema.createIndex("idx_taxonomies_name").ifNotExists().on("taxonomies").column("name").execute();
|
|
2800
|
-
await db.schema.dropIndex("idx_taxonomies_name_locale").ifExists().execute();
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
//#endregion
|
|
2804
|
-
//#region src/database/migrations/050_media_usage_index_status.ts
|
|
2805
|
-
var _050_media_usage_index_status_exports = /* @__PURE__ */ __exportAll({
|
|
2806
|
-
down: () => down$24,
|
|
2807
|
-
up: () => up$24
|
|
2808
|
-
});
|
|
2809
|
-
const SOURCE_TABLE = "_emdash_media_usage_sources";
|
|
2810
|
-
const DUPLICATE_COLUMN_RE$3 = /(?:duplicate column|column .* already exists|already exists.*column)/i;
|
|
2811
|
-
/**
|
|
2812
|
-
* Media usage index metadata.
|
|
2813
|
-
*
|
|
2814
|
-
* DDL-only by design: adds freshness/completeness metadata and scope status
|
|
2815
|
-
* tracking for later reference-index reconciliation. No content scans, no
|
|
2816
|
-
* runtime imports, and no backfill.
|
|
2817
|
-
*/
|
|
2818
|
-
async function up$24(db) {
|
|
2819
|
-
await addSourceMetadataColumns(db);
|
|
2820
|
-
await db.schema.createTable("_emdash_media_usage_index_status").ifNotExists().addColumn("adapter_id", "text", (c) => c.notNull()).addColumn("scope_type", "text", (c) => c.notNull()).addColumn("scope_key", "text", (c) => c.notNull()).addColumn("status", "text", (c) => c.notNull()).addColumn("schema_version", "integer", (c) => c.notNull().defaultTo(1)).addColumn("started_at", "text").addColumn("completed_at", "text").addColumn("cursor", "text").addColumn("indexed_source_count", "integer", (c) => c.notNull().defaultTo(0)).addColumn("failed_source_count", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_error_code", "text").addColumn("updated_at", "text", (c) => c.notNull().defaultTo(currentTimestamp(db))).addPrimaryKeyConstraint("_emdash_media_usage_index_status_pk", [
|
|
2821
|
-
"adapter_id",
|
|
2822
|
-
"scope_type",
|
|
2823
|
-
"scope_key"
|
|
2824
|
-
]).execute();
|
|
2825
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_completeness").ifNotExists().on("_emdash_media_usage_sources").columns([
|
|
2826
|
-
"source_type",
|
|
2827
|
-
"collection_slug",
|
|
2828
|
-
"source_completeness"
|
|
2829
|
-
]).execute();
|
|
2830
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_fingerprint").ifNotExists().on("_emdash_media_usage_sources").column("source_fingerprint").execute();
|
|
2831
|
-
await db.schema.createIndex("idx__emdash_media_usage_index_status_status").ifNotExists().on("_emdash_media_usage_index_status").columns(["adapter_id", "status"]).execute();
|
|
2832
|
-
}
|
|
2833
|
-
async function down$24(db) {
|
|
2834
|
-
await db.schema.dropIndex("idx__emdash_media_usage_index_status_status").ifExists().execute();
|
|
2835
|
-
await db.schema.dropIndex("idx__emdash_media_usage_sources_fingerprint").ifExists().execute();
|
|
2836
|
-
await db.schema.dropIndex("idx__emdash_media_usage_sources_completeness").ifExists().execute();
|
|
2837
|
-
await db.schema.dropTable("_emdash_media_usage_index_status").ifExists().execute();
|
|
2838
|
-
await dropSourceMetadataColumns(db);
|
|
2839
|
-
}
|
|
2840
|
-
async function addSourceMetadataColumns(db) {
|
|
2841
|
-
await addColumnIfMissing$1(db, "source_updated_at", () => db.schema.alterTable(SOURCE_TABLE).addColumn("source_updated_at", "text").execute());
|
|
2842
|
-
await addColumnIfMissing$1(db, "source_version", () => db.schema.alterTable(SOURCE_TABLE).addColumn("source_version", "integer").execute());
|
|
2843
|
-
await addColumnIfMissing$1(db, "source_fingerprint", () => db.schema.alterTable(SOURCE_TABLE).addColumn("source_fingerprint", "text").execute());
|
|
2844
|
-
await addColumnIfMissing$1(db, "source_completeness", () => db.schema.alterTable(SOURCE_TABLE).addColumn("source_completeness", "text", (c) => c.notNull().defaultTo("unknown")).execute());
|
|
2845
|
-
await addColumnIfMissing$1(db, "last_attempted_at", () => db.schema.alterTable(SOURCE_TABLE).addColumn("last_attempted_at", "text").execute());
|
|
2846
|
-
await addColumnIfMissing$1(db, "last_error_code", () => db.schema.alterTable(SOURCE_TABLE).addColumn("last_error_code", "text").execute());
|
|
2847
|
-
}
|
|
2848
|
-
async function addColumnIfMissing$1(db, columnName, addColumn) {
|
|
2849
|
-
if (await columnExists(db, SOURCE_TABLE, columnName)) return;
|
|
2850
|
-
try {
|
|
2851
|
-
await addColumn();
|
|
2852
|
-
} catch (error) {
|
|
2853
|
-
if (DUPLICATE_COLUMN_RE$3.test(deepErrorMessage$4(error))) {
|
|
2854
|
-
if (await columnExists(db, SOURCE_TABLE, columnName)) return;
|
|
2855
|
-
}
|
|
2856
|
-
throw error;
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
function deepErrorMessage$4(error) {
|
|
2860
|
-
if (error instanceof Error) {
|
|
2861
|
-
const own = error.message ?? "";
|
|
2862
|
-
if (error.cause) {
|
|
2863
|
-
const causeMessage = deepErrorMessage$4(error.cause);
|
|
2864
|
-
return own ? `${own}: ${causeMessage}` : causeMessage;
|
|
2865
|
-
}
|
|
2866
|
-
return own;
|
|
2867
|
-
}
|
|
2868
|
-
if (typeof error === "string") return error;
|
|
2869
|
-
try {
|
|
2870
|
-
return JSON.stringify(error);
|
|
2871
|
-
} catch {
|
|
2872
|
-
return String(error);
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
async function dropSourceMetadataColumns(db) {
|
|
2876
|
-
for (const columnName of [
|
|
2877
|
-
"last_error_code",
|
|
2878
|
-
"last_attempted_at",
|
|
2879
|
-
"source_completeness",
|
|
2880
|
-
"source_fingerprint",
|
|
2881
|
-
"source_version",
|
|
2882
|
-
"source_updated_at"
|
|
2883
|
-
]) if (await columnExists(db, SOURCE_TABLE, columnName)) await db.schema.alterTable(SOURCE_TABLE).dropColumn(columnName).execute();
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
//#endregion
|
|
2887
|
-
//#region src/database/migrations/051_content_taxonomies_denorm.ts
|
|
2888
|
-
var _051_content_taxonomies_denorm_exports = /* @__PURE__ */ __exportAll({
|
|
2889
|
-
down: () => down$23,
|
|
2890
|
-
up: () => up$23
|
|
2891
|
-
});
|
|
2892
|
-
/**
|
|
2893
|
-
* Denormalize the filter + sort columns from `ec_*` onto `content_taxonomies`
|
|
2894
|
-
* and mirror `ec_*`'s composite sort indexes onto the pivot (#1834).
|
|
2895
|
-
*
|
|
2896
|
-
* A taxonomy-filtered listing used to apply the term filter as a correlated
|
|
2897
|
-
* `EXISTS` on `SELECT * FROM ec_<collection> … ORDER BY … LIMIT ?`. The pivot
|
|
2898
|
-
* carried only `(collection, entry_id, taxonomy_id)`, so the filter
|
|
2899
|
-
* (`deleted_at`, `status`, `locale`) and the sort (`published_at`/`created_at`)
|
|
2900
|
-
* could only be evaluated on the `ec_*` row. On stats-blind SQLite/D1 a
|
|
2901
|
-
* selective term never fills the `LIMIT`, so the query walked the whole
|
|
2902
|
-
* collection (~75k D1 rows read for a one-row page).
|
|
2903
|
-
*
|
|
2904
|
-
* Copying those columns onto the pivot and indexing them keyed by
|
|
2905
|
-
* `(taxonomy_id, collection, …)` lets the loader seek the term and walk a
|
|
2906
|
-
* sort-ordered pivot index, short-circuiting on `LIMIT`, then touch `ec_*` only
|
|
2907
|
-
* by primary key to hydrate the page.
|
|
2908
|
-
*
|
|
2909
|
-
* The columns are advisory (D1 has no transactions; the write-path re-stamp is
|
|
2910
|
-
* non-atomic), so the read path re-checks the real predicates on the joined
|
|
2911
|
-
* `ec_*` row — see loader.ts. `updated_at` is deliberately not denormalized: it
|
|
2912
|
-
* moves on every edit and is seldom a public sort, so its write cost outweighs
|
|
2913
|
-
* its read value; `updated_at`-sorted taxonomy listings temp-sort instead.
|
|
2914
|
-
*
|
|
2915
|
-
* Order matters: add the columns, backfill, THEN build the indexes so each
|
|
2916
|
-
* index is built once over populated data rather than maintained through the
|
|
2917
|
-
* backfill `UPDATE`.
|
|
2918
|
-
*
|
|
2919
|
-
* Forward-only.
|
|
2920
|
-
*/
|
|
2921
|
-
const DENORM_COLUMNS = [
|
|
2922
|
-
"status",
|
|
2923
|
-
"scheduled_at",
|
|
2924
|
-
"deleted_at",
|
|
2925
|
-
"locale",
|
|
2926
|
-
"published_at",
|
|
2927
|
-
"created_at"
|
|
2928
|
-
];
|
|
2929
|
-
const INDEXES = [
|
|
2930
|
-
{
|
|
2931
|
-
name: "idx_content_taxonomies_pub",
|
|
2932
|
-
columns: "taxonomy_id, collection, deleted_at, published_at DESC, entry_id DESC"
|
|
2933
|
-
},
|
|
2934
|
-
{
|
|
2935
|
-
name: "idx_content_taxonomies_crt",
|
|
2936
|
-
columns: "taxonomy_id, collection, deleted_at, created_at DESC, entry_id DESC"
|
|
2937
|
-
},
|
|
2938
|
-
{
|
|
2939
|
-
name: "idx_content_taxonomies_loc_pub",
|
|
2940
|
-
columns: "taxonomy_id, collection, deleted_at, locale, published_at DESC, entry_id DESC"
|
|
2941
|
-
},
|
|
2942
|
-
{
|
|
2943
|
-
name: "idx_content_taxonomies_loc_crt",
|
|
2944
|
-
columns: "taxonomy_id, collection, deleted_at, locale, created_at DESC, entry_id DESC"
|
|
2945
|
-
}
|
|
2946
|
-
];
|
|
2947
|
-
async function up$23(db) {
|
|
2948
|
-
for (const column of DENORM_COLUMNS) {
|
|
2949
|
-
if (await columnExists(db, "content_taxonomies", column)) continue;
|
|
2950
|
-
await sql`
|
|
2951
|
-
ALTER TABLE content_taxonomies ADD COLUMN ${sql.ref(column)} TEXT
|
|
2952
|
-
`.execute(db);
|
|
2953
|
-
}
|
|
2954
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
2955
|
-
for (const tableName of tableNames) {
|
|
2956
|
-
validateIdentifier(tableName, "content table name");
|
|
2957
|
-
const slug = tableName.slice(3);
|
|
2958
|
-
await sql`
|
|
2959
|
-
UPDATE content_taxonomies
|
|
2960
|
-
SET (status, scheduled_at, deleted_at, locale, published_at, created_at) = (
|
|
2961
|
-
SELECT status, scheduled_at, deleted_at, locale, published_at, created_at
|
|
2962
|
-
FROM ${sql.ref(tableName)}
|
|
2963
|
-
WHERE ${sql.ref(tableName)}.id = content_taxonomies.entry_id
|
|
2964
|
-
)
|
|
2965
|
-
WHERE collection = ${slug}
|
|
2966
|
-
`.execute(db);
|
|
2967
|
-
}
|
|
2968
|
-
for (const index of INDEXES) await sql`
|
|
2969
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(index.name)}
|
|
2970
|
-
ON content_taxonomies (${sql.raw(index.columns)})
|
|
2971
|
-
`.execute(db);
|
|
2972
|
-
}
|
|
2973
|
-
async function down$23(db) {
|
|
2974
|
-
for (const index of INDEXES) await sql`DROP INDEX IF EXISTS ${sql.ref(index.name)}`.execute(db);
|
|
2975
|
-
for (const column of DENORM_COLUMNS) await sql`
|
|
2976
|
-
ALTER TABLE content_taxonomies DROP COLUMN ${sql.ref(column)}
|
|
2977
|
-
`.execute(db);
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
//#endregion
|
|
2981
|
-
//#region src/database/migrations/052_media_usage_read_index.ts
|
|
2982
|
-
var _052_media_usage_read_index_exports = /* @__PURE__ */ __exportAll({
|
|
2983
|
-
down: () => down$22,
|
|
2984
|
-
up: () => up$22
|
|
2985
|
-
});
|
|
2986
|
-
const OLD_INDEX = "idx__emdash_media_usage_media_id";
|
|
2987
|
-
const READ_INDEX = "idx__emdash_media_usage_media_source_generation";
|
|
2988
|
-
async function up$22(db) {
|
|
2989
|
-
await db.schema.createIndex(READ_INDEX).ifNotExists().on("_emdash_media_usage").columns([
|
|
2990
|
-
"media_id",
|
|
2991
|
-
"source_key",
|
|
2992
|
-
"generation"
|
|
2993
|
-
]).execute();
|
|
2994
|
-
await db.schema.dropIndex(OLD_INDEX).ifExists().execute();
|
|
2995
|
-
}
|
|
2996
|
-
async function down$22(db) {
|
|
2997
|
-
await db.schema.createIndex(OLD_INDEX).ifNotExists().on("_emdash_media_usage").column("media_id").execute();
|
|
2998
|
-
await db.schema.dropIndex(READ_INDEX).ifExists().execute();
|
|
2999
|
-
}
|
|
3000
|
-
|
|
3001
|
-
//#endregion
|
|
3002
|
-
//#region src/database/migrations/053_plugin_mcp_tools.ts
|
|
3003
|
-
var _053_plugin_mcp_tools_exports = /* @__PURE__ */ __exportAll({
|
|
3004
|
-
down: () => down$21,
|
|
3005
|
-
up: () => up$21
|
|
3006
|
-
});
|
|
3007
|
-
/** Persist explicit per-plugin MCP enablement and the declaration consented to by an admin. */
|
|
3008
|
-
async function up$21(db) {
|
|
3009
|
-
if (!await columnExists(db, "_plugin_state", "mcp_tools_enabled")) await db.schema.alterTable("_plugin_state").addColumn("mcp_tools_enabled", "integer", (col) => col.notNull().defaultTo(0)).execute();
|
|
3010
|
-
if (!await columnExists(db, "_plugin_state", "mcp_tools_consent")) await db.schema.alterTable("_plugin_state").addColumn("mcp_tools_consent", "text").execute();
|
|
3011
|
-
}
|
|
3012
|
-
async function down$21(db) {
|
|
3013
|
-
await db.schema.alterTable("_plugin_state").dropColumn("mcp_tools_consent").execute();
|
|
3014
|
-
await db.schema.alterTable("_plugin_state").dropColumn("mcp_tools_enabled").execute();
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
//#endregion
|
|
3018
|
-
//#region src/database/migrations/054_media_upload_attempts.ts
|
|
3019
|
-
var _054_media_upload_attempts_exports = /* @__PURE__ */ __exportAll({
|
|
3020
|
-
down: () => down$20,
|
|
3021
|
-
up: () => up$20
|
|
3022
|
-
});
|
|
3023
|
-
async function up$20(db) {
|
|
3024
|
-
await db.schema.createTable("_emdash_media_upload_attempts").ifNotExists().addColumn("storage_key", "text", (col) => col.primaryKey()).addColumn("media_id", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("active")).addColumn("created_at", "text", (col) => col.notNull().defaultTo(currentTimestamp(db))).addColumn("updated_at", "text", (col) => col.notNull().defaultTo(currentTimestamp(db))).execute();
|
|
3025
|
-
await db.schema.createIndex("idx_media_upload_attempts_media_id").ifNotExists().on("_emdash_media_upload_attempts").column("media_id").execute();
|
|
3026
|
-
await db.schema.createIndex("idx_media_upload_attempts_status_created_at").ifNotExists().on("_emdash_media_upload_attempts").columns(["status", "created_at"]).execute();
|
|
3027
|
-
await db.schema.createIndex("idx_media_storage_key").ifNotExists().on("media").column("storage_key").execute();
|
|
3028
|
-
}
|
|
3029
|
-
async function down$20(db) {
|
|
3030
|
-
await db.schema.dropIndex("idx_media_storage_key").ifExists().execute();
|
|
3031
|
-
await db.schema.dropTable("_emdash_media_upload_attempts").ifExists().execute();
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
//#endregion
|
|
3035
|
-
//#region src/database/migrations/055_content_translation_group_locale_index.ts
|
|
3036
|
-
var _055_content_translation_group_locale_index_exports = /* @__PURE__ */ __exportAll({
|
|
3037
|
-
down: () => down$19,
|
|
3038
|
-
up: () => up$19
|
|
3039
|
-
});
|
|
3040
|
-
/**
|
|
3041
|
-
* Migration: replace the single-column content `translation_group` index with
|
|
3042
|
-
* one index per translation-group read shape.
|
|
3043
|
-
*
|
|
3044
|
-
* There are two shapes. Translation-group reads filter `deleted_at IS NULL`
|
|
3045
|
-
* with `translation_group = ?` / `IN (...)` and order by locale; menu and
|
|
3046
|
-
* reference resolution looks a group up by `translation_group` (+ `locale`)
|
|
3047
|
-
* alone, with no `deleted_at` term.
|
|
3048
|
-
*
|
|
3049
|
-
* Neither shape can borrow the other's index. Seeking migration 041's
|
|
3050
|
-
* `(deleted_at, locale, ...)` composites on `deleted_at` alone already returns
|
|
3051
|
-
* rows in locale order, so a stats-blind planner prefers them over a
|
|
3052
|
-
* single-column `translation_group` index and reads every non-deleted row in
|
|
3053
|
-
* the table; the batched variant is worse still, because the `IN (...)` list
|
|
3054
|
-
* multiplies the planner's row estimate for a `translation_group`-leading index
|
|
3055
|
-
* and it falls back to a `deleted_at` composite from a handful of groups
|
|
3056
|
-
* onward. Going the other way, an index leading with `deleted_at` cannot seek a
|
|
3057
|
-
* lookup that never constrains that column. D1 never has `sqlite_stat1`, so the
|
|
3058
|
-
* index shape is the only lever and each shape needs a prefix matching it term
|
|
3059
|
-
* for term.
|
|
3060
|
-
*
|
|
3061
|
-
* Forward-only and idempotent (`IF NOT EXISTS`).
|
|
3062
|
-
*
|
|
3063
|
-
* Index names use short `tg_locale` / `del_tg_locale` suffixes rather than
|
|
3064
|
-
* spelling out `translation_group`: Postgres truncates identifiers to 63 bytes,
|
|
3065
|
-
* and the longer forms truncate to the same value for long collection slugs.
|
|
3066
|
-
* Keep these identical to the names in `schema/registry.ts`.
|
|
3067
|
-
*/
|
|
3068
|
-
async function up$19(db) {
|
|
3069
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
3070
|
-
for (const tableName of tableNames) {
|
|
3071
|
-
await sql`
|
|
3072
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_tg_locale`)}
|
|
3073
|
-
ON ${sql.ref(tableName)} (translation_group, locale)
|
|
3074
|
-
`.execute(db);
|
|
3075
|
-
await sql`
|
|
3076
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_del_tg_locale`)}
|
|
3077
|
-
ON ${sql.ref(tableName)} (deleted_at, translation_group, locale)
|
|
3078
|
-
`.execute(db);
|
|
3079
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_translation_group`)}`.execute(db);
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
async function down$19(db) {
|
|
3083
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
3084
|
-
for (const tableName of tableNames) {
|
|
3085
|
-
await sql`
|
|
3086
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_translation_group`)}
|
|
3087
|
-
ON ${sql.ref(tableName)} (translation_group)
|
|
3088
|
-
`.execute(db);
|
|
3089
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_del_tg_locale`)}`.execute(db);
|
|
3090
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_tg_locale`)}`.execute(db);
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
|
|
3094
|
-
//#endregion
|
|
3095
|
-
//#region src/database/migrations/056_taxonomy_term_sort_order.ts
|
|
3096
|
-
var _056_taxonomy_term_sort_order_exports = /* @__PURE__ */ __exportAll({
|
|
3097
|
-
down: () => down$18,
|
|
3098
|
-
up: () => up$18
|
|
3099
|
-
});
|
|
3100
|
-
/**
|
|
3101
|
-
* Add `taxonomies.sort_order` and mint a position for every existing term.
|
|
3102
|
-
*
|
|
3103
|
-
* A position belongs to a `translation_group`, not a row: every row of a group
|
|
3104
|
-
* carries the same value. Groups are numbered by `(label, id)` — the order term
|
|
3105
|
-
* listings used before this migration — taking the label from the group's lowest
|
|
3106
|
-
* row id. That locale's rendered order is unchanged; every other locale is
|
|
3107
|
-
* re-sorted to match it, since only one locale's collation can win.
|
|
3108
|
-
*
|
|
3109
|
-
* Sibling groups are keyed on the raw `parent_id` column, which holds the
|
|
3110
|
-
* parent's translation_group, so a group whose rows disagree about their parent
|
|
3111
|
-
* is reconciled before numbering — otherwise it lands in whichever sibling group
|
|
3112
|
-
* one arbitrary row happens to name.
|
|
3113
|
-
*
|
|
3114
|
-
* Only the `ALTER` is guarded; the passes below run unconditionally so a run that
|
|
3115
|
-
* dies partway can be retried. Guarding the whole migration strands a partial
|
|
3116
|
-
* numbering, because the retry early-returns on the column it added.
|
|
3117
|
-
*/
|
|
3118
|
-
/**
|
|
3119
|
-
* Translation groups per `UPDATE`. Each costs three bound parameters — a CASE
|
|
3120
|
-
* `WHEN`/`THEN` pair plus one slot in the `IN` list — which keeps a statement
|
|
3121
|
-
* inside D1's 100-parameter ceiling. Writing one group per statement instead
|
|
3122
|
-
* spends a subrequest per term, and a large taxonomy exhausts the 1,000 a
|
|
3123
|
-
* Worker gets on the free plan before the migration finishes.
|
|
3124
|
-
*/
|
|
3125
|
-
const GROUPS_PER_UPDATE = 32;
|
|
3126
|
-
async function up$18(db) {
|
|
3127
|
-
const fresh = !await columnExists(db, "taxonomies", "sort_order");
|
|
3128
|
-
if (fresh) await db.schema.alterTable("taxonomies").addColumn("sort_order", "integer", (col) => col.notNull().defaultTo(0)).execute();
|
|
3129
|
-
await sql`UPDATE taxonomies SET translation_group = id WHERE translation_group IS NULL`.execute(db);
|
|
3130
|
-
const { rows } = await sql`
|
|
3131
|
-
SELECT id, name, label, parent_id, translation_group FROM taxonomies
|
|
3132
|
-
`.execute(db);
|
|
3133
|
-
await repairParents(db, rows);
|
|
3134
|
-
await mintPositions(db, rows, fresh);
|
|
3135
|
-
}
|
|
3136
|
-
/**
|
|
3137
|
-
* Give every row of a translation_group the same `parent_id`, mutating `rows` so
|
|
3138
|
-
* the numbering below sees the reconciled tree.
|
|
3139
|
-
*
|
|
3140
|
-
* `parent_id` holds the parent's translation_group, so it is locale-agnostic and
|
|
3141
|
-
* a group's rows should all agree. Reparenting used to write a single row, which
|
|
3142
|
-
* left a term nested in the locale it was moved in and a root everywhere else.
|
|
3143
|
-
* A non-null parent wins: it is the move somebody made, and the null is the row
|
|
3144
|
-
* that missed it.
|
|
3145
|
-
*
|
|
3146
|
-
* The winner is copied from a sibling row, so it already satisfies the self-FK
|
|
3147
|
-
* on `parent_id` and can't be rewritten into a dangling reference.
|
|
3148
|
-
*/
|
|
3149
|
-
async function repairParents(db, rows) {
|
|
3150
|
-
const byGroup = /* @__PURE__ */ new Map();
|
|
3151
|
-
for (const row of rows) {
|
|
3152
|
-
const group = row.translation_group;
|
|
3153
|
-
let members = byGroup.get(group);
|
|
3154
|
-
if (!members) byGroup.set(group, members = []);
|
|
3155
|
-
members.push(row);
|
|
3156
|
-
}
|
|
3157
|
-
const winners = /* @__PURE__ */ new Map();
|
|
3158
|
-
for (const [group, members] of byGroup) {
|
|
3159
|
-
if (new Set(members.map((member) => member.parent_id)).size < 2) continue;
|
|
3160
|
-
const parented = members.filter((member) => member.parent_id !== null);
|
|
3161
|
-
const winner = (parented.find((member) => member.id === group) ?? parented.toSorted((a, b) => a.id.localeCompare(b.id))[0])?.parent_id;
|
|
3162
|
-
if (winner === void 0 || winner === null) continue;
|
|
3163
|
-
winners.set(group, winner);
|
|
3164
|
-
for (const member of members) member.parent_id = winner;
|
|
3165
|
-
}
|
|
3166
|
-
await applyByGroup(db, "parent_id", winners);
|
|
3167
|
-
}
|
|
3168
|
-
/**
|
|
3169
|
-
* Number each sibling group by `(label, id)`.
|
|
3170
|
-
*
|
|
3171
|
-
* When the column was just added every row already reads 0, so those positions
|
|
3172
|
-
* are left to the default. On a retry the column is already there and may hold
|
|
3173
|
-
* numbers from the run that failed, so every position is written explicitly.
|
|
3174
|
-
*/
|
|
3175
|
-
async function mintPositions(db, rows, fresh) {
|
|
3176
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3177
|
-
for (const row of rows) {
|
|
3178
|
-
const group = row.translation_group;
|
|
3179
|
-
const chosen = groups.get(group);
|
|
3180
|
-
if (chosen && chosen.id <= row.id) continue;
|
|
3181
|
-
groups.set(group, {
|
|
3182
|
-
sibling: row.name + "/" + (row.parent_id ?? ""),
|
|
3183
|
-
label: row.label ?? "",
|
|
3184
|
-
id: row.id
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
const siblings = /* @__PURE__ */ new Map();
|
|
3188
|
-
for (const [group, { sibling, label, id }] of groups) {
|
|
3189
|
-
let members = siblings.get(sibling);
|
|
3190
|
-
if (!members) siblings.set(sibling, members = []);
|
|
3191
|
-
members.push({
|
|
3192
|
-
group,
|
|
3193
|
-
label,
|
|
3194
|
-
id
|
|
3195
|
-
});
|
|
3196
|
-
}
|
|
3197
|
-
const positions = /* @__PURE__ */ new Map();
|
|
3198
|
-
for (const members of siblings.values()) {
|
|
3199
|
-
members.sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
|
|
3200
|
-
for (const [position, member] of members.entries()) {
|
|
3201
|
-
if (position === 0 && fresh) continue;
|
|
3202
|
-
positions.set(member.group, position);
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
await applyByGroup(db, "sort_order", positions);
|
|
3206
|
-
}
|
|
3207
|
-
/**
|
|
3208
|
-
* Set one column per translation_group, chunked to stay inside the parameter
|
|
3209
|
-
* ceiling.
|
|
3210
|
-
*/
|
|
3211
|
-
async function applyByGroup(db, column, values) {
|
|
3212
|
-
const entries = [...values];
|
|
3213
|
-
const valueType = column === "sort_order" ? sql`INTEGER` : sql`TEXT`;
|
|
3214
|
-
for (let index = 0; index < entries.length; index += GROUPS_PER_UPDATE) {
|
|
3215
|
-
const chunk = entries.slice(index, index + GROUPS_PER_UPDATE);
|
|
3216
|
-
const arms = sql.join(chunk.map(([group, value]) => sql`WHEN ${group} THEN CAST(${value} AS ${valueType})`), sql` `);
|
|
3217
|
-
const keys = sql.join(chunk.map(([group]) => sql`${group}`));
|
|
3218
|
-
await sql`
|
|
3219
|
-
UPDATE taxonomies
|
|
3220
|
-
SET ${sql.ref(column)} = CASE translation_group ${arms} END
|
|
3221
|
-
WHERE translation_group IN (${keys})
|
|
3222
|
-
`.execute(db);
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3225
|
-
async function down$18(db) {
|
|
3226
|
-
if (await columnExists(db, "taxonomies", "sort_order")) await db.schema.alterTable("taxonomies").dropColumn("sort_order").execute();
|
|
3227
|
-
}
|
|
3228
|
-
|
|
3229
|
-
//#endregion
|
|
3230
|
-
//#region src/database/migrations/057_collection_hidden.ts
|
|
3231
|
-
var _057_collection_hidden_exports = /* @__PURE__ */ __exportAll({
|
|
3232
|
-
down: () => down$17,
|
|
3233
|
-
up: () => up$17
|
|
3234
|
-
});
|
|
3235
|
-
/**
|
|
3236
|
-
* Migration: hide a collection from the admin sidebar.
|
|
3237
|
-
*
|
|
3238
|
-
* Adds `hidden` to `_emdash_collections`. A hidden collection stays fully
|
|
3239
|
-
* functional (REST API, MCP, plugin hooks, direct `/content/:collection`
|
|
3240
|
-
* URLs) — only its auto-generated sidebar entry is omitted, so plugins that
|
|
3241
|
-
* own a collection end to end can steer editors to their own admin UI.
|
|
3242
|
-
*/
|
|
3243
|
-
async function up$17(db) {
|
|
3244
|
-
if (!await columnExists(db, "_emdash_collections", "hidden")) await db.schema.alterTable("_emdash_collections").addColumn("hidden", "integer", (col) => col.notNull().defaultTo(0)).execute();
|
|
3245
|
-
}
|
|
3246
|
-
async function down$17(db) {
|
|
3247
|
-
await db.schema.alterTable("_emdash_collections").dropColumn("hidden").execute();
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
//#endregion
|
|
3251
|
-
//#region src/database/migrations/058_collection_sort_order.ts
|
|
3252
|
-
var _058_collection_sort_order_exports = /* @__PURE__ */ __exportAll({
|
|
3253
|
-
down: () => down$16,
|
|
3254
|
-
up: () => up$16
|
|
3255
|
-
});
|
|
3256
|
-
/**
|
|
3257
|
-
* Migration: explicit collection order in the admin sidebar.
|
|
3258
|
-
*
|
|
3259
|
-
* Adds `sort_order` to `_emdash_collections`. A NULL `sort_order` means no
|
|
3260
|
-
* explicit position: those collections sort after the ordered ones, keeping
|
|
3261
|
-
* the alphabetical-by-slug order.
|
|
3262
|
-
*/
|
|
3263
|
-
async function up$16(db) {
|
|
3264
|
-
if (!await columnExists(db, "_emdash_collections", "sort_order")) await db.schema.alterTable("_emdash_collections").addColumn("sort_order", "integer").execute();
|
|
3265
|
-
}
|
|
3266
|
-
async function down$16(db) {
|
|
3267
|
-
await db.schema.alterTable("_emdash_collections").dropColumn("sort_order").execute();
|
|
3268
|
-
}
|
|
3269
|
-
|
|
3270
|
-
//#endregion
|
|
3271
|
-
//#region src/database/migrations/059_revision_prune_queue.ts
|
|
3272
|
-
var _059_revision_prune_queue_exports = /* @__PURE__ */ __exportAll({
|
|
3273
|
-
down: () => down$15,
|
|
3274
|
-
up: () => up$15
|
|
3275
|
-
});
|
|
3276
|
-
const REVISION_KEEP_COUNT = 50;
|
|
3277
|
-
async function up$15(db) {
|
|
3278
|
-
await db.schema.createTable("_emdash_revision_prune_queue").ifNotExists().addColumn("collection", "text", (column) => column.notNull()).addColumn("entry_id", "text", (column) => column.notNull()).addColumn("revision_id", "text", (column) => column.notNull()).addPrimaryKeyConstraint("revision_prune_queue_pk", ["collection", "entry_id"]).execute();
|
|
3279
|
-
await db.schema.createIndex("idx_revision_prune_queue_revision_id").ifNotExists().on("_emdash_revision_prune_queue").column("revision_id").execute();
|
|
3280
|
-
await sql`
|
|
3281
|
-
INSERT INTO _emdash_revision_prune_queue (collection, entry_id, revision_id)
|
|
3282
|
-
SELECT collection, entry_id, MAX(id)
|
|
3283
|
-
FROM revisions
|
|
3284
|
-
WHERE true
|
|
3285
|
-
GROUP BY collection, entry_id
|
|
3286
|
-
HAVING COUNT(*) > ${REVISION_KEEP_COUNT}
|
|
3287
|
-
ON CONFLICT (collection, entry_id)
|
|
3288
|
-
DO UPDATE SET revision_id = excluded.revision_id
|
|
3289
|
-
`.execute(db);
|
|
3290
|
-
}
|
|
3291
|
-
async function down$15(db) {
|
|
3292
|
-
await db.schema.dropTable("_emdash_revision_prune_queue").ifExists().execute();
|
|
3293
|
-
}
|
|
3294
|
-
|
|
3295
|
-
//#endregion
|
|
3296
|
-
//#region src/database/migrations/060_collection_admin_config.ts
|
|
3297
|
-
var _060_collection_admin_config_exports = /* @__PURE__ */ __exportAll({
|
|
3298
|
-
down: () => down$14,
|
|
3299
|
-
up: () => up$14
|
|
3300
|
-
});
|
|
3301
|
-
/** Persist collection-level admin presentation options. */
|
|
3302
|
-
async function up$14(db) {
|
|
3303
|
-
if (!await columnExists(db, "_emdash_collections", "admin_config")) await db.schema.alterTable("_emdash_collections").addColumn("admin_config", "text").execute();
|
|
3304
|
-
}
|
|
3305
|
-
async function down$14(db) {
|
|
3306
|
-
if (await columnExists(db, "_emdash_collections", "admin_config")) await db.schema.alterTable("_emdash_collections").dropColumn("admin_config").execute();
|
|
3307
|
-
}
|
|
3308
|
-
|
|
3309
|
-
//#endregion
|
|
3310
|
-
//#region src/database/migrations/061_media_usage_cleanup.ts
|
|
3311
|
-
var _061_media_usage_cleanup_exports = /* @__PURE__ */ __exportAll({
|
|
3312
|
-
down: () => down$13,
|
|
3313
|
-
up: () => up$13
|
|
3314
|
-
});
|
|
3315
|
-
const CLEANUP_TASK_KEY = "projection_gc";
|
|
3316
|
-
const INITIAL_ELIGIBLE_AT = "1970-01-01T00:00:00.000Z";
|
|
3317
|
-
async function up$13(db) {
|
|
3318
|
-
await db.schema.createIndex("idx__emdash_media_usage_cleanup_scan").ifNotExists().on("_emdash_media_usage").columns([
|
|
3319
|
-
"created_at",
|
|
3320
|
-
"id",
|
|
3321
|
-
"source_key",
|
|
3322
|
-
"generation"
|
|
3323
|
-
]).execute();
|
|
3324
|
-
await db.schema.createTable("_emdash_media_usage_cleanup").ifNotExists().addColumn("task_key", "text", (c) => c.primaryKey()).addColumn("lease_token", "text").addColumn("lease_expires_at", "text").addColumn("next_eligible_at", "text", (c) => c.notNull()).addColumn("cursor_created_at", "text").addColumn("cursor_id", "text").addColumn("scan_before_at", "text").addColumn("consecutive_failures", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_started_at", "text").addColumn("last_completed_at", "text").addColumn("last_candidate_count", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_deleted_orphans", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_deleted_stale", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_deleted_abandoned", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_deleted_write_leases", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_backlog_lower_bound", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_scan_has_more", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_duration_ms", "integer", (c) => c.notNull().defaultTo(0)).addColumn("last_error_code", "text").addColumn("updated_at", "text", (c) => c.notNull().defaultTo(currentTimestamp(db))).execute();
|
|
3325
|
-
await db.schema.createTable("_emdash_media_usage_generation_writes").ifNotExists().addColumn("source_key", "text", (c) => c.notNull()).addColumn("generation", "text", (c) => c.notNull()).addColumn("lease_token", "text", (c) => c.primaryKey()).addColumn("expires_at", "text", (c) => c.notNull()).addColumn("created_at", "text", (c) => c.notNull().defaultTo(currentTimestamp(db))).addUniqueConstraint("_emdash_media_usage_generation_writes_source_generation", ["source_key", "generation"]).execute();
|
|
3326
|
-
await db.schema.createIndex("idx__emdash_media_usage_generation_writes_expiry").ifNotExists().on("_emdash_media_usage_generation_writes").columns(["expires_at", "lease_token"]).execute();
|
|
3327
|
-
await sql`
|
|
3328
|
-
INSERT INTO _emdash_media_usage_cleanup (task_key, next_eligible_at)
|
|
3329
|
-
VALUES (${CLEANUP_TASK_KEY}, ${INITIAL_ELIGIBLE_AT})
|
|
3330
|
-
ON CONFLICT (task_key) DO NOTHING
|
|
3331
|
-
`.execute(db);
|
|
3332
|
-
}
|
|
3333
|
-
async function down$13(db) {
|
|
3334
|
-
await db.schema.dropIndex("idx__emdash_media_usage_generation_writes_expiry").ifExists().execute();
|
|
3335
|
-
await db.schema.dropTable("_emdash_media_usage_generation_writes").ifExists().execute();
|
|
3336
|
-
await db.schema.dropTable("_emdash_media_usage_cleanup").ifExists().execute();
|
|
3337
|
-
await db.schema.dropIndex("idx__emdash_media_usage_cleanup_scan").ifExists().execute();
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
//#endregion
|
|
3341
|
-
//#region src/database/migrations/062_media_usage_cleanup_fence.ts
|
|
3342
|
-
var _062_media_usage_cleanup_fence_exports = /* @__PURE__ */ __exportAll({
|
|
3343
|
-
down: () => down$12,
|
|
3344
|
-
up: () => up$12
|
|
3345
|
-
});
|
|
3346
|
-
async function up$12(db) {
|
|
3347
|
-
if (!await columnExists(db, "_emdash_media_usage", "cleanup_lease_token")) await db.schema.alterTable("_emdash_media_usage").addColumn("cleanup_lease_token", "text").execute();
|
|
3348
|
-
await db.schema.createTable("_emdash_media_usage_cleanup_fence").ifNotExists().addColumn("task_key", "text", (c) => c.primaryKey()).addColumn("generation_floor", "text", (c) => c.notNull()).addColumn("updated_at", "text", (c) => c.notNull().defaultTo(currentTimestamp(db))).execute();
|
|
3349
|
-
if (isPostgres(db)) {
|
|
3350
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_record_cleanup_fence ON _emdash_media_usage").execute(db);
|
|
3351
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_fence_source_generation_update ON _emdash_media_usage_sources").execute(db);
|
|
3352
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_fence_source_generation_insert ON _emdash_media_usage_sources").execute(db);
|
|
3353
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_lock_cleanup_update ON _emdash_media_usage_sources").execute(db);
|
|
3354
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_lock_cleanup_insert ON _emdash_media_usage_sources").execute(db);
|
|
3355
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_lock_cleanup_delete ON _emdash_media_usage_sources").execute(db);
|
|
3356
|
-
await sql.raw(`
|
|
3357
|
-
CREATE OR REPLACE FUNCTION emdash_media_usage_lock_cleanup()
|
|
3358
|
-
RETURNS trigger
|
|
3359
|
-
LANGUAGE plpgsql
|
|
3360
|
-
AS $$
|
|
3361
|
-
BEGIN
|
|
3362
|
-
PERFORM 1
|
|
3363
|
-
FROM _emdash_media_usage_cleanup AS cleanup
|
|
3364
|
-
WHERE cleanup.task_key = 'projection_gc'
|
|
3365
|
-
FOR SHARE;
|
|
3366
|
-
RETURN NULL;
|
|
3367
|
-
END;
|
|
3368
|
-
$$
|
|
3369
|
-
`).execute(db);
|
|
3370
|
-
await sql.raw(`
|
|
3371
|
-
CREATE OR REPLACE FUNCTION emdash_media_usage_fence_source_generation()
|
|
3372
|
-
RETURNS trigger
|
|
3373
|
-
LANGUAGE plpgsql
|
|
3374
|
-
AS $$
|
|
3375
|
-
BEGIN
|
|
3376
|
-
IF EXISTS (
|
|
3377
|
-
SELECT 1
|
|
3378
|
-
FROM _emdash_media_usage_cleanup_fence AS fence
|
|
3379
|
-
WHERE fence.task_key = 'projection_gc'
|
|
3380
|
-
AND NEW.current_generation <= fence.generation_floor
|
|
3381
|
-
) AND NOT EXISTS (
|
|
3382
|
-
SELECT 1
|
|
3383
|
-
FROM _emdash_media_usage_generation_writes AS writer
|
|
3384
|
-
WHERE writer.source_key = NEW.source_key
|
|
3385
|
-
AND writer.generation = NEW.current_generation
|
|
3386
|
-
AND writer.expires_at::timestamptz > clock_timestamp()
|
|
3387
|
-
) THEN
|
|
3388
|
-
RETURN NULL;
|
|
3389
|
-
END IF;
|
|
3390
|
-
RETURN NEW;
|
|
3391
|
-
END;
|
|
3392
|
-
$$
|
|
3393
|
-
`).execute(db);
|
|
3394
|
-
await sql.raw(`
|
|
3395
|
-
CREATE TRIGGER emdash_media_usage_lock_cleanup_insert
|
|
3396
|
-
BEFORE INSERT ON _emdash_media_usage_sources
|
|
3397
|
-
FOR EACH STATEMENT
|
|
3398
|
-
EXECUTE FUNCTION emdash_media_usage_lock_cleanup()
|
|
3399
|
-
`).execute(db);
|
|
3400
|
-
await sql.raw(`
|
|
3401
|
-
CREATE TRIGGER emdash_media_usage_lock_cleanup_update
|
|
3402
|
-
BEFORE UPDATE OF current_generation ON _emdash_media_usage_sources
|
|
3403
|
-
FOR EACH STATEMENT
|
|
3404
|
-
EXECUTE FUNCTION emdash_media_usage_lock_cleanup()
|
|
3405
|
-
`).execute(db);
|
|
3406
|
-
await sql.raw(`
|
|
3407
|
-
CREATE TRIGGER emdash_media_usage_lock_cleanup_delete
|
|
3408
|
-
BEFORE DELETE ON _emdash_media_usage_sources
|
|
3409
|
-
FOR EACH STATEMENT
|
|
3410
|
-
EXECUTE FUNCTION emdash_media_usage_lock_cleanup()
|
|
3411
|
-
`).execute(db);
|
|
3412
|
-
await sql.raw(`
|
|
3413
|
-
CREATE OR REPLACE FUNCTION emdash_media_usage_record_cleanup_fence()
|
|
3414
|
-
RETURNS trigger
|
|
3415
|
-
LANGUAGE plpgsql
|
|
3416
|
-
AS $$
|
|
3417
|
-
BEGIN
|
|
3418
|
-
IF OLD.cleanup_lease_token IS NOT NULL AND EXISTS (
|
|
3419
|
-
SELECT 1
|
|
3420
|
-
FROM _emdash_media_usage_cleanup AS cleanup
|
|
3421
|
-
WHERE cleanup.task_key = 'projection_gc'
|
|
3422
|
-
AND cleanup.lease_token = OLD.cleanup_lease_token
|
|
3423
|
-
) THEN
|
|
3424
|
-
INSERT INTO _emdash_media_usage_cleanup_fence (
|
|
3425
|
-
task_key,
|
|
3426
|
-
generation_floor,
|
|
3427
|
-
updated_at
|
|
3428
|
-
)
|
|
3429
|
-
VALUES ('projection_gc', OLD.generation, CURRENT_TIMESTAMP::text)
|
|
3430
|
-
ON CONFLICT (task_key) DO UPDATE SET
|
|
3431
|
-
generation_floor = CASE
|
|
3432
|
-
WHEN EXCLUDED.generation_floor > _emdash_media_usage_cleanup_fence.generation_floor
|
|
3433
|
-
THEN EXCLUDED.generation_floor
|
|
3434
|
-
ELSE _emdash_media_usage_cleanup_fence.generation_floor
|
|
3435
|
-
END,
|
|
3436
|
-
updated_at = EXCLUDED.updated_at;
|
|
3437
|
-
END IF;
|
|
3438
|
-
RETURN OLD;
|
|
3439
|
-
END;
|
|
3440
|
-
$$
|
|
3441
|
-
`).execute(db);
|
|
3442
|
-
await sql.raw(`
|
|
3443
|
-
CREATE TRIGGER emdash_media_usage_fence_source_generation_insert
|
|
3444
|
-
BEFORE INSERT ON _emdash_media_usage_sources
|
|
3445
|
-
FOR EACH ROW
|
|
3446
|
-
EXECUTE FUNCTION emdash_media_usage_fence_source_generation()
|
|
3447
|
-
`).execute(db);
|
|
3448
|
-
await sql.raw(`
|
|
3449
|
-
CREATE TRIGGER emdash_media_usage_fence_source_generation_update
|
|
3450
|
-
BEFORE UPDATE OF current_generation ON _emdash_media_usage_sources
|
|
3451
|
-
FOR EACH ROW
|
|
3452
|
-
EXECUTE FUNCTION emdash_media_usage_fence_source_generation()
|
|
3453
|
-
`).execute(db);
|
|
3454
|
-
await sql.raw(`
|
|
3455
|
-
CREATE TRIGGER emdash_media_usage_record_cleanup_fence
|
|
3456
|
-
BEFORE DELETE ON _emdash_media_usage
|
|
3457
|
-
FOR EACH ROW
|
|
3458
|
-
WHEN (OLD.cleanup_lease_token IS NOT NULL)
|
|
3459
|
-
EXECUTE FUNCTION emdash_media_usage_record_cleanup_fence()
|
|
3460
|
-
`).execute(db);
|
|
3461
|
-
return;
|
|
3462
|
-
}
|
|
3463
|
-
await sql.raw(`
|
|
3464
|
-
CREATE TRIGGER IF NOT EXISTS emdash_media_usage_fence_source_generation_insert
|
|
3465
|
-
BEFORE INSERT ON _emdash_media_usage_sources
|
|
3466
|
-
WHEN EXISTS (
|
|
3467
|
-
SELECT 1
|
|
3468
|
-
FROM _emdash_media_usage_cleanup_fence AS fence
|
|
3469
|
-
WHERE fence.task_key = 'projection_gc'
|
|
3470
|
-
AND NEW.current_generation <= fence.generation_floor
|
|
3471
|
-
) AND NOT EXISTS (
|
|
3472
|
-
SELECT 1
|
|
3473
|
-
FROM _emdash_media_usage_generation_writes AS writer
|
|
3474
|
-
WHERE writer.source_key = NEW.source_key
|
|
3475
|
-
AND writer.generation = NEW.current_generation
|
|
3476
|
-
AND writer.expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
3477
|
-
)
|
|
3478
|
-
BEGIN
|
|
3479
|
-
SELECT RAISE(IGNORE);
|
|
3480
|
-
END
|
|
3481
|
-
`).execute(db);
|
|
3482
|
-
await sql.raw(`
|
|
3483
|
-
CREATE TRIGGER IF NOT EXISTS emdash_media_usage_fence_source_generation_update
|
|
3484
|
-
BEFORE UPDATE OF current_generation ON _emdash_media_usage_sources
|
|
3485
|
-
WHEN NEW.current_generation <> OLD.current_generation
|
|
3486
|
-
AND EXISTS (
|
|
3487
|
-
SELECT 1
|
|
3488
|
-
FROM _emdash_media_usage_cleanup_fence AS fence
|
|
3489
|
-
WHERE fence.task_key = 'projection_gc'
|
|
3490
|
-
AND NEW.current_generation <= fence.generation_floor
|
|
3491
|
-
) AND NOT EXISTS (
|
|
3492
|
-
SELECT 1
|
|
3493
|
-
FROM _emdash_media_usage_generation_writes AS writer
|
|
3494
|
-
WHERE writer.source_key = NEW.source_key
|
|
3495
|
-
AND writer.generation = NEW.current_generation
|
|
3496
|
-
AND writer.expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
3497
|
-
)
|
|
3498
|
-
BEGIN
|
|
3499
|
-
SELECT RAISE(IGNORE);
|
|
3500
|
-
END
|
|
3501
|
-
`).execute(db);
|
|
3502
|
-
await sql.raw(`
|
|
3503
|
-
CREATE TRIGGER IF NOT EXISTS emdash_media_usage_record_cleanup_fence
|
|
3504
|
-
BEFORE DELETE ON _emdash_media_usage
|
|
3505
|
-
WHEN OLD.cleanup_lease_token IS NOT NULL AND EXISTS (
|
|
3506
|
-
SELECT 1
|
|
3507
|
-
FROM _emdash_media_usage_cleanup AS cleanup
|
|
3508
|
-
WHERE cleanup.task_key = 'projection_gc'
|
|
3509
|
-
AND cleanup.lease_token = OLD.cleanup_lease_token
|
|
3510
|
-
)
|
|
3511
|
-
BEGIN
|
|
3512
|
-
INSERT INTO _emdash_media_usage_cleanup_fence (
|
|
3513
|
-
task_key,
|
|
3514
|
-
generation_floor,
|
|
3515
|
-
updated_at
|
|
3516
|
-
)
|
|
3517
|
-
VALUES ('projection_gc', OLD.generation, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
3518
|
-
ON CONFLICT (task_key) DO UPDATE SET
|
|
3519
|
-
generation_floor = CASE
|
|
3520
|
-
WHEN excluded.generation_floor > _emdash_media_usage_cleanup_fence.generation_floor
|
|
3521
|
-
THEN excluded.generation_floor
|
|
3522
|
-
ELSE _emdash_media_usage_cleanup_fence.generation_floor
|
|
3523
|
-
END,
|
|
3524
|
-
updated_at = excluded.updated_at;
|
|
3525
|
-
END
|
|
3526
|
-
`).execute(db);
|
|
3527
|
-
}
|
|
3528
|
-
async function down$12(db) {
|
|
3529
|
-
if (isPostgres(db)) {
|
|
3530
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_record_cleanup_fence ON _emdash_media_usage").execute(db);
|
|
3531
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_fence_source_generation_update ON _emdash_media_usage_sources").execute(db);
|
|
3532
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_fence_source_generation_insert ON _emdash_media_usage_sources").execute(db);
|
|
3533
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_lock_cleanup_update ON _emdash_media_usage_sources").execute(db);
|
|
3534
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_lock_cleanup_insert ON _emdash_media_usage_sources").execute(db);
|
|
3535
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_lock_cleanup_delete ON _emdash_media_usage_sources").execute(db);
|
|
3536
|
-
await sql.raw("DROP FUNCTION IF EXISTS emdash_media_usage_record_cleanup_fence()").execute(db);
|
|
3537
|
-
await sql.raw("DROP FUNCTION IF EXISTS emdash_media_usage_fence_source_generation()").execute(db);
|
|
3538
|
-
await sql.raw("DROP FUNCTION IF EXISTS emdash_media_usage_lock_cleanup()").execute(db);
|
|
3539
|
-
} else {
|
|
3540
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_record_cleanup_fence").execute(db);
|
|
3541
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_fence_source_generation_update").execute(db);
|
|
3542
|
-
await sql.raw("DROP TRIGGER IF EXISTS emdash_media_usage_fence_source_generation_insert").execute(db);
|
|
3543
|
-
}
|
|
3544
|
-
await db.schema.dropTable("_emdash_media_usage_cleanup_fence").ifExists().execute();
|
|
3545
|
-
if (await columnExists(db, "_emdash_media_usage", "cleanup_lease_token")) await db.schema.alterTable("_emdash_media_usage").dropColumn("cleanup_lease_token").execute();
|
|
3546
|
-
}
|
|
3547
|
-
|
|
3548
|
-
//#endregion
|
|
3549
|
-
//#region src/database/migrations/063_media_usage_incremental_work.ts
|
|
3550
|
-
var _063_media_usage_incremental_work_exports = /* @__PURE__ */ __exportAll({
|
|
3551
|
-
down: () => down$11,
|
|
3552
|
-
up: () => up$11
|
|
3553
|
-
});
|
|
3554
|
-
const ACTIVATION_KEY$1 = "incremental_capture";
|
|
3555
|
-
const CONTENT_ADAPTER_ID = "content-media";
|
|
3556
|
-
const COLLECTION_SCOPE = "collection";
|
|
3557
|
-
const DUPLICATE_COLUMN_RE$2 = /(?:duplicate column|column .* already exists|already exists.*column)/i;
|
|
3558
|
-
async function up$11(db) {
|
|
3559
|
-
await db.schema.createTable("_emdash_media_usage_activation").ifNotExists().addColumn("task_key", "text", (column) => column.primaryKey()).addColumn("state", "text", (column) => column.notNull().defaultTo("expanded")).addColumn("runtime_generation", "integer", (column) => column.notNull().defaultTo(1)).addColumn("collection_cursor", "text").addColumn("drain_confirmed_at", "text").addColumn("lease_token", "text").addColumn("lease_expires_at", "text").addColumn("attempt_count", "integer", (column) => column.notNull().defaultTo(0)).addColumn("last_attempted_at", "text").addColumn("last_error_code", "text").addColumn("activated_at", "text").addColumn("created_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp$2(db))).addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp$2(db))).execute();
|
|
3560
|
-
await sql`
|
|
3561
|
-
INSERT INTO _emdash_media_usage_activation (task_key, state)
|
|
3562
|
-
VALUES (${ACTIVATION_KEY$1}, 'expanded')
|
|
3563
|
-
ON CONFLICT (task_key) DO NOTHING
|
|
3564
|
-
`.execute(db);
|
|
3565
|
-
await db.schema.createTable("_emdash_media_usage_work").ifNotExists().addColumn("collection_id", "text", (column) => column.notNull()).addColumn("collection_slug", "text", (column) => column.notNull()).addColumn("content_id", "text", (column) => column.notNull()).addColumn("change_epoch", "bigint", (column) => column.notNull()).addColumn("work_version", "bigint", (column) => column.notNull().defaultTo(1)).addColumn("state", "text", (column) => column.notNull().defaultTo("pending")).addColumn("attempt_count", "integer", (column) => column.notNull().defaultTo(0)).addColumn("next_attempt_at", "text", (column) => column.notNull()).addColumn("lease_token", "text").addColumn("lease_expires_at", "text").addColumn("last_attempted_at", "text").addColumn("last_error_code", "text").addColumn("created_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp$2(db))).addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp$2(db))).addPrimaryKeyConstraint("_emdash_media_usage_work_pk", ["collection_id", "content_id"]).execute();
|
|
3566
|
-
await addStatusColumns(db);
|
|
3567
|
-
await addSourceColumns(db);
|
|
3568
|
-
await db.schema.createIndex("idx__emdash_media_usage_work_due").ifNotExists().on("_emdash_media_usage_work").columns([
|
|
3569
|
-
"state",
|
|
3570
|
-
"next_attempt_at",
|
|
3571
|
-
"updated_at",
|
|
3572
|
-
"collection_id",
|
|
3573
|
-
"content_id"
|
|
3574
|
-
]).execute();
|
|
3575
|
-
await db.schema.createIndex("idx__emdash_media_usage_work_lease").ifNotExists().on("_emdash_media_usage_work").columns([
|
|
3576
|
-
"state",
|
|
3577
|
-
"lease_expires_at",
|
|
3578
|
-
"collection_id",
|
|
3579
|
-
"content_id"
|
|
3580
|
-
]).execute();
|
|
3581
|
-
await db.schema.createIndex("idx__emdash_media_usage_work_operator").ifNotExists().on("_emdash_media_usage_work").columns([
|
|
3582
|
-
"collection_id",
|
|
3583
|
-
"state",
|
|
3584
|
-
"updated_at",
|
|
3585
|
-
"content_id"
|
|
3586
|
-
]).execute();
|
|
3587
|
-
await db.schema.createIndex("idx__emdash_media_usage_status_collection").ifNotExists().unique().on("_emdash_media_usage_index_status").columns([
|
|
3588
|
-
"adapter_id",
|
|
3589
|
-
"scope_type",
|
|
3590
|
-
"collection_id"
|
|
3591
|
-
]).execute();
|
|
3592
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_identity").ifNotExists().on("_emdash_media_usage_sources").columns([
|
|
3593
|
-
"source_type",
|
|
3594
|
-
"collection_id",
|
|
3595
|
-
"content_id",
|
|
3596
|
-
"source_variant"
|
|
3597
|
-
]).execute();
|
|
3598
|
-
await sql`
|
|
3599
|
-
DELETE FROM _emdash_media_usage_index_status
|
|
3600
|
-
WHERE adapter_id = ${CONTENT_ADAPTER_ID}
|
|
3601
|
-
AND scope_type = ${COLLECTION_SCOPE}
|
|
3602
|
-
AND (
|
|
3603
|
-
(
|
|
3604
|
-
collection_id IS NULL
|
|
3605
|
-
AND NOT EXISTS (
|
|
3606
|
-
SELECT 1
|
|
3607
|
-
FROM _emdash_collections AS collection
|
|
3608
|
-
WHERE collection.slug = _emdash_media_usage_index_status.scope_key
|
|
3609
|
-
)
|
|
3610
|
-
)
|
|
3611
|
-
OR (
|
|
3612
|
-
collection_id IS NOT NULL
|
|
3613
|
-
AND capture_state = 'installing'
|
|
3614
|
-
AND EXISTS (
|
|
3615
|
-
SELECT 1
|
|
3616
|
-
FROM _emdash_media_usage_activation AS activation
|
|
3617
|
-
WHERE activation.task_key = ${ACTIVATION_KEY$1}
|
|
3618
|
-
AND activation.state = 'expanded'
|
|
3619
|
-
)
|
|
3620
|
-
AND NOT EXISTS (
|
|
3621
|
-
SELECT 1
|
|
3622
|
-
FROM _emdash_collections AS collection
|
|
3623
|
-
WHERE collection.id = _emdash_media_usage_index_status.collection_id
|
|
3624
|
-
AND collection.slug = _emdash_media_usage_index_status.scope_key
|
|
3625
|
-
)
|
|
3626
|
-
)
|
|
3627
|
-
)
|
|
3628
|
-
`.execute(db);
|
|
3629
|
-
await sql`
|
|
3630
|
-
UPDATE _emdash_media_usage_index_status
|
|
3631
|
-
SET collection_id = (
|
|
3632
|
-
SELECT collection.id
|
|
3633
|
-
FROM _emdash_collections AS collection
|
|
3634
|
-
WHERE collection.slug = _emdash_media_usage_index_status.scope_key
|
|
3635
|
-
),
|
|
3636
|
-
reconciliation_required = 1,
|
|
3637
|
-
capture_state = COALESCE(capture_state, 'installing')
|
|
3638
|
-
WHERE adapter_id = ${CONTENT_ADAPTER_ID}
|
|
3639
|
-
AND scope_type = ${COLLECTION_SCOPE}
|
|
3640
|
-
AND (
|
|
3641
|
-
collection_id IS NULL
|
|
3642
|
-
OR capture_state IS NULL
|
|
3643
|
-
)
|
|
3644
|
-
AND EXISTS (
|
|
3645
|
-
SELECT 1
|
|
3646
|
-
FROM _emdash_collections AS collection
|
|
3647
|
-
WHERE collection.id = COALESCE(
|
|
3648
|
-
_emdash_media_usage_index_status.collection_id,
|
|
3649
|
-
collection.id
|
|
3650
|
-
)
|
|
3651
|
-
AND collection.slug = _emdash_media_usage_index_status.scope_key
|
|
3652
|
-
)
|
|
3653
|
-
`.execute(db);
|
|
3654
|
-
}
|
|
3655
|
-
async function down$11(db) {
|
|
3656
|
-
await assertRollbackIsEmptyAndInactive(db);
|
|
3657
|
-
await db.schema.dropIndex("idx__emdash_media_usage_sources_identity").ifExists().execute();
|
|
3658
|
-
await db.schema.dropIndex("idx__emdash_media_usage_status_collection").ifExists().execute();
|
|
3659
|
-
await db.schema.dropIndex("idx__emdash_media_usage_work_operator").ifExists().execute();
|
|
3660
|
-
await db.schema.dropIndex("idx__emdash_media_usage_work_lease").ifExists().execute();
|
|
3661
|
-
await db.schema.dropIndex("idx__emdash_media_usage_work_due").ifExists().execute();
|
|
3662
|
-
await db.schema.dropTable("_emdash_media_usage_work").ifExists().execute();
|
|
3663
|
-
await db.schema.dropTable("_emdash_media_usage_activation").ifExists().execute();
|
|
3664
|
-
if (isPostgres(db)) await sql`DROP FUNCTION IF EXISTS emdash_media_usage_capture_work()`.execute(db);
|
|
3665
|
-
for (const columnName of ["identity_version", "collection_id"]) if (await columnExists(db, "_emdash_media_usage_sources", columnName)) await db.schema.alterTable("_emdash_media_usage_sources").dropColumn(columnName).execute();
|
|
3666
|
-
for (const columnName of [
|
|
3667
|
-
"capture_state",
|
|
3668
|
-
"last_incremental_success_at",
|
|
3669
|
-
"reconciliation_required",
|
|
3670
|
-
"change_epoch",
|
|
3671
|
-
"collection_id"
|
|
3672
|
-
]) if (await columnExists(db, "_emdash_media_usage_index_status", columnName)) await db.schema.alterTable("_emdash_media_usage_index_status").dropColumn(columnName).execute();
|
|
3673
|
-
}
|
|
3674
|
-
async function addStatusColumns(db) {
|
|
3675
|
-
await addColumnIfMissing(db, "_emdash_media_usage_index_status", "collection_id", () => db.schema.alterTable("_emdash_media_usage_index_status").addColumn("collection_id", "text").execute());
|
|
3676
|
-
await addColumnIfMissing(db, "_emdash_media_usage_index_status", "change_epoch", () => db.schema.alterTable("_emdash_media_usage_index_status").addColumn("change_epoch", "bigint", (column) => column.notNull().defaultTo(0)).execute());
|
|
3677
|
-
await addColumnIfMissing(db, "_emdash_media_usage_index_status", "reconciliation_required", () => db.schema.alterTable("_emdash_media_usage_index_status").addColumn("reconciliation_required", "integer", (column) => column.notNull().defaultTo(0)).execute());
|
|
3678
|
-
await addStatusTextColumn(db, "last_incremental_success_at");
|
|
3679
|
-
await addStatusTextColumn(db, "capture_state");
|
|
3680
|
-
}
|
|
3681
|
-
async function assertRollbackIsEmptyAndInactive(db) {
|
|
3682
|
-
if (await hasCaptureTriggers(db)) throw new Error("Cannot roll back media usage capture while capture triggers are installed");
|
|
3683
|
-
if (await tableExists(db, "_emdash_media_usage_activation")) {
|
|
3684
|
-
const activation = await sql`
|
|
3685
|
-
SELECT state
|
|
3686
|
-
FROM _emdash_media_usage_activation
|
|
3687
|
-
WHERE task_key = ${ACTIVATION_KEY$1}
|
|
3688
|
-
`.execute(db);
|
|
3689
|
-
if (activation.rows[0] && activation.rows[0].state !== "expanded") throw new Error("Cannot roll back media usage capture after activation has started");
|
|
3690
|
-
}
|
|
3691
|
-
if (await tableExists(db, "_emdash_media_usage_work")) {
|
|
3692
|
-
if ((await sql`
|
|
3693
|
-
SELECT 1 AS present FROM _emdash_media_usage_work LIMIT 1
|
|
3694
|
-
`.execute(db)).rows.length > 0) throw new Error("Cannot roll back media usage capture while durable work exists");
|
|
3695
|
-
}
|
|
3696
|
-
const sourceHasCollectionId = await columnExists(db, "_emdash_media_usage_sources", "collection_id");
|
|
3697
|
-
const sourceHasIdentityVersion = await columnExists(db, "_emdash_media_usage_sources", "identity_version");
|
|
3698
|
-
if (sourceHasCollectionId || sourceHasIdentityVersion) {
|
|
3699
|
-
if ((sourceHasCollectionId ? await sql`
|
|
3700
|
-
SELECT 1 AS present
|
|
3701
|
-
FROM _emdash_media_usage_sources
|
|
3702
|
-
WHERE collection_id IS NOT NULL
|
|
3703
|
-
LIMIT 1
|
|
3704
|
-
`.execute(db) : await sql`
|
|
3705
|
-
SELECT 1 AS present
|
|
3706
|
-
FROM _emdash_media_usage_sources
|
|
3707
|
-
WHERE identity_version IS NOT NULL
|
|
3708
|
-
LIMIT 1
|
|
3709
|
-
`.execute(db)).rows.length > 0) throw new Error("Cannot roll back media usage capture after canonical sources exist");
|
|
3710
|
-
if (sourceHasCollectionId && sourceHasIdentityVersion) {
|
|
3711
|
-
if ((await sql`
|
|
3712
|
-
SELECT 1 AS present
|
|
3713
|
-
FROM _emdash_media_usage_sources
|
|
3714
|
-
WHERE identity_version IS NOT NULL
|
|
3715
|
-
LIMIT 1
|
|
3716
|
-
`.execute(db)).rows.length > 0) throw new Error("Cannot roll back media usage capture after canonical sources exist");
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
if (await columnExists(db, "_emdash_media_usage_index_status", "capture_state")) {
|
|
3720
|
-
if ((await sql`
|
|
3721
|
-
SELECT 1 AS present FROM _emdash_media_usage_index_status
|
|
3722
|
-
WHERE capture_state IN ('active', 'deleting') LIMIT 1
|
|
3723
|
-
`.execute(db)).rows.length > 0) throwRollbackLifecycleError();
|
|
3724
|
-
}
|
|
3725
|
-
if (await columnExists(db, "_emdash_media_usage_index_status", "change_epoch")) {
|
|
3726
|
-
if ((await sql`
|
|
3727
|
-
SELECT 1 AS present FROM _emdash_media_usage_index_status
|
|
3728
|
-
WHERE change_epoch <> 0 LIMIT 1
|
|
3729
|
-
`.execute(db)).rows.length > 0) throwRollbackLifecycleError();
|
|
3730
|
-
}
|
|
3731
|
-
if (await columnExists(db, "_emdash_media_usage_index_status", "last_incremental_success_at")) {
|
|
3732
|
-
if ((await sql`
|
|
3733
|
-
SELECT 1 AS present FROM _emdash_media_usage_index_status
|
|
3734
|
-
WHERE last_incremental_success_at IS NOT NULL LIMIT 1
|
|
3735
|
-
`.execute(db)).rows.length > 0) throwRollbackLifecycleError();
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
function throwRollbackLifecycleError() {
|
|
3739
|
-
throw new Error("Cannot roll back media usage capture while capture lifecycle state exists");
|
|
3740
|
-
}
|
|
3741
|
-
async function hasCaptureTriggers(db) {
|
|
3742
|
-
if (isPostgres(db)) return (await sql`
|
|
3743
|
-
SELECT EXISTS (
|
|
3744
|
-
SELECT 1
|
|
3745
|
-
FROM pg_trigger AS trigger
|
|
3746
|
-
INNER JOIN pg_class AS relation ON relation.oid = trigger.tgrelid
|
|
3747
|
-
INNER JOIN pg_namespace AS namespace ON namespace.oid = relation.relnamespace
|
|
3748
|
-
WHERE namespace.nspname = current_schema()
|
|
3749
|
-
AND NOT trigger.tgisinternal
|
|
3750
|
-
AND left(trigger.tgname, 10) = 'emdash_mu_'
|
|
3751
|
-
) AS present
|
|
3752
|
-
`.execute(db)).rows[0]?.present === true;
|
|
3753
|
-
return (await sql`
|
|
3754
|
-
SELECT 1 AS present
|
|
3755
|
-
FROM sqlite_master
|
|
3756
|
-
WHERE type = 'trigger' AND substr(name, 1, 10) = 'emdash_mu_'
|
|
3757
|
-
LIMIT 1
|
|
3758
|
-
`.execute(db)).rows.length > 0;
|
|
3759
|
-
}
|
|
3760
|
-
async function addStatusTextColumn(db, columnName) {
|
|
3761
|
-
await addColumnIfMissing(db, "_emdash_media_usage_index_status", columnName, () => db.schema.alterTable("_emdash_media_usage_index_status").addColumn(columnName, "text").execute());
|
|
3762
|
-
}
|
|
3763
|
-
async function addSourceColumns(db) {
|
|
3764
|
-
await addColumnIfMissing(db, "_emdash_media_usage_sources", "collection_id", () => db.schema.alterTable("_emdash_media_usage_sources").addColumn("collection_id", "text").execute());
|
|
3765
|
-
await addColumnIfMissing(db, "_emdash_media_usage_sources", "identity_version", () => db.schema.alterTable("_emdash_media_usage_sources").addColumn("identity_version", "integer").execute());
|
|
3766
|
-
}
|
|
3767
|
-
async function addColumnIfMissing(db, tableName, columnName, addColumn) {
|
|
3768
|
-
if (await columnExists(db, tableName, columnName)) return;
|
|
3769
|
-
try {
|
|
3770
|
-
await addColumn();
|
|
3771
|
-
} catch (error) {
|
|
3772
|
-
if (DUPLICATE_COLUMN_RE$2.test(deepErrorMessage$3(error))) {
|
|
3773
|
-
if (await columnExists(db, tableName, columnName)) return;
|
|
3774
|
-
}
|
|
3775
|
-
throw error;
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
function sortableUtcTimestamp$2(db) {
|
|
3779
|
-
if (isPostgres(db)) return sql`to_char(clock_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
3780
|
-
return sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;
|
|
3781
|
-
}
|
|
3782
|
-
function deepErrorMessage$3(error) {
|
|
3783
|
-
if (error instanceof Error) {
|
|
3784
|
-
const own = error.message ?? "";
|
|
3785
|
-
if (error.cause) {
|
|
3786
|
-
const causeMessage = deepErrorMessage$3(error.cause);
|
|
3787
|
-
return own ? `${own}: ${causeMessage}` : causeMessage;
|
|
3788
|
-
}
|
|
3789
|
-
return own;
|
|
3790
|
-
}
|
|
3791
|
-
if (typeof error === "string") return error;
|
|
3792
|
-
try {
|
|
3793
|
-
return JSON.stringify(error);
|
|
3794
|
-
} catch {
|
|
3795
|
-
return String(error);
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
|
|
3799
|
-
//#endregion
|
|
3800
|
-
//#region src/database/migrations/064_fts_plain_text.ts
|
|
3801
|
-
var _064_fts_plain_text_exports = /* @__PURE__ */ __exportAll({
|
|
3802
|
-
down: () => down$10,
|
|
3803
|
-
up: () => up$10
|
|
3804
|
-
});
|
|
3805
|
-
async function up$10(db) {
|
|
3806
|
-
if (!isSqlite(db)) return;
|
|
3807
|
-
const collections = await sql`
|
|
3808
|
-
SELECT slug, search_config FROM _emdash_collections
|
|
3809
|
-
WHERE search_config IS NOT NULL
|
|
3810
|
-
`.execute(db);
|
|
3811
|
-
for (const collection of collections.rows) {
|
|
3812
|
-
if (!isSearchEnabled(collection.search_config)) continue;
|
|
3813
|
-
try {
|
|
3814
|
-
validateIdentifier(collection.slug, "collection slug");
|
|
3815
|
-
} catch (error) {
|
|
3816
|
-
console.warn(`[migration 064] skipping FTS rebuild for collection "${collection.slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
3817
|
-
continue;
|
|
3818
|
-
}
|
|
3819
|
-
const fields = await getSearchableFields(db, collection.slug);
|
|
3820
|
-
if (fields.length === 0) continue;
|
|
3821
|
-
await rebuildIndex(db, collection.slug, fields, searchTokenizer(collection.search_config));
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
/**
|
|
3825
|
-
* Forward-only. Down is a no-op: the FTS tables are managed by FTSManager
|
|
3826
|
-
* at runtime and the self-contained shape remains fully functional for
|
|
3827
|
-
* older code paths that only MATCH and join on id.
|
|
3828
|
-
*/
|
|
3829
|
-
async function down$10(_db) {}
|
|
3830
|
-
function isSearchEnabled(searchConfig) {
|
|
3831
|
-
if (!searchConfig) return false;
|
|
3832
|
-
try {
|
|
3833
|
-
const parsed = JSON.parse(searchConfig);
|
|
3834
|
-
return typeof parsed === "object" && parsed !== null && "enabled" in parsed && parsed.enabled === true;
|
|
3835
|
-
} catch {
|
|
3836
|
-
return false;
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
|
-
/**
|
|
3840
|
-
* Tokenizers this migration knows how to carry across the rebuild — a frozen
|
|
3841
|
-
* copy of SEARCH_TOKENIZERS as of this migration, not an import: a replay
|
|
3842
|
-
* after the live allowlist changes must not reinterpret configs this
|
|
3843
|
-
* migration never shipped with.
|
|
3844
|
-
*/
|
|
3845
|
-
const KNOWN_TOKENIZERS = [
|
|
3846
|
-
"porter unicode61",
|
|
3847
|
-
"unicode61",
|
|
3848
|
-
"trigram"
|
|
3849
|
-
];
|
|
3850
|
-
/**
|
|
3851
|
-
* Tokenizer for the rebuilt table, from the collection's search_config.
|
|
3852
|
-
* Values outside the allowlist (or unparsable config) fall back to the
|
|
3853
|
-
* default rather than reaching the raw CREATE VIRTUAL TABLE statement.
|
|
3854
|
-
*/
|
|
3855
|
-
function searchTokenizer(searchConfig) {
|
|
3856
|
-
if (!searchConfig) return "porter unicode61";
|
|
3857
|
-
try {
|
|
3858
|
-
const parsed = JSON.parse(searchConfig);
|
|
3859
|
-
if (typeof parsed === "object" && parsed !== null && "tokenize" in parsed) {
|
|
3860
|
-
const configured = KNOWN_TOKENIZERS.find((tokenizer) => tokenizer === parsed.tokenize);
|
|
3861
|
-
if (configured !== void 0) return configured;
|
|
3862
|
-
}
|
|
3863
|
-
} catch {
|
|
3864
|
-
return "porter unicode61";
|
|
3865
|
-
}
|
|
3866
|
-
return "porter unicode61";
|
|
3867
|
-
}
|
|
3868
|
-
async function getSearchableFields(db, collectionSlug) {
|
|
3869
|
-
const rows = await sql`
|
|
3870
|
-
SELECT f.slug, f.type FROM _emdash_fields f
|
|
3871
|
-
INNER JOIN _emdash_collections c ON c.id = f.collection_id
|
|
3872
|
-
WHERE c.slug = ${collectionSlug} AND f.searchable = 1
|
|
3873
|
-
`.execute(db);
|
|
3874
|
-
const out = [];
|
|
3875
|
-
for (const row of rows.rows) try {
|
|
3876
|
-
validateIdentifier(row.slug, "searchable field name");
|
|
3877
|
-
out.push(row);
|
|
3878
|
-
} catch {
|
|
3879
|
-
console.warn(`[migration 064] skipping invalid searchable field "${row.slug}" on collection "${collectionSlug}"`);
|
|
3880
|
-
}
|
|
3881
|
-
return out;
|
|
3882
|
-
}
|
|
3883
|
-
/** Indexed-value expression for one field; lock-step with FTSManager.searchValueExpr. */
|
|
3884
|
-
function searchValueExpr(ref, fieldType) {
|
|
3885
|
-
if (fieldType !== "portableText") return ref;
|
|
3886
|
-
return `CASE WHEN ${ref} IS NULL THEN NULL WHEN json_valid(${ref}) AND json_type(${ref}) IN ('array', 'object') THEN (SELECT group_concat(j.value, ' ') FROM json_tree(${ref}) AS j WHERE j.key IN ('text', 'alt', 'caption', 'code') AND j.type = 'text') ELSE ${ref} END`;
|
|
3887
|
-
}
|
|
3888
|
-
async function rebuildIndex(db, collectionSlug, fields, tokenizer) {
|
|
3889
|
-
const ftsTable = `_emdash_fts_${collectionSlug}`;
|
|
3890
|
-
const contentTable = `ec_${collectionSlug}`;
|
|
3891
|
-
const slugs = fields.map((f) => f.slug);
|
|
3892
|
-
const columnList = [
|
|
3893
|
-
"id UNINDEXED",
|
|
3894
|
-
"locale UNINDEXED",
|
|
3895
|
-
...slugs
|
|
3896
|
-
].join(", ");
|
|
3897
|
-
const fieldList = slugs.join(", ");
|
|
3898
|
-
const newValueList = fields.map((f) => searchValueExpr(`NEW.${f.slug}`, f.type)).join(", ");
|
|
3899
|
-
const changedCondition = [
|
|
3900
|
-
"deleted_at",
|
|
3901
|
-
"locale",
|
|
3902
|
-
...slugs
|
|
3903
|
-
].map((f) => `OLD.${f} IS NOT NEW.${f}`).join(" OR ");
|
|
3904
|
-
const selectValueList = fields.map((f) => searchValueExpr(`"${contentTable}"."${f.slug}"`, f.type)).join(", ");
|
|
3905
|
-
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_insert"`).execute(db);
|
|
3906
|
-
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_update"`).execute(db);
|
|
3907
|
-
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_delete"`).execute(db);
|
|
3908
|
-
await sql.raw(`DROP TABLE IF EXISTS "${ftsTable}"`).execute(db);
|
|
3909
|
-
await sql.raw(`
|
|
3910
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS "${ftsTable}" USING fts5(
|
|
3911
|
-
${columnList},
|
|
3912
|
-
tokenize='${tokenizer}'
|
|
3913
|
-
)
|
|
3914
|
-
`).execute(db);
|
|
3915
|
-
await sql.raw(`
|
|
3916
|
-
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_insert"
|
|
3917
|
-
AFTER INSERT ON "${contentTable}"
|
|
3918
|
-
WHEN NEW.deleted_at IS NULL
|
|
3919
|
-
BEGIN
|
|
3920
|
-
INSERT OR REPLACE INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
3921
|
-
VALUES (NEW.rowid, NEW.id, NEW.locale, ${newValueList});
|
|
3922
|
-
END
|
|
3923
|
-
`).execute(db);
|
|
3924
|
-
await sql.raw(`
|
|
3925
|
-
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_update"
|
|
3926
|
-
AFTER UPDATE ON "${contentTable}"
|
|
3927
|
-
WHEN ${changedCondition}
|
|
3928
|
-
BEGIN
|
|
3929
|
-
DELETE FROM "${ftsTable}" WHERE rowid = OLD.rowid;
|
|
3930
|
-
INSERT INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
3931
|
-
SELECT NEW.rowid, NEW.id, NEW.locale, ${newValueList}
|
|
3932
|
-
WHERE NEW.deleted_at IS NULL;
|
|
3933
|
-
END
|
|
3934
|
-
`).execute(db);
|
|
3935
|
-
await sql.raw(`
|
|
3936
|
-
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_delete"
|
|
3937
|
-
AFTER DELETE ON "${contentTable}"
|
|
3938
|
-
BEGIN
|
|
3939
|
-
DELETE FROM "${ftsTable}" WHERE rowid = OLD.rowid;
|
|
3940
|
-
END
|
|
3941
|
-
`).execute(db);
|
|
3942
|
-
await sql.raw(`
|
|
3943
|
-
INSERT OR REPLACE INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
3944
|
-
SELECT rowid, id, locale, ${selectValueList} FROM "${contentTable}"
|
|
3945
|
-
WHERE deleted_at IS NULL
|
|
3946
|
-
`).execute(db);
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
|
-
//#endregion
|
|
3950
|
-
//#region src/database/migrations/065_media_usage_collection_deletion.ts
|
|
3951
|
-
var _065_media_usage_collection_deletion_exports = /* @__PURE__ */ __exportAll({
|
|
3952
|
-
down: () => down$9,
|
|
3953
|
-
up: () => up$9
|
|
3954
|
-
});
|
|
3955
|
-
async function up$9(db) {
|
|
3956
|
-
await db.schema.createTable("_emdash_media_usage_collection_deletions").ifNotExists().addColumn("collection_id", "text", (column) => column.notNull().primaryKey()).addColumn("collection_slug", "text", (column) => column.notNull().unique()).addColumn("force_delete", "integer", (column) => column.notNull()).addColumn("state", "text", (column) => column.notNull().defaultTo("pending")).addColumn("phase", "text", (column) => column.notNull().defaultTo("fence")).addColumn("work_cursor", "text").addColumn("source_key", "text").addColumn("occurrence_cursor", "text").addColumn("attempt_count", "integer", (column) => column.notNull().defaultTo(0)).addColumn("next_attempt_at", "text", (column) => column.notNull()).addColumn("lease_token", "text").addColumn("lease_expires_at", "text").addColumn("last_error_code", "text").addColumn("created_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp$1(db))).addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp$1(db))).execute();
|
|
3957
|
-
await db.schema.createIndex("idx__emdash_media_usage_collection_deletions_due").ifNotExists().on("_emdash_media_usage_collection_deletions").columns([
|
|
3958
|
-
"state",
|
|
3959
|
-
"next_attempt_at",
|
|
3960
|
-
"updated_at",
|
|
3961
|
-
"collection_id"
|
|
3962
|
-
]).execute();
|
|
3963
|
-
await db.schema.createIndex("idx__emdash_media_usage_collection_deletions_lease").ifNotExists().on("_emdash_media_usage_collection_deletions").columns([
|
|
3964
|
-
"state",
|
|
3965
|
-
"lease_expires_at",
|
|
3966
|
-
"updated_at",
|
|
3967
|
-
"collection_id"
|
|
3968
|
-
]).execute();
|
|
3969
|
-
await db.schema.createIndex("idx__emdash_media_usage_collection_deletions_operator").ifNotExists().on("_emdash_media_usage_collection_deletions").columns([
|
|
3970
|
-
"state",
|
|
3971
|
-
"updated_at",
|
|
3972
|
-
"collection_id"
|
|
3973
|
-
]).execute();
|
|
3974
|
-
await db.schema.createIndex("idx__emdash_media_usage_sources_collection_cursor").ifNotExists().on("_emdash_media_usage_sources").columns([
|
|
3975
|
-
"source_type",
|
|
3976
|
-
"collection_id",
|
|
3977
|
-
"source_key"
|
|
3978
|
-
]).execute();
|
|
3979
|
-
await db.schema.createIndex("idx__emdash_media_usage_source_cursor").ifNotExists().on("_emdash_media_usage").columns(["source_key", "id"]).execute();
|
|
3980
|
-
}
|
|
3981
|
-
async function down$9(db) {
|
|
3982
|
-
if ((await sql`
|
|
3983
|
-
SELECT 1 AS present
|
|
3984
|
-
FROM _emdash_media_usage_collection_deletions
|
|
3985
|
-
LIMIT 1
|
|
3986
|
-
`.execute(db)).rows.length > 0) throw new Error("Cannot roll back while durable collection deletion evidence exists");
|
|
3987
|
-
await db.schema.dropIndex("idx__emdash_media_usage_source_cursor").ifExists().execute();
|
|
3988
|
-
await db.schema.dropIndex("idx__emdash_media_usage_sources_collection_cursor").ifExists().execute();
|
|
3989
|
-
await db.schema.dropTable("_emdash_media_usage_collection_deletions").ifExists().execute();
|
|
3990
|
-
}
|
|
3991
|
-
function sortableUtcTimestamp$1(db) {
|
|
3992
|
-
if (isPostgres(db)) return sql`to_char(clock_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
3993
|
-
return sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
//#endregion
|
|
3997
|
-
//#region src/database/migrations/066_media_usage_reconciliation.ts
|
|
3998
|
-
var _066_media_usage_reconciliation_exports = /* @__PURE__ */ __exportAll({
|
|
3999
|
-
down: () => down$8,
|
|
4000
|
-
up: () => up$8
|
|
4001
|
-
});
|
|
4002
|
-
const ACTIVATION_KEY = "incremental_capture";
|
|
4003
|
-
async function up$8(db) {
|
|
4004
|
-
await db.schema.createTable("_emdash_media_usage_reconciliations").ifNotExists().addColumn("collection_id", "text", (column) => column.notNull().primaryKey()).addColumn("collection_slug", "text", (column) => column.notNull()).addColumn("run_token", "text", (column) => column.notNull()).addColumn("target_epoch", "bigint").addColumn("field_fingerprint", "text").addColumn("state", "text", (column) => column.notNull().defaultTo("pending")).addColumn("phase", "text", (column) => column.notNull().defaultTo("scan")).addColumn("scan_cursor", "text").addColumn("scan_upper_id", "text").addColumn("source_cursor", "text").addColumn("source_upper_key", "text").addColumn("attempt_count", "integer", (column) => column.notNull().defaultTo(0)).addColumn("next_attempt_at", "text", (column) => column.notNull()).addColumn("lease_token", "text").addColumn("lease_expires_at", "text").addColumn("last_error_code", "text").addColumn("created_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp(db))).addColumn("updated_at", "text", (column) => column.notNull().defaultTo(sortableUtcTimestamp(db))).execute();
|
|
4005
|
-
await db.schema.createIndex("idx__emdash_media_usage_reconciliations_due").ifNotExists().on("_emdash_media_usage_reconciliations").columns([
|
|
4006
|
-
"state",
|
|
4007
|
-
"next_attempt_at",
|
|
4008
|
-
"updated_at",
|
|
4009
|
-
"collection_id"
|
|
4010
|
-
]).execute();
|
|
4011
|
-
await db.schema.createIndex("idx__emdash_media_usage_reconciliations_lease").ifNotExists().on("_emdash_media_usage_reconciliations").columns([
|
|
4012
|
-
"state",
|
|
4013
|
-
"lease_expires_at",
|
|
4014
|
-
"updated_at",
|
|
4015
|
-
"collection_id"
|
|
4016
|
-
]).execute();
|
|
4017
|
-
await db.schema.createIndex("idx__emdash_media_usage_reconciliations_failed").ifNotExists().on("_emdash_media_usage_reconciliations").columns([
|
|
4018
|
-
"state",
|
|
4019
|
-
"updated_at",
|
|
4020
|
-
"collection_id"
|
|
4021
|
-
]).execute();
|
|
4022
|
-
await db.schema.createIndex("idx__emdash_media_usage_status_reconciliation").ifNotExists().on("_emdash_media_usage_index_status").columns([
|
|
4023
|
-
"adapter_id",
|
|
4024
|
-
"scope_type",
|
|
4025
|
-
"capture_state",
|
|
4026
|
-
"reconciliation_required",
|
|
4027
|
-
"collection_id"
|
|
4028
|
-
]).execute();
|
|
4029
|
-
if (!await columnExists(db, "_emdash_media_usage_activation", "media_usage_maintenance_turn")) await db.schema.alterTable("_emdash_media_usage_activation").addColumn("media_usage_maintenance_turn", "integer", (column) => column.notNull().defaultTo(2)).execute();
|
|
4030
|
-
}
|
|
4031
|
-
async function down$8(db) {
|
|
4032
|
-
if (await tableExists(db, "_emdash_media_usage_reconciliations")) {
|
|
4033
|
-
if ((await sql`
|
|
4034
|
-
SELECT 1 AS present
|
|
4035
|
-
FROM _emdash_media_usage_reconciliations
|
|
4036
|
-
LIMIT 1
|
|
4037
|
-
`.execute(db)).rows.length > 0) throw new Error("Cannot roll back while durable reconciliation evidence exists");
|
|
4038
|
-
}
|
|
4039
|
-
if ((await sql`
|
|
4040
|
-
SELECT state
|
|
4041
|
-
FROM _emdash_media_usage_activation
|
|
4042
|
-
WHERE task_key = ${ACTIVATION_KEY}
|
|
4043
|
-
`.execute(db)).rows[0]?.state !== "expanded") throw new Error("Cannot roll back media usage reconciliation after activation has started");
|
|
4044
|
-
await db.schema.dropIndex("idx__emdash_media_usage_status_reconciliation").ifExists().execute();
|
|
4045
|
-
await db.schema.dropIndex("idx__emdash_media_usage_reconciliations_failed").ifExists().execute();
|
|
4046
|
-
await db.schema.dropIndex("idx__emdash_media_usage_reconciliations_lease").ifExists().execute();
|
|
4047
|
-
await db.schema.dropIndex("idx__emdash_media_usage_reconciliations_due").ifExists().execute();
|
|
4048
|
-
await db.schema.dropTable("_emdash_media_usage_reconciliations").ifExists().execute();
|
|
4049
|
-
if (await columnExists(db, "_emdash_media_usage_activation", "media_usage_maintenance_turn")) await db.schema.alterTable("_emdash_media_usage_activation").dropColumn("media_usage_maintenance_turn").execute();
|
|
4050
|
-
}
|
|
4051
|
-
function sortableUtcTimestamp(db) {
|
|
4052
|
-
if (isPostgres(db)) return sql`to_char(clock_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
4053
|
-
return sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;
|
|
4054
|
-
}
|
|
4055
|
-
|
|
4056
|
-
//#endregion
|
|
4057
|
-
//#region src/database/migrations/067_indexed_content_fields.ts
|
|
4058
|
-
var _067_indexed_content_fields_exports = /* @__PURE__ */ __exportAll({
|
|
4059
|
-
down: () => down$7,
|
|
4060
|
-
up: () => up$7
|
|
4061
|
-
});
|
|
4062
|
-
const FIELD_ID_PATTERN = /^[0-9A-Z]{26}$/;
|
|
4063
|
-
/** Mark custom fields whose structured list queries are backed by a physical index. */
|
|
4064
|
-
async function up$7(db) {
|
|
4065
|
-
if (!await columnExists(db, "_emdash_fields", "indexed")) await db.schema.alterTable("_emdash_fields").addColumn("indexed", "integer", (column) => column.notNull().defaultTo(0)).execute();
|
|
4066
|
-
}
|
|
4067
|
-
async function down$7(db) {
|
|
4068
|
-
if (!await columnExists(db, "_emdash_fields", "indexed")) return;
|
|
4069
|
-
const indexedFields = await sql`
|
|
4070
|
-
SELECT id FROM _emdash_fields WHERE indexed = 1
|
|
4071
|
-
`.execute(db);
|
|
4072
|
-
for (const field of indexedFields.rows) {
|
|
4073
|
-
if (!FIELD_ID_PATTERN.test(field.id)) throw new Error(`Invalid indexed field id "${field.id}"`);
|
|
4074
|
-
await sql`
|
|
4075
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_cf_${field.id.toLowerCase()}`)}
|
|
4076
|
-
`.execute(db);
|
|
4077
|
-
await sql`
|
|
4078
|
-
DROP INDEX IF EXISTS ${sql.ref(`idx_cf_${field.id.toLowerCase()}_loc`)}
|
|
4079
|
-
`.execute(db);
|
|
4080
|
-
}
|
|
4081
|
-
await db.schema.alterTable("_emdash_fields").dropColumn("indexed").execute();
|
|
4082
|
-
}
|
|
4083
|
-
|
|
4084
|
-
//#endregion
|
|
4085
|
-
//#region src/database/migrations/068_content_taxonomy_entry_groups.ts
|
|
4086
|
-
var _068_content_taxonomy_entry_groups_exports = /* @__PURE__ */ __exportAll({
|
|
4087
|
-
down: () => down$6,
|
|
4088
|
-
up: () => up$6
|
|
4089
|
-
});
|
|
4090
|
-
const LEGACY_DENORM_INDEXES = [
|
|
4091
|
-
"idx_content_taxonomies_pub",
|
|
4092
|
-
"idx_content_taxonomies_crt",
|
|
4093
|
-
"idx_content_taxonomies_loc_pub",
|
|
4094
|
-
"idx_content_taxonomies_loc_crt"
|
|
4095
|
-
];
|
|
4096
|
-
/**
|
|
4097
|
-
* Store the content translation_group in content_taxonomies.entry_id.
|
|
4098
|
-
* Existing per-locale rows are merged into one assignment per content group
|
|
4099
|
-
* and term group. Rows whose content table or content entry no longer exists
|
|
4100
|
-
* are left untouched.
|
|
4101
|
-
*/
|
|
4102
|
-
async function up$6(db) {
|
|
4103
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
4104
|
-
for (const tableName of tableNames) {
|
|
4105
|
-
validateIdentifier(tableName, "content table name");
|
|
4106
|
-
const collection = tableName.slice(3);
|
|
4107
|
-
await sql`
|
|
4108
|
-
INSERT INTO content_taxonomies (collection, entry_id, taxonomy_id)
|
|
4109
|
-
SELECT DISTINCT ${collection}, content.translation_group, pivot.taxonomy_id
|
|
4110
|
-
FROM content_taxonomies AS pivot
|
|
4111
|
-
INNER JOIN ${sql.ref(tableName)} AS content ON content.id = pivot.entry_id
|
|
4112
|
-
WHERE pivot.collection = ${collection}
|
|
4113
|
-
AND content.translation_group IS NOT NULL
|
|
4114
|
-
ON CONFLICT (collection, entry_id, taxonomy_id) DO NOTHING
|
|
4115
|
-
`.execute(db);
|
|
4116
|
-
await sql`
|
|
4117
|
-
DELETE FROM content_taxonomies
|
|
4118
|
-
WHERE collection = ${collection}
|
|
4119
|
-
AND entry_id IN (
|
|
4120
|
-
SELECT id
|
|
4121
|
-
FROM ${sql.ref(tableName)}
|
|
4122
|
-
WHERE translation_group IS NOT NULL AND id != translation_group
|
|
4123
|
-
)
|
|
4124
|
-
`.execute(db);
|
|
4125
|
-
}
|
|
4126
|
-
for (const indexName of LEGACY_DENORM_INDEXES) await sql`DROP INDEX IF EXISTS ${sql.ref(indexName)}`.execute(db);
|
|
4127
|
-
await db.schema.createIndex("idx_content_taxonomies_group_lookup").ifNotExists().on("content_taxonomies").columns([
|
|
4128
|
-
"taxonomy_id",
|
|
4129
|
-
"collection",
|
|
4130
|
-
"entry_id"
|
|
4131
|
-
]).execute();
|
|
4132
|
-
}
|
|
4133
|
-
/** The collapsed per-locale rows cannot be reconstructed. */
|
|
4134
|
-
async function down$6(_db) {}
|
|
4135
|
-
|
|
4136
|
-
//#endregion
|
|
4137
|
-
//#region src/database/migrations/069_collection_title_date_fields.ts
|
|
4138
|
-
var _069_collection_title_date_fields_exports = /* @__PURE__ */ __exportAll({
|
|
4139
|
-
down: () => down$5,
|
|
4140
|
-
up: () => up$5
|
|
4141
|
-
});
|
|
4142
|
-
/**
|
|
4143
|
-
* Migration: configurable title and date fields for collections
|
|
4144
|
-
*
|
|
4145
|
-
* Adds `title_field` and `date_field` columns to `_emdash_collections` so a
|
|
4146
|
-
* collection can override which field powers the Title column and which field
|
|
4147
|
-
* powers the Date column in the admin content list. Both are nullable — NULL
|
|
4148
|
-
* preserves the current defaults (title-style display, last-updated date).
|
|
4149
|
-
*
|
|
4150
|
-
* Column adds/drops are guarded by `columnExists`: D1 has no transactions, so a
|
|
4151
|
-
* migration can partially apply without being recorded and later re-run.
|
|
4152
|
-
*/
|
|
4153
|
-
async function up$5(db) {
|
|
4154
|
-
if (!await columnExists(db, "_emdash_collections", "title_field")) await sql`
|
|
4155
|
-
ALTER TABLE _emdash_collections
|
|
4156
|
-
ADD COLUMN title_field TEXT
|
|
4157
|
-
`.execute(db);
|
|
4158
|
-
if (!await columnExists(db, "_emdash_collections", "date_field")) await sql`
|
|
4159
|
-
ALTER TABLE _emdash_collections
|
|
4160
|
-
ADD COLUMN date_field TEXT
|
|
4161
|
-
`.execute(db);
|
|
4162
|
-
}
|
|
4163
|
-
async function down$5(db) {
|
|
4164
|
-
if (await columnExists(db, "_emdash_collections", "date_field")) await sql`
|
|
4165
|
-
ALTER TABLE _emdash_collections
|
|
4166
|
-
DROP COLUMN date_field
|
|
4167
|
-
`.execute(db);
|
|
4168
|
-
if (await columnExists(db, "_emdash_collections", "title_field")) await sql`
|
|
4169
|
-
ALTER TABLE _emdash_collections
|
|
4170
|
-
DROP COLUMN title_field
|
|
4171
|
-
`.execute(db);
|
|
4172
|
-
}
|
|
4173
|
-
|
|
4174
|
-
//#endregion
|
|
4175
|
-
//#region src/database/migrations/070_collection_routable.ts
|
|
4176
|
-
var _070_collection_routable_exports = /* @__PURE__ */ __exportAll({
|
|
4177
|
-
down: () => down$4,
|
|
4178
|
-
up: () => up$4
|
|
4179
|
-
});
|
|
4180
|
-
async function up$4(db) {
|
|
4181
|
-
if (!await columnExists(db, "_emdash_collections", "routable")) await db.schema.alterTable("_emdash_collections").addColumn("routable", "integer", (col) => col.notNull().defaultTo(1)).execute();
|
|
4182
|
-
}
|
|
4183
|
-
async function down$4(db) {
|
|
4184
|
-
if (await columnExists(db, "_emdash_collections", "routable")) await db.schema.alterTable("_emdash_collections").dropColumn("routable").execute();
|
|
4185
|
-
}
|
|
4186
|
-
|
|
4187
|
-
//#endregion
|
|
4188
|
-
//#region src/database/migrations/071_restore_content_bylines_table.ts
|
|
4189
|
-
var _071_restore_content_bylines_table_exports = /* @__PURE__ */ __exportAll({
|
|
4190
|
-
down: () => down$3,
|
|
4191
|
-
up: () => up$3
|
|
4192
|
-
});
|
|
4193
|
-
/**
|
|
4194
|
-
* Restore `_emdash_content_bylines` where migration 040's SQLite rebuild
|
|
4195
|
-
* stopped between dropping the old table and renaming the staged copy.
|
|
4196
|
-
*
|
|
4197
|
-
* 040 guards its rebuild on `PRAGMA foreign_key_list`, which returns no rows
|
|
4198
|
-
* for a missing table, so a retry after that partial run skips the rename and
|
|
4199
|
-
* leaves `_emdash_content_bylines_new` behind with the credits inside. The
|
|
4200
|
-
* indexes went with the dropped table.
|
|
4201
|
-
*
|
|
4202
|
-
* The Postgres path of 040 alters the table in place, so this is a no-op there.
|
|
4203
|
-
*/
|
|
4204
|
-
async function up$3(db) {
|
|
4205
|
-
if (!isSqlite(db)) return;
|
|
4206
|
-
if (!await tableExists(db, "_emdash_content_bylines")) {
|
|
4207
|
-
if (!await tableExists(db, "_emdash_content_bylines_new")) return;
|
|
4208
|
-
await sql`ALTER TABLE _emdash_content_bylines_new RENAME TO _emdash_content_bylines`.execute(db);
|
|
4209
|
-
}
|
|
4210
|
-
await db.schema.createIndex("idx_content_bylines_content").ifNotExists().on("_emdash_content_bylines").columns([
|
|
4211
|
-
"collection_slug",
|
|
4212
|
-
"content_id",
|
|
4213
|
-
"sort_order"
|
|
4214
|
-
]).execute();
|
|
4215
|
-
await db.schema.createIndex("idx_content_bylines_byline").ifNotExists().on("_emdash_content_bylines").column("byline_id").execute();
|
|
4216
|
-
}
|
|
4217
|
-
async function down$3(_db) {}
|
|
4218
|
-
|
|
4219
|
-
//#endregion
|
|
4220
|
-
//#region src/database/migrations/072_media_folders.ts
|
|
4221
|
-
var _072_media_folders_exports = /* @__PURE__ */ __exportAll({
|
|
4222
|
-
down: () => down$2,
|
|
4223
|
-
up: () => up$2
|
|
4224
|
-
});
|
|
4225
|
-
const DUPLICATE_COLUMN_RE$1 = /(?:duplicate column|column .* already exists|already exists.*column)/i;
|
|
4226
|
-
async function up$2(db) {
|
|
4227
|
-
await db.schema.createTable("media_folders").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("name", "text", (col) => col.notNull()).addColumn("name_key", "text", (col) => col.notNull().unique()).execute();
|
|
4228
|
-
await addFolderColumnIfMissing(db, () => db.schema.alterTable("media").addColumn("folder_id", "text", (col) => col.references("media_folders.id").onDelete("set null")).execute());
|
|
4229
|
-
await db.schema.createIndex("idx_media_folder_id").ifNotExists().on("media").column("folder_id").execute();
|
|
4230
|
-
}
|
|
4231
|
-
async function down$2(db) {
|
|
4232
|
-
await db.schema.dropIndex("idx_media_folder_id").ifExists().execute();
|
|
4233
|
-
if (await columnExists(db, "media", "folder_id")) await db.schema.alterTable("media").dropColumn("folder_id").execute();
|
|
4234
|
-
await db.schema.dropTable("media_folders").ifExists().execute();
|
|
4235
|
-
}
|
|
4236
|
-
async function addFolderColumnIfMissing(db, addColumn) {
|
|
4237
|
-
if (await columnExists(db, "media", "folder_id")) return;
|
|
4238
|
-
try {
|
|
4239
|
-
await addColumn();
|
|
4240
|
-
} catch (error) {
|
|
4241
|
-
if (DUPLICATE_COLUMN_RE$1.test(deepErrorMessage$2(error))) {
|
|
4242
|
-
if (await columnExists(db, "media", "folder_id")) return;
|
|
4243
|
-
}
|
|
4244
|
-
throw error;
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
function deepErrorMessage$2(error) {
|
|
4248
|
-
if (error instanceof Error) {
|
|
4249
|
-
const own = error.message ?? "";
|
|
4250
|
-
if (error.cause) {
|
|
4251
|
-
const causeMessage = deepErrorMessage$2(error.cause);
|
|
4252
|
-
return own ? `${own}: ${causeMessage}` : causeMessage;
|
|
4253
|
-
}
|
|
4254
|
-
return own;
|
|
4255
|
-
}
|
|
4256
|
-
if (typeof error === "string") return error;
|
|
4257
|
-
try {
|
|
4258
|
-
return JSON.stringify(error);
|
|
4259
|
-
} catch {
|
|
4260
|
-
return String(error);
|
|
4261
|
-
}
|
|
4262
|
-
}
|
|
4263
|
-
|
|
4264
|
-
//#endregion
|
|
4265
|
-
//#region src/database/migrations/073_media_focal_point.ts
|
|
4266
|
-
var _073_media_focal_point_exports = /* @__PURE__ */ __exportAll({
|
|
4267
|
-
addFocalColumnIfMissing: () => addFocalColumnIfMissing,
|
|
4268
|
-
down: () => down$1,
|
|
4269
|
-
up: () => up$1
|
|
4270
|
-
});
|
|
4271
|
-
const DUPLICATE_COLUMN_RE = /(?:duplicate column|column .* already exists|already exists.*column)/i;
|
|
4272
|
-
async function up$1(db) {
|
|
4273
|
-
await addFocalColumnIfMissing(db, "focal_x", () => db.schema.alterTable("media").addColumn("focal_x", "real").execute());
|
|
4274
|
-
await addFocalColumnIfMissing(db, "focal_y", () => db.schema.alterTable("media").addColumn("focal_y", "real").execute());
|
|
4275
|
-
}
|
|
4276
|
-
async function down$1(db) {
|
|
4277
|
-
if (await columnExists(db, "media", "focal_y")) await db.schema.alterTable("media").dropColumn("focal_y").execute();
|
|
4278
|
-
if (await columnExists(db, "media", "focal_x")) await db.schema.alterTable("media").dropColumn("focal_x").execute();
|
|
4279
|
-
}
|
|
4280
|
-
async function addFocalColumnIfMissing(db, column, addColumn) {
|
|
4281
|
-
if (await columnExists(db, "media", column)) return;
|
|
4282
|
-
try {
|
|
4283
|
-
await addColumn();
|
|
4284
|
-
} catch (error) {
|
|
4285
|
-
if (DUPLICATE_COLUMN_RE.test(deepErrorMessage$1(error))) {
|
|
4286
|
-
if (await columnExists(db, "media", column)) return;
|
|
4287
|
-
}
|
|
4288
|
-
throw error;
|
|
4289
|
-
}
|
|
4290
|
-
}
|
|
4291
|
-
function deepErrorMessage$1(error) {
|
|
4292
|
-
if (error instanceof Error) {
|
|
4293
|
-
const own = error.message ?? "";
|
|
4294
|
-
if (error.cause) {
|
|
4295
|
-
const causeMessage = deepErrorMessage$1(error.cause);
|
|
4296
|
-
return own ? `${own}: ${causeMessage}` : causeMessage;
|
|
4297
|
-
}
|
|
4298
|
-
return own;
|
|
4299
|
-
}
|
|
4300
|
-
if (typeof error === "string") return error;
|
|
4301
|
-
try {
|
|
4302
|
-
return JSON.stringify(error);
|
|
4303
|
-
} catch {
|
|
4304
|
-
return String(error);
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
|
|
4308
|
-
//#endregion
|
|
4309
|
-
//#region src/database/migrations/074_content_deleted_scheduled_index.ts
|
|
4310
|
-
var _074_content_deleted_scheduled_index_exports = /* @__PURE__ */ __exportAll({
|
|
4311
|
-
down: () => down,
|
|
4312
|
-
up: () => up
|
|
4313
|
-
});
|
|
4314
|
-
/**
|
|
4315
|
-
* Migration: replace the single-column partial `scheduled_at` index on content
|
|
4316
|
-
* tables with a `(deleted_at, scheduled_at)` composite.
|
|
4317
|
-
*
|
|
4318
|
-
* The scheduled-publishing sweep filters `scheduled_at` by range and
|
|
4319
|
-
* `deleted_at IS NULL`, ordered by `scheduled_at`. A `scheduled_at`-only index
|
|
4320
|
-
* cannot satisfy the soft-delete term, so a stats-blind planner seeks a
|
|
4321
|
-
* `deleted_at`-leading composite instead and reads every live row. Leading with
|
|
4322
|
-
* `deleted_at` lets one index serve the equality, the range, and the ORDER BY.
|
|
4323
|
-
* D1 never has `sqlite_stat1`, so the index shape is the only lever.
|
|
4324
|
-
*
|
|
4325
|
-
* The partial predicate from migration 030 is kept so the index stays
|
|
4326
|
-
* proportional to scheduled content rather than to the table.
|
|
4327
|
-
*
|
|
4328
|
-
* Forward-only and idempotent (`IF NOT EXISTS`).
|
|
4329
|
-
*
|
|
4330
|
-
* The short `del_sched` suffix keeps the name inside Postgres's 63-byte
|
|
4331
|
-
* identifier limit for long collection slugs. Keep it identical to the name in
|
|
4332
|
-
* `schema/registry.ts`.
|
|
4333
|
-
*/
|
|
4334
|
-
async function up(db) {
|
|
4335
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
4336
|
-
for (const tableName of tableNames) {
|
|
4337
|
-
await sql`
|
|
4338
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_del_sched`)}
|
|
4339
|
-
ON ${sql.ref(tableName)} (deleted_at, scheduled_at)
|
|
4340
|
-
WHERE scheduled_at IS NOT NULL
|
|
4341
|
-
`.execute(db);
|
|
4342
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_scheduled`)}`.execute(db);
|
|
4343
|
-
}
|
|
4344
|
-
}
|
|
4345
|
-
async function down(db) {
|
|
4346
|
-
const tableNames = await listTablesLike(db, "ec_%");
|
|
4347
|
-
for (const tableName of tableNames) {
|
|
4348
|
-
await sql`
|
|
4349
|
-
CREATE INDEX IF NOT EXISTS ${sql.ref(`idx_${tableName}_scheduled`)}
|
|
4350
|
-
ON ${sql.ref(tableName)} (scheduled_at)
|
|
4351
|
-
WHERE scheduled_at IS NOT NULL
|
|
4352
|
-
`.execute(db);
|
|
4353
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_del_sched`)}`.execute(db);
|
|
4354
|
-
}
|
|
4355
|
-
}
|
|
4356
|
-
|
|
4357
|
-
//#endregion
|
|
4358
|
-
//#region src/database/migrations/runner.ts
|
|
4359
|
-
const MIGRATIONS = Object.freeze({
|
|
4360
|
-
"001_initial": _001_initial_exports,
|
|
4361
|
-
"002_media_status": _002_media_status_exports,
|
|
4362
|
-
"003_schema_registry": _003_schema_registry_exports,
|
|
4363
|
-
"004_plugins": _004_plugins_exports,
|
|
4364
|
-
"005_menus": _005_menus_exports,
|
|
4365
|
-
"006_taxonomy_defs": _006_taxonomy_defs_exports,
|
|
4366
|
-
"007_widgets": _007_widgets_exports,
|
|
4367
|
-
"008_auth": _008_auth_exports,
|
|
4368
|
-
"009_user_disabled": _009_user_disabled_exports,
|
|
4369
|
-
"011_sections": _011_sections_exports,
|
|
4370
|
-
"012_search": _012_search_exports,
|
|
4371
|
-
"013_scheduled_publishing": _013_scheduled_publishing_exports,
|
|
4372
|
-
"014_draft_revisions": _014_draft_revisions_exports,
|
|
4373
|
-
"015_indexes": _015_indexes_exports,
|
|
4374
|
-
"016_api_tokens": _016_api_tokens_exports,
|
|
4375
|
-
"017_authorization_codes": _017_authorization_codes_exports,
|
|
4376
|
-
"018_seo": _018_seo_exports,
|
|
4377
|
-
"019_i18n": _019_i18n_exports,
|
|
4378
|
-
"020_collection_url_pattern": _020_collection_url_pattern_exports,
|
|
4379
|
-
"021_remove_section_categories": _021_remove_section_categories_exports,
|
|
4380
|
-
"022_marketplace_plugin_state": _022_marketplace_plugin_state_exports,
|
|
4381
|
-
"023_plugin_metadata": _023_plugin_metadata_exports,
|
|
4382
|
-
"024_media_placeholders": _024_media_placeholders_exports,
|
|
4383
|
-
"025_oauth_clients": _025_oauth_clients_exports,
|
|
4384
|
-
"026_cron_tasks": _026_cron_tasks_exports,
|
|
4385
|
-
"027_comments": _027_comments_exports,
|
|
4386
|
-
"028_drop_author_url": _028_drop_author_url_exports,
|
|
4387
|
-
"029_redirects": _029_redirects_exports,
|
|
4388
|
-
"030_widen_scheduled_index": _030_widen_scheduled_index_exports,
|
|
4389
|
-
"031_bylines": _031_bylines_exports,
|
|
4390
|
-
"032_rate_limits": _032_rate_limits_exports,
|
|
4391
|
-
"033_optimize_content_indexes": _033_optimize_content_indexes_exports,
|
|
4392
|
-
"034_published_at_index": _034_published_at_index_exports,
|
|
4393
|
-
"035_bounded_404_log": _035_bounded_404_log_exports,
|
|
4394
|
-
"036_i18n_menus_and_taxonomies": _036_i18n_menus_and_taxonomies_exports,
|
|
4395
|
-
"037_credential_algorithm": _037_credential_algorithm_exports,
|
|
4396
|
-
"038_registry_plugin_state": _038_registry_plugin_state_exports,
|
|
4397
|
-
"039_fix_fts5_triggers": _039_fix_fts5_triggers_exports,
|
|
4398
|
-
"040_byline_i18n": _040_byline_i18n_exports,
|
|
4399
|
-
"041_content_locale_list_index": _041_content_locale_list_index_exports,
|
|
4400
|
-
"042_byline_fields": _042_byline_fields_exports,
|
|
4401
|
-
"043_content_references": _043_content_references_exports,
|
|
4402
|
-
"044_comment_reactions": _044_comment_reactions_exports,
|
|
4403
|
-
"045_taxonomy_parent_group": _045_taxonomy_parent_group_exports,
|
|
4404
|
-
"046_media_usage_index": _046_media_usage_index_exports,
|
|
4405
|
-
"047_restore_taxonomy_parent_index": _047_restore_taxonomy_parent_index_exports,
|
|
4406
|
-
"048_restore_content_taxonomies_term_index": _048_restore_content_taxonomies_term_index_exports,
|
|
4407
|
-
"049_taxonomies_name_locale_index": _049_taxonomies_name_locale_index_exports,
|
|
4408
|
-
"050_media_usage_index_status": _050_media_usage_index_status_exports,
|
|
4409
|
-
"051_content_taxonomies_denorm": _051_content_taxonomies_denorm_exports,
|
|
4410
|
-
"052_media_usage_read_index": _052_media_usage_read_index_exports,
|
|
4411
|
-
"053_plugin_mcp_tools": _053_plugin_mcp_tools_exports,
|
|
4412
|
-
"054_media_upload_attempts": _054_media_upload_attempts_exports,
|
|
4413
|
-
"055_content_translation_group_locale_index": _055_content_translation_group_locale_index_exports,
|
|
4414
|
-
"056_taxonomy_term_sort_order": _056_taxonomy_term_sort_order_exports,
|
|
4415
|
-
"057_collection_hidden": _057_collection_hidden_exports,
|
|
4416
|
-
"058_collection_sort_order": _058_collection_sort_order_exports,
|
|
4417
|
-
"059_revision_prune_queue": _059_revision_prune_queue_exports,
|
|
4418
|
-
"060_collection_admin_config": _060_collection_admin_config_exports,
|
|
4419
|
-
"061_media_usage_cleanup": _061_media_usage_cleanup_exports,
|
|
4420
|
-
"062_media_usage_cleanup_fence": _062_media_usage_cleanup_fence_exports,
|
|
4421
|
-
"063_media_usage_incremental_work": _063_media_usage_incremental_work_exports,
|
|
4422
|
-
"064_fts_plain_text": _064_fts_plain_text_exports,
|
|
4423
|
-
"065_media_usage_collection_deletion": _065_media_usage_collection_deletion_exports,
|
|
4424
|
-
"066_media_usage_reconciliation": _066_media_usage_reconciliation_exports,
|
|
4425
|
-
"067_indexed_content_fields": _067_indexed_content_fields_exports,
|
|
4426
|
-
"068_content_taxonomy_entry_groups": _068_content_taxonomy_entry_groups_exports,
|
|
4427
|
-
"069_collection_title_date_fields": _069_collection_title_date_fields_exports,
|
|
4428
|
-
"070_collection_routable": _070_collection_routable_exports,
|
|
4429
|
-
"071_restore_content_bylines_table": _071_restore_content_bylines_table_exports,
|
|
4430
|
-
"072_media_folders": _072_media_folders_exports,
|
|
4431
|
-
"073_media_focal_point": _073_media_focal_point_exports,
|
|
4432
|
-
"074_content_deleted_scheduled_index": _074_content_deleted_scheduled_index_exports
|
|
4433
|
-
});
|
|
4434
|
-
/** Ordered names from the statically registered migration set. */
|
|
4435
|
-
const MIGRATION_NAMES = Object.freeze(Object.keys(MIGRATIONS));
|
|
4436
|
-
/** Total number of registered migrations. Exported for use in tests. */
|
|
4437
|
-
const MIGRATION_COUNT = MIGRATION_NAMES.length;
|
|
4438
|
-
/**
|
|
4439
|
-
* Migration provider that uses statically imported migrations.
|
|
4440
|
-
* This approach works well with bundlers and avoids filesystem access.
|
|
4441
|
-
*/
|
|
4442
|
-
var StaticMigrationProvider = class {
|
|
4443
|
-
async getMigrations() {
|
|
4444
|
-
return MIGRATIONS;
|
|
4445
|
-
}
|
|
4446
|
-
};
|
|
4447
|
-
/**
|
|
4448
|
-
* Thrown when another instance held the migration lock for the whole wait
|
|
4449
|
-
* window. This is NOT a migration failure — the holder may simply be slow
|
|
4450
|
-
* (e.g. many pending migrations over a remote Postgres connection) — so
|
|
4451
|
-
* callers with failure-backoff logic (`getDatabase` in emdash-runtime.ts)
|
|
4452
|
-
* exempt it: the next request waits again instead of backing off, and init
|
|
4453
|
-
* recovers as soon as the holder finishes.
|
|
4454
|
-
*/
|
|
4455
|
-
var ConcurrentMigrationTimeoutError = class extends Error {
|
|
4456
|
-
constructor(waitMs) {
|
|
4457
|
-
super(`Timed out waiting for another instance's migrations: the migration lock was still held after ${waitMs}ms. It may still be applying migrations, or its migration may be failing repeatedly.`);
|
|
4458
|
-
this.name = "ConcurrentMigrationTimeoutError";
|
|
4459
|
-
}
|
|
4460
|
-
};
|
|
4461
|
-
/** Custom migration table name */
|
|
4462
|
-
const MIGRATION_TABLE = "_emdash_migrations";
|
|
4463
|
-
const MIGRATION_LOCK_TABLE = "_emdash_migrations_lock";
|
|
4464
|
-
function createMigrator(db, options) {
|
|
4465
|
-
return new Migrator({
|
|
4466
|
-
db,
|
|
4467
|
-
provider: new StaticMigrationProvider(),
|
|
4468
|
-
migrationTableName: MIGRATION_TABLE,
|
|
4469
|
-
migrationLockTableName: MIGRATION_LOCK_TABLE,
|
|
4470
|
-
migrationTableSchema: options?.migrationTableSchema
|
|
4471
|
-
});
|
|
4472
|
-
}
|
|
4473
|
-
/**
|
|
4474
|
-
* Get migration status
|
|
4475
|
-
*/
|
|
4476
|
-
async function getMigrationStatus(db, options) {
|
|
4477
|
-
const migrations = await createMigrator(db, options).getMigrations();
|
|
4478
|
-
const applied = [];
|
|
4479
|
-
const pending = [];
|
|
4480
|
-
for (const migration of migrations) if (migration.executedAt) applied.push(migration.name);
|
|
4481
|
-
else pending.push(migration.name);
|
|
4482
|
-
return {
|
|
4483
|
-
applied,
|
|
4484
|
-
pending
|
|
4485
|
-
};
|
|
4486
|
-
}
|
|
4487
|
-
/** Pattern for escaping special regex characters. Matches the shared helper in `database/repositories/content.ts`. */
|
|
4488
|
-
const REGEX_ESCAPE_PATTERN = /[.*+?^${}()|[\]\\]/g;
|
|
4489
|
-
/** Escape special regex characters so a string can be embedded literally in `new RegExp()`. */
|
|
4490
|
-
function escapeRegExp(value) {
|
|
4491
|
-
return value.replace(REGEX_ESCAPE_PATTERN, "\\$&");
|
|
4492
|
-
}
|
|
4493
|
-
/**
|
|
4494
|
-
* Pattern used to detect the concurrent-migration race. The Kysely
|
|
4495
|
-
* `SqliteAdapter.acquireMigrationLock` is a no-op (inherited by `kysely-d1`
|
|
4496
|
-
* and our `EmDashD1Dialect`), so two isolates running migrations against the
|
|
4497
|
-
* same database can both attempt `INSERT INTO _emdash_migrations` for the
|
|
4498
|
-
* same migration name. The losing insert fails with a UNIQUE constraint
|
|
4499
|
-
* error, which is benign: the other isolate is applying the same schema.
|
|
4500
|
-
*
|
|
4501
|
-
* We match on the table name (not the full error text) because different
|
|
4502
|
-
* SQLite drivers phrase the message differently
|
|
4503
|
-
* (`UNIQUE constraint failed: _emdash_migrations.name` for file-backed SQLite,
|
|
4504
|
-
* `D1_ERROR: UNIQUE constraint failed: _emdash_migrations.name: SQLITE_CONSTRAINT`
|
|
4505
|
-
* for D1, etc.). The pattern is built from `MIGRATION_TABLE` so a rename
|
|
4506
|
-
* cannot silently disable race detection.
|
|
4507
|
-
*/
|
|
4508
|
-
const MIGRATION_RACE_PATTERN = new RegExp(`UNIQUE constraint failed: ${escapeRegExp(MIGRATION_TABLE)}\\.name`, "i");
|
|
4509
|
-
/**
|
|
4510
|
-
* How long to wait for a concurrent migrator to finish before giving up.
|
|
4511
|
-
* Exported because the db init lock's reclaim deadline must comfortably
|
|
4512
|
-
* exceed it (see DB_INIT_DEADLINE_MS in emdash-runtime.ts) — a healthy
|
|
4513
|
-
* init can legitimately block this long inside waitForConcurrentMigrator.
|
|
4514
|
-
*/
|
|
4515
|
-
const MIGRATION_RACE_WAIT_MS = 1e4;
|
|
4516
|
-
/** Polling interval while waiting for a concurrent migrator. */
|
|
4517
|
-
const MIGRATION_RACE_POLL_MS = 100;
|
|
4518
|
-
/**
|
|
4519
|
-
* Pattern used to detect "table does not exist" errors across the dialects
|
|
4520
|
-
* EmDash supports. The phrasing differs by driver:
|
|
4521
|
-
*
|
|
4522
|
-
* - SQLite: `no such table: _emdash_migrations`
|
|
4523
|
-
* - D1: `D1_ERROR: no such table: _emdash_migrations: SQLITE_ERROR`
|
|
4524
|
-
* - PostgreSQL: `relation "_emdash_migrations" does not exist`
|
|
4525
|
-
* (also occasionally `table "_emdash_migrations" does not exist`)
|
|
4526
|
-
*
|
|
4527
|
-
* We deliberately match on the migration table name (rather than using the
|
|
4528
|
-
* generic `isMissingTableError` helper) so an unexpected missing-table error
|
|
4529
|
-
* naming a different table — implausible today since
|
|
4530
|
-
* `getAppliedMigrationCount` only references `MIGRATION_TABLE`, but cheap
|
|
4531
|
-
* insurance against future edits — is not silently swallowed. The pattern is
|
|
4532
|
-
* built from `MIGRATION_TABLE` so a rename cannot drift.
|
|
4533
|
-
*/
|
|
4534
|
-
const MIGRATION_TABLE_MISSING_PATTERN = new RegExp(`(?:no such table:\\s*(?:[a-z][a-z0-9_]*\\.)?${escapeRegExp(MIGRATION_TABLE)}\\b|(?:relation|table)\\s+"?(?:[a-z][a-z0-9_]*\\.)?${escapeRegExp(MIGRATION_TABLE)}"?\\s+does(?:n't| not) exist\\b)`, "i");
|
|
4535
|
-
/**
|
|
4536
|
-
* Read the count of applied migrations.
|
|
4537
|
-
*
|
|
4538
|
-
* Returns `null` only when the migration table does not exist yet (which is
|
|
4539
|
-
* the normal state on a fresh database before the first migration runs).
|
|
4540
|
-
* Any other error is rethrown so callers — particularly
|
|
4541
|
-
* `waitForConcurrentMigrator` — don't silently mask connection failures,
|
|
4542
|
-
* permission errors, or other unexpected driver problems behind a 10s wait
|
|
4543
|
-
* and a bogus "we're done" verdict.
|
|
4544
|
-
*/
|
|
4545
|
-
async function getAppliedMigrationCount(db) {
|
|
4546
|
-
try {
|
|
4547
|
-
const result = await sql`
|
|
4548
|
-
SELECT COUNT(*) as count FROM ${sql.ref(MIGRATION_TABLE)}
|
|
4549
|
-
`.execute(db);
|
|
4550
|
-
return Number(result.rows[0]?.count ?? 0);
|
|
4551
|
-
} catch (error) {
|
|
4552
|
-
if (MIGRATION_TABLE_MISSING_PATTERN.test(deepErrorMessage(error))) return null;
|
|
4553
|
-
throw error;
|
|
4554
|
-
}
|
|
4555
|
-
}
|
|
4556
|
-
/**
|
|
4557
|
-
* Wait for a concurrent migrator to finish applying all migrations.
|
|
4558
|
-
*
|
|
4559
|
-
* Resolves to `true` once the migration table contains at least
|
|
4560
|
-
* `MIGRATION_COUNT` rows (i.e. every migration this build knows about has
|
|
4561
|
-
* been recorded), `false` if the deadline elapses first. We use `>=` rather
|
|
4562
|
-
* than `===` so that an old isolate observing a database that has already
|
|
4563
|
-
* been migrated by a newer build still treats the wait as settled instead
|
|
4564
|
-
* of timing out.
|
|
4565
|
-
*/
|
|
4566
|
-
async function waitForConcurrentMigrator(db, waitMs = MIGRATION_RACE_WAIT_MS) {
|
|
4567
|
-
const deadline = Date.now() + waitMs;
|
|
4568
|
-
while (Date.now() < deadline) {
|
|
4569
|
-
const count = await getAppliedMigrationCount(db);
|
|
4570
|
-
if (count !== null && count >= MIGRATION_COUNT) return true;
|
|
4571
|
-
await new Promise((resolve) => setTimeout(resolve, MIGRATION_RACE_POLL_MS));
|
|
4572
|
-
}
|
|
4573
|
-
const finalCount = await getAppliedMigrationCount(db);
|
|
4574
|
-
return finalCount !== null && finalCount >= MIGRATION_COUNT;
|
|
4575
|
-
}
|
|
4576
|
-
/** Extract the deepest error message available from a thrown value. */
|
|
4577
|
-
function deepErrorMessage(error) {
|
|
4578
|
-
if (error instanceof Error) {
|
|
4579
|
-
const own = error.message ?? "";
|
|
4580
|
-
if (error.cause) {
|
|
4581
|
-
const causeMsg = deepErrorMessage(error.cause);
|
|
4582
|
-
return own ? `${own}: ${causeMsg}` : causeMsg;
|
|
4583
|
-
}
|
|
4584
|
-
return own;
|
|
4585
|
-
}
|
|
4586
|
-
if (typeof error === "string") return error;
|
|
4587
|
-
try {
|
|
4588
|
-
return JSON.stringify(error);
|
|
4589
|
-
} catch {
|
|
4590
|
-
return String(error);
|
|
4591
|
-
}
|
|
4592
|
-
}
|
|
4593
|
-
/**
|
|
4594
|
-
* Read exact migration status without invoking Kysely's migration
|
|
4595
|
-
* introspection. Registered names retain execution order; unknown database
|
|
4596
|
-
* records are sorted so reports remain stable across dialects.
|
|
4597
|
-
*/
|
|
4598
|
-
async function getExactMigrationStatus(db, options) {
|
|
4599
|
-
const table = options?.migrationTableSchema ? sql`${sql.ref(options.migrationTableSchema)}.${sql.ref(MIGRATION_TABLE)}` : sql.ref(MIGRATION_TABLE);
|
|
4600
|
-
let rows;
|
|
4601
|
-
try {
|
|
4602
|
-
rows = (await sql`SELECT name FROM ${table}`.execute(db)).rows;
|
|
4603
|
-
} catch (error) {
|
|
4604
|
-
if (MIGRATION_TABLE_MISSING_PATTERN.test(deepErrorMessage(error))) return {
|
|
4605
|
-
knownApplied: [],
|
|
4606
|
-
pending: [...MIGRATION_NAMES],
|
|
4607
|
-
unknownApplied: []
|
|
4608
|
-
};
|
|
4609
|
-
throw error;
|
|
4610
|
-
}
|
|
4611
|
-
const appliedNames = new Set(rows.map((row) => row.name));
|
|
4612
|
-
const knownApplied = MIGRATION_NAMES.filter((name) => appliedNames.has(name));
|
|
4613
|
-
const pending = MIGRATION_NAMES.filter((name) => !appliedNames.has(name));
|
|
4614
|
-
const knownNames = new Set(MIGRATION_NAMES);
|
|
4615
|
-
return {
|
|
4616
|
-
knownApplied,
|
|
4617
|
-
pending,
|
|
4618
|
-
unknownApplied: [...appliedNames].filter((name) => !knownNames.has(name)).toSorted()
|
|
4619
|
-
};
|
|
4620
|
-
}
|
|
4621
|
-
/**
|
|
4622
|
-
* Run all pending migrations.
|
|
4623
|
-
*
|
|
4624
|
-
* Includes a fast-path: if the migration table already exists and contains
|
|
4625
|
-
* at least MIGRATION_COUNT rows, all migrations this build knows about have
|
|
4626
|
-
* been applied and we can skip the Kysely Migrator entirely. This avoids
|
|
4627
|
-
* the expensive `pragma_table_info` introspection that Kysely runs for
|
|
4628
|
-
* every table in the database (twice!) just to check if the migration
|
|
4629
|
-
* tables exist. On D1 with ~57 tables, that's ~116 queries saved per init.
|
|
4630
|
-
*
|
|
4631
|
-
* Concurrent-migration safety: the Kysely Migrator's `acquireMigrationLock`
|
|
4632
|
-
* is a no-op for SQLite (and therefore D1), so two callers running this
|
|
4633
|
-
* concurrently against the same database will both try to apply pending
|
|
4634
|
-
* migrations. SQLite serializes the writes, but the loser still surfaces a
|
|
4635
|
-
* `UNIQUE constraint failed: _emdash_migrations.name` error. We treat that
|
|
4636
|
-
* specific error as benign: another caller is already applying the same
|
|
4637
|
-
* schema. We wait for the concurrent migrator to finish, then return
|
|
4638
|
-
* success. This matches the user-observable expectation that running
|
|
4639
|
-
* migrations twice in a row is a no-op.
|
|
4640
|
-
*/
|
|
4641
|
-
async function runMigrations(db, options) {
|
|
4642
|
-
if (!options?.migrationTableSchema) {
|
|
4643
|
-
const initialCount = await getAppliedMigrationCount(db);
|
|
4644
|
-
if (initialCount !== null && initialCount >= MIGRATION_COUNT) return { applied: [] };
|
|
4645
|
-
}
|
|
4646
|
-
const { error, results } = await createMigrator(db, options).migrateToLatest();
|
|
4647
|
-
const applied = results?.filter((r) => r.status === "Success").map((r) => r.migrationName) ?? [];
|
|
4648
|
-
if (error) {
|
|
4649
|
-
const msg = deepErrorMessage(error);
|
|
4650
|
-
const failedMigration = results?.find((r) => r.status === "Error");
|
|
4651
|
-
const lockBusy = msg.includes(MIGRATION_LOCK_BUSY_MESSAGE);
|
|
4652
|
-
if (MIGRATION_RACE_PATTERN.test(msg) || lockBusy) {
|
|
4653
|
-
if (await waitForConcurrentMigrator(db, options?.raceWaitMs)) return { applied };
|
|
4654
|
-
if (lockBusy) throw new ConcurrentMigrationTimeoutError(options?.raceWaitMs ?? MIGRATION_RACE_WAIT_MS);
|
|
4655
|
-
}
|
|
4656
|
-
const failedSuffix = failedMigration ? ` (migration: ${failedMigration.migrationName})` : "";
|
|
4657
|
-
throw new Error(`Migration failed: ${msg || "unknown error"}${failedSuffix}`);
|
|
4658
|
-
}
|
|
4659
|
-
return { applied };
|
|
4660
|
-
}
|
|
4661
|
-
/**
|
|
4662
|
-
* Rollback the last migration
|
|
4663
|
-
*/
|
|
4664
|
-
async function rollbackMigration(db, options) {
|
|
4665
|
-
const { error, results } = await createMigrator(db, options).migrateDown();
|
|
4666
|
-
const rolledBack = results?.[0]?.status === "Success" ? results[0].migrationName : null;
|
|
4667
|
-
if (error) {
|
|
4668
|
-
const msg = error instanceof Error ? error.message : JSON.stringify(error);
|
|
4669
|
-
throw new Error(`Rollback failed: ${msg}`);
|
|
4670
|
-
}
|
|
4671
|
-
return { rolledBack };
|
|
4672
|
-
}
|
|
4673
|
-
|
|
4674
|
-
//#endregion
|
|
4675
|
-
export { getMigrationStatus as a, __exportAll as c, getExactMigrationStatus as i, MIGRATION_NAMES as n, rollbackMigration as o, MIGRATION_RACE_WAIT_MS as r, runMigrations as s, ConcurrentMigrationTimeoutError as t };
|
|
4676
|
-
//# sourceMappingURL=runner-BLIDKpmA.mjs.map
|