emdash 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-BMyNSSZi.d.mts → adapters-Bb1vG41D.d.mts} +33 -2
- package/dist/adapters-Bb1vG41D.d.mts.map +1 -0
- package/dist/{allowed-origins-CG1pACtS.mjs → allowed-origins-DW7sYyqg.mjs} +2 -2
- package/dist/{allowed-origins-CG1pACtS.mjs.map → allowed-origins-DW7sYyqg.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +22 -21
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +6 -5
- package/dist/{api-D2hunekQ.mjs → api-BeBglVxU.mjs} +98 -407
- package/dist/api-BeBglVxU.mjs.map +1 -0
- package/dist/{api-tokens-CSND0i1z.mjs → api-tokens-KO24G1Fd.mjs} +2 -2
- package/dist/{api-tokens-CSND0i1z.mjs.map → api-tokens-KO24G1Fd.mjs.map} +1 -1
- package/dist/{apply-kwNEJW2l.mjs → apply-kUDt80-G.mjs} +131 -107
- package/dist/apply-kUDt80-G.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +163 -49
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.mjs +8 -8
- package/dist/astro/middleware/media-usage-write-fence.d.mts +7 -0
- package/dist/astro/middleware/media-usage-write-fence.d.mts.map +1 -0
- package/dist/astro/middleware/media-usage-write-fence.mjs +35 -0
- package/dist/astro/middleware/media-usage-write-fence.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +10 -7
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +3 -3
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +12 -11
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +1763 -173
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +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 +12 -11
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +12 -11
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +12 -11
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
- package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -13
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -11
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/index.mjs +12 -11
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +36 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +36 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +15 -14
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/work/index.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +37 -0
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts +8 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +37 -0
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +51 -43
- 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 +51 -43
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -42
- 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 +47 -42
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +50 -42
- 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 +50 -42
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +47 -42
- 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 +47 -42
- 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 +50 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +50 -42
- 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 +51 -43
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +47 -42
- 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 +51 -43
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +47 -42
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +47 -42
- 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 +47 -42
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +9 -8
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -9
- package/dist/astro/routes/api/auth/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
- 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 +12 -11
- 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 +10 -9
- 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 +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/options.mjs +14 -13
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +13 -12
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
- 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.mjs +15 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +14 -13
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -13
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +18 -17
- 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 +18 -17
- 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.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +44 -31
- 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.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
- 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 +11 -10
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +10 -8
- 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/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +18 -16
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +15 -15
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +14 -12
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +13 -12
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +14 -14
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -16
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +28 -27
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +5 -5
- package/dist/astro/routes/api/mcp.d.mts.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +174 -72
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +13 -11
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
- package/dist/astro/routes/api/media/_id_/usage.mjs +17 -16
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +16 -15
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
- package/dist/astro/routes/api/media/providers/index.mjs +4 -4
- package/dist/astro/routes/api/media/upload-url.mjs +11 -10
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +18 -15
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/items.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_/translations.mjs +11 -11
- package/dist/astro/routes/api/menus/_name_.mjs +11 -11
- package/dist/astro/routes/api/menus/index.mjs +11 -11
- 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 +152 -6
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +8 -7
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +13 -12
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/index.mjs +18 -17
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +13 -12
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +18 -17
- 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 +47 -42
- 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 +47 -42
- 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 +47 -42
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +47 -42
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +47 -42
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +47 -42
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +10 -9
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +47 -42
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +47 -42
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +13 -13
- package/dist/astro/routes/api/search/index.mjs +12 -12
- package/dist/astro/routes/api/search/rebuild.mjs +13 -13
- package/dist/astro/routes/api/search/stats.mjs +8 -8
- package/dist/astro/routes/api/search/suggest.mjs +12 -12
- package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +12 -11
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings.mjs +14 -13
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +14 -13
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +13 -12
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +30 -31
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
- package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/index.mjs +31 -35
- 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 +8 -8
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +18 -16
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts +8 -0
- package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +46 -0
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_.d.mts +19 -0
- package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -0
- package/dist/astro/routes/api/taxonomies/_name_.mjs +88 -0
- package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -0
- package/dist/astro/routes/api/taxonomies/index.mjs +16 -15
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +6 -6
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- 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 +10 -9
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -6
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
- 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 +7 -6
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
- package/dist/astro/routes/sitemap.xml.mjs +8 -7
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +35 -17
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-qRhWQnel.mjs → authorize-DUElxgOQ.mjs} +2 -2
- package/dist/{authorize-qRhWQnel.mjs.map → authorize-DUElxgOQ.mjs.map} +1 -1
- package/dist/{backup-CtA6LDNw.mjs → backup-DWYpJlLj.mjs} +4 -4
- package/dist/{backup-CtA6LDNw.mjs.map → backup-DWYpJlLj.mjs.map} +1 -1
- package/dist/{base64-B-PsqheR.mjs → base64-CvLhUKQv.mjs} +1 -1
- package/dist/{base64-B-PsqheR.mjs.map → base64-CvLhUKQv.mjs.map} +1 -1
- package/dist/bundle-vn56sLos.mjs +663 -0
- package/dist/bundle-vn56sLos.mjs.map +1 -0
- package/dist/{byline-fields-CNc0Yq5i.mjs → byline-fields-txaLGoME.mjs} +2 -2
- package/dist/{byline-fields-CNc0Yq5i.mjs.map → byline-fields-txaLGoME.mjs.map} +1 -1
- package/dist/{byline-registry-ZkPKqEwC.mjs → byline-registry-DDun8sxh.mjs} +2 -2
- package/dist/{byline-registry-ZkPKqEwC.mjs.map → byline-registry-DDun8sxh.mjs.map} +1 -1
- package/dist/{byline-Cp6csL84.mjs → byline-yjvWq4ip.mjs} +5 -5
- package/dist/{byline-Cp6csL84.mjs.map → byline-yjvWq4ip.mjs.map} +1 -1
- package/dist/{bylines-cf-C9lmF.mjs → bylines-Bu7pchQ4.mjs} +5 -5
- package/dist/{bylines-cf-C9lmF.mjs.map → bylines-Bu7pchQ4.mjs.map} +1 -1
- package/dist/{bylines-CEj5CdT4.mjs → bylines-DjXS5p0P.mjs} +7 -7
- package/dist/{bylines-CEj5CdT4.mjs.map → bylines-DjXS5p0P.mjs.map} +1 -1
- package/dist/{cache-C9FptTFR.mjs → cache-C46-3Irl.mjs} +3 -3
- package/dist/{cache-C9FptTFR.mjs.map → cache-C46-3Irl.mjs.map} +1 -1
- package/dist/{challenge-store-D9VNKELo.mjs → challenge-store-SIL5DsF2.mjs} +1 -1
- package/dist/{challenge-store-D9VNKELo.mjs.map → challenge-store-SIL5DsF2.mjs.map} +1 -1
- package/dist/{chunks-DbYzib_k.mjs → chunks-fiZ8G9RJ.mjs} +2 -2
- package/dist/{chunks-DbYzib_k.mjs.map → chunks-fiZ8G9RJ.mjs.map} +1 -1
- package/dist/cli/index.mjs +655 -677
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +74 -4
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +25 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/collection-deletion-DQPvMD8R.mjs +779 -0
- package/dist/collection-deletion-DQPvMD8R.mjs.map +1 -0
- package/dist/{comment-BIkJ6Fil.mjs → comment-Dxlep6cE.mjs} +2 -2
- package/dist/{comment-BIkJ6Fil.mjs.map → comment-Dxlep6cE.mjs.map} +1 -1
- package/dist/{comment-reaction-D-laEceb.mjs → comment-reaction-D-2J84UE.mjs} +2 -2
- package/dist/{comment-reaction-D-laEceb.mjs.map → comment-reaction-D-2J84UE.mjs.map} +1 -1
- package/dist/{comments-0_w6KChA.mjs → comments-CTfZtLmM.mjs} +3 -3
- package/dist/{comments-0_w6KChA.mjs.map → comments-CTfZtLmM.mjs.map} +1 -1
- package/dist/{components-BPxrRjpE.mjs → components-CQy3lh9x.mjs} +1 -1
- package/dist/{components-BPxrRjpE.mjs.map → components-CQy3lh9x.mjs.map} +1 -1
- package/dist/{config-DyF3OKg_.mjs → config-C3EBstHi.mjs} +18 -2
- package/dist/config-C3EBstHi.mjs.map +1 -0
- package/dist/config-COQzTe5Q.d.mts +34 -0
- package/dist/config-COQzTe5Q.d.mts.map +1 -0
- package/dist/{content-CJ94NEx5.mjs → content-ZHCc1ngN.mjs} +509 -78
- package/dist/content-ZHCc1ngN.mjs.map +1 -0
- package/dist/content-list-query-wJ2WN8Vp.d.mts +26 -0
- package/dist/content-list-query-wJ2WN8Vp.d.mts.map +1 -0
- package/dist/content-refresh-B_8SJzuU.mjs +2560 -0
- package/dist/content-refresh-B_8SJzuU.mjs.map +1 -0
- package/dist/{context-C_1nrqnW.mjs → context-ZldbZcle.mjs} +25 -17
- package/dist/context-ZldbZcle.mjs.map +1 -0
- package/dist/{cron-DRV-UC4K.mjs → cron-Y9aLtgrA.mjs} +1 -1
- package/dist/{cron-DRV-UC4K.mjs.map → cron-Y9aLtgrA.mjs.map} +1 -1
- package/dist/{dashboard-DINnNVVU.mjs → dashboard-DvHbk_cv.mjs} +44 -10
- package/dist/dashboard-DvHbk_cv.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -4
- package/dist/db/index.mjs +31 -8
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql-migrations.d.mts +11 -0
- package/dist/db/libsql-migrations.d.mts.map +1 -0
- package/dist/db/libsql-migrations.mjs +57 -0
- package/dist/db/libsql-migrations.mjs.map +1 -0
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +10 -0
- package/dist/db/postgres-migrations.d.mts.map +1 -0
- package/dist/db/postgres-migrations.mjs +47 -0
- package/dist/db/postgres-migrations.mjs.map +1 -0
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite-migrations.d.mts +10 -0
- package/dist/db/sqlite-migrations.d.mts.map +1 -0
- package/dist/db/sqlite-migrations.mjs +28 -0
- package/dist/db/sqlite-migrations.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-CcWLaRiR.mjs → db-errors-Cl-sKYTm.mjs} +1 -1
- package/dist/{db-errors-CcWLaRiR.mjs.map → db-errors-Cl-sKYTm.mjs.map} +1 -1
- package/dist/{default-BD2FG0xV.mjs → default-DdDl3fxv.mjs} +1 -1
- package/dist/{default-BD2FG0xV.mjs.map → default-DdDl3fxv.mjs.map} +1 -1
- package/dist/{deferred-tasks-C-WRCcKo.d.mts → deferred-tasks-SDWxrgzJ.d.mts} +1 -1
- package/dist/{deferred-tasks-C-WRCcKo.d.mts.map → deferred-tasks-SDWxrgzJ.d.mts.map} +1 -1
- package/dist/{device-flow-CTqd8mud.mjs → device-flow-C0GdD_z8.mjs} +4 -4
- package/dist/{device-flow-CTqd8mud.mjs.map → device-flow-C0GdD_z8.mjs.map} +1 -1
- package/dist/direct-executor-CDXeJCSs.mjs +85 -0
- package/dist/direct-executor-CDXeJCSs.mjs.map +1 -0
- package/dist/dist-D_6CKR8z.mjs +810 -0
- package/dist/dist-D_6CKR8z.mjs.map +1 -0
- package/dist/{email-console-C6WdcWbL.mjs → email-console-BHNjIwiB.mjs} +1 -1
- package/dist/{email-console-C6WdcWbL.mjs.map → email-console-BHNjIwiB.mjs.map} +1 -1
- package/dist/{enrich-CFJJgxs_.mjs → enrich-_ySMEvEE.mjs} +2 -2
- package/dist/{enrich-CFJJgxs_.mjs.map → enrich-_ySMEvEE.mjs.map} +1 -1
- package/dist/{error-oh_9i6bR.mjs → error-DupyJZE2.mjs} +3 -3
- package/dist/{error-oh_9i6bR.mjs.map → error-DupyJZE2.mjs.map} +1 -1
- package/dist/{errors-BucQ8VYz.mjs → errors-CLUPm7aD.mjs} +14 -2
- package/dist/errors-CLUPm7aD.mjs.map +1 -0
- package/dist/{escape-CXps5F_v.mjs → escape-DUY2r2Zs.mjs} +1 -1
- package/dist/{escape-CXps5F_v.mjs.map → escape-DUY2r2Zs.mjs.map} +1 -1
- package/dist/{field-defs-cache-WD82dMQq.mjs → field-defs-cache-BzwBlJrR.mjs} +4 -4
- package/dist/{field-defs-cache-WD82dMQq.mjs.map → field-defs-cache-BzwBlJrR.mjs.map} +1 -1
- package/dist/{fts-manager-BITakW_R.mjs → fts-manager-DyZEohZC.mjs} +76 -50
- package/dist/fts-manager-DyZEohZC.mjs.map +1 -0
- package/dist/{hreflang-BdZuVBhL.mjs → hreflang-BBe83kKa.mjs} +7 -7
- package/dist/{hreflang-BdZuVBhL.mjs.map → hreflang-BBe83kKa.mjs.map} +1 -1
- package/dist/{hreflang-BaLCNcDx.d.mts → hreflang-DwT2E0Sa.d.mts} +2 -2
- package/dist/{hreflang-BaLCNcDx.d.mts.map → hreflang-DwT2E0Sa.d.mts.map} +1 -1
- package/dist/identity-Ys6Dlc9C.mjs +35 -0
- package/dist/identity-Ys6Dlc9C.mjs.map +1 -0
- package/dist/{import-C-9uqyT3.mjs → import-DPkLmeXO.mjs} +5 -5
- package/dist/{import-C-9uqyT3.mjs.map → import-DPkLmeXO.mjs.map} +1 -1
- package/dist/{index-M37iz29I.d.mts → index-BVBZj4-7.d.mts} +236 -104
- package/dist/index-BVBZj4-7.d.mts.map +1 -0
- package/dist/{index-MGw47uWq.d.mts → index-C9KuQyO-.d.mts} +5 -10
- package/dist/index-C9KuQyO-.d.mts.map +1 -0
- package/dist/index.d.mts +21 -19
- package/dist/index.mjs +73 -67
- package/dist/{init-lock-DJkX6Hto.mjs → init-lock-6b309ZrF.mjs} +1 -1
- package/dist/{init-lock-DJkX6Hto.mjs.map → init-lock-6b309ZrF.mjs.map} +1 -1
- package/dist/{load-DV1DZuUJ.mjs → load-YJ2xl3ra.mjs} +2 -2
- package/dist/{load-DV1DZuUJ.mjs.map → load-YJ2xl3ra.mjs.map} +1 -1
- package/dist/{loader-CGugY7Zl.mjs → loader-CdG_Ci3H.mjs} +95 -53
- package/dist/loader-CdG_Ci3H.mjs.map +1 -0
- package/dist/manifest-builder-B252yQUf.mjs +189 -0
- package/dist/manifest-builder-B252yQUf.mjs.map +1 -0
- package/dist/{manifest-schema-Da7kj23y.mjs → manifest-schema-BXbUAHod.mjs} +1 -1
- package/dist/{manifest-schema-Da7kj23y.mjs.map → manifest-schema-BXbUAHod.mjs.map} +1 -1
- package/dist/media/image-endpoint.mjs +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +3 -2
- package/dist/media/index.mjs.map +1 -1
- package/dist/media/local-runtime.d.mts +12 -12
- package/dist/media/local-runtime.mjs +6 -5
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-OhYL-7Io.mjs → media-BWWWb8kg.mjs} +92 -3
- package/dist/media-BWWWb8kg.mjs.map +1 -0
- package/dist/{media-BP7S-nHT.mjs → media-CzhAn2--.mjs} +2 -2
- package/dist/{media-BP7S-nHT.mjs.map → media-CzhAn2--.mjs.map} +1 -1
- package/dist/{media-allowlist-D-peH1H5.mjs → media-allowlist-D6x-Us02.mjs} +14 -3
- package/dist/media-allowlist-D6x-Us02.mjs.map +1 -0
- package/dist/{media-upload-DtrwU2B0.mjs → media-upload-CHBVn6m-.mjs} +10 -8
- package/dist/{media-upload-DtrwU2B0.mjs.map → media-upload-CHBVn6m-.mjs.map} +1 -1
- package/dist/{media-url-DYs_YLOx.mjs → media-url-BvoX88FA.mjs} +1 -1
- package/dist/{media-url-DYs_YLOx.mjs.map → media-url-BvoX88FA.mjs.map} +1 -1
- package/dist/{media-usage-BlxfTkN0.mjs → media-usage-BZCydfvu.mjs} +137 -60
- package/dist/media-usage-BZCydfvu.mjs.map +1 -0
- package/dist/media-usage-work-CLo_6tct.mjs +152 -0
- package/dist/media-usage-work-CLo_6tct.mjs.map +1 -0
- package/dist/media-usage-work-DrCbqBcl.mjs +424 -0
- package/dist/media-usage-work-DrCbqBcl.mjs.map +1 -0
- package/dist/media-usage-write-fence-aHEgN1zB.mjs +42 -0
- package/dist/media-usage-write-fence-aHEgN1zB.mjs.map +1 -0
- package/dist/{menus-Y54RDPIN.mjs → menus-DSo5QeW0.mjs} +351 -95
- package/dist/menus-DSo5QeW0.mjs.map +1 -0
- package/dist/{menus-BcQWxeXu.mjs → menus-TCNSd2vp.mjs} +3 -3
- package/dist/{menus-BcQWxeXu.mjs.map → menus-TCNSd2vp.mjs.map} +1 -1
- package/dist/migrations/index.d.mts +22 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +7 -0
- package/dist/{mode-BWyTBe3I.mjs → mode-BDoH_WcF.mjs} +1 -1
- package/dist/{mode-BWyTBe3I.mjs.map → mode-BDoH_WcF.mjs.map} +1 -1
- package/dist/{normalize-C-SHXmra.mjs → normalize-Bc-s_r-a.mjs} +1 -1
- package/dist/{normalize-C-SHXmra.mjs.map → normalize-Bc-s_r-a.mjs.map} +1 -1
- package/dist/{oauth-authorization-ClvktmrL.mjs → oauth-authorization-iOikmrJd.mjs} +4 -4
- package/dist/{oauth-authorization-ClvktmrL.mjs.map → oauth-authorization-iOikmrJd.mjs.map} +1 -1
- package/dist/{oauth-clients-8ao-U66l.mjs → oauth-clients-CvI0nHMJ.mjs} +1 -1
- package/dist/{oauth-clients-8ao-U66l.mjs.map → oauth-clients-CvI0nHMJ.mjs.map} +1 -1
- package/dist/{oauth-state-store-B1ofdTiQ.mjs → oauth-state-store-Ba8KZlwW.mjs} +1 -1
- package/dist/{oauth-state-store-B1ofdTiQ.mjs.map → oauth-state-store-Ba8KZlwW.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-CscEuIiR.mjs → oauth-user-lookup-C5SQwGYC.mjs} +1 -1
- package/dist/{oauth-user-lookup-CscEuIiR.mjs.map → oauth-user-lookup-C5SQwGYC.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{options-CA4eVX6P.d.mts → options--hhz-jZ1.d.mts} +4 -3
- package/dist/options--hhz-jZ1.d.mts.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-CJKNuewM.mjs → parse-DFbuc-Fh.mjs} +2 -2
- package/dist/{parse-CJKNuewM.mjs.map → parse-DFbuc-Fh.mjs.map} +1 -1
- package/dist/{passkey-config-D-zrzMJI.mjs → passkey-config-DFAmUyuE.mjs} +1 -1
- package/dist/{passkey-config-D-zrzMJI.mjs.map → passkey-config-DFAmUyuE.mjs.map} +1 -1
- package/dist/{placeholder-DaeNS-VG.d.mts → placeholder-Dp0K3RkD.d.mts} +1 -1
- package/dist/{placeholder-DaeNS-VG.d.mts.map → placeholder-Dp0K3RkD.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +8 -1
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-utils.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.mjs +7 -4
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/policy-TluUIxuu.mjs +45 -0
- package/dist/policy-TluUIxuu.mjs.map +1 -0
- package/dist/{portable-text-B_kqBmXB.mjs → portable-text-imBW5cHK.mjs} +1 -1
- package/dist/{portable-text-B_kqBmXB.mjs.map → portable-text-imBW5cHK.mjs.map} +1 -1
- package/dist/{preview-BU6PaLpM.mjs → preview-PW-nOnq4.mjs} +2 -2
- package/dist/{preview-BU6PaLpM.mjs.map → preview-PW-nOnq4.mjs.map} +1 -1
- package/dist/protocol-DjsJHTlK.d.mts +52 -0
- package/dist/protocol-DjsJHTlK.d.mts.map +1 -0
- package/dist/{public-url-Ox9oOlgp.mjs → public-url-CWoFhQT8.mjs} +1 -1
- package/dist/{public-url-Ox9oOlgp.mjs.map → public-url-CWoFhQT8.mjs.map} +1 -1
- package/dist/{query-rC9HKv1N.mjs → query-BWs7O5Il.mjs} +3 -3
- package/dist/{query-rC9HKv1N.mjs.map → query-BWs7O5Il.mjs.map} +1 -1
- package/dist/{query-C5w7PsRQ.mjs → query-zTVAR1Sl.mjs} +31 -49
- package/dist/query-zTVAR1Sl.mjs.map +1 -0
- package/dist/{rate-limit-kug6q99V.mjs → rate-limit-Bx4g9m3p.mjs} +2 -2
- package/dist/{rate-limit-kug6q99V.mjs.map → rate-limit-Bx4g9m3p.mjs.map} +1 -1
- package/dist/{redirect-Egf2R9yP.mjs → redirect-BgAOGHDq.mjs} +1 -1
- package/dist/{redirect-Egf2R9yP.mjs.map → redirect-BgAOGHDq.mjs.map} +1 -1
- package/dist/{redirect-DIJxXKOG.mjs → redirect-COsS6SWJ.mjs} +2 -2
- package/dist/{redirect-DIJxXKOG.mjs.map → redirect-COsS6SWJ.mjs.map} +1 -1
- package/dist/{redirects-G8wAhWAf.mjs → redirects-DTDHWH31.mjs} +133 -14
- package/dist/redirects-DTDHWH31.mjs.map +1 -0
- package/dist/{redirects-Bzm812bY.mjs → redirects-DyChwDkG.mjs} +4 -4
- package/dist/{redirects-Bzm812bY.mjs.map → redirects-DyChwDkG.mjs.map} +1 -1
- package/dist/registry-Cqa2I749.mjs +1275 -0
- package/dist/registry-Cqa2I749.mjs.map +1 -0
- package/dist/{relation-Cbe3Sd3m.mjs → relation-BnHB0T4J.mjs} +3 -3
- package/dist/{relation-Cbe3Sd3m.mjs.map → relation-BnHB0T4J.mjs.map} +1 -1
- package/dist/{relations-Bw_E1TXo.mjs → relations-BBApI4e2.mjs} +3 -3
- package/dist/{relations-Bw_E1TXo.mjs.map → relations-BBApI4e2.mjs.map} +1 -1
- package/dist/{relations-G2ta7GIl.d.mts → relations-BHo5A8Zf.d.mts} +367 -33
- package/dist/relations-BHo5A8Zf.d.mts.map +1 -0
- package/dist/{relations-qHwpUe66.mjs → relations-D5sM5fW0.mjs} +6 -6
- package/dist/{relations-qHwpUe66.mjs.map → relations-D5sM5fW0.mjs.map} +1 -1
- package/dist/request-context.d.mts +1 -1
- package/dist/{request-meta-Df-jFbAd.mjs → request-meta-DgZJh3zG.mjs} +2 -2
- package/dist/{request-meta-Df-jFbAd.mjs.map → request-meta-DgZJh3zG.mjs.map} +1 -1
- package/dist/{resolve-DZzG4HqM.mjs → resolve-DOhVEvty.mjs} +2 -2
- package/dist/{resolve-DZzG4HqM.mjs.map → resolve-DOhVEvty.mjs.map} +1 -1
- package/dist/{runner-BaADli8l.d.mts → runner-BYHZn3Jy.d.mts} +16 -3
- package/dist/runner-BYHZn3Jy.d.mts.map +1 -0
- package/dist/{runner-BFTrkYhu.mjs → runner-C1T11CfC.mjs} +1190 -257
- package/dist/runner-C1T11CfC.mjs.map +1 -0
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +4 -3
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-2AOsSn2v.mjs +390 -0
- package/dist/schema-2AOsSn2v.mjs.map +1 -0
- package/dist/{schema-CiQXr6_F.mjs → schema-DZ5YVVZj.mjs} +3 -3
- package/dist/{schema-CiQXr6_F.mjs.map → schema-DZ5YVVZj.mjs.map} +1 -1
- package/dist/{scopes-CNJVT5i5.mjs → scopes-8ClPF2bC.mjs} +3 -3
- package/dist/{scopes-CNJVT5i5.mjs.map → scopes-8ClPF2bC.mjs.map} +1 -1
- package/dist/{search-FZhx5dDU.mjs → search-u1QYdOCG.mjs} +25 -15
- package/dist/search-u1QYdOCG.mjs.map +1 -0
- package/dist/{secrets-DLd8RXee.mjs → secrets-BEgsAQEU.mjs} +3 -3
- package/dist/{secrets-DLd8RXee.mjs.map → secrets-BEgsAQEU.mjs.map} +1 -1
- package/dist/{sections-BrIrMUCn.mjs → sections-DmHviGyM.mjs} +3 -3
- package/dist/{sections-BrIrMUCn.mjs.map → sections-DmHviGyM.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +24 -22
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +16 -15
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-CobNfOvb.mjs → seo-AFpVvTqr.mjs} +5 -3
- package/dist/seo-AFpVvTqr.mjs.map +1 -0
- package/dist/{seo-DTHRntNC.mjs → seo-Dq4G-RrO.mjs} +2 -2
- package/dist/{seo-DTHRntNC.mjs.map → seo-Dq4G-RrO.mjs.map} +1 -1
- package/dist/{service-B-pYGPgR.mjs → service-BpArayPr.mjs} +3 -3
- package/dist/{service-B-pYGPgR.mjs.map → service-BpArayPr.mjs.map} +1 -1
- package/dist/{session-user-BvM9-zNl.mjs → session-user-BEorKVT-.mjs} +1 -1
- package/dist/{session-user-BvM9-zNl.mjs.map → session-user-BEorKVT-.mjs.map} +1 -1
- package/dist/{settings-DPHau-WH.mjs → settings-DE9xtzqd.mjs} +3 -3
- package/dist/{settings-DPHau-WH.mjs.map → settings-DE9xtzqd.mjs.map} +1 -1
- package/dist/{settings-kJhGiQ4z.mjs → settings-xRdoUZr0.mjs} +4 -4
- package/dist/{settings-kJhGiQ4z.mjs.map → settings-xRdoUZr0.mjs.map} +1 -1
- package/dist/{setup-complete-u50Qm8rY.mjs → setup-complete-BnT8pq_C.mjs} +1 -1
- package/dist/{setup-complete-u50Qm8rY.mjs.map → setup-complete-BnT8pq_C.mjs.map} +1 -1
- package/dist/{setup-nonce-DCjiiOD2.mjs → setup-nonce-CBitf_oQ.mjs} +1 -1
- package/dist/{setup-nonce-DCjiiOD2.mjs.map → setup-nonce-CBitf_oQ.mjs.map} +1 -1
- package/dist/{single-flight-cache-DW3w11Pu.mjs → single-flight-cache-B0eLKS6K.mjs} +2 -2
- package/dist/{single-flight-cache-DW3w11Pu.mjs.map → single-flight-cache-B0eLKS6K.mjs.map} +1 -1
- package/dist/{site-url-I-lJ5kma.mjs → site-url-DjpIXMXJ.mjs} +2 -2
- package/dist/{site-url-I-lJ5kma.mjs.map → site-url-DjpIXMXJ.mjs.map} +1 -1
- package/dist/slugify-B994zkXC.mjs +17 -0
- package/dist/slugify-B994zkXC.mjs.map +1 -0
- package/dist/{snapshot-I_fjLDaD.mjs → snapshot-BBlGkb2q.mjs} +1 -1
- package/dist/{snapshot-I_fjLDaD.mjs.map → snapshot-BBlGkb2q.mjs.map} +1 -1
- package/dist/{ssrf-CTa6v21i.mjs → ssrf-Cpvg_n3A.mjs} +1 -1
- package/dist/{ssrf-CTa6v21i.mjs.map → ssrf-Cpvg_n3A.mjs.map} +1 -1
- package/dist/{state-CQvoefYP.mjs → state-DYwPue40.mjs} +1 -1
- package/dist/{state-CQvoefYP.mjs.map → state-DYwPue40.mjs.map} +1 -1
- package/dist/{status-CIsmlC1E.mjs → status-CWVYpbD8.mjs} +1 -1
- package/dist/{status-CIsmlC1E.mjs.map → status-CWVYpbD8.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +3 -3
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/target-DixqqV5i.mjs +27 -0
- package/dist/target-DixqqV5i.mjs.map +1 -0
- package/dist/{taxonomies-CLN1N92E.mjs → taxonomies-BlrncHi9.mjs} +75 -64
- package/dist/taxonomies-BlrncHi9.mjs.map +1 -0
- package/dist/{taxonomies-CKuaZER5.mjs → taxonomies-OHUIiIpI.mjs} +280 -56
- package/dist/taxonomies-OHUIiIpI.mjs.map +1 -0
- package/dist/{taxonomy-pOrB2kZe.mjs → taxonomy-BqCZ1TdU.mjs} +131 -78
- package/dist/taxonomy-BqCZ1TdU.mjs.map +1 -0
- package/dist/{tokens-DagaJGK8.mjs → tokens-C39klX8R.mjs} +2 -2
- package/dist/{tokens-DagaJGK8.mjs.map → tokens-C39klX8R.mjs.map} +1 -1
- package/dist/{transport-DVsa5YYo.mjs → transport-DBJwa-Si.mjs} +1 -1
- package/dist/{transport-DVsa5YYo.mjs.map → transport-DBJwa-Si.mjs.map} +1 -1
- package/dist/{transport-CAwTaTp_.d.mts → transport-RQE36APG.d.mts} +1 -1
- package/dist/{transport-CAwTaTp_.d.mts.map → transport-RQE36APG.d.mts.map} +1 -1
- package/dist/{trusted-proxy-D20_7fyX.mjs → trusted-proxy-CEVekpby.mjs} +1 -1
- package/dist/{trusted-proxy-D20_7fyX.mjs.map → trusted-proxy-CEVekpby.mjs.map} +1 -1
- package/dist/{types-NBI3rF-_.mjs → types-75KIqzL9.mjs} +18 -2
- package/dist/types-75KIqzL9.mjs.map +1 -0
- package/dist/{types-BjxTKxrZ.mjs → types-BW1syrlw.mjs} +1 -1
- package/dist/{types-BjxTKxrZ.mjs.map → types-BW1syrlw.mjs.map} +1 -1
- package/dist/{types-DLG4IFEQ.d.mts → types-BcqdcT-r.d.mts} +1 -1
- package/dist/{types-DLG4IFEQ.d.mts.map → types-BcqdcT-r.d.mts.map} +1 -1
- package/dist/{types-LoagAGzH.d.mts → types-BntycPTF.d.mts} +1 -1
- package/dist/{types-LoagAGzH.d.mts.map → types-BntycPTF.d.mts.map} +1 -1
- package/dist/{types-BnFP1M3J.d.mts → types-C2pMvO62.d.mts} +32 -10
- package/dist/types-C2pMvO62.d.mts.map +1 -0
- package/dist/{types-Cd8iuS1J.mjs → types-CQPWhPxE.mjs} +9 -12
- package/dist/types-CQPWhPxE.mjs.map +1 -0
- package/dist/{types-C8oz_ayJ.d.mts → types-CXnOzDe1.d.mts} +1 -1
- package/dist/{types-C8oz_ayJ.d.mts.map → types-CXnOzDe1.d.mts.map} +1 -1
- package/dist/types-CtzytVXn.mjs.map +1 -1
- package/dist/{types-BGWPMiBO.d.mts → types-DN64_zzZ.d.mts} +44 -5
- package/dist/types-DN64_zzZ.d.mts.map +1 -0
- package/dist/{types-Dc4ntQjQ.d.mts → types-DQIDRKF2.d.mts} +124 -1
- package/dist/types-DQIDRKF2.d.mts.map +1 -0
- package/dist/{types-CbUcuxxc.d.mts → types-DWRhk9xZ.d.mts} +1 -1
- package/dist/{types-CbUcuxxc.d.mts.map → types-DWRhk9xZ.d.mts.map} +1 -1
- package/dist/{types-BK6U01Ro.d.mts → types-Dfk8KI5R.d.mts} +1 -1
- package/dist/{types-BK6U01Ro.d.mts.map → types-Dfk8KI5R.d.mts.map} +1 -1
- package/dist/{types-Cz8hNch2.d.mts → types-DrJrVCT1.d.mts} +25 -5
- package/dist/types-DrJrVCT1.d.mts.map +1 -0
- package/dist/{upload-attempts-D8QAFLUJ.mjs → upload-attempts-D4FS8LT6.mjs} +1 -1
- package/dist/{upload-attempts-D8QAFLUJ.mjs.map → upload-attempts-D4FS8LT6.mjs.map} +1 -1
- package/dist/url-pattern-BHS0V5Ng.mjs +33 -0
- package/dist/url-pattern-BHS0V5Ng.mjs.map +1 -0
- package/dist/{user-DBrq4UVq.mjs → user-DJZfUJVE.mjs} +3 -3
- package/dist/{user-DBrq4UVq.mjs.map → user-DJZfUJVE.mjs.map} +1 -1
- package/dist/{utils-hLzi_2Fn.mjs → utils-D4vD_Y98.mjs} +3 -3
- package/dist/{utils-hLzi_2Fn.mjs.map → utils-D4vD_Y98.mjs.map} +1 -1
- package/dist/{validate-mKo3RxBM.mjs → validate-BMLBvxOC.mjs} +24 -5
- package/dist/validate-BMLBvxOC.mjs.map +1 -0
- package/dist/{validate-C3oSmA7F.d.mts → validate-CTqt5nuE.d.mts} +15 -7
- package/dist/validate-CTqt5nuE.d.mts.map +1 -0
- package/dist/{validation-BgpucsvF.mjs → validation-npvENUCB.mjs} +11 -10
- package/dist/{validation-BgpucsvF.mjs.map → validation-npvENUCB.mjs.map} +1 -1
- package/dist/version-DB_fqCSI.mjs +7 -0
- package/dist/{version-C4PnpmoV.mjs.map → version-DB_fqCSI.mjs.map} +1 -1
- package/dist/{widgets-CJJtkiKh.mjs → widgets-BD3ho7Rq.mjs} +3 -3
- package/dist/{widgets-CJJtkiKh.mjs.map → widgets-BD3ho7Rq.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-9PPSy8X6.mjs → wxr-taxonomies-B1R3vBoD.mjs} +13 -32
- package/dist/{wxr-taxonomies-9PPSy8X6.mjs.map → wxr-taxonomies-B1R3vBoD.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts → wxr-taxonomies-Ca68766D.d.mts} +1 -1
- package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts.map → wxr-taxonomies-Ca68766D.d.mts.map} +1 -1
- package/dist/{zod-generator-CG64Hnbh.mjs → zod-generator-LC-on_Vb.mjs} +52 -5
- package/dist/zod-generator-LC-on_Vb.mjs.map +1 -0
- package/package.json +32 -8
- package/src/api/errors.ts +12 -0
- package/src/api/handlers/content.ts +127 -29
- package/src/api/handlers/dashboard.ts +12 -2
- package/src/api/handlers/index.ts +16 -0
- package/src/api/handlers/manifest.ts +2 -0
- package/src/api/handlers/media-allowlist.ts +13 -2
- package/src/api/handlers/media-usage-work.ts +188 -0
- package/src/api/handlers/media-usage.ts +5 -3
- package/src/api/handlers/schema.ts +9 -3
- package/src/api/handlers/seo.ts +5 -2
- package/src/api/handlers/taxonomies.ts +345 -82
- package/src/api/media-usage-write-fence.ts +64 -0
- package/src/api/openapi/document.ts +199 -0
- package/src/api/schemas/common.ts +3 -1
- package/src/api/schemas/content.ts +122 -1
- package/src/api/schemas/media-usage.ts +133 -0
- package/src/api/schemas/schema.ts +78 -3
- package/src/api/schemas/taxonomies.ts +40 -8
- package/src/api/types.ts +1 -0
- package/src/astro/integration/index.ts +101 -56
- package/src/astro/integration/routes.ts +34 -4
- package/src/astro/integration/runtime.ts +18 -0
- package/src/astro/integration/virtual-modules.ts +25 -1
- package/src/astro/integration/vite-config.ts +16 -1
- package/src/astro/middleware/media-usage-write-fence.ts +30 -0
- package/src/astro/middleware/redirect.ts +12 -2
- package/src/astro/middleware/scoped-db.ts +21 -0
- package/src/astro/middleware.ts +140 -16
- package/src/astro/routes/api/admin/media-usage/collection-deletions/index.ts +22 -0
- package/src/astro/routes/api/admin/media-usage/collection-deletions/retry.ts +22 -0
- package/src/astro/routes/api/admin/media-usage/work/index.ts +26 -0
- package/src/astro/routes/api/admin/media-usage/work/retry.ts +26 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +4 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +4 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +4 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +4 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +0 -4
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +67 -31
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -10
- package/src/astro/routes/api/mcp.ts +1 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +7 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +1 -7
- package/src/astro/routes/api/setup/dev-bypass.ts +2 -7
- package/src/astro/routes/api/setup/index.ts +5 -13
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +1 -0
- package/src/astro/routes/api/taxonomies/[name]/translations.ts +48 -0
- package/src/astro/routes/api/taxonomies/[name].ts +102 -0
- package/src/astro/types.ts +25 -4
- package/src/cleanup.ts +6 -12
- package/src/cli/commands/doctor.ts +193 -6
- package/src/cli/commands/export-seed.ts +4 -1
- package/src/cli/commands/migrate.ts +593 -0
- package/src/cli/index.ts +2 -23
- package/src/client/index.ts +119 -0
- package/src/components/Block.astro +12 -0
- package/src/components/InlinePortableTextEditor.tsx +89 -1
- package/src/content/converters/mark-safety.ts +120 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +25 -16
- package/src/content/converters/prosemirror-to-portable-text.ts +12 -2
- package/src/content-list-query.ts +29 -0
- package/src/database/index.ts +8 -2
- package/src/database/migrations/060_collection_admin_config.ts +16 -0
- package/src/database/migrations/061_media_usage_cleanup.ts +77 -0
- package/src/database/migrations/062_media_usage_cleanup_fence.ts +295 -0
- package/src/database/migrations/063_media_usage_incremental_work.ts +397 -0
- package/src/database/migrations/064_fts_plain_text.ts +253 -0
- package/src/database/migrations/065_media_usage_collection_deletion.ts +85 -0
- package/src/database/migrations/066_media_usage_reconciliation.ts +115 -0
- package/src/database/migrations/067_indexed_content_fields.ts +39 -0
- package/src/database/migrations/068_content_taxonomy_entry_groups.ts +61 -0
- package/src/database/migrations/069_collection_title_date_fields.ts +45 -0
- package/src/database/migrations/070_collection_routable.ts +18 -0
- package/src/database/migrations/policy.ts +70 -0
- package/src/database/migrations/runner.ts +71 -3
- package/src/database/repositories/content.ts +861 -96
- package/src/database/repositories/media-usage-work.ts +848 -0
- package/src/database/repositories/media-usage.ts +1502 -138
- package/src/database/repositories/revision.ts +17 -6
- package/src/database/repositories/taxonomy.ts +234 -103
- package/src/database/repositories/types.ts +51 -2
- package/src/database/types.ts +134 -16
- package/src/db/adapters.ts +78 -2
- package/src/db/index.ts +6 -1
- package/src/db/libsql-migrations.ts +72 -0
- package/src/db/postgres-migrations.ts +59 -0
- package/src/db/sqlite-migrations.ts +37 -0
- package/src/emdash-runtime.ts +271 -35
- package/src/fields/file.ts +7 -4
- package/src/fields/image.ts +8 -1
- package/src/fields/types.ts +18 -5
- package/src/i18n/config.ts +32 -0
- package/src/i18n/normalize.ts +31 -0
- package/src/i18n/repair-locale-casing.ts +0 -14
- package/src/i18n/taxonomy-locale-diagnostic.ts +47 -0
- package/src/import/wxr-taxonomies.ts +7 -47
- package/src/index.ts +32 -3
- package/src/loader.ts +119 -65
- package/src/mcp/server.ts +242 -34
- package/src/media/usage/activation.ts +626 -0
- package/src/media/usage/capture-triggers.ts +543 -0
- package/src/media/usage/cleanup.ts +305 -0
- package/src/media/usage/collection-deletion-processor.ts +356 -0
- package/src/media/usage/collection-deletion.ts +836 -0
- package/src/media/usage/content-fields.ts +40 -3
- package/src/media/usage/content-refresh.ts +356 -47
- package/src/media/usage/content-repair.ts +180 -61
- package/src/media/usage/content-snapshots.ts +85 -105
- package/src/media/usage/projection-fingerprint.ts +129 -0
- package/src/media/usage/reconciliation-processor.ts +372 -0
- package/src/media/usage/reconciliation.ts +1117 -0
- package/src/media/usage/source-key.ts +4 -0
- package/src/media/usage/types.ts +2 -0
- package/src/media/usage/work-processor.ts +248 -0
- package/src/menus/index.ts +246 -141
- package/src/migrations/config-loader.ts +205 -0
- package/src/migrations/direct-executor.ts +141 -0
- package/src/migrations/identity.ts +46 -0
- package/src/migrations/index.ts +19 -0
- package/src/migrations/integration-metadata.ts +67 -0
- package/src/migrations/manifest-builder.ts +55 -0
- package/src/migrations/manifest-writer.ts +63 -0
- package/src/migrations/manifest.ts +197 -0
- package/src/migrations/protocol.ts +59 -0
- package/src/migrations/target.ts +35 -0
- package/src/plugin-types.ts +8 -0
- package/src/plugins/adapt-sandbox-entry.ts +24 -5
- package/src/plugins/context.ts +24 -7
- package/src/plugins/index.ts +9 -0
- package/src/plugins/routes.ts +43 -1
- package/src/plugins/sandbox/index.ts +10 -1
- package/src/plugins/sandbox/types.ts +78 -1
- package/src/plugins/scheduler/node.ts +13 -1
- package/src/plugins/scheduler/types.ts +2 -0
- package/src/plugins/types.ts +39 -2
- package/src/query.ts +29 -37
- package/src/scheduler-health.ts +46 -0
- package/src/schema/registry.ts +777 -255
- package/src/schema/types.ts +50 -8
- package/src/schema/url-pattern.ts +33 -0
- package/src/schema/zod-generator.ts +77 -5
- package/src/search/fts-manager.ts +117 -76
- package/src/search/query.ts +32 -11
- package/src/search/types.ts +2 -0
- package/src/seed/apply.ts +68 -15
- package/src/seed/types.ts +11 -3
- package/src/seed/validate.ts +52 -2
- package/src/taxonomies/index.ts +77 -75
- package/src/taxonomies/term-counts.ts +21 -8
- package/src/utils/slugify.ts +1 -29
- package/src/virtual-modules.d.ts +24 -0
- package/dist/adapters-BMyNSSZi.d.mts.map +0 -1
- package/dist/api-D2hunekQ.mjs.map +0 -1
- package/dist/apply-kwNEJW2l.mjs.map +0 -1
- package/dist/config-DyF3OKg_.mjs.map +0 -1
- package/dist/content-CJ94NEx5.mjs.map +0 -1
- package/dist/content-refresh-Xw7aKmou.mjs +0 -1629
- package/dist/content-refresh-Xw7aKmou.mjs.map +0 -1
- package/dist/context-C_1nrqnW.mjs.map +0 -1
- package/dist/dashboard-DINnNVVU.mjs.map +0 -1
- package/dist/errors-BucQ8VYz.mjs.map +0 -1
- package/dist/fts-manager-BITakW_R.mjs.map +0 -1
- package/dist/index-M37iz29I.d.mts.map +0 -1
- package/dist/index-MGw47uWq.d.mts.map +0 -1
- package/dist/loader-CGugY7Zl.mjs.map +0 -1
- package/dist/media-OhYL-7Io.mjs.map +0 -1
- package/dist/media-allowlist-D-peH1H5.mjs.map +0 -1
- package/dist/media-usage-BlxfTkN0.mjs.map +0 -1
- package/dist/menus-Y54RDPIN.mjs.map +0 -1
- package/dist/options-CA4eVX6P.d.mts.map +0 -1
- package/dist/query-C5w7PsRQ.mjs.map +0 -1
- package/dist/redirects-G8wAhWAf.mjs.map +0 -1
- package/dist/registry-Dbodb4Uo.mjs +0 -840
- package/dist/registry-Dbodb4Uo.mjs.map +0 -1
- package/dist/relations-G2ta7GIl.d.mts.map +0 -1
- package/dist/runner-BFTrkYhu.mjs.map +0 -1
- package/dist/runner-BaADli8l.d.mts.map +0 -1
- package/dist/search-FZhx5dDU.mjs.map +0 -1
- package/dist/seo-CobNfOvb.mjs.map +0 -1
- package/dist/slugify-C_tqlU4G.mjs +0 -30
- package/dist/slugify-C_tqlU4G.mjs.map +0 -1
- package/dist/taxonomies-CKuaZER5.mjs.map +0 -1
- package/dist/taxonomies-CLN1N92E.mjs.map +0 -1
- package/dist/taxonomy-pOrB2kZe.mjs.map +0 -1
- package/dist/types-BGWPMiBO.d.mts.map +0 -1
- package/dist/types-BnFP1M3J.d.mts.map +0 -1
- package/dist/types-Cd8iuS1J.mjs.map +0 -1
- package/dist/types-Cz8hNch2.d.mts.map +0 -1
- package/dist/types-Dc4ntQjQ.d.mts.map +0 -1
- package/dist/types-NBI3rF-_.mjs.map +0 -1
- package/dist/validate-C3oSmA7F.d.mts.map +0 -1
- package/dist/validate-mKo3RxBM.mjs.map +0 -1
- package/dist/version-C4PnpmoV.mjs +0 -7
- package/dist/zod-generator-CG64Hnbh.mjs.map +0 -1
- /package/dist/{api-tokens-Dai9dND0.mjs → api-tokens-rIXC6LdE.mjs} +0 -0
- /package/dist/{ssrf-Cj568Dgs.mjs → ssrf-DpXgKsna.mjs} +0 -0
- /package/dist/{types-DKuWPYV5.mjs → types-CP1CflWf.mjs} +0 -0
|
@@ -1,94 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { r as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-C1T11CfC.mjs";
|
|
2
|
+
import { t as validateIdentifier } from "../validate-BpwoDOgt.mjs";
|
|
3
|
+
import { c as isSqlite, p as tableExists, s as isPostgres, u as listTablesLike } from "../dialect-helpers-BSbfaO3F.mjs";
|
|
4
|
+
import { r as getI18nConfig, s as setI18nConfig } from "../config-C3EBstHi.mjs";
|
|
5
|
+
import { C as createHookPipeline, M as AuditRepository, T as definePlugin, _ as PluginRouteRegistry, b as toRouteCallerInfo, c as getSandboxRouteErrorDetails, t as getMenu, v as buildRouteMeta, w as resolveExclusiveHooks, x as EmailPipeline, y as parseRouteInput } from "../menus-DSo5QeW0.mjs";
|
|
5
6
|
import { n as createDeferredTaskTracker, t as after } from "../after-CBU2vkoO.mjs";
|
|
6
7
|
import { createRequestMetrics, getRequestContext, runWithContext } from "../request-context.mjs";
|
|
7
8
|
import { o as getLastContentWriteAt } from "../object-cache-71tPfV4A.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import "../
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import "../
|
|
14
|
-
import "../
|
|
15
|
-
import "../
|
|
9
|
+
import { i as MAX_COLLECTION_LIST_COLUMNS } from "../types-75KIqzL9.mjs";
|
|
10
|
+
import { i as RevisionRepository, t as ContentRepository } from "../content-ZHCc1ngN.mjs";
|
|
11
|
+
import { n as isMissingTableError } from "../db-errors-Cl-sKYTm.mjs";
|
|
12
|
+
import "../slugify-B994zkXC.mjs";
|
|
13
|
+
import "../base64-CvLhUKQv.mjs";
|
|
14
|
+
import { n as ContentMutationConflictError } from "../types-CQPWhPxE.mjs";
|
|
15
|
+
import { t as MediaRepository } from "../media-CzhAn2--.mjs";
|
|
16
|
+
import "../user-DJZfUJVE.mjs";
|
|
17
|
+
import { t as withTransaction } from "../transaction-NQj4VJ7Z.mjs";
|
|
18
|
+
import "../taxonomy-BqCZ1TdU.mjs";
|
|
19
|
+
import "../comment-Dxlep6cE.mjs";
|
|
16
20
|
import { t as OptionsRepository } from "../options-CBXIHUoT.mjs";
|
|
17
|
-
import { r as createSiteInfo } from "../context-
|
|
18
|
-
import "../menus-
|
|
19
|
-
import "../redirect-
|
|
21
|
+
import { r as createSiteInfo } from "../context-ZldbZcle.mjs";
|
|
22
|
+
import "../menus-TCNSd2vp.mjs";
|
|
23
|
+
import "../redirect-COsS6SWJ.mjs";
|
|
20
24
|
import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-BSUptuJR.mjs";
|
|
21
|
-
import "../byline-registry-
|
|
22
|
-
import { n as initWithLock, t as createInitLock } from "../init-lock-
|
|
23
|
-
import "../field-defs-cache-
|
|
24
|
-
import "../byline-
|
|
25
|
-
import "../relation-
|
|
26
|
-
import { n as normalizeMediaValue } from "../normalize-
|
|
27
|
-
import "../
|
|
28
|
-
import "../
|
|
29
|
-
import
|
|
30
|
-
import "../cache-
|
|
31
|
-
import
|
|
32
|
-
import "../
|
|
25
|
+
import "../byline-registry-DDun8sxh.mjs";
|
|
26
|
+
import { n as initWithLock, t as createInitLock } from "../init-lock-6b309ZrF.mjs";
|
|
27
|
+
import "../field-defs-cache-BzwBlJrR.mjs";
|
|
28
|
+
import "../byline-yjvWq4ip.mjs";
|
|
29
|
+
import "../relation-BnHB0T4J.mjs";
|
|
30
|
+
import { n as normalizeMediaValue } from "../normalize-Bc-s_r-a.mjs";
|
|
31
|
+
import "../dist-D_6CKR8z.mjs";
|
|
32
|
+
import "../enrich-_ySMEvEE.mjs";
|
|
33
|
+
import "../seo-Dq4G-RrO.mjs";
|
|
34
|
+
import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-B0eLKS6K.mjs";
|
|
35
|
+
import "../cache-C46-3Irl.mjs";
|
|
36
|
+
import { t as FTSManager } from "../fts-manager-DyZEohZC.mjs";
|
|
37
|
+
import "../resolve-DOhVEvty.mjs";
|
|
33
38
|
import { createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption } from "../database/instrumentation.mjs";
|
|
34
|
-
import {
|
|
35
|
-
import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-
|
|
36
|
-
import { $ as
|
|
37
|
-
import "../dashboard-
|
|
39
|
+
import { s as getDb } from "../loader-CdG_Ci3H.mjs";
|
|
40
|
+
import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-BlrncHi9.mjs";
|
|
41
|
+
import { $ as handleContentUnschedule, A as handleMediaUpdate, B as handleContentDelete, D as handleMediaDelete, E as handleMediaCreate, F as handleContentAuthors, G as handleContentList, H as handleContentDuplicate, I as handleContentCompare, J as handleContentPublish, K as handleContentListTrashed, L as handleContentCountScheduled, M as handleRevisionList, N as handleRevisionRestore, O as handleMediaGet, Q as handleContentUnpublish, R as handleContentCountTrashed, U as handleContentGet, V as handleContentDiscardDraft, W as handleContentGetIncludingTrashed, X as handleContentSchedule, Y as handleContentRestore, Z as handleContentTranslations, _ as loadBundleFromR2, et as handleContentUpdate, j as handleRevisionGet, k as handleMediaList, q as handleContentPermanentDelete, s as normalizeRegistryConfig, tt as validateRev, v as syncDeclaredStorageIndexes, z as handleContentCreate } from "../api-BeBglVxU.mjs";
|
|
42
|
+
import { n as recordSchedulerHeartbeatSafely } from "../dashboard-DvHbk_cv.mjs";
|
|
38
43
|
import { r as hashString } from "../hash-Cg0TXJPV.mjs";
|
|
39
|
-
import { a as findNonTranslatableSiblingContentIds,
|
|
40
|
-
import "../media-usage-
|
|
41
|
-
import
|
|
42
|
-
import "../
|
|
43
|
-
import "../
|
|
44
|
-
import "../
|
|
45
|
-
import "../
|
|
46
|
-
import
|
|
47
|
-
import "../
|
|
48
|
-
import
|
|
49
|
-
import "../
|
|
50
|
-
import "../
|
|
51
|
-
import
|
|
52
|
-
import "../
|
|
53
|
-
import "../
|
|
54
|
-
import "../
|
|
55
|
-
import "../
|
|
56
|
-
import "../
|
|
57
|
-
import "../
|
|
58
|
-
import "../
|
|
59
|
-
import "../
|
|
60
|
-
import
|
|
61
|
-
import "../
|
|
62
|
-
import
|
|
63
|
-
import
|
|
64
|
-
import "../
|
|
65
|
-
import
|
|
66
|
-
import {
|
|
67
|
-
import "../
|
|
68
|
-
import "../
|
|
69
|
-
import "../
|
|
70
|
-
import "../
|
|
71
|
-
import "../
|
|
72
|
-
import "../
|
|
73
|
-
import
|
|
74
|
-
import "../
|
|
75
|
-
import "../
|
|
76
|
-
import "../
|
|
77
|
-
import "../
|
|
44
|
+
import { _ as loadContentMediaUsageFields, a as findNonTranslatableSiblingContentIds, g as buildContentMediaUsageFieldFingerprint, l as refreshContentMediaUsageAfterWrite, r as deleteContentMediaUsage, s as markContentMediaUsageCollectionStale, u as refreshContentMediaUsageForWork, v as CONTENT_SOURCE_SCHEMA_VERSION, y as MediaUsageRepository } from "../content-refresh-B_8SJzuU.mjs";
|
|
45
|
+
import { t as MediaUsageWorkRepository } from "../media-usage-work-DrCbqBcl.mjs";
|
|
46
|
+
import "../media-usage-BZCydfvu.mjs";
|
|
47
|
+
import { n as collectionDeletionCurrentTimestamp, r as deleteActivatedMediaUsageCollection, t as MediaUsageCollectionDeletionRepository } from "../collection-deletion-DQPvMD8R.mjs";
|
|
48
|
+
import "../media-usage-work-CLo_6tct.mjs";
|
|
49
|
+
import { n as SchemaRegistry } from "../registry-Cqa2I749.mjs";
|
|
50
|
+
import "../query-BWs7O5Il.mjs";
|
|
51
|
+
import "../zod-generator-LC-on_Vb.mjs";
|
|
52
|
+
import "../schema-DZ5YVVZj.mjs";
|
|
53
|
+
import "../schema-2AOsSn2v.mjs";
|
|
54
|
+
import "../seo-AFpVvTqr.mjs";
|
|
55
|
+
import { t as PluginStateRepository } from "../state-DYwPue40.mjs";
|
|
56
|
+
import "../sections-DmHviGyM.mjs";
|
|
57
|
+
import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-xRdoUZr0.mjs";
|
|
58
|
+
import "../settings-DE9xtzqd.mjs";
|
|
59
|
+
import "../taxonomies-OHUIiIpI.mjs";
|
|
60
|
+
import { r as normalizeManifestRoute } from "../manifest-schema-BXbUAHod.mjs";
|
|
61
|
+
import "../types-CP1CflWf.mjs";
|
|
62
|
+
import "../ssrf-Cpvg_n3A.mjs";
|
|
63
|
+
import "../error-DupyJZE2.mjs";
|
|
64
|
+
import "../parse-DFbuc-Fh.mjs";
|
|
65
|
+
import "../media-BWWWb8kg.mjs";
|
|
66
|
+
import "../redirects-DTDHWH31.mjs";
|
|
67
|
+
import "../relations-BBApI4e2.mjs";
|
|
68
|
+
import "../status-CWVYpbD8.mjs";
|
|
69
|
+
import { a as invalidateUrlPatternCache } from "../query-zTVAR1Sl.mjs";
|
|
70
|
+
import "../import-DPkLmeXO.mjs";
|
|
71
|
+
import { t as getTrustedProxyHeaders } from "../trusted-proxy-CEVekpby.mjs";
|
|
72
|
+
import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-DgZJh3zG.mjs";
|
|
73
|
+
import "../ssrf-DpXgKsna.mjs";
|
|
74
|
+
import { n as CronExecutor } from "../cron-Y9aLtgrA.mjs";
|
|
75
|
+
import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "../email-console-BHNjIwiB.mjs";
|
|
76
|
+
import { n as assertMediaUsageActivationWriteAllowed } from "../media-usage-write-fence-aHEgN1zB.mjs";
|
|
77
|
+
import "../utils-D4vD_Y98.mjs";
|
|
78
|
+
import "../tokens-C39klX8R.mjs";
|
|
79
|
+
import "../preview-PW-nOnq4.mjs";
|
|
80
|
+
import "../hreflang-BBe83kKa.mjs";
|
|
81
|
+
import "../comment-reaction-D-2J84UE.mjs";
|
|
82
|
+
import "../bylines-DjXS5p0P.mjs";
|
|
83
|
+
import { n as getWidgetAreas } from "../widgets-BD3ho7Rq.mjs";
|
|
84
|
+
import "../validate-BMLBvxOC.mjs";
|
|
85
|
+
import "../apply-kUDt80-G.mjs";
|
|
86
|
+
import "../load-YJ2xl3ra.mjs";
|
|
87
|
+
import "../search-u1QYdOCG.mjs";
|
|
78
88
|
import "../index.mjs";
|
|
79
|
-
import { n as
|
|
80
|
-
import { t as
|
|
81
|
-
import "../
|
|
82
|
-
import
|
|
83
|
-
import {
|
|
84
|
-
import { t as
|
|
85
|
-
import {
|
|
89
|
+
import { i as resolveRuntimeMigrationMode, n as enforceRuntimeMigrationPolicy, t as PendingMigrationsError } from "../policy-TluUIxuu.mjs";
|
|
90
|
+
import { n as VERSION, t as COMMIT } from "../version-DB_fqCSI.mjs";
|
|
91
|
+
import { t as getAuthMode } from "../mode-BDoH_WcF.mjs";
|
|
92
|
+
import "../snapshot-BBlGkb2q.mjs";
|
|
93
|
+
import { u as maybeRunScheduledBackup } from "../backup-DWYpJlLj.mjs";
|
|
94
|
+
import { t as cleanupExpiredChallenges } from "../challenge-store-SIL5DsF2.mjs";
|
|
95
|
+
import { t as removeUploadAttempt } from "../upload-attempts-D4FS8LT6.mjs";
|
|
96
|
+
import { a as validateEncryptionKeyAtStartup } from "../secrets-BEgsAQEU.mjs";
|
|
86
97
|
import { isRecord } from "../plugin-utils.mjs";
|
|
87
|
-
import { t as resolveSessionUser } from "../session-user-
|
|
98
|
+
import { t as resolveSessionUser } from "../session-user-BEorKVT-.mjs";
|
|
88
99
|
import { Kysely, sql } from "kysely";
|
|
100
|
+
import { ulid } from "ulidx";
|
|
89
101
|
import { z } from "zod";
|
|
90
102
|
import { Permissions } from "@emdash-cms/auth";
|
|
91
103
|
import { defineMiddleware } from "astro:middleware";
|
|
104
|
+
import { buildTime } from "virtual:emdash/build";
|
|
92
105
|
import virtualConfig from "virtual:emdash/config";
|
|
93
106
|
import { createCoalescingDialect, createDialect, createRequestScopedDb } from "virtual:emdash/dialect";
|
|
94
107
|
import { mediaProviders } from "virtual:emdash/media-providers";
|
|
@@ -105,9 +118,7 @@ async function repairLocaleCasing(db, configuredLocales) {
|
|
|
105
118
|
const tableNames = await listTablesLike(db, "ec_%");
|
|
106
119
|
for (const tableName of tableNames) {
|
|
107
120
|
const table = sql.ref(tableName);
|
|
108
|
-
const
|
|
109
|
-
for (const locale of configuredLocales) {
|
|
110
|
-
await sql`
|
|
121
|
+
for (const locale of configuredLocales) await sql`
|
|
111
122
|
UPDATE ${table} AS target
|
|
112
123
|
SET locale = ${locale}
|
|
113
124
|
WHERE lower(target.locale) = lower(${locale})
|
|
@@ -125,20 +136,1165 @@ async function repairLocaleCasing(db, configuredLocales) {
|
|
|
125
136
|
AND candidate.locale != ${locale}
|
|
126
137
|
)
|
|
127
138
|
`.execute(db);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/i18n/taxonomy-locale-diagnostic.ts
|
|
144
|
+
const REPAIR_GUIDE = "https://docs.emdashcms.com/guides/internationalization/#repairing-taxonomy-locale-mismatches";
|
|
145
|
+
async function warnAboutUnconfiguredTaxonomyLocales(db, configuredLocales, definitionLocales) {
|
|
146
|
+
const supportedLocales = configuredLocales.length > 0 ? configuredLocales : ["en"];
|
|
147
|
+
const definitionRows = definitionLocales === void 0 ? await db.selectFrom("_emdash_taxonomy_defs").select("locale").distinct().where("locale", "not in", supportedLocales).execute() : [...new Set(definitionLocales)].filter((locale) => !supportedLocales.includes(locale)).map((locale) => ({ locale }));
|
|
148
|
+
const termRows = await db.selectFrom("taxonomies").select("locale").distinct().where("locale", "not in", supportedLocales).execute();
|
|
149
|
+
const mismatches = [...definitionRows.map(({ locale }) => ({
|
|
150
|
+
source: "definitions",
|
|
151
|
+
locale
|
|
152
|
+
})), ...termRows.map(({ locale }) => ({
|
|
153
|
+
source: "terms",
|
|
154
|
+
locale
|
|
155
|
+
}))].toSorted((a, b) => a.source.localeCompare(b.source) || a.locale.localeCompare(b.locale));
|
|
156
|
+
if (mismatches.length === 0) return;
|
|
157
|
+
const details = mismatches.map(({ source, locale }) => `${source}: ${locale}`).join("; ");
|
|
158
|
+
console.warn(`EmDash: Taxonomy rows use locales outside the configured locales (${supportedLocales.join(", ")}): ${details}. Locale-scoped reads may not return these rows. Review and repair them explicitly: ${REPAIR_GUIDE}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/media/usage/collection-deletion-processor.ts
|
|
163
|
+
const MEDIA_USAGE_COLLECTION_DELETION_LIMITS = Object.freeze({
|
|
164
|
+
candidatesPerTick: 4,
|
|
165
|
+
deletionsPerTick: 1,
|
|
166
|
+
rowsPerBatch: 50,
|
|
167
|
+
leaseDurationSeconds: 300,
|
|
168
|
+
maxAttempts: 5,
|
|
169
|
+
retryBaseSeconds: 30,
|
|
170
|
+
retryMaxSeconds: 900,
|
|
171
|
+
maxQueriesPerTick: 30
|
|
172
|
+
});
|
|
173
|
+
async function processDueMediaUsageCollectionDeletions(db) {
|
|
174
|
+
const repository = new MediaUsageCollectionDeletionRepository(db);
|
|
175
|
+
const candidates = await repository.findDue(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.candidatesPerTick);
|
|
176
|
+
if (candidates.length === 0) return {
|
|
177
|
+
candidateCount: 0,
|
|
178
|
+
claimedCount: 0,
|
|
179
|
+
outcome: "idle"
|
|
180
|
+
};
|
|
181
|
+
let claim = null;
|
|
182
|
+
for (const candidate of candidates) {
|
|
183
|
+
claim = await repository.claim({
|
|
184
|
+
collectionId: candidate.collectionId,
|
|
185
|
+
phase: candidate.phase,
|
|
186
|
+
leaseDurationSeconds: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.leaseDurationSeconds
|
|
187
|
+
});
|
|
188
|
+
if (claim?.leaseToken) break;
|
|
189
|
+
}
|
|
190
|
+
if (!claim?.leaseToken) return {
|
|
191
|
+
candidateCount: candidates.length,
|
|
192
|
+
claimedCount: 0,
|
|
193
|
+
outcome: "claim_lost"
|
|
194
|
+
};
|
|
195
|
+
try {
|
|
196
|
+
const processed = await processClaimedDeletion(db, claim);
|
|
197
|
+
if (!processed.finalized && !processed.released && !await repository.release(claim)) return {
|
|
198
|
+
candidateCount: candidates.length,
|
|
199
|
+
claimedCount: 1,
|
|
200
|
+
outcome: "claim_lost"
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
candidateCount: candidates.length,
|
|
204
|
+
claimedCount: 1,
|
|
205
|
+
outcome: processed.finalized ? "finalized" : "progress"
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxAttempts;
|
|
209
|
+
if (!await repository.recordFailure({
|
|
210
|
+
collectionId: claim.collectionId,
|
|
211
|
+
leaseToken: claim.leaseToken,
|
|
212
|
+
errorCode: "MEDIA_USAGE_COLLECTION_DELETION_FAILED",
|
|
213
|
+
terminal,
|
|
214
|
+
retryDelaySeconds: retryDelaySeconds$2(claim.attemptCount)
|
|
215
|
+
})) return {
|
|
216
|
+
candidateCount: candidates.length,
|
|
217
|
+
claimedCount: 1,
|
|
218
|
+
outcome: "claim_lost"
|
|
219
|
+
};
|
|
220
|
+
console.error("[media-usage:collection-deletion] Processing failed:", error);
|
|
221
|
+
return {
|
|
222
|
+
candidateCount: candidates.length,
|
|
223
|
+
claimedCount: 1,
|
|
224
|
+
outcome: terminal ? "failed" : "retry"
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function processClaimedDeletion(db, claim) {
|
|
229
|
+
if (claim.phase === "fence" || claim.phase === "registry" || claim.phase === "table") {
|
|
230
|
+
await deleteActivatedMediaUsageCollection(db, {
|
|
231
|
+
collectionId: claim.collectionId,
|
|
232
|
+
collectionSlug: claim.collectionSlug,
|
|
233
|
+
forceDelete: claim.forceDelete
|
|
234
|
+
}, {
|
|
235
|
+
frontPhaseLimit: 1,
|
|
236
|
+
claimed: claim
|
|
237
|
+
});
|
|
238
|
+
return {
|
|
239
|
+
finalized: false,
|
|
240
|
+
released: true
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (claim.phase === "work") await processWorkBatch(db, claim);
|
|
244
|
+
if (claim.phase === "sources") await processSourceBatch(db, claim);
|
|
245
|
+
if (claim.phase === "status") await processStatus(db, claim);
|
|
246
|
+
if (claim.phase === "finalize") {
|
|
247
|
+
await finalizeDeletion(db, claim);
|
|
248
|
+
return {
|
|
249
|
+
finalized: true,
|
|
250
|
+
released: true
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
finalized: false,
|
|
255
|
+
released: false
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function processWorkBatch(db, claim) {
|
|
259
|
+
await withTransaction(db, async (trx) => {
|
|
260
|
+
const rows = await trx.selectFrom("_emdash_media_usage_work").select("content_id").where("collection_id", "=", claim.collectionId).$if(claim.workCursor !== null, (query) => query.where("content_id", ">", claim.workCursor)).orderBy("content_id", "asc").limit(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch + 1).execute();
|
|
261
|
+
const batch = rows.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
|
|
262
|
+
if (batch.length > 0) await trx.deleteFrom("_emdash_media_usage_work").where("collection_id", "=", claim.collectionId).where("content_id", "in", batch.map((row) => row.content_id)).where(liveLeaseGuard(trx, claim)).execute();
|
|
263
|
+
await updateDeletion(trx, claim, {
|
|
264
|
+
phase: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? "work" : "sources",
|
|
265
|
+
work_cursor: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? batch.at(-1).content_id : null
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
async function processSourceBatch(db, claim) {
|
|
271
|
+
await withTransaction(db, async (trx) => {
|
|
272
|
+
let sourceKey = claim.sourceKey;
|
|
273
|
+
if (!sourceKey) {
|
|
274
|
+
const source = await trx.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_type", "=", "content").where("collection_id", "=", claim.collectionId).orderBy("source_key", "asc").limit(1).executeTakeFirst();
|
|
275
|
+
if (!source) {
|
|
276
|
+
await updateDeletion(trx, claim, {
|
|
277
|
+
phase: "status",
|
|
278
|
+
source_key: null,
|
|
279
|
+
occurrence_cursor: null
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
sourceKey = source.source_key;
|
|
284
|
+
await updateDeletion(trx, claim, {
|
|
285
|
+
source_key: sourceKey,
|
|
286
|
+
occurrence_cursor: null
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const occurrences = await trx.selectFrom("_emdash_media_usage").select("id").where("source_key", "=", sourceKey).$if(claim.occurrenceCursor !== null, (query) => query.where("id", ">", claim.occurrenceCursor)).orderBy("id", "asc").limit(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch + 1).execute();
|
|
290
|
+
const batch = occurrences.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);
|
|
291
|
+
if (batch.length > 0) await trx.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).where("id", "in", batch.map((row) => row.id)).where(liveLeaseGuard(trx, claim)).execute();
|
|
292
|
+
if (occurrences.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch) {
|
|
293
|
+
await updateDeletion(trx, claim, {
|
|
294
|
+
source_key: sourceKey,
|
|
295
|
+
occurrence_cursor: batch.at(-1).id
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where("source_type", "=", "content").where("collection_id", "=", claim.collectionId).where(liveLeaseGuard(trx, claim)).execute();
|
|
300
|
+
await updateDeletion(trx, claim, {
|
|
301
|
+
source_key: null,
|
|
302
|
+
occurrence_cursor: null
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
async function processStatus(db, claim) {
|
|
308
|
+
await withTransaction(db, async (trx) => {
|
|
309
|
+
if (await exactCleanupRowsRemain(trx, claim, false)) throw new Error("Collection deletion cleanup is incomplete");
|
|
310
|
+
await trx.deleteFrom("_emdash_media_usage_reconciliations").where("collection_id", "=", claim.collectionId).where("collection_slug", "=", claim.collectionSlug).where(liveLeaseGuard(trx, claim)).execute();
|
|
311
|
+
await trx.deleteFrom("_emdash_media_usage_index_status").where("adapter_id", "=", "content-media").where("scope_type", "=", "collection").where("scope_key", "=", claim.collectionSlug).where("collection_id", "=", claim.collectionId).where(liveLeaseGuard(trx, claim)).execute();
|
|
312
|
+
await updateDeletion(trx, claim, { phase: "finalize" });
|
|
313
|
+
});
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
async function finalizeDeletion(db, claim) {
|
|
317
|
+
if (await tableExists(db, `ec_${claim.collectionSlug}`)) throw new Error("Collection table still exists during deletion finalization");
|
|
318
|
+
if (await exactCleanupRowsRemain(db, claim)) throw new Error("Collection deletion is incomplete");
|
|
319
|
+
if (await db.selectFrom("_emdash_collections").select("id").where("id", "=", claim.collectionId).where("slug", "=", claim.collectionSlug).executeTakeFirst()) throw new Error("Collection registry identity still exists");
|
|
320
|
+
const result = await db.deleteFrom("_emdash_media_usage_collection_deletions").where("collection_id", "=", claim.collectionId).where("collection_slug", "=", claim.collectionSlug).where("state", "=", "leased").where("phase", "=", "finalize").where("lease_token", "=", claim.leaseToken).where(liveLeaseGuard(db, claim)).executeTakeFirst();
|
|
321
|
+
if (Number(result.numDeletedRows ?? 0) !== 1) throw new Error("Collection deletion lost finalization");
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
async function exactCleanupRowsRemain(db, claim, includeStatus = true) {
|
|
325
|
+
const row = (await sql`
|
|
326
|
+
SELECT
|
|
327
|
+
EXISTS (
|
|
328
|
+
SELECT 1 FROM _emdash_media_usage_work
|
|
329
|
+
WHERE collection_id = ${claim.collectionId}
|
|
330
|
+
) AS work_present,
|
|
331
|
+
EXISTS (
|
|
332
|
+
SELECT 1 FROM _emdash_media_usage_sources
|
|
333
|
+
WHERE source_type = 'content' AND collection_id = ${claim.collectionId}
|
|
334
|
+
) AS source_present,
|
|
335
|
+
EXISTS (
|
|
336
|
+
SELECT 1 FROM _emdash_media_usage_index_status
|
|
337
|
+
WHERE adapter_id = 'content-media'
|
|
338
|
+
AND scope_type = 'collection'
|
|
339
|
+
AND scope_key = ${claim.collectionSlug}
|
|
340
|
+
AND collection_id = ${claim.collectionId}
|
|
341
|
+
) AS status_present
|
|
342
|
+
`.execute(db)).rows[0];
|
|
343
|
+
return Boolean(row?.work_present) || Boolean(row?.source_present) || includeStatus && Boolean(row?.status_present);
|
|
344
|
+
}
|
|
345
|
+
async function updateDeletion(db, claim, values) {
|
|
346
|
+
const result = await db.updateTable("_emdash_media_usage_collection_deletions").set({
|
|
347
|
+
...values,
|
|
348
|
+
attempt_count: 0,
|
|
349
|
+
last_error_code: null,
|
|
350
|
+
updated_at: collectionDeletionCurrentTimestamp(db)
|
|
351
|
+
}).where("collection_id", "=", claim.collectionId).where("state", "=", "leased").where("lease_token", "=", claim.leaseToken).where(liveLeaseGuard(db, claim)).executeTakeFirst();
|
|
352
|
+
if (Number(result.numUpdatedRows ?? 0) !== 1) throw new Error("Collection deletion lease was lost");
|
|
353
|
+
}
|
|
354
|
+
function liveLeaseGuard(db, claim) {
|
|
355
|
+
return isPostgres(db) ? sql`EXISTS (
|
|
356
|
+
SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
|
|
357
|
+
WHERE deletion.collection_id = ${claim.collectionId}
|
|
358
|
+
AND deletion.state = 'leased'
|
|
359
|
+
AND deletion.lease_token = ${claim.leaseToken}
|
|
360
|
+
AND deletion.lease_expires_at::timestamptz > clock_timestamp()
|
|
361
|
+
)` : sql`EXISTS (
|
|
362
|
+
SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
|
|
363
|
+
WHERE deletion.collection_id = ${claim.collectionId}
|
|
364
|
+
AND deletion.state = 'leased'
|
|
365
|
+
AND deletion.lease_token = ${claim.leaseToken}
|
|
366
|
+
AND deletion.lease_expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
367
|
+
)`;
|
|
368
|
+
}
|
|
369
|
+
function retryDelaySeconds$2(attemptCount) {
|
|
370
|
+
return Math.min(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryMaxSeconds, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/media/usage/reconciliation.ts
|
|
375
|
+
const ACTIVATION_KEY = "incremental_capture";
|
|
376
|
+
const CONTENT_ADAPTER_ID = "content-media";
|
|
377
|
+
const COLLECTION_SCOPE = "collection";
|
|
378
|
+
const MAX_CANDIDATES = 100;
|
|
379
|
+
const MAX_PORTABLE_DURATION_SECONDS = 365 * 24 * 60 * 60;
|
|
380
|
+
const STABLE_ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
381
|
+
var MediaUsageReconciliationRepository = class {
|
|
382
|
+
constructor(db) {
|
|
383
|
+
this.db = db;
|
|
384
|
+
}
|
|
385
|
+
async findByIdentity(collectionId, runToken) {
|
|
386
|
+
assertIdentity({
|
|
387
|
+
collectionId,
|
|
388
|
+
runToken
|
|
389
|
+
});
|
|
390
|
+
const row = await this.db.selectFrom("_emdash_media_usage_reconciliations").selectAll().where("collection_id", "=", collectionId).where("run_token", "=", runToken).executeTakeFirst();
|
|
391
|
+
return row ? rowToRecord(row) : null;
|
|
392
|
+
}
|
|
393
|
+
async beginRun(claim) {
|
|
394
|
+
const now = timestampOffset(this.db, 0);
|
|
395
|
+
const sameRun = sql`status = 'running' AND cursor = ${claim.runToken}`;
|
|
396
|
+
return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
|
|
397
|
+
status: "running",
|
|
398
|
+
started_at: sql`CASE WHEN ${sameRun} THEN started_at ELSE ${now} END`,
|
|
399
|
+
completed_at: null,
|
|
400
|
+
cursor: claim.runToken,
|
|
401
|
+
indexed_source_count: 0,
|
|
402
|
+
failed_source_count: 0,
|
|
403
|
+
last_error_code: null,
|
|
404
|
+
change_epoch: sql`CASE WHEN ${sameRun} THEN change_epoch ELSE change_epoch + 1 END`,
|
|
405
|
+
reconciliation_required: 1,
|
|
406
|
+
updated_at: now
|
|
407
|
+
}).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", claim.collectionId).where("status.scope_key", "=", claim.collectionSlug).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where((eb) => eb.or([eb("status.status", "!=", "running"), eb("status.cursor", "=", claim.runToken)])).where(this.liveClaimExists(claim)).returning("change_epoch").executeTakeFirst())?.change_epoch ?? null;
|
|
408
|
+
}
|
|
409
|
+
async findScanUpperId(claim) {
|
|
410
|
+
const tableName = contentTableName(claim.collectionSlug);
|
|
411
|
+
return (await sql`
|
|
412
|
+
SELECT content.id
|
|
413
|
+
FROM ${sql.ref(tableName)} AS content
|
|
414
|
+
WHERE ${this.liveClaimExistsSql(claim)}
|
|
415
|
+
ORDER BY content.id DESC
|
|
416
|
+
LIMIT 1
|
|
417
|
+
`.execute(this.db)).rows[0]?.id ?? null;
|
|
418
|
+
}
|
|
419
|
+
async initializeScan(input) {
|
|
420
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
421
|
+
target_epoch: input.targetEpoch,
|
|
422
|
+
field_fingerprint: input.fieldFingerprint,
|
|
423
|
+
phase: "scan",
|
|
424
|
+
scan_cursor: null,
|
|
425
|
+
scan_upper_id: input.scanUpperId,
|
|
426
|
+
source_cursor: null,
|
|
427
|
+
source_upper_key: null,
|
|
428
|
+
attempt_count: 0,
|
|
429
|
+
last_error_code: null,
|
|
430
|
+
updated_at: timestampOffset(this.db, 0)
|
|
431
|
+
}).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "is", null).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch)).executeTakeFirst();
|
|
432
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
433
|
+
}
|
|
434
|
+
async findScanPage(reconciliation, limit) {
|
|
435
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("Reconciliation scan page limit must be from 1 to 50");
|
|
436
|
+
if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
|
|
437
|
+
const tableName = contentTableName(reconciliation.collectionSlug);
|
|
438
|
+
const lowerBound = reconciliation.scanCursor ? sql`AND content.id > ${reconciliation.scanCursor}` : sql``;
|
|
439
|
+
const upperBound = reconciliation.scanUpperId ? sql`AND content.id <= ${reconciliation.scanUpperId}` : sql`AND 1 = 0`;
|
|
440
|
+
return (await sql`
|
|
441
|
+
SELECT content.id
|
|
442
|
+
FROM ${sql.ref(tableName)} AS content
|
|
443
|
+
WHERE 1 = 1
|
|
444
|
+
${lowerBound}
|
|
445
|
+
${upperBound}
|
|
446
|
+
AND ${this.liveClaimExistsSql(reconciliation)}
|
|
447
|
+
ORDER BY content.id ASC
|
|
448
|
+
LIMIT ${limit}
|
|
449
|
+
`.execute(this.db)).rows.map((row) => row.id);
|
|
450
|
+
}
|
|
451
|
+
async checkpointScan(input) {
|
|
452
|
+
let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
453
|
+
scan_cursor: input.nextCursor,
|
|
454
|
+
attempt_count: 0,
|
|
455
|
+
last_error_code: null,
|
|
456
|
+
updated_at: timestampOffset(this.db, 0)
|
|
457
|
+
}).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "scan").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch));
|
|
458
|
+
query = input.previousCursor ? query.where("reconciliation.scan_cursor", "=", input.previousCursor) : query.where("reconciliation.scan_cursor", "is", null);
|
|
459
|
+
const result = await query.executeTakeFirst();
|
|
460
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
461
|
+
}
|
|
462
|
+
async ownsRun(claim, targetEpoch) {
|
|
463
|
+
const result = await sql`
|
|
464
|
+
SELECT ${this.statusOwnsRun(claim, targetEpoch)} AS owned
|
|
465
|
+
`.execute(this.db);
|
|
466
|
+
return Boolean(result.rows[0]?.owned);
|
|
467
|
+
}
|
|
468
|
+
async restartRun(claim, previousEpoch) {
|
|
469
|
+
const now = timestampOffset(this.db, 0);
|
|
470
|
+
const interruptedRestart = sql`status = 'running'
|
|
471
|
+
AND cursor = ${claim.runToken}
|
|
472
|
+
AND change_epoch > ${previousEpoch}`;
|
|
473
|
+
return (await this.db.updateTable("_emdash_media_usage_index_status as status").set({
|
|
474
|
+
status: "running",
|
|
475
|
+
started_at: sql`CASE WHEN ${interruptedRestart} THEN started_at ELSE ${now} END`,
|
|
476
|
+
completed_at: null,
|
|
477
|
+
cursor: claim.runToken,
|
|
478
|
+
last_error_code: null,
|
|
479
|
+
change_epoch: sql`CASE WHEN ${interruptedRestart} THEN change_epoch ELSE change_epoch + 1 END`,
|
|
480
|
+
reconciliation_required: 1,
|
|
481
|
+
updated_at: now
|
|
482
|
+
}).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", claim.collectionId).where("status.scope_key", "=", claim.collectionSlug).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where((eb) => eb.or([eb("status.status", "!=", "running"), eb("status.cursor", "=", claim.runToken)])).where(this.liveClaimExists(claim)).returning("change_epoch").executeTakeFirst())?.change_epoch ?? null;
|
|
483
|
+
}
|
|
484
|
+
async restartScan(input) {
|
|
485
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
486
|
+
target_epoch: input.targetEpoch,
|
|
487
|
+
field_fingerprint: input.fieldFingerprint,
|
|
488
|
+
phase: "scan",
|
|
489
|
+
scan_cursor: null,
|
|
490
|
+
scan_upper_id: input.scanUpperId,
|
|
491
|
+
source_cursor: null,
|
|
492
|
+
source_upper_key: null,
|
|
493
|
+
attempt_count: 0,
|
|
494
|
+
next_attempt_at: timestampOffset(this.db, 0),
|
|
495
|
+
last_error_code: null,
|
|
496
|
+
updated_at: timestampOffset(this.db, 0)
|
|
497
|
+
}).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.previousEpoch).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch)).executeTakeFirst();
|
|
498
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
499
|
+
}
|
|
500
|
+
async findWorkBarrier(collectionId) {
|
|
501
|
+
if (!collectionId) throw new Error("Reconciliation work barrier requires a collection ID");
|
|
502
|
+
const failed = await this.db.selectFrom("_emdash_media_usage_work").select("last_error_code").where("collection_id", "=", collectionId).where("state", "=", "failed").orderBy("content_id").limit(1).executeTakeFirst();
|
|
503
|
+
if (failed) return {
|
|
504
|
+
state: "failed",
|
|
505
|
+
errorCode: failed.last_error_code ?? "MEDIA_USAGE_PROCESSING_FAILED"
|
|
506
|
+
};
|
|
507
|
+
return await this.db.selectFrom("_emdash_media_usage_work").select("content_id").where("collection_id", "=", collectionId).limit(1).executeTakeFirst() ? { state: "pending" } : { state: "empty" };
|
|
508
|
+
}
|
|
509
|
+
async findSourceUpperKey(claim, targetEpoch) {
|
|
510
|
+
return (await this.db.selectFrom("_emdash_media_usage_sources as source").select("source.source_key").where("source.source_type", "=", "content").where("source.collection_id", "=", claim.collectionId).where("source.identity_version", "=", 1).where(this.liveClaimExists(claim)).where(this.statusOwnsRun(claim, targetEpoch)).orderBy("source.source_key", "desc").limit(1).executeTakeFirst())?.source_key ?? null;
|
|
511
|
+
}
|
|
512
|
+
async transitionToSources(input) {
|
|
513
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
514
|
+
phase: "sources",
|
|
515
|
+
source_cursor: null,
|
|
516
|
+
source_upper_key: input.sourceUpperKey,
|
|
517
|
+
attempt_count: 0,
|
|
518
|
+
last_error_code: null,
|
|
519
|
+
updated_at: timestampOffset(this.db, 0)
|
|
520
|
+
}).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.field_fingerprint", "=", input.fieldFingerprint).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "scan").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch)).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_work as work").select("work.content_id").where("work.collection_id", "=", input.claim.collectionId)))).executeTakeFirst();
|
|
521
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
522
|
+
}
|
|
523
|
+
async findSourcePage(reconciliation, limit) {
|
|
524
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("Reconciliation source page limit must be from 1 to 50");
|
|
525
|
+
if (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];
|
|
526
|
+
let query = this.db.selectFrom("_emdash_media_usage_sources as source").select([
|
|
527
|
+
"source.source_key",
|
|
528
|
+
"source.content_id",
|
|
529
|
+
"source.source_variant"
|
|
530
|
+
]).where("source.source_type", "=", "content").where("source.collection_id", "=", reconciliation.collectionId).where("source.identity_version", "=", 1).where(this.liveClaimExists(reconciliation)).where(this.statusOwnsRun(reconciliation, reconciliation.targetEpoch));
|
|
531
|
+
if (reconciliation.sourceCursor) query = query.where("source.source_key", ">", reconciliation.sourceCursor);
|
|
532
|
+
if (reconciliation.sourceUpperKey) query = query.where("source.source_key", "<=", reconciliation.sourceUpperKey);
|
|
533
|
+
else query = query.where(sql`1 = 0`);
|
|
534
|
+
return (await query.orderBy("source.source_key").limit(limit).execute()).map((row) => ({
|
|
535
|
+
sourceKey: row.source_key,
|
|
536
|
+
contentId: row.content_id,
|
|
537
|
+
sourceVariant: row.source_variant
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
async findMissingContentIds(collectionSlug, contentIds) {
|
|
541
|
+
const unique = [...new Set(contentIds)];
|
|
542
|
+
if (unique.length === 0) return [];
|
|
543
|
+
if (unique.length > 50 || unique.some((contentId) => !contentId)) throw new Error("Reconciliation source page has invalid content identity");
|
|
544
|
+
const tableName = contentTableName(collectionSlug);
|
|
545
|
+
const existing = await sql`
|
|
546
|
+
SELECT id FROM ${sql.ref(tableName)} WHERE id IN (${sql.join(unique)})
|
|
547
|
+
`.execute(this.db);
|
|
548
|
+
const present = new Set(existing.rows.map((row) => row.id));
|
|
549
|
+
return unique.filter((contentId) => !present.has(contentId));
|
|
550
|
+
}
|
|
551
|
+
async checkpointSources(input) {
|
|
552
|
+
let query = this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
553
|
+
source_cursor: input.nextCursor,
|
|
554
|
+
attempt_count: 0,
|
|
555
|
+
last_error_code: null,
|
|
556
|
+
updated_at: timestampOffset(this.db, 0)
|
|
557
|
+
}).where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "sources").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db)).where(this.statusOwnsRun(input.claim, input.targetEpoch));
|
|
558
|
+
query = input.previousCursor ? query.where("reconciliation.source_cursor", "=", input.previousCursor) : query.where("reconciliation.source_cursor", "is", null);
|
|
559
|
+
const result = await query.executeTakeFirst();
|
|
560
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
561
|
+
}
|
|
562
|
+
async finishFailedCoverage(collectionId, runToken) {
|
|
563
|
+
assertIdentity({
|
|
564
|
+
collectionId,
|
|
565
|
+
runToken
|
|
566
|
+
});
|
|
567
|
+
const now = timestampOffset(this.db, 0);
|
|
568
|
+
const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
|
|
569
|
+
status: sql`CASE WHEN EXISTS (
|
|
570
|
+
SELECT 1 FROM _emdash_media_usage_sources AS source
|
|
571
|
+
WHERE source.source_type = 'content'
|
|
572
|
+
AND source.collection_id = ${collectionId}
|
|
573
|
+
AND source.identity_version = 1
|
|
574
|
+
LIMIT 1
|
|
575
|
+
) THEN 'partial' ELSE 'failed' END`,
|
|
576
|
+
completed_at: null,
|
|
577
|
+
cursor: null,
|
|
578
|
+
last_error_code: sql`CASE WHEN (
|
|
579
|
+
SELECT reconciliation.last_error_code
|
|
580
|
+
FROM _emdash_media_usage_reconciliations AS reconciliation
|
|
581
|
+
WHERE reconciliation.collection_id = ${collectionId}
|
|
582
|
+
AND reconciliation.run_token = ${runToken}
|
|
583
|
+
) = 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
|
|
584
|
+
THEN COALESCE(
|
|
585
|
+
(SELECT work.last_error_code
|
|
586
|
+
FROM _emdash_media_usage_work AS work
|
|
587
|
+
WHERE work.collection_id = ${collectionId} AND work.state = 'failed'
|
|
588
|
+
ORDER BY work.content_id LIMIT 1),
|
|
589
|
+
'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'
|
|
590
|
+
)
|
|
591
|
+
ELSE (
|
|
592
|
+
SELECT reconciliation.last_error_code
|
|
593
|
+
FROM _emdash_media_usage_reconciliations AS reconciliation
|
|
594
|
+
WHERE reconciliation.collection_id = ${collectionId}
|
|
595
|
+
AND reconciliation.run_token = ${runToken}
|
|
596
|
+
) END`,
|
|
597
|
+
reconciliation_required: 1,
|
|
598
|
+
updated_at: now
|
|
599
|
+
}).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", collectionId).where("status.status", "=", "running").where("status.cursor", "=", runToken).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_reconciliations as reconciliation").select("reconciliation.collection_id").where("reconciliation.collection_id", "=", collectionId).where("reconciliation.run_token", "=", runToken).where("reconciliation.state", "=", "failed"))).executeTakeFirst();
|
|
600
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
601
|
+
}
|
|
602
|
+
async finalizeCoverage(input) {
|
|
603
|
+
const now = timestampOffset(this.db, 0);
|
|
604
|
+
const result = await this.db.updateTable("_emdash_media_usage_index_status as status").set({
|
|
605
|
+
status: "complete",
|
|
606
|
+
schema_version: input.schemaVersion,
|
|
607
|
+
completed_at: now,
|
|
608
|
+
cursor: null,
|
|
609
|
+
last_error_code: null,
|
|
610
|
+
reconciliation_required: 0,
|
|
611
|
+
updated_at: now
|
|
612
|
+
}).where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.collection_id", "=", input.claim.collectionId).where("status.scope_key", "=", input.claim.collectionSlug).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where("status.status", "=", "running").where("status.cursor", "=", input.claim.runToken).where("status.change_epoch", "=", input.targetEpoch).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_work as work").select("work.content_id").where("work.collection_id", "=", input.claim.collectionId)))).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_reconciliations as reconciliation").innerJoin("_emdash_collections as collection", (join) => join.onRef("collection.id", "=", "reconciliation.collection_id").onRef("collection.slug", "=", "reconciliation.collection_slug")).select("reconciliation.collection_id").where("reconciliation.collection_id", "=", input.claim.collectionId).where("reconciliation.run_token", "=", input.claim.runToken).where("reconciliation.target_epoch", "=", input.targetEpoch).where("reconciliation.field_fingerprint", "=", input.fieldFingerprint).where("reconciliation.state", "=", "leased").where("reconciliation.phase", "=", "sources").where("reconciliation.lease_token", "=", input.claim.leaseToken).where(liveLease(this.db, "reconciliation.lease_expires_at")).where((inner) => inner.not(inner.exists(inner.selectFrom("_emdash_media_usage_collection_deletions as deletion").select("deletion.collection_id").where("deletion.collection_id", "=", input.claim.collectionId)))))).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_activation as activation").select("activation.task_key").where("activation.task_key", "=", ACTIVATION_KEY).where("activation.state", "=", "active"))).executeTakeFirst();
|
|
613
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
614
|
+
}
|
|
615
|
+
async deleteFinalized(claim) {
|
|
616
|
+
const result = await this.db.deleteFrom("_emdash_media_usage_reconciliations as reconciliation").where("reconciliation.collection_id", "=", claim.collectionId).where("reconciliation.run_token", "=", claim.runToken).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", claim.leaseToken).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_index_status as status").select("status.collection_id").where("status.collection_id", "=", claim.collectionId).where("status.scope_key", "=", claim.collectionSlug).where("status.status", "=", "complete").where("status.reconciliation_required", "=", 0))).executeTakeFirst();
|
|
617
|
+
return Number(result.numDeletedRows ?? 0) === 1;
|
|
618
|
+
}
|
|
619
|
+
async deleteOneObsolete() {
|
|
620
|
+
return (await sql`
|
|
621
|
+
DELETE FROM _emdash_media_usage_reconciliations
|
|
622
|
+
WHERE (collection_id, run_token) IN (
|
|
623
|
+
SELECT reconciliation.collection_id, reconciliation.run_token
|
|
624
|
+
FROM _emdash_media_usage_reconciliations AS reconciliation
|
|
625
|
+
INNER JOIN _emdash_media_usage_index_status AS status
|
|
626
|
+
ON status.collection_id = reconciliation.collection_id
|
|
627
|
+
AND status.scope_key = reconciliation.collection_slug
|
|
628
|
+
WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
|
|
629
|
+
AND status.scope_type = ${COLLECTION_SCOPE}
|
|
630
|
+
AND status.reconciliation_required = 0
|
|
631
|
+
ORDER BY reconciliation.updated_at, reconciliation.collection_id
|
|
632
|
+
LIMIT 1
|
|
633
|
+
)
|
|
634
|
+
RETURNING collection_id
|
|
635
|
+
`.execute(this.db)).rows.length === 1;
|
|
636
|
+
}
|
|
637
|
+
liveClaimExists(claim) {
|
|
638
|
+
return this.liveClaimExistsSql(claim);
|
|
639
|
+
}
|
|
640
|
+
liveClaimExistsSql(claim) {
|
|
641
|
+
return sql`EXISTS (
|
|
642
|
+
SELECT 1
|
|
643
|
+
FROM _emdash_media_usage_reconciliations AS reconciliation
|
|
644
|
+
INNER JOIN _emdash_collections AS collection
|
|
645
|
+
ON collection.id = reconciliation.collection_id
|
|
646
|
+
AND collection.slug = reconciliation.collection_slug
|
|
647
|
+
WHERE reconciliation.collection_id = ${claim.collectionId}
|
|
648
|
+
AND reconciliation.collection_slug = ${claim.collectionSlug}
|
|
649
|
+
AND reconciliation.run_token = ${claim.runToken}
|
|
650
|
+
AND reconciliation.state = 'leased'
|
|
651
|
+
AND reconciliation.lease_token = ${claim.leaseToken}
|
|
652
|
+
AND ${liveLease(this.db, "reconciliation.lease_expires_at")}
|
|
653
|
+
AND EXISTS (
|
|
654
|
+
SELECT 1 FROM _emdash_media_usage_activation AS activation
|
|
655
|
+
WHERE activation.task_key = ${ACTIVATION_KEY}
|
|
656
|
+
AND activation.state = 'active'
|
|
657
|
+
)
|
|
658
|
+
AND NOT EXISTS (
|
|
659
|
+
SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
|
|
660
|
+
WHERE deletion.collection_id = reconciliation.collection_id
|
|
661
|
+
)
|
|
662
|
+
)`;
|
|
663
|
+
}
|
|
664
|
+
statusOwnsRun(claim, targetEpoch) {
|
|
665
|
+
return sql`EXISTS (
|
|
666
|
+
SELECT 1 FROM _emdash_media_usage_index_status AS status
|
|
667
|
+
WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
|
|
668
|
+
AND status.scope_type = ${COLLECTION_SCOPE}
|
|
669
|
+
AND status.collection_id = ${claim.collectionId}
|
|
670
|
+
AND status.scope_key = ${claim.collectionSlug}
|
|
671
|
+
AND status.capture_state = 'active'
|
|
672
|
+
AND status.reconciliation_required = 1
|
|
673
|
+
AND status.status = 'running'
|
|
674
|
+
AND status.cursor = ${claim.runToken}
|
|
675
|
+
AND status.change_epoch = ${targetEpoch}
|
|
676
|
+
)`;
|
|
677
|
+
}
|
|
678
|
+
async seedNextCandidate() {
|
|
679
|
+
const runToken = ulid();
|
|
680
|
+
const now = timestampOffset(this.db, 0);
|
|
681
|
+
return (await sql`
|
|
682
|
+
INSERT INTO _emdash_media_usage_reconciliations (
|
|
683
|
+
collection_id,
|
|
684
|
+
collection_slug,
|
|
685
|
+
run_token,
|
|
686
|
+
next_attempt_at,
|
|
687
|
+
updated_at
|
|
688
|
+
)
|
|
689
|
+
SELECT status.collection_id, status.scope_key, ${runToken}, ${now}, ${now}
|
|
690
|
+
FROM _emdash_media_usage_index_status AS status
|
|
691
|
+
INNER JOIN _emdash_collections AS collection
|
|
692
|
+
ON collection.id = status.collection_id
|
|
693
|
+
AND collection.slug = status.scope_key
|
|
694
|
+
WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
|
|
695
|
+
AND status.scope_type = ${COLLECTION_SCOPE}
|
|
696
|
+
AND status.capture_state = 'active'
|
|
697
|
+
AND status.reconciliation_required = 1
|
|
698
|
+
AND status.collection_id IS NOT NULL
|
|
699
|
+
AND EXISTS (
|
|
700
|
+
SELECT 1 FROM _emdash_media_usage_activation AS activation
|
|
701
|
+
WHERE activation.task_key = ${ACTIVATION_KEY}
|
|
702
|
+
AND activation.state = 'active'
|
|
703
|
+
)
|
|
704
|
+
AND NOT EXISTS (
|
|
705
|
+
SELECT 1 FROM _emdash_media_usage_reconciliations AS existing
|
|
706
|
+
WHERE existing.collection_id = status.collection_id
|
|
707
|
+
)
|
|
708
|
+
AND NOT EXISTS (
|
|
709
|
+
SELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion
|
|
710
|
+
WHERE deletion.collection_id = status.collection_id
|
|
711
|
+
)
|
|
712
|
+
ORDER BY status.collection_id
|
|
713
|
+
LIMIT 1
|
|
714
|
+
ON CONFLICT (collection_id) DO NOTHING
|
|
715
|
+
RETURNING collection_id
|
|
716
|
+
`.execute(this.db)).rows.length === 1;
|
|
717
|
+
}
|
|
718
|
+
async findDue(limit) {
|
|
719
|
+
assertLimit(limit);
|
|
720
|
+
const nextAttemptIsDue = timestampIsDue(this.db, "next_attempt_at");
|
|
721
|
+
return (await sql`
|
|
722
|
+
WITH pending_candidates AS (
|
|
723
|
+
SELECT * FROM _emdash_media_usage_reconciliations
|
|
724
|
+
WHERE state = 'pending' AND ${nextAttemptIsDue}
|
|
725
|
+
ORDER BY next_attempt_at, updated_at, collection_id
|
|
726
|
+
LIMIT ${limit}
|
|
727
|
+
), retry_candidates AS (
|
|
728
|
+
SELECT * FROM _emdash_media_usage_reconciliations
|
|
729
|
+
WHERE state = 'retry' AND ${nextAttemptIsDue}
|
|
730
|
+
ORDER BY next_attempt_at, updated_at, collection_id
|
|
731
|
+
LIMIT ${limit}
|
|
732
|
+
), leased_candidates AS (
|
|
733
|
+
SELECT * FROM _emdash_media_usage_reconciliations
|
|
734
|
+
WHERE state = 'leased' AND ${timestampIsDue(this.db, "lease_expires_at")}
|
|
735
|
+
ORDER BY lease_expires_at, updated_at, collection_id
|
|
736
|
+
LIMIT ${limit}
|
|
737
|
+
), candidates AS (
|
|
738
|
+
SELECT * FROM pending_candidates
|
|
739
|
+
UNION ALL SELECT * FROM retry_candidates
|
|
740
|
+
UNION ALL SELECT * FROM leased_candidates
|
|
741
|
+
)
|
|
742
|
+
SELECT * FROM candidates
|
|
743
|
+
ORDER BY CASE WHEN state = 'leased' THEN lease_expires_at ELSE next_attempt_at END,
|
|
744
|
+
updated_at,
|
|
745
|
+
collection_id
|
|
746
|
+
LIMIT ${limit}
|
|
747
|
+
`.execute(this.db)).rows.map(rowToRecord);
|
|
748
|
+
}
|
|
749
|
+
async findFailed(limit) {
|
|
750
|
+
assertLimit(limit);
|
|
751
|
+
return (await this.db.selectFrom("_emdash_media_usage_reconciliations as reconciliation").innerJoin("_emdash_media_usage_index_status as status", (join) => join.onRef("status.collection_id", "=", "reconciliation.collection_id").onRef("status.scope_key", "=", "reconciliation.collection_slug")).selectAll("reconciliation").where("reconciliation.state", "=", "failed").where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where((eb) => eb.or([
|
|
752
|
+
eb("status.reconciliation_required", "=", 0),
|
|
753
|
+
eb.and([eb("status.status", "=", "running"), eb("status.cursor", "=", eb.ref("reconciliation.run_token"))]),
|
|
754
|
+
eb.and([eb("status.cursor", "is", null), eb("status.change_epoch", ">", eb.ref("reconciliation.target_epoch"))])
|
|
755
|
+
])).orderBy("reconciliation.updated_at").orderBy("reconciliation.collection_id").limit(limit).execute()).map(rowToRecord);
|
|
756
|
+
}
|
|
757
|
+
async claim(input) {
|
|
758
|
+
assertIdentity(input);
|
|
759
|
+
assertDuration(input.leaseDurationSeconds, "lease duration");
|
|
760
|
+
const leaseToken = ulid();
|
|
761
|
+
const row = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
762
|
+
state: "leased",
|
|
763
|
+
lease_token: leaseToken,
|
|
764
|
+
lease_expires_at: timestampOffset(this.db, input.leaseDurationSeconds),
|
|
765
|
+
updated_at: timestampOffset(this.db, 0)
|
|
766
|
+
}).where("reconciliation.collection_id", "=", input.collectionId).where("reconciliation.run_token", "=", input.runToken).where((eb) => eb.or([eb.and([eb("reconciliation.state", "in", ["pending", "retry"]), timestampIsDue(this.db, "reconciliation.next_attempt_at")]), eb.and([eb("reconciliation.state", "=", "leased"), timestampIsDue(this.db, "reconciliation.lease_expires_at")])])).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_activation as activation").select("activation.task_key").where("activation.task_key", "=", ACTIVATION_KEY).where("activation.state", "=", "active"))).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_index_status as status").innerJoin("_emdash_collections as collection", (join) => join.onRef("collection.id", "=", "status.collection_id").onRef("collection.slug", "=", "status.scope_key")).select("status.collection_id").whereRef("status.collection_id", "=", "reconciliation.collection_id").whereRef("status.scope_key", "=", "reconciliation.collection_slug").where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1))).where((eb) => eb.not(eb.exists(eb.selectFrom("_emdash_media_usage_collection_deletions as deletion").select("deletion.collection_id").whereRef("deletion.collection_id", "=", "reconciliation.collection_id")))).returningAll().executeTakeFirst();
|
|
767
|
+
return row ? {
|
|
768
|
+
...rowToRecord(row),
|
|
769
|
+
leaseToken
|
|
770
|
+
} : null;
|
|
771
|
+
}
|
|
772
|
+
async release(input) {
|
|
773
|
+
assertLeaseIdentity(input);
|
|
774
|
+
assertDuration(input.delaySeconds, "release delay", true);
|
|
775
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
|
|
776
|
+
state: "pending",
|
|
777
|
+
next_attempt_at: timestampOffset(this.db, input.delaySeconds),
|
|
778
|
+
lease_token: null,
|
|
779
|
+
lease_expires_at: null,
|
|
780
|
+
updated_at: timestampOffset(this.db, 0)
|
|
781
|
+
}).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
|
|
782
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
783
|
+
}
|
|
784
|
+
async recordFailure(input) {
|
|
785
|
+
assertLeaseIdentity(input);
|
|
786
|
+
if (!STABLE_ERROR_CODE_PATTERN.test(input.errorCode)) throw new Error("Reconciliation failure requires a stable error code");
|
|
787
|
+
assertDuration(input.retryDelaySeconds, "retry delay", true);
|
|
788
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations").set({
|
|
789
|
+
state: input.terminal ? "failed" : sql`CASE WHEN attempt_count >= 4 THEN 'failed' ELSE 'retry' END`,
|
|
790
|
+
...input.terminal ? { target_epoch: sql`COALESCE(
|
|
791
|
+
target_epoch,
|
|
792
|
+
(SELECT status.change_epoch
|
|
793
|
+
FROM _emdash_media_usage_index_status AS status
|
|
794
|
+
WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}
|
|
795
|
+
AND status.scope_type = ${COLLECTION_SCOPE}
|
|
796
|
+
AND status.collection_id = ${input.collectionId}
|
|
797
|
+
AND status.status = 'running'
|
|
798
|
+
AND status.cursor = ${input.runToken})
|
|
799
|
+
)` } : {},
|
|
800
|
+
attempt_count: sql`attempt_count + 1`,
|
|
801
|
+
next_attempt_at: timestampOffset(this.db, input.retryDelaySeconds),
|
|
802
|
+
lease_token: null,
|
|
803
|
+
lease_expires_at: null,
|
|
804
|
+
last_error_code: input.errorCode,
|
|
805
|
+
updated_at: timestampOffset(this.db, 0)
|
|
806
|
+
}).where("collection_id", "=", input.collectionId).where("run_token", "=", input.runToken).where("state", "=", "leased").where("lease_token", "=", input.leaseToken).where(liveLease(this.db)).executeTakeFirst();
|
|
807
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
808
|
+
}
|
|
809
|
+
async recordEntryFailure(claim) {
|
|
810
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
811
|
+
state: "failed",
|
|
812
|
+
attempt_count: sql`attempt_count + 1`,
|
|
813
|
+
next_attempt_at: timestampOffset(this.db, 0),
|
|
814
|
+
lease_token: null,
|
|
815
|
+
lease_expires_at: null,
|
|
816
|
+
last_error_code: "MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED",
|
|
817
|
+
updated_at: timestampOffset(this.db, 0)
|
|
818
|
+
}).where("reconciliation.collection_id", "=", claim.collectionId).where("reconciliation.run_token", "=", claim.runToken).where("reconciliation.state", "=", "leased").where("reconciliation.lease_token", "=", claim.leaseToken).where(liveLease(this.db)).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_work as work").select("work.content_id").where("work.collection_id", "=", claim.collectionId).where("work.state", "=", "failed"))).executeTakeFirst();
|
|
819
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
820
|
+
}
|
|
821
|
+
async resetFailedForNewEpoch(observed) {
|
|
822
|
+
const collectionId = "collection_id" in observed ? observed.collection_id : observed.collectionId;
|
|
823
|
+
const runToken = "run_token" in observed ? observed.run_token : observed.runToken;
|
|
824
|
+
const targetEpoch = "target_epoch" in observed ? observed.target_epoch : observed.targetEpoch;
|
|
825
|
+
if (targetEpoch === null) return false;
|
|
826
|
+
const now = timestampOffset(this.db, 0);
|
|
827
|
+
const result = await this.db.updateTable("_emdash_media_usage_reconciliations as reconciliation").set({
|
|
828
|
+
state: "pending",
|
|
829
|
+
phase: "scan",
|
|
830
|
+
target_epoch: null,
|
|
831
|
+
field_fingerprint: null,
|
|
832
|
+
scan_cursor: null,
|
|
833
|
+
scan_upper_id: null,
|
|
834
|
+
source_cursor: null,
|
|
835
|
+
source_upper_key: null,
|
|
836
|
+
attempt_count: 0,
|
|
837
|
+
next_attempt_at: now,
|
|
838
|
+
lease_token: null,
|
|
839
|
+
lease_expires_at: null,
|
|
840
|
+
last_error_code: null,
|
|
841
|
+
updated_at: now
|
|
842
|
+
}).where("reconciliation.collection_id", "=", collectionId).where("reconciliation.run_token", "=", runToken).where("reconciliation.state", "=", "failed").where("reconciliation.target_epoch", "=", targetEpoch).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_index_status as status").select("status.collection_id").whereRef("status.collection_id", "=", "reconciliation.collection_id").whereRef("status.scope_key", "=", "reconciliation.collection_slug").where("status.adapter_id", "=", CONTENT_ADAPTER_ID).where("status.scope_type", "=", COLLECTION_SCOPE).where("status.capture_state", "=", "active").where("status.reconciliation_required", "=", 1).where("status.cursor", "is", null).where("status.change_epoch", ">", targetEpoch))).executeTakeFirst();
|
|
843
|
+
return Number(result.numUpdatedRows ?? 0) === 1;
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
function rowToRecord(row) {
|
|
847
|
+
if (!isState(row.state) || !isPhase(row.phase) || !Number.isSafeInteger(row.attempt_count)) throw new Error("Invalid media usage reconciliation lifecycle");
|
|
848
|
+
return {
|
|
849
|
+
collectionId: row.collection_id,
|
|
850
|
+
collectionSlug: row.collection_slug,
|
|
851
|
+
runToken: row.run_token,
|
|
852
|
+
targetEpoch: row.target_epoch,
|
|
853
|
+
fieldFingerprint: row.field_fingerprint,
|
|
854
|
+
state: row.state,
|
|
855
|
+
phase: row.phase,
|
|
856
|
+
scanCursor: row.scan_cursor,
|
|
857
|
+
scanUpperId: row.scan_upper_id,
|
|
858
|
+
sourceCursor: row.source_cursor,
|
|
859
|
+
sourceUpperKey: row.source_upper_key,
|
|
860
|
+
attemptCount: row.attempt_count,
|
|
861
|
+
nextAttemptAt: row.next_attempt_at,
|
|
862
|
+
leaseToken: row.lease_token,
|
|
863
|
+
leaseExpiresAt: row.lease_expires_at,
|
|
864
|
+
lastErrorCode: row.last_error_code,
|
|
865
|
+
createdAt: row.created_at,
|
|
866
|
+
updatedAt: row.updated_at
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function isState(state) {
|
|
870
|
+
return state === "pending" || state === "retry" || state === "leased" || state === "failed";
|
|
871
|
+
}
|
|
872
|
+
function isPhase(phase) {
|
|
873
|
+
return phase === "scan" || phase === "sources";
|
|
874
|
+
}
|
|
875
|
+
function assertLimit(limit) {
|
|
876
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CANDIDATES) throw new Error("Reconciliation candidate limit must be from 1 to 100");
|
|
877
|
+
}
|
|
878
|
+
function assertIdentity(input) {
|
|
879
|
+
if (!input.collectionId || !input.runToken) throw new Error("Reconciliation requires an exact collection and run token");
|
|
880
|
+
}
|
|
881
|
+
function assertLeaseIdentity(input) {
|
|
882
|
+
assertIdentity(input);
|
|
883
|
+
if (!input.leaseToken) throw new Error("Reconciliation requires a lease token");
|
|
884
|
+
}
|
|
885
|
+
function assertDuration(value, label, allowZero = false) {
|
|
886
|
+
if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > MAX_PORTABLE_DURATION_SECONDS) throw new Error(`Reconciliation ${label} is outside the portable range`);
|
|
887
|
+
}
|
|
888
|
+
function liveLease(db, column = "lease_expires_at") {
|
|
889
|
+
const expiry = sql.ref(column);
|
|
890
|
+
return isPostgres(db) ? sql`${expiry} > to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')` : sql`${expiry} > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
891
|
+
}
|
|
892
|
+
function timestampIsDue(db, column) {
|
|
893
|
+
const value = sql.ref(column);
|
|
894
|
+
return isPostgres(db) ? sql`${value} <= to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')` : sql`${value} <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
895
|
+
}
|
|
896
|
+
function timestampOffset(db, offsetSeconds) {
|
|
897
|
+
if (isPostgres(db)) return sql`to_char(
|
|
898
|
+
(clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),
|
|
899
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
900
|
+
)`;
|
|
901
|
+
return sql`strftime(
|
|
902
|
+
'%Y-%m-%dT%H:%M:%fZ',
|
|
903
|
+
'now',
|
|
904
|
+
${`${offsetSeconds >= 0 ? "+" : ""}${offsetSeconds} seconds`}
|
|
905
|
+
)`;
|
|
906
|
+
}
|
|
907
|
+
function contentTableName(collectionSlug) {
|
|
908
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
909
|
+
const tableName = `ec_${collectionSlug}`;
|
|
910
|
+
validateIdentifier(tableName, "content table");
|
|
911
|
+
return tableName;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/media/usage/reconciliation-processor.ts
|
|
916
|
+
const MEDIA_USAGE_RECONCILIATION_LIMITS = Object.freeze({
|
|
917
|
+
candidatesPerTick: 4,
|
|
918
|
+
pageSize: 50,
|
|
919
|
+
leaseDurationSeconds: 60,
|
|
920
|
+
maxAttempts: 5,
|
|
921
|
+
retryBaseSeconds: 30,
|
|
922
|
+
retryMaxSeconds: 900,
|
|
923
|
+
retryJitterRatio: .25,
|
|
924
|
+
maxQueriesPerTick: 20
|
|
925
|
+
});
|
|
926
|
+
async function processDueMediaUsageReconciliation(db) {
|
|
927
|
+
if ((await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state !== "active") return "inactive";
|
|
928
|
+
const reconciliation = new MediaUsageReconciliationRepository(db);
|
|
929
|
+
if (await reconciliation.deleteOneObsolete()) return "completed";
|
|
930
|
+
const [failed] = await reconciliation.findFailed(1);
|
|
931
|
+
if (failed) {
|
|
932
|
+
if (await reconciliation.finishFailedCoverage(failed.collectionId, failed.runToken)) return "failed";
|
|
933
|
+
if (await reconciliation.resetFailedForNewEpoch(failed)) return "advanced";
|
|
934
|
+
}
|
|
935
|
+
await reconciliation.seedNextCandidate();
|
|
936
|
+
const candidates = await reconciliation.findDue(MEDIA_USAGE_RECONCILIATION_LIMITS.candidatesPerTick);
|
|
937
|
+
let claim = null;
|
|
938
|
+
for (const candidate of candidates) {
|
|
939
|
+
claim = await reconciliation.claim({
|
|
940
|
+
collectionId: candidate.collectionId,
|
|
941
|
+
runToken: candidate.runToken,
|
|
942
|
+
leaseDurationSeconds: MEDIA_USAGE_RECONCILIATION_LIMITS.leaseDurationSeconds
|
|
943
|
+
});
|
|
944
|
+
if (claim) break;
|
|
945
|
+
}
|
|
946
|
+
if (!claim) return candidates.length === 0 ? "not_due" : "claim_lost";
|
|
947
|
+
try {
|
|
948
|
+
return await processClaimedReconciliation(db, claim);
|
|
949
|
+
} catch (error) {
|
|
950
|
+
const terminal = claim.attemptCount + 1 >= MEDIA_USAGE_RECONCILIATION_LIMITS.maxAttempts;
|
|
951
|
+
if (!await reconciliation.recordFailure({
|
|
952
|
+
collectionId: claim.collectionId,
|
|
953
|
+
runToken: claim.runToken,
|
|
954
|
+
leaseToken: claim.leaseToken,
|
|
955
|
+
errorCode: "MEDIA_USAGE_RECONCILIATION_FAILED",
|
|
956
|
+
retryDelaySeconds: retryDelaySeconds$1(claim.attemptCount),
|
|
957
|
+
terminal
|
|
958
|
+
})) return "claim_lost";
|
|
959
|
+
if (terminal) await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
|
|
960
|
+
console.error("[media-usage:reconciliation] Processing failed:", error);
|
|
961
|
+
return terminal ? "failed" : "retry";
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async function processClaimedMediaUsageReconciliationScan(db, claim, options = {}) {
|
|
965
|
+
const reconciliation = new MediaUsageReconciliationRepository(db);
|
|
966
|
+
let current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
|
|
967
|
+
if (!current || current.leaseToken !== claim.leaseToken || current.phase !== "scan") return "deferred";
|
|
968
|
+
let fields;
|
|
969
|
+
let fieldFingerprint;
|
|
970
|
+
if (current.targetEpoch === null) {
|
|
971
|
+
const targetEpoch = await reconciliation.beginRun(claim);
|
|
972
|
+
if (targetEpoch === null) {
|
|
973
|
+
await reconciliation.release({
|
|
974
|
+
...claim,
|
|
975
|
+
delaySeconds: 30
|
|
976
|
+
});
|
|
977
|
+
return "deferred";
|
|
978
|
+
}
|
|
979
|
+
fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
|
|
980
|
+
fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
|
|
981
|
+
const scanUpperId = fields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
|
|
982
|
+
if (!await reconciliation.initializeScan({
|
|
983
|
+
claim,
|
|
984
|
+
targetEpoch,
|
|
985
|
+
fieldFingerprint,
|
|
986
|
+
scanUpperId
|
|
987
|
+
})) return "deferred";
|
|
988
|
+
current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
|
|
989
|
+
if (!current) return "deferred";
|
|
990
|
+
} else {
|
|
991
|
+
fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
|
|
992
|
+
fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
|
|
993
|
+
}
|
|
994
|
+
if (current.fieldFingerprint !== fieldFingerprint || current.targetEpoch === null) return "restart_required";
|
|
995
|
+
const contentIds = await reconciliation.findScanPage(current, 50);
|
|
996
|
+
if (contentIds.length === 0) {
|
|
997
|
+
if (options.releaseOnExhausted ?? true) await reconciliation.release({
|
|
998
|
+
...claim,
|
|
999
|
+
delaySeconds: 30
|
|
1000
|
+
});
|
|
1001
|
+
return "exhausted";
|
|
1002
|
+
}
|
|
1003
|
+
await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
|
|
1004
|
+
collectionId: claim.collectionId,
|
|
1005
|
+
collectionSlug: claim.collectionSlug,
|
|
1006
|
+
runToken: claim.runToken,
|
|
1007
|
+
leaseToken: claim.leaseToken,
|
|
1008
|
+
changeEpoch: current.targetEpoch,
|
|
1009
|
+
phase: "scan",
|
|
1010
|
+
contentIds
|
|
1011
|
+
});
|
|
1012
|
+
const nextCursor = contentIds.at(-1);
|
|
1013
|
+
if (!await reconciliation.checkpointScan({
|
|
1014
|
+
claim,
|
|
1015
|
+
targetEpoch: current.targetEpoch,
|
|
1016
|
+
previousCursor: current.scanCursor,
|
|
1017
|
+
nextCursor
|
|
1018
|
+
})) return "deferred";
|
|
1019
|
+
if (!await reconciliation.release({
|
|
1020
|
+
...claim,
|
|
1021
|
+
delaySeconds: 0
|
|
1022
|
+
})) return "deferred";
|
|
1023
|
+
return "advanced";
|
|
1024
|
+
}
|
|
1025
|
+
async function processClaimedReconciliation(db, claim) {
|
|
1026
|
+
const reconciliation = new MediaUsageReconciliationRepository(db);
|
|
1027
|
+
let current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);
|
|
1028
|
+
if (!current || current.leaseToken !== claim.leaseToken) return "claim_lost";
|
|
1029
|
+
if (current.targetEpoch !== null && !await reconciliation.ownsRun(claim, current.targetEpoch)) return restartReconciliation(db, reconciliation, claim, current);
|
|
1030
|
+
if (current.phase === "scan") {
|
|
1031
|
+
const outcome = await processClaimedMediaUsageReconciliationScan(db, claim, { releaseOnExhausted: false });
|
|
1032
|
+
if (outcome === "restart_required") {
|
|
1033
|
+
current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
|
|
1034
|
+
return restartReconciliation(db, reconciliation, claim, current);
|
|
1035
|
+
}
|
|
1036
|
+
if (outcome !== "exhausted") return outcome;
|
|
1037
|
+
current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken) ?? current;
|
|
1038
|
+
const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
|
|
1039
|
+
if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
|
|
1040
|
+
if (barrier.state === "pending") {
|
|
1041
|
+
await reconciliation.release({
|
|
1042
|
+
...claim,
|
|
1043
|
+
delaySeconds: 30
|
|
1044
|
+
});
|
|
1045
|
+
return "deferred";
|
|
1046
|
+
}
|
|
1047
|
+
if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
|
|
1048
|
+
const sourceUpperKey = await reconciliation.findSourceUpperKey(claim, current.targetEpoch);
|
|
1049
|
+
if (!await reconciliation.transitionToSources({
|
|
1050
|
+
claim,
|
|
1051
|
+
targetEpoch: current.targetEpoch,
|
|
1052
|
+
fieldFingerprint: current.fieldFingerprint,
|
|
1053
|
+
sourceUpperKey
|
|
1054
|
+
})) return "claim_lost";
|
|
1055
|
+
if (!await reconciliation.release({
|
|
1056
|
+
...claim,
|
|
1057
|
+
delaySeconds: 0
|
|
1058
|
+
})) return "claim_lost";
|
|
1059
|
+
return "advanced";
|
|
1060
|
+
}
|
|
1061
|
+
return processSourcePhase(db, reconciliation, claim, current);
|
|
1062
|
+
}
|
|
1063
|
+
async function processSourcePhase(db, reconciliation, claim, current) {
|
|
1064
|
+
if (current.targetEpoch === null || current.fieldFingerprint === null) return "claim_lost";
|
|
1065
|
+
const fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
|
|
1066
|
+
const fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);
|
|
1067
|
+
if (fieldFingerprint !== current.fieldFingerprint) return restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint);
|
|
1068
|
+
const page = await reconciliation.findSourcePage(current, MEDIA_USAGE_RECONCILIATION_LIMITS.pageSize);
|
|
1069
|
+
if (page.length > 0) {
|
|
1070
|
+
if (page.some((source) => !source.contentId || source.sourceVariant !== "columns" && source.sourceVariant !== "draft_overlay")) return failReconciliation(reconciliation, claim, "MEDIA_USAGE_RECONCILIATION_INVALID_SOURCE", false);
|
|
1071
|
+
const contentIds = [...new Set(page.map((source) => source.contentId))];
|
|
1072
|
+
const enqueueIds = fields.extractionFields.length === 0 ? contentIds : await reconciliation.findMissingContentIds(claim.collectionSlug, contentIds);
|
|
1073
|
+
if (enqueueIds.length > 0) await new MediaUsageWorkRepository(db).enqueueReconciliationPage({
|
|
1074
|
+
collectionId: claim.collectionId,
|
|
1075
|
+
collectionSlug: claim.collectionSlug,
|
|
1076
|
+
runToken: claim.runToken,
|
|
1077
|
+
leaseToken: claim.leaseToken,
|
|
1078
|
+
changeEpoch: current.targetEpoch,
|
|
1079
|
+
phase: "sources",
|
|
1080
|
+
contentIds: enqueueIds
|
|
1081
|
+
});
|
|
1082
|
+
if (!await reconciliation.checkpointSources({
|
|
1083
|
+
claim,
|
|
1084
|
+
targetEpoch: current.targetEpoch,
|
|
1085
|
+
previousCursor: current.sourceCursor,
|
|
1086
|
+
nextCursor: page.at(-1).sourceKey
|
|
1087
|
+
})) return "claim_lost";
|
|
1088
|
+
if (!await reconciliation.release({
|
|
1089
|
+
...claim,
|
|
1090
|
+
delaySeconds: 0
|
|
1091
|
+
})) return "claim_lost";
|
|
1092
|
+
return "advanced";
|
|
1093
|
+
}
|
|
1094
|
+
const barrier = await reconciliation.findWorkBarrier(claim.collectionId);
|
|
1095
|
+
if (barrier.state === "failed") return failReconciliation(reconciliation, claim, barrier.errorCode, true);
|
|
1096
|
+
if (barrier.state === "pending") {
|
|
1097
|
+
await reconciliation.release({
|
|
1098
|
+
...claim,
|
|
1099
|
+
delaySeconds: 30
|
|
1100
|
+
});
|
|
1101
|
+
return "deferred";
|
|
1102
|
+
}
|
|
1103
|
+
if (!await reconciliation.finalizeCoverage({
|
|
1104
|
+
claim,
|
|
1105
|
+
targetEpoch: current.targetEpoch,
|
|
1106
|
+
fieldFingerprint,
|
|
1107
|
+
schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION
|
|
1108
|
+
})) return "claim_lost";
|
|
1109
|
+
if (!await reconciliation.deleteFinalized(claim)) return "claim_lost";
|
|
1110
|
+
return "completed";
|
|
1111
|
+
}
|
|
1112
|
+
async function restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint) {
|
|
1113
|
+
if (current.targetEpoch === null) return "claim_lost";
|
|
1114
|
+
const discoveredFields = fields ?? await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);
|
|
1115
|
+
const fingerprint = fieldFingerprint ?? await buildContentMediaUsageFieldFingerprint(discoveredFields);
|
|
1116
|
+
const targetEpoch = await reconciliation.restartRun(claim, current.targetEpoch);
|
|
1117
|
+
if (targetEpoch === null) {
|
|
1118
|
+
await reconciliation.release({
|
|
1119
|
+
...claim,
|
|
1120
|
+
delaySeconds: 30
|
|
1121
|
+
});
|
|
1122
|
+
return "deferred";
|
|
1123
|
+
}
|
|
1124
|
+
const scanUpperId = discoveredFields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);
|
|
1125
|
+
if (!await reconciliation.restartScan({
|
|
1126
|
+
claim,
|
|
1127
|
+
previousEpoch: current.targetEpoch,
|
|
1128
|
+
targetEpoch,
|
|
1129
|
+
fieldFingerprint: fingerprint,
|
|
1130
|
+
scanUpperId
|
|
1131
|
+
})) return "claim_lost";
|
|
1132
|
+
if (!await reconciliation.release({
|
|
1133
|
+
...claim,
|
|
1134
|
+
delaySeconds: 0
|
|
1135
|
+
})) return "claim_lost";
|
|
1136
|
+
return "advanced";
|
|
1137
|
+
}
|
|
1138
|
+
async function failReconciliation(reconciliation, claim, errorCode, entryFailure) {
|
|
1139
|
+
if (!(entryFailure ? await reconciliation.recordEntryFailure(claim) : await reconciliation.recordFailure({
|
|
1140
|
+
collectionId: claim.collectionId,
|
|
1141
|
+
runToken: claim.runToken,
|
|
1142
|
+
leaseToken: claim.leaseToken,
|
|
1143
|
+
errorCode,
|
|
1144
|
+
retryDelaySeconds: 0,
|
|
1145
|
+
terminal: true
|
|
1146
|
+
}))) return "claim_lost";
|
|
1147
|
+
await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);
|
|
1148
|
+
return "failed";
|
|
1149
|
+
}
|
|
1150
|
+
function retryDelaySeconds$1(attemptCount) {
|
|
1151
|
+
const exponential = Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, MEDIA_USAGE_RECONCILIATION_LIMITS.retryBaseSeconds * 2 ** attemptCount);
|
|
1152
|
+
const jitter = Math.floor(exponential * MEDIA_USAGE_RECONCILIATION_LIMITS.retryJitterRatio * Math.random());
|
|
1153
|
+
return Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, exponential + jitter);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
//#endregion
|
|
1157
|
+
//#region src/media/usage/work-processor.ts
|
|
1158
|
+
const MEDIA_USAGE_WORK_PROCESSING_LIMITS = Object.freeze({
|
|
1159
|
+
candidatesPerTick: 4,
|
|
1160
|
+
jobsPerTick: 1,
|
|
1161
|
+
maxTickDurationMs: 5e3,
|
|
1162
|
+
leaseDurationSeconds: 60,
|
|
1163
|
+
maxAttempts: 5,
|
|
1164
|
+
retryBaseSeconds: 30,
|
|
1165
|
+
retryMaxSeconds: 900,
|
|
1166
|
+
retryJitterRatio: .25,
|
|
1167
|
+
ordinaryStatementsPerJob: 20
|
|
1168
|
+
});
|
|
1169
|
+
async function processMediaUsageWorkAfterWrite(db, collectionSlug, contentId) {
|
|
1170
|
+
if (!await isIncrementalCaptureActive(db)) return {
|
|
1171
|
+
outcome: "inactive",
|
|
1172
|
+
claimed: false
|
|
1173
|
+
};
|
|
1174
|
+
const repo = new MediaUsageWorkRepository(db);
|
|
1175
|
+
const work = await repo.findWorkForContent(collectionSlug, contentId);
|
|
1176
|
+
if (!work) return {
|
|
1177
|
+
outcome: "not_due",
|
|
1178
|
+
claimed: false
|
|
1179
|
+
};
|
|
1180
|
+
return processCandidate(db, repo, work);
|
|
1181
|
+
}
|
|
1182
|
+
async function processDueMediaUsageWork(db) {
|
|
1183
|
+
const startedAt = Date.now();
|
|
1184
|
+
const result = {
|
|
1185
|
+
candidateCount: 0,
|
|
1186
|
+
claimedCount: 0,
|
|
1187
|
+
completedCount: 0,
|
|
1188
|
+
retryCount: 0,
|
|
1189
|
+
failedCount: 0,
|
|
1190
|
+
supersededCount: 0,
|
|
1191
|
+
obsoleteCount: 0,
|
|
1192
|
+
durationMs: 0,
|
|
1193
|
+
admissionClosed: false
|
|
1194
|
+
};
|
|
1195
|
+
if (!await isIncrementalCaptureActive(db)) {
|
|
1196
|
+
result.durationMs = Date.now() - startedAt;
|
|
1197
|
+
return result;
|
|
1198
|
+
}
|
|
1199
|
+
const repo = new MediaUsageWorkRepository(db);
|
|
1200
|
+
const candidates = await repo.findDueWork(MEDIA_USAGE_WORK_PROCESSING_LIMITS.candidatesPerTick);
|
|
1201
|
+
result.candidateCount = candidates.length;
|
|
1202
|
+
for (const candidate of candidates) {
|
|
1203
|
+
if (result.claimedCount >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.jobsPerTick || Date.now() - startedAt >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxTickDurationMs) {
|
|
1204
|
+
result.admissionClosed = true;
|
|
1205
|
+
break;
|
|
140
1206
|
}
|
|
1207
|
+
const processed = await processCandidate(db, repo, candidate);
|
|
1208
|
+
if (processed.claimed) result.claimedCount++;
|
|
1209
|
+
if (processed.outcome === "completed") result.completedCount++;
|
|
1210
|
+
if (processed.outcome === "retry") result.retryCount++;
|
|
1211
|
+
if (processed.outcome === "failed") result.failedCount++;
|
|
1212
|
+
if (processed.outcome === "superseded") result.supersededCount++;
|
|
1213
|
+
if (processed.outcome === "obsolete") result.obsoleteCount++;
|
|
141
1214
|
}
|
|
1215
|
+
result.durationMs = Date.now() - startedAt;
|
|
1216
|
+
return result;
|
|
1217
|
+
}
|
|
1218
|
+
async function processCandidate(db, repo, candidate) {
|
|
1219
|
+
const claimed = await repo.claimWork({
|
|
1220
|
+
collectionId: candidate.collectionId,
|
|
1221
|
+
contentId: candidate.contentId,
|
|
1222
|
+
workVersion: candidate.workVersion,
|
|
1223
|
+
leaseDurationSeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.leaseDurationSeconds
|
|
1224
|
+
});
|
|
1225
|
+
if (!claimed?.leaseToken) return {
|
|
1226
|
+
outcome: "claim_lost",
|
|
1227
|
+
claimed: false
|
|
1228
|
+
};
|
|
1229
|
+
const lease = {
|
|
1230
|
+
collectionId: claimed.collectionId,
|
|
1231
|
+
contentId: claimed.contentId,
|
|
1232
|
+
workVersion: claimed.workVersion,
|
|
1233
|
+
leaseToken: claimed.leaseToken
|
|
1234
|
+
};
|
|
1235
|
+
if (!await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug)) return {
|
|
1236
|
+
outcome: await repo.completeWork(lease) ? "obsolete" : "superseded",
|
|
1237
|
+
claimed: true
|
|
1238
|
+
};
|
|
1239
|
+
const refresh = await refreshContentMediaUsageForWork(db, claimed.collectionId, claimed.collectionSlug, claimed.contentId);
|
|
1240
|
+
if (refresh.success) {
|
|
1241
|
+
const completed = await repo.completeWork(lease);
|
|
1242
|
+
if (completed) await new MediaUsageRepository(db).recordIncrementalSuccess({
|
|
1243
|
+
collectionId: claimed.collectionId,
|
|
1244
|
+
collectionSlug: claimed.collectionSlug
|
|
1245
|
+
});
|
|
1246
|
+
return {
|
|
1247
|
+
outcome: completed ? "completed" : "superseded",
|
|
1248
|
+
claimed: true
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
if (!await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug)) return {
|
|
1252
|
+
outcome: await repo.completeWork(lease) ? "obsolete" : "superseded",
|
|
1253
|
+
claimed: true
|
|
1254
|
+
};
|
|
1255
|
+
const errorCode = processingErrorCode(refresh.errorCode);
|
|
1256
|
+
if (errorCode === "MEDIA_USAGE_RESOURCE_LIMIT" || claimed.attemptCount + 1 >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts) {
|
|
1257
|
+
const failed = await repo.failWork({
|
|
1258
|
+
...lease,
|
|
1259
|
+
errorCode
|
|
1260
|
+
});
|
|
1261
|
+
if (failed) await new MediaUsageRepository(db).recordIncrementalFailure({
|
|
1262
|
+
collectionId: claimed.collectionId,
|
|
1263
|
+
collectionSlug: claimed.collectionSlug,
|
|
1264
|
+
contentId: claimed.contentId,
|
|
1265
|
+
workVersion: claimed.workVersion,
|
|
1266
|
+
errorCode
|
|
1267
|
+
});
|
|
1268
|
+
return {
|
|
1269
|
+
outcome: failed ? "failed" : "superseded",
|
|
1270
|
+
claimed: true
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
return {
|
|
1274
|
+
outcome: await repo.retryWork({
|
|
1275
|
+
...lease,
|
|
1276
|
+
errorCode,
|
|
1277
|
+
retryDelaySeconds: retryDelaySeconds(claimed.attemptCount)
|
|
1278
|
+
}) ? "retry" : "superseded",
|
|
1279
|
+
claimed: true
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
async function isIncrementalCaptureActive(db) {
|
|
1283
|
+
return (await db.selectFrom("_emdash_media_usage_activation").select("state").where("task_key", "=", "incremental_capture").executeTakeFirst())?.state === "active";
|
|
1284
|
+
}
|
|
1285
|
+
async function collectionIdentityIsCurrent(db, collectionId, collectionSlug) {
|
|
1286
|
+
return await db.selectFrom("_emdash_collections").select("id").where("id", "=", collectionId).where("slug", "=", collectionSlug).executeTakeFirst() !== void 0;
|
|
1287
|
+
}
|
|
1288
|
+
function retryDelaySeconds(attemptCount) {
|
|
1289
|
+
const exponential = Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds * 2 ** attemptCount);
|
|
1290
|
+
const jitter = Math.floor(exponential * MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryJitterRatio * Math.random());
|
|
1291
|
+
return Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, exponential + jitter);
|
|
1292
|
+
}
|
|
1293
|
+
function processingErrorCode(errorCode) {
|
|
1294
|
+
if (errorCode === "DRAFT_REVISION_NOT_FOUND" || errorCode === "DRAFT_REVISION_MISMATCH" || errorCode === "DRAFT_REVISION_INVALID") return "MEDIA_USAGE_SNAPSHOT_FAILED";
|
|
1295
|
+
if (errorCode === "CONTENT_USAGE_GENERATION_CONFLICT") return "MEDIA_USAGE_GENERATION_CONFLICT";
|
|
1296
|
+
if (errorCode === "CONTENT_USAGE_RESOURCE_LIMIT") return "MEDIA_USAGE_RESOURCE_LIMIT";
|
|
1297
|
+
return "MEDIA_USAGE_PROCESSING_FAILED";
|
|
142
1298
|
}
|
|
143
1299
|
|
|
144
1300
|
//#endregion
|
|
@@ -155,32 +1311,225 @@ function createSandboxRunnerOptions(options, siteInfo) {
|
|
|
155
1311
|
}
|
|
156
1312
|
|
|
157
1313
|
//#endregion
|
|
158
|
-
//#region src/media/usage/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
1314
|
+
//#region src/media/usage/cleanup.ts
|
|
1315
|
+
const MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT = 250;
|
|
1316
|
+
const MEDIA_USAGE_CLEANUP_DELETE_LIMIT = 50;
|
|
1317
|
+
const MEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT = 49;
|
|
1318
|
+
const MEDIA_USAGE_CLEANUP_INTERVAL_MS = 60 * 1e3;
|
|
1319
|
+
const MEDIA_USAGE_CLEANUP_LEASE_MS = 300 * 1e3;
|
|
1320
|
+
const MEDIA_USAGE_CLEANUP_SAFETY_WINDOW_MS = 3600 * 1e3;
|
|
1321
|
+
const MEDIA_USAGE_CLEANUP_TIME_BUDGET_MS = 5 * 1e3;
|
|
165
1322
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Delete media-usage occurrence rows that can no longer be read: superseded
|
|
172
|
-
* generations, generations abandoned by losing CAS writers, and occurrences
|
|
173
|
-
* whose source row is gone.
|
|
1323
|
+
* Reclaims a bounded window of obsolete media-usage occurrences.
|
|
1324
|
+
*
|
|
1325
|
+
* The persisted claim makes a cron tick single-flight across Worker isolates
|
|
1326
|
+
* and Node processes.
|
|
174
1327
|
*/
|
|
175
|
-
async function
|
|
176
|
-
const
|
|
1328
|
+
async function cleanupMediaUsage(db) {
|
|
1329
|
+
const startedMs = Date.now();
|
|
1330
|
+
const canIssueStatement = () => withinBudget(startedMs);
|
|
177
1331
|
const repo = new MediaUsageRepository(db);
|
|
1332
|
+
const leaseToken = ulid();
|
|
1333
|
+
const claim = await repo.claimMediaUsageCleanup({
|
|
1334
|
+
leaseToken,
|
|
1335
|
+
leaseDurationSeconds: MEDIA_USAGE_CLEANUP_LEASE_MS / 1e3,
|
|
1336
|
+
nextEligibleDelaySeconds: MEDIA_USAGE_CLEANUP_INTERVAL_MS / 1e3,
|
|
1337
|
+
sweepSafetyWindowSeconds: MEDIA_USAGE_CLEANUP_SAFETY_WINDOW_MS / 1e3
|
|
1338
|
+
});
|
|
1339
|
+
if (!claim) return emptyResult("skipped", elapsedSince(startedMs));
|
|
1340
|
+
let candidateRows = 0;
|
|
1341
|
+
let deletedOrphans = 0;
|
|
1342
|
+
let deletedStale = 0;
|
|
1343
|
+
let deletedAbandoned = 0;
|
|
1344
|
+
let deletedWriteLeases = 0;
|
|
1345
|
+
let backlogLowerBound = 0;
|
|
1346
|
+
let scanHasMore = false;
|
|
1347
|
+
let nextCursor = claim.cursor;
|
|
1348
|
+
let sweepComplete = false;
|
|
1349
|
+
try {
|
|
1350
|
+
if (canIssueStatement()) deletedWriteLeases = await repo.deleteExpiredGenerationWriteLeases(MEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT, cleanupLease(leaseToken), canIssueStatement);
|
|
1351
|
+
if (canIssueStatement()) {
|
|
1352
|
+
const cutoff = claim.scanBeforeAt;
|
|
1353
|
+
const candidates = await repo.findMediaUsageCleanupCandidates({
|
|
1354
|
+
cutoff,
|
|
1355
|
+
cursor: claim.cursor,
|
|
1356
|
+
limit: MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT,
|
|
1357
|
+
cleanupLease: cleanupLease(leaseToken)
|
|
1358
|
+
});
|
|
1359
|
+
candidateRows = candidates.length;
|
|
1360
|
+
scanHasMore = candidates.length === MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT;
|
|
1361
|
+
const selected = selectCleanupCandidates(candidates, claim.claimedAt);
|
|
1362
|
+
backlogLowerBound = selected.orphanIds.length + selected.staleIds.length + selected.abandonedIds.length;
|
|
1363
|
+
const completedTargets = /* @__PURE__ */ new Set();
|
|
1364
|
+
let canContinue = true;
|
|
1365
|
+
if (canIssueStatement() && selected.orphanIds.length > 0) {
|
|
1366
|
+
deletedOrphans = await repo.deleteOrphanOccurrencesOlderThan(cutoff, selected.orphanIds.length, {
|
|
1367
|
+
candidateIds: selected.orphanIds,
|
|
1368
|
+
cleanupLease: cleanupLease(leaseToken),
|
|
1369
|
+
canIssueStatement
|
|
1370
|
+
});
|
|
1371
|
+
canContinue = deletedOrphans === selected.orphanIds.length;
|
|
1372
|
+
if (canContinue) completedTargets.add("orphan");
|
|
1373
|
+
}
|
|
1374
|
+
if (canContinue && canIssueStatement() && selected.staleIds.length > 0) {
|
|
1375
|
+
deletedStale = await repo.deleteStaleGenerationsOlderThan(cutoff, selected.staleIds.length, {
|
|
1376
|
+
candidateIds: selected.staleIds,
|
|
1377
|
+
cleanupLease: cleanupLease(leaseToken),
|
|
1378
|
+
canIssueStatement
|
|
1379
|
+
});
|
|
1380
|
+
canContinue = deletedStale === selected.staleIds.length;
|
|
1381
|
+
if (canContinue) completedTargets.add("stale");
|
|
1382
|
+
}
|
|
1383
|
+
if (canContinue && canIssueStatement() && selected.abandonedIds.length > 0) {
|
|
1384
|
+
deletedAbandoned = await repo.deleteAbandonedGenerationsOlderThan(cutoff, selected.abandonedIds.length, {
|
|
1385
|
+
candidateIds: selected.abandonedIds,
|
|
1386
|
+
cleanupLease: cleanupLease(leaseToken),
|
|
1387
|
+
canIssueStatement
|
|
1388
|
+
});
|
|
1389
|
+
if (deletedAbandoned === selected.abandonedIds.length) completedTargets.add("abandoned");
|
|
1390
|
+
}
|
|
1391
|
+
const hasIncompleteTargets = selected.entries.some((entry) => entry.target !== null && !completedTargets.has(entry.target));
|
|
1392
|
+
sweepComplete = !scanHasMore && selected.entries.length === candidates.length && !hasIncompleteTargets;
|
|
1393
|
+
nextCursor = sweepComplete ? null : cursorAfterCompletedCandidates(selected, completedTargets, claim.cursor);
|
|
1394
|
+
}
|
|
1395
|
+
const durationMs = elapsedSince(startedMs);
|
|
1396
|
+
return {
|
|
1397
|
+
status: await repo.completeMediaUsageCleanup({
|
|
1398
|
+
leaseToken,
|
|
1399
|
+
nextCursor,
|
|
1400
|
+
sweepComplete,
|
|
1401
|
+
candidateCount: candidateRows,
|
|
1402
|
+
deletedOrphans,
|
|
1403
|
+
deletedStale,
|
|
1404
|
+
deletedAbandoned,
|
|
1405
|
+
deletedWriteLeases,
|
|
1406
|
+
backlogLowerBound,
|
|
1407
|
+
scanHasMore,
|
|
1408
|
+
durationMs
|
|
1409
|
+
}) ? "completed" : "skipped",
|
|
1410
|
+
candidateRows,
|
|
1411
|
+
deletedRows: deletedOrphans + deletedStale + deletedAbandoned,
|
|
1412
|
+
deletedOrphans,
|
|
1413
|
+
deletedStale,
|
|
1414
|
+
deletedAbandoned,
|
|
1415
|
+
deletedWriteLeases,
|
|
1416
|
+
backlogLowerBound,
|
|
1417
|
+
scanHasMore,
|
|
1418
|
+
durationMs
|
|
1419
|
+
};
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
const durationMs = elapsedSince(startedMs);
|
|
1422
|
+
const failures = Math.min(claim.consecutiveFailures + 1, 5);
|
|
1423
|
+
try {
|
|
1424
|
+
await repo.failMediaUsageCleanup({
|
|
1425
|
+
leaseToken,
|
|
1426
|
+
retryDelaySeconds: failureDelayMs(failures) / 1e3,
|
|
1427
|
+
consecutiveFailures: failures,
|
|
1428
|
+
durationMs,
|
|
1429
|
+
errorCode: "MEDIA_USAGE_CLEANUP_FAILED"
|
|
1430
|
+
});
|
|
1431
|
+
} catch (failureError) {
|
|
1432
|
+
console.error("[media-usage-cleanup] Failed to record cleanup failure:", failureError);
|
|
1433
|
+
}
|
|
1434
|
+
console.error("[media-usage-cleanup] Cleanup failed:", error);
|
|
1435
|
+
return {
|
|
1436
|
+
...emptyResult("failed", durationMs),
|
|
1437
|
+
candidateRows,
|
|
1438
|
+
deletedRows: deletedOrphans + deletedStale + deletedAbandoned,
|
|
1439
|
+
deletedOrphans,
|
|
1440
|
+
deletedStale,
|
|
1441
|
+
deletedAbandoned,
|
|
1442
|
+
deletedWriteLeases,
|
|
1443
|
+
backlogLowerBound,
|
|
1444
|
+
scanHasMore
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
function selectCleanupCandidates(candidates, activeLeaseAt) {
|
|
1449
|
+
const orphanIds = [];
|
|
1450
|
+
const staleIds = [];
|
|
1451
|
+
const abandonedIds = [];
|
|
1452
|
+
const entries = [];
|
|
1453
|
+
for (const candidate of candidates) {
|
|
1454
|
+
if (hasActiveWriteLease(candidate, activeLeaseAt)) {
|
|
1455
|
+
entries.push({
|
|
1456
|
+
candidate,
|
|
1457
|
+
target: null
|
|
1458
|
+
});
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
const target = cleanupTarget(candidate);
|
|
1462
|
+
if (target === null) {
|
|
1463
|
+
entries.push({
|
|
1464
|
+
candidate,
|
|
1465
|
+
target: null
|
|
1466
|
+
});
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
if (orphanIds.length + staleIds.length + abandonedIds.length >= MEDIA_USAGE_CLEANUP_DELETE_LIMIT) break;
|
|
1470
|
+
if (target === "orphan") orphanIds.push(candidate.id);
|
|
1471
|
+
if (target === "stale") staleIds.push(candidate.id);
|
|
1472
|
+
if (target === "abandoned") abandonedIds.push(candidate.id);
|
|
1473
|
+
entries.push({
|
|
1474
|
+
candidate,
|
|
1475
|
+
target
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
178
1478
|
return {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
1479
|
+
orphanIds,
|
|
1480
|
+
staleIds,
|
|
1481
|
+
abandonedIds,
|
|
1482
|
+
entries
|
|
182
1483
|
};
|
|
183
1484
|
}
|
|
1485
|
+
function cleanupTarget(candidate) {
|
|
1486
|
+
if (candidate.currentGeneration === null) return "orphan";
|
|
1487
|
+
if (candidate.currentGeneration === candidate.generation || candidate.indexedAt === null) return null;
|
|
1488
|
+
return candidate.createdAt < candidate.indexedAt ? "stale" : "abandoned";
|
|
1489
|
+
}
|
|
1490
|
+
function hasActiveWriteLease(candidate, activeLeaseAt) {
|
|
1491
|
+
return candidate.writeLeaseExpiresAt !== null && candidate.writeLeaseExpiresAt > activeLeaseAt;
|
|
1492
|
+
}
|
|
1493
|
+
function cursorFor(candidate) {
|
|
1494
|
+
return {
|
|
1495
|
+
createdAt: candidate.createdAt,
|
|
1496
|
+
id: candidate.id
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function cursorAfterCompletedCandidates(selected, completedTargets, priorCursor) {
|
|
1500
|
+
let cursor = priorCursor;
|
|
1501
|
+
for (const entry of selected.entries) {
|
|
1502
|
+
if (entry.target !== null && !completedTargets.has(entry.target)) break;
|
|
1503
|
+
cursor = cursorFor(entry.candidate);
|
|
1504
|
+
}
|
|
1505
|
+
return cursor;
|
|
1506
|
+
}
|
|
1507
|
+
function emptyResult(status, durationMs) {
|
|
1508
|
+
return {
|
|
1509
|
+
status,
|
|
1510
|
+
candidateRows: 0,
|
|
1511
|
+
deletedRows: 0,
|
|
1512
|
+
deletedOrphans: 0,
|
|
1513
|
+
deletedStale: 0,
|
|
1514
|
+
deletedAbandoned: 0,
|
|
1515
|
+
deletedWriteLeases: 0,
|
|
1516
|
+
backlogLowerBound: 0,
|
|
1517
|
+
scanHasMore: false,
|
|
1518
|
+
durationMs
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
function withinBudget(startedMs) {
|
|
1522
|
+
return elapsedSince(startedMs) < MEDIA_USAGE_CLEANUP_TIME_BUDGET_MS;
|
|
1523
|
+
}
|
|
1524
|
+
function elapsedSince(startedMs) {
|
|
1525
|
+
return Math.max(0, Date.now() - startedMs);
|
|
1526
|
+
}
|
|
1527
|
+
function failureDelayMs(consecutiveFailures) {
|
|
1528
|
+
return Math.min(2 ** (consecutiveFailures - 1), 15) * MEDIA_USAGE_CLEANUP_INTERVAL_MS;
|
|
1529
|
+
}
|
|
1530
|
+
function cleanupLease(leaseToken) {
|
|
1531
|
+
return { leaseToken };
|
|
1532
|
+
}
|
|
184
1533
|
|
|
185
1534
|
//#endregion
|
|
186
1535
|
//#region src/cleanup.ts
|
|
@@ -215,9 +1564,7 @@ async function runSystemCleanup(db, storage) {
|
|
|
215
1564
|
pendingUploadFiles: -1,
|
|
216
1565
|
uploadAttempts: -1,
|
|
217
1566
|
revisionsPruned: -1,
|
|
218
|
-
|
|
219
|
-
mediaUsageAbandonedGenerations: -1,
|
|
220
|
-
mediaUsageOrphanOccurrences: -1
|
|
1567
|
+
mediaUsage: -1
|
|
221
1568
|
};
|
|
222
1569
|
try {
|
|
223
1570
|
result.challenges = await cleanupExpiredChallenges(db);
|
|
@@ -265,12 +1612,10 @@ async function runSystemCleanup(db, storage) {
|
|
|
265
1612
|
console.error("[cleanup] Failed to prune revisions:", error);
|
|
266
1613
|
}
|
|
267
1614
|
try {
|
|
268
|
-
const
|
|
269
|
-
result.
|
|
270
|
-
result.mediaUsageAbandonedGenerations = usage.abandonedGenerations;
|
|
271
|
-
result.mediaUsageOrphanOccurrences = usage.orphanOccurrences;
|
|
1615
|
+
const mediaUsage = await cleanupMediaUsage(db);
|
|
1616
|
+
result.mediaUsage = mediaUsage.status === "failed" ? -1 : mediaUsage.deletedRows;
|
|
272
1617
|
} catch (error) {
|
|
273
|
-
console.error("[cleanup] Failed to clean media
|
|
1618
|
+
console.error("[cleanup] Failed to clean media usage:", error);
|
|
274
1619
|
}
|
|
275
1620
|
return result;
|
|
276
1621
|
}
|
|
@@ -469,6 +1814,15 @@ const DRAFT_ONLY_UPDATE_KEYS = new Set([
|
|
|
469
1814
|
"skipRevision"
|
|
470
1815
|
]);
|
|
471
1816
|
const MAX_DRAFT_STAGE_ATTEMPTS = 32;
|
|
1817
|
+
const LIST_COLUMN_FIELD_TYPES = new Set([
|
|
1818
|
+
"string",
|
|
1819
|
+
"number",
|
|
1820
|
+
"integer",
|
|
1821
|
+
"boolean",
|
|
1822
|
+
"datetime",
|
|
1823
|
+
"select",
|
|
1824
|
+
"multiSelect"
|
|
1825
|
+
]);
|
|
472
1826
|
/**
|
|
473
1827
|
* Convert a ContentItem to Record<string, unknown> for hook consumption.
|
|
474
1828
|
* Hooks receive the full item as a flat record.
|
|
@@ -552,6 +1906,49 @@ const marketplaceManifestCache = /* @__PURE__ */ new Map();
|
|
|
552
1906
|
/** Route metadata for sandboxed plugins: pluginId -> routeName -> RouteMeta */
|
|
553
1907
|
const sandboxedRouteMetaCache = /* @__PURE__ */ new Map();
|
|
554
1908
|
let sandboxRunner = null;
|
|
1909
|
+
const MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS = Object.freeze({
|
|
1910
|
+
entryWork: MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob,
|
|
1911
|
+
collectionDeletion: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick,
|
|
1912
|
+
reconciliation: MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick,
|
|
1913
|
+
maxClassQueries: Math.max(MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick, MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick),
|
|
1914
|
+
eventCeiling: 40
|
|
1915
|
+
});
|
|
1916
|
+
async function runScheduledMediaUsageLane(db) {
|
|
1917
|
+
if ((getRequestContext()?.metrics?.dbCount ?? 0) + 1 + MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.maxClassQueries > MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.eventCeiling) return {
|
|
1918
|
+
outcome: "admission_closed",
|
|
1919
|
+
taskClass: null,
|
|
1920
|
+
turn: null
|
|
1921
|
+
};
|
|
1922
|
+
const activation = await db.updateTable("_emdash_media_usage_activation").set({ media_usage_maintenance_turn: sql`(media_usage_maintenance_turn + 1) % 3` }).where("task_key", "=", "incremental_capture").where("state", "=", "active").returning("media_usage_maintenance_turn").executeTakeFirst();
|
|
1923
|
+
if (!activation) return {
|
|
1924
|
+
outcome: "inactive",
|
|
1925
|
+
taskClass: null,
|
|
1926
|
+
turn: null
|
|
1927
|
+
};
|
|
1928
|
+
const turn = activation.media_usage_maintenance_turn;
|
|
1929
|
+
if (turn === 0) {
|
|
1930
|
+
await processDueMediaUsageWork(db);
|
|
1931
|
+
return {
|
|
1932
|
+
outcome: "processed",
|
|
1933
|
+
taskClass: "entry_work",
|
|
1934
|
+
turn
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
if (turn === 1) {
|
|
1938
|
+
await processDueMediaUsageCollectionDeletions(db);
|
|
1939
|
+
return {
|
|
1940
|
+
outcome: "processed",
|
|
1941
|
+
taskClass: "collection_deletion",
|
|
1942
|
+
turn
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
await processDueMediaUsageReconciliation(db);
|
|
1946
|
+
return {
|
|
1947
|
+
outcome: "processed",
|
|
1948
|
+
taskClass: "reconciliation",
|
|
1949
|
+
turn
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
555
1952
|
/**
|
|
556
1953
|
* EmDashRuntime - singleton per worker
|
|
557
1954
|
*/
|
|
@@ -663,7 +2060,14 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
663
2060
|
* Returns the items promoted so callers can invalidate their cache tags.
|
|
664
2061
|
*/
|
|
665
2062
|
async publishScheduled() {
|
|
666
|
-
return
|
|
2063
|
+
return this.publishScheduledWithFence();
|
|
2064
|
+
}
|
|
2065
|
+
async publishScheduledWithFence(onPublished) {
|
|
2066
|
+
await assertMediaUsageActivationWriteAllowed(this.db);
|
|
2067
|
+
return publishDueContent(this.db, {
|
|
2068
|
+
publish: (collection, id, options) => this.handleContentPublish(collection, id, options),
|
|
2069
|
+
onPublished
|
|
2070
|
+
});
|
|
667
2071
|
}
|
|
668
2072
|
/**
|
|
669
2073
|
* Run the full scheduled-maintenance batch: cron tasks, scheduled
|
|
@@ -694,10 +2098,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
694
2098
|
}
|
|
695
2099
|
let published = [];
|
|
696
2100
|
try {
|
|
697
|
-
published = await
|
|
698
|
-
publish: (collection, id, opts) => this.handleContentPublish(collection, id, opts),
|
|
699
|
-
onPublished: options.onPublished
|
|
700
|
-
});
|
|
2101
|
+
published = await this.publishScheduledWithFence(options.onPublished);
|
|
701
2102
|
} catch (error) {
|
|
702
2103
|
console.error("[scheduled-publish] Sweep failed:", error);
|
|
703
2104
|
}
|
|
@@ -712,8 +2113,12 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
712
2113
|
console.error("[plugins] Storage index sync failed:", error);
|
|
713
2114
|
}
|
|
714
2115
|
await maybeRunScheduledBackup(this.db, this.storage ?? void 0);
|
|
2116
|
+
await recordSchedulerHeartbeatSafely(this.db);
|
|
715
2117
|
return { published };
|
|
716
2118
|
}
|
|
2119
|
+
async runScheduledMediaUsageTasks() {
|
|
2120
|
+
return runScheduledMediaUsageLane(this.db);
|
|
2121
|
+
}
|
|
717
2122
|
/**
|
|
718
2123
|
* Materialize plugin-declared storage indexes, once per process.
|
|
719
2124
|
*
|
|
@@ -1013,7 +2418,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1013
2418
|
});
|
|
1014
2419
|
}
|
|
1015
2420
|
};
|
|
1016
|
-
const db = await phase("rt.db", "DB init +
|
|
2421
|
+
const db = await phase("rt.db", "DB init + migration policy", () => EmDashRuntime.getDatabase(deps));
|
|
1017
2422
|
const resolveDb = () => {
|
|
1018
2423
|
return getRequestContext()?.db ?? db;
|
|
1019
2424
|
};
|
|
@@ -1032,6 +2437,14 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1032
2437
|
const ownsConfiguredDb = !!deps.config.database && !(reqCtx?.dbIsIsolated && reqCtx.db);
|
|
1033
2438
|
let readDb = db;
|
|
1034
2439
|
let readDbDisposable;
|
|
2440
|
+
const disposeReadDb = async () => {
|
|
2441
|
+
const disposable = readDbDisposable;
|
|
2442
|
+
readDbDisposable = void 0;
|
|
2443
|
+
if (!disposable) return;
|
|
2444
|
+
try {
|
|
2445
|
+
await disposable.destroy();
|
|
2446
|
+
} catch {}
|
|
2447
|
+
};
|
|
1035
2448
|
if (ownsConfiguredDb && deps.createCoalescingDialect && deps.config.database) try {
|
|
1036
2449
|
const dialect = deps.createCoalescingDialect(deps.config.database.config);
|
|
1037
2450
|
if (dialect) {
|
|
@@ -1045,6 +2458,10 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1045
2458
|
readDb = db;
|
|
1046
2459
|
}
|
|
1047
2460
|
const optionsRepo = new OptionsRepository(readDb);
|
|
2461
|
+
let missingManualSchemaError;
|
|
2462
|
+
const captureMissingManualSchema = (error) => {
|
|
2463
|
+
if ((deps.migrationMode ?? "auto") === "manual" && isMissingTableError(error)) missingManualSchemaError ??= error;
|
|
2464
|
+
};
|
|
1048
2465
|
const readSiteInfo = async () => {
|
|
1049
2466
|
const siteOpts = await optionsRepo.getMany([
|
|
1050
2467
|
"emdash:site_title",
|
|
@@ -1064,11 +2481,15 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1064
2481
|
try {
|
|
1065
2482
|
const states = await readDb.selectFrom("_plugin_state").select(["plugin_id", "status"]).execute();
|
|
1066
2483
|
pluginStates = new Map(states.map((s) => [s.plugin_id, s.status]));
|
|
1067
|
-
} catch {
|
|
2484
|
+
} catch (error) {
|
|
2485
|
+
captureMissingManualSchema(error);
|
|
2486
|
+
}
|
|
1068
2487
|
}), phase("rt.site", "Site info options", async () => {
|
|
1069
2488
|
try {
|
|
1070
2489
|
siteInfo = await readSiteInfo();
|
|
1071
|
-
} catch {
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
captureMissingManualSchema(error);
|
|
2492
|
+
}
|
|
1072
2493
|
})];
|
|
1073
2494
|
if (ownsConfiguredDb) coldStartReads.push(phase("rt.seedcheck", "Auto-seed gate", async () => {
|
|
1074
2495
|
try {
|
|
@@ -1084,9 +2505,15 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1084
2505
|
collectionCount: collectionCount.count,
|
|
1085
2506
|
setupDone
|
|
1086
2507
|
};
|
|
1087
|
-
} catch {
|
|
2508
|
+
} catch (error) {
|
|
2509
|
+
captureMissingManualSchema(error);
|
|
2510
|
+
}
|
|
1088
2511
|
}));
|
|
1089
2512
|
await Promise.all(coldStartReads);
|
|
2513
|
+
if (missingManualSchemaError) {
|
|
2514
|
+
await disposeReadDb();
|
|
2515
|
+
throw missingManualSchemaError;
|
|
2516
|
+
}
|
|
1090
2517
|
if (localeCasingRepairVersion && (configuredLocales.some((locale) => locale.includes("-") || locale !== locale.toLowerCase()) || storedLocaleCasingRepairVersion !== void 0) && storedLocaleCasingRepairVersion !== localeCasingRepairVersion) await phase("rt.locale", "Repair locale casing", async () => {
|
|
1091
2518
|
await repairLocaleCasing(db, configuredLocales);
|
|
1092
2519
|
await new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);
|
|
@@ -1096,9 +2523,9 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1096
2523
|
const seedHolder = getSeedHolder();
|
|
1097
2524
|
try {
|
|
1098
2525
|
await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
|
|
1099
|
-
const { applySeed } = await import("../apply-
|
|
1100
|
-
const { loadSeed } = await import("../load-
|
|
1101
|
-
const { validateSeed } = await import("../validate-
|
|
2526
|
+
const { applySeed } = await import("../apply-kUDt80-G.mjs").then((n) => n.n);
|
|
2527
|
+
const { loadSeed } = await import("../load-YJ2xl3ra.mjs").then((n) => n.r);
|
|
2528
|
+
const { validateSeed } = await import("../validate-BMLBvxOC.mjs").then((n) => n.n);
|
|
1102
2529
|
const seed = await loadSeed();
|
|
1103
2530
|
if (validateSeed(seed).valid) {
|
|
1104
2531
|
await applySeed(db, seed, { onConflict: "skip" });
|
|
@@ -1115,9 +2542,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1115
2542
|
} catch {}
|
|
1116
2543
|
} catch {}
|
|
1117
2544
|
}
|
|
1118
|
-
|
|
1119
|
-
await readDbDisposable.destroy();
|
|
1120
|
-
} catch {}
|
|
2545
|
+
await disposeReadDb();
|
|
1121
2546
|
const enabledPlugins = /* @__PURE__ */ new Set();
|
|
1122
2547
|
for (const plugin of deps.plugins) {
|
|
1123
2548
|
const status = pluginStates.get(plugin.id);
|
|
@@ -1179,6 +2604,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1179
2604
|
const pipelineFactoryOptions = {
|
|
1180
2605
|
db,
|
|
1181
2606
|
getDb: resolveDb,
|
|
2607
|
+
beforeContentWrite: () => assertMediaUsageActivationWriteAllowed(resolveDb()),
|
|
1182
2608
|
storage: storage ?? void 0,
|
|
1183
2609
|
siteInfo
|
|
1184
2610
|
};
|
|
@@ -1229,10 +2655,19 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1229
2655
|
if (deps.createScheduler) {
|
|
1230
2656
|
const scheduler = deps.createScheduler(cronExecutor);
|
|
1231
2657
|
cronScheduler = scheduler;
|
|
2658
|
+
const runMediaUsageMaintenance = async () => {
|
|
2659
|
+
const runtime = runtimeRef.current;
|
|
2660
|
+
if (runtime) await runtime.runScheduledMediaUsageTasks();
|
|
2661
|
+
else await runScheduledMediaUsageLane(db);
|
|
2662
|
+
};
|
|
1232
2663
|
scheduler.setSystemCleanup(async () => {
|
|
1233
2664
|
try {
|
|
1234
2665
|
const runtime = runtimeRef.current;
|
|
1235
|
-
|
|
2666
|
+
if (runtime) await runtime.publishScheduled();
|
|
2667
|
+
else {
|
|
2668
|
+
await assertMediaUsageActivationWriteAllowed(db);
|
|
2669
|
+
await publishDueContent(db);
|
|
2670
|
+
}
|
|
1236
2671
|
} catch (error) {
|
|
1237
2672
|
console.error("[scheduled-publish] Sweep failed:", error);
|
|
1238
2673
|
}
|
|
@@ -1247,7 +2682,14 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1247
2682
|
console.error("[plugins] Storage index sync failed:", error);
|
|
1248
2683
|
}
|
|
1249
2684
|
await maybeRunScheduledBackup(db, storage ?? void 0);
|
|
2685
|
+
await recordSchedulerHeartbeatSafely(db);
|
|
2686
|
+
if (!scheduler.setMediaUsageMaintenance) try {
|
|
2687
|
+
await runMediaUsageMaintenance();
|
|
2688
|
+
} catch (error) {
|
|
2689
|
+
console.error("[media-usage] Scheduled maintenance failed:", error);
|
|
2690
|
+
}
|
|
1250
2691
|
});
|
|
2692
|
+
scheduler.setMediaUsageMaintenance?.(runMediaUsageMaintenance);
|
|
1251
2693
|
scheduler.start();
|
|
1252
2694
|
}
|
|
1253
2695
|
} catch (error) {
|
|
@@ -1322,9 +2764,9 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1322
2764
|
log: kyselyLogOption()
|
|
1323
2765
|
});
|
|
1324
2766
|
try {
|
|
1325
|
-
await
|
|
2767
|
+
await enforceRuntimeMigrationPolicy(db, deps.migrationMode ?? "auto");
|
|
1326
2768
|
} catch (error) {
|
|
1327
|
-
if (!(error instanceof ConcurrentMigrationTimeoutError)) holder.failures.set(cacheKey, {
|
|
2769
|
+
if (!(error instanceof ConcurrentMigrationTimeoutError) && !(error instanceof PendingMigrationsError)) holder.failures.set(cacheKey, {
|
|
1328
2770
|
at: Date.now(),
|
|
1329
2771
|
message: error instanceof Error ? error.message : String(error)
|
|
1330
2772
|
});
|
|
@@ -1407,6 +2849,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1407
2849
|
if (!deps.sandboxEnabled) return sandboxedPluginCache;
|
|
1408
2850
|
if (!sandboxRunner && deps.createSandboxRunner) sandboxRunner = deps.createSandboxRunner(createSandboxRunnerOptions({
|
|
1409
2851
|
db,
|
|
2852
|
+
beforeContentWrite: () => assertMediaUsageActivationWriteAllowed(db),
|
|
1410
2853
|
mediaStorage: mediaStorage ? {
|
|
1411
2854
|
upload: (opts) => mediaStorage.upload({
|
|
1412
2855
|
key: opts.key,
|
|
@@ -1472,6 +2915,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1472
2915
|
static async loadInstalledSandboxedPlugins(source, db, storage, deps, cache, siteInfo) {
|
|
1473
2916
|
if (!sandboxRunner && deps.createSandboxRunner) sandboxRunner = deps.createSandboxRunner(createSandboxRunnerOptions({
|
|
1474
2917
|
db,
|
|
2918
|
+
beforeContentWrite: () => assertMediaUsageActivationWriteAllowed(db),
|
|
1475
2919
|
mediaStorage: {
|
|
1476
2920
|
upload: (opts) => storage.upload({
|
|
1477
2921
|
key: opts.key,
|
|
@@ -1673,13 +3117,33 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1673
3117
|
if ((field.type === "repeater" || field.type === "file" || field.type === "image") && field.validation) entry.validation = { ...field.validation };
|
|
1674
3118
|
fields[field.slug] = entry;
|
|
1675
3119
|
}
|
|
3120
|
+
const configuredListColumns = collection.admin?.listColumns ?? [];
|
|
3121
|
+
const fieldTypes = new Map(collection.fields.map((field) => [field.slug, field.type]));
|
|
3122
|
+
const listColumns = [];
|
|
3123
|
+
for (const slug of configuredListColumns) {
|
|
3124
|
+
if (listColumns.includes(slug)) continue;
|
|
3125
|
+
const fieldType = fieldTypes.get(slug);
|
|
3126
|
+
if (!fieldType || !LIST_COLUMN_FIELD_TYPES.has(fieldType)) {
|
|
3127
|
+
console.warn(`EmDash: Ignoring unsupported or unknown list column "${slug}" in collection "${collection.slug}".`);
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
if (listColumns.length >= MAX_COLLECTION_LIST_COLUMNS) {
|
|
3131
|
+
console.warn(`EmDash: Collection "${collection.slug}" declares more than ${MAX_COLLECTION_LIST_COLUMNS} list columns; extra columns are ignored.`);
|
|
3132
|
+
break;
|
|
3133
|
+
}
|
|
3134
|
+
listColumns.push(slug);
|
|
3135
|
+
}
|
|
1676
3136
|
manifestCollections[collection.slug] = {
|
|
1677
3137
|
label: collection.label,
|
|
1678
3138
|
labelSingular: collection.labelSingular || collection.label,
|
|
1679
3139
|
supports: collection.supports || [],
|
|
1680
3140
|
hasSeo: collection.hasSeo,
|
|
1681
3141
|
urlPattern: collection.urlPattern,
|
|
3142
|
+
routable: collection.routable !== false,
|
|
3143
|
+
titleField: collection.titleField,
|
|
3144
|
+
dateField: collection.dateField,
|
|
1682
3145
|
...collection.hidden ? { hidden: true } : {},
|
|
3146
|
+
listColumns: listColumns.length > 0 ? listColumns : void 0,
|
|
1683
3147
|
fields
|
|
1684
3148
|
};
|
|
1685
3149
|
}
|
|
@@ -1739,21 +3203,33 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1739
3203
|
};
|
|
1740
3204
|
}
|
|
1741
3205
|
let manifestTaxonomies = [];
|
|
3206
|
+
let taxonomyDefinitionLocales = [];
|
|
1742
3207
|
try {
|
|
1743
|
-
|
|
3208
|
+
const rows = await this.db.selectFrom("_emdash_taxonomy_defs").selectAll().orderBy("name").execute();
|
|
3209
|
+
taxonomyDefinitionLocales = rows.map((row) => row.locale);
|
|
3210
|
+
manifestTaxonomies = rows.map((row) => ({
|
|
3211
|
+
id: row.id,
|
|
1744
3212
|
name: row.name,
|
|
1745
3213
|
label: row.label,
|
|
1746
3214
|
labelSingular: row.label_singular ?? void 0,
|
|
1747
3215
|
hierarchical: row.hierarchical === 1,
|
|
1748
|
-
collections: parseStringArray(row.collections).toSorted()
|
|
3216
|
+
collections: parseStringArray(row.collections).toSorted(),
|
|
3217
|
+
locale: row.locale,
|
|
3218
|
+
translationGroup: row.translation_group ?? row.id
|
|
1749
3219
|
}));
|
|
1750
3220
|
} catch (error) {
|
|
1751
3221
|
console.debug("EmDash: Could not load taxonomy definitions:", error);
|
|
1752
3222
|
}
|
|
3223
|
+
try {
|
|
3224
|
+
const configuredLocales = virtualConfig?.i18n?.locales ?? getI18nConfig()?.locales ?? [];
|
|
3225
|
+
await warnAboutUnconfiguredTaxonomyLocales(this.db, configuredLocales, taxonomyDefinitionLocales);
|
|
3226
|
+
} catch (error) {
|
|
3227
|
+
console.warn("[i18n] taxonomy locale diagnostic failed:", error);
|
|
3228
|
+
}
|
|
1753
3229
|
const manifestHash = await hashString(JSON.stringify(manifestCollections) + JSON.stringify(manifestPlugins) + JSON.stringify(manifestTaxonomies));
|
|
1754
3230
|
const authMode = getAuthMode(this.config);
|
|
1755
3231
|
const authModeValue = authMode.type === "external" ? authMode.providerType : "passkey";
|
|
1756
|
-
const i18nConfig = virtualConfig?.i18n;
|
|
3232
|
+
const i18nConfig = virtualConfig?.i18n ?? getI18nConfig();
|
|
1757
3233
|
const i18n = i18nConfig && i18nConfig.locales && i18nConfig.locales.length > 1 ? {
|
|
1758
3234
|
defaultLocale: i18nConfig.defaultLocale,
|
|
1759
3235
|
locales: i18nConfig.locales
|
|
@@ -1769,6 +3245,10 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1769
3245
|
taxonomies: manifestTaxonomies,
|
|
1770
3246
|
authMode: authModeValue,
|
|
1771
3247
|
i18n,
|
|
3248
|
+
contentLocale: {
|
|
3249
|
+
defaultLocale: i18nConfig?.defaultLocale ?? "en",
|
|
3250
|
+
implicit: i18nConfig === null
|
|
3251
|
+
},
|
|
1772
3252
|
marketplace: !!this.config.marketplace,
|
|
1773
3253
|
registry
|
|
1774
3254
|
};
|
|
@@ -1874,7 +3354,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1874
3354
|
if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(body.data, collection, true)).content;
|
|
1875
3355
|
processedData = await this.runSandboxedBeforeSave(processedData, collection, true);
|
|
1876
3356
|
processedData = await this.normalizeMediaFields(collection, processedData);
|
|
1877
|
-
const { validateContentData } = await import("../validation-
|
|
3357
|
+
const { validateContentData } = await import("../validation-npvENUCB.mjs");
|
|
1878
3358
|
const validation = await validateContentData(this.db, collection, processedData, { partial: false });
|
|
1879
3359
|
if (!validation.ok) return {
|
|
1880
3360
|
success: false,
|
|
@@ -1917,7 +3397,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
1917
3397
|
if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(bodyWithoutRev.data, collection, false)).content;
|
|
1918
3398
|
processedData = await this.runSandboxedBeforeSave(processedData, collection, false);
|
|
1919
3399
|
processedData = await this.normalizeMediaFields(collection, processedData);
|
|
1920
|
-
const { validateContentData } = await import("../validation-
|
|
3400
|
+
const { validateContentData } = await import("../validation-npvENUCB.mjs");
|
|
1921
3401
|
const validation = await validateContentData(this.db, collection, processedData, { partial: true });
|
|
1922
3402
|
if (!validation.ok) return {
|
|
1923
3403
|
success: false,
|
|
@@ -2233,13 +3713,16 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2233
3713
|
}
|
|
2234
3714
|
async refreshContentUsageAfterSuccessfulWrite(collection, contentIds) {
|
|
2235
3715
|
for (const contentId of new Set(contentIds)) try {
|
|
3716
|
+
if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") return;
|
|
2236
3717
|
await refreshContentMediaUsageAfterWrite(this.db, collection, contentId);
|
|
2237
3718
|
} catch (error) {
|
|
2238
3719
|
console.error(`[media-usage] Failed after content write ${collection}/${contentId}:`, error);
|
|
3720
|
+
return;
|
|
2239
3721
|
}
|
|
2240
3722
|
}
|
|
2241
3723
|
async deleteContentUsageAfterSuccessfulPermanentDelete(collection, contentId) {
|
|
2242
3724
|
try {
|
|
3725
|
+
if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") return;
|
|
2243
3726
|
const result = await deleteContentMediaUsage(this.db, collection, contentId);
|
|
2244
3727
|
if (!result.success) console.error(`[media-usage] Usage delete for ${collection}/${contentId} finished with ${result.errorCode}`);
|
|
2245
3728
|
} catch (error) {
|
|
@@ -2285,7 +3768,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2285
3768
|
if (!meta) return null;
|
|
2286
3769
|
return meta.admin?.settingsSchema ?? {};
|
|
2287
3770
|
}
|
|
2288
|
-
async handlePluginApiRoute(pluginId, _method, path, request) {
|
|
3771
|
+
async handlePluginApiRoute(pluginId, _method, path, request, user) {
|
|
2289
3772
|
if (!this.isPluginEnabled(pluginId)) return {
|
|
2290
3773
|
success: false,
|
|
2291
3774
|
error: {
|
|
@@ -2293,6 +3776,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2293
3776
|
message: `Plugin not enabled: ${pluginId}`
|
|
2294
3777
|
}
|
|
2295
3778
|
};
|
|
3779
|
+
const caller = user ? toRouteCallerInfo(user) : void 0;
|
|
2296
3780
|
const trustedPlugin = this.configuredPlugins.find((p) => p.id === pluginId);
|
|
2297
3781
|
if (trustedPlugin && this.enabledPlugins.has(trustedPlugin.id)) {
|
|
2298
3782
|
const routeRegistry = new PluginRouteRegistry({
|
|
@@ -2306,11 +3790,12 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2306
3790
|
const body = await parseRouteInput(request);
|
|
2307
3791
|
return routeRegistry.invoke(pluginId, routeKey, {
|
|
2308
3792
|
request,
|
|
2309
|
-
body
|
|
3793
|
+
body,
|
|
3794
|
+
user: caller
|
|
2310
3795
|
});
|
|
2311
3796
|
}
|
|
2312
3797
|
const sandboxedPlugin = this.findSandboxedPlugin(pluginId);
|
|
2313
|
-
if (sandboxedPlugin) return this.handleSandboxedRoute(sandboxedPlugin, path, request);
|
|
3798
|
+
if (sandboxedPlugin) return this.handleSandboxedRoute(sandboxedPlugin, path, request, caller);
|
|
2314
3799
|
return {
|
|
2315
3800
|
success: false,
|
|
2316
3801
|
error: {
|
|
@@ -2385,7 +3870,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2385
3870
|
...tool.outputSchema ? { outputSchema: z.toJSONSchema(tool.outputSchema, { target: "draft-7" }) } : {}
|
|
2386
3871
|
})).toSorted((a, b) => a.name.localeCompare(b.name)));
|
|
2387
3872
|
}
|
|
2388
|
-
async handlePluginMcpTool(pluginId, toolName, route, input, actorId, request) {
|
|
3873
|
+
async handlePluginMcpTool(pluginId, toolName, route, input, actorId, request, caller) {
|
|
2389
3874
|
const requestMeta = extractRequestMeta(request, getTrustedProxyHeaders(this.config));
|
|
2390
3875
|
const audit = new AuditRepository(this.db);
|
|
2391
3876
|
const headers = new Headers(request.headers);
|
|
@@ -2396,7 +3881,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2396
3881
|
headers,
|
|
2397
3882
|
body: JSON.stringify(input)
|
|
2398
3883
|
});
|
|
2399
|
-
const result = await this.handlePluginApiRoute(pluginId, "POST", route, internalRequest);
|
|
3884
|
+
const result = await this.handlePluginApiRoute(pluginId, "POST", route, internalRequest, caller);
|
|
2400
3885
|
await audit.log({
|
|
2401
3886
|
actorId,
|
|
2402
3887
|
actorIp: requestMeta.ip ?? void 0,
|
|
@@ -2625,7 +4110,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2625
4110
|
runAfterUnscheduleHooks(content, collection) {
|
|
2626
4111
|
this.runDeferredContentHook("content:afterUnschedule", content, collection);
|
|
2627
4112
|
}
|
|
2628
|
-
async handleSandboxedRoute(plugin, path, request) {
|
|
4113
|
+
async handleSandboxedRoute(plugin, path, request, user) {
|
|
2629
4114
|
const routeName = path.replace(LEADING_SLASH_PATTERN, "");
|
|
2630
4115
|
const body = await parseRouteInput(request);
|
|
2631
4116
|
try {
|
|
@@ -2637,11 +4122,21 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2637
4122
|
url: request.url,
|
|
2638
4123
|
method: request.method,
|
|
2639
4124
|
headers,
|
|
2640
|
-
meta
|
|
4125
|
+
meta,
|
|
4126
|
+
user
|
|
2641
4127
|
})
|
|
2642
4128
|
};
|
|
2643
4129
|
} catch (error) {
|
|
2644
4130
|
console.error(`EmDash: Sandboxed plugin route error:`, error);
|
|
4131
|
+
const sandboxRouteError = getSandboxRouteErrorDetails(error);
|
|
4132
|
+
if (sandboxRouteError) return {
|
|
4133
|
+
success: false,
|
|
4134
|
+
status: sandboxRouteError.status,
|
|
4135
|
+
error: {
|
|
4136
|
+
code: sandboxRouteError.code,
|
|
4137
|
+
message: sandboxRouteError.message
|
|
4138
|
+
}
|
|
4139
|
+
};
|
|
2645
4140
|
return {
|
|
2646
4141
|
success: false,
|
|
2647
4142
|
error: {
|
|
@@ -2760,6 +4255,21 @@ function createPublicMediaUrlResolver(storage) {
|
|
|
2760
4255
|
* middleware.ts.
|
|
2761
4256
|
*/
|
|
2762
4257
|
const ASTRO_COOKIES_SYMBOL = Symbol.for("astro.cookies");
|
|
4258
|
+
/**
|
|
4259
|
+
* Whether the request ended authenticated, for the db adapter's `commit()`.
|
|
4260
|
+
* `isAuthenticated` is captured before the route runs, so it misses
|
|
4261
|
+
* login/signup/invite requests that create the Astro session mid-request;
|
|
4262
|
+
* `session.set()` writes the `astro-session` cookie into the jar synchronously,
|
|
4263
|
+
* so scanning the outgoing cookies detects them. Only outgoing cookies count —
|
|
4264
|
+
* a stale `astro-session` cookie arriving on an anonymous render must not
|
|
4265
|
+
* qualify, or its replica-read bookmark would overwrite a fresher one from an
|
|
4266
|
+
* earlier authenticated write.
|
|
4267
|
+
*/
|
|
4268
|
+
function requestEndedAuthenticated(isAuthenticated, cookies) {
|
|
4269
|
+
if (isAuthenticated) return true;
|
|
4270
|
+
for (const header of cookies.headers()) if (header.startsWith("astro-session=")) return true;
|
|
4271
|
+
return false;
|
|
4272
|
+
}
|
|
2763
4273
|
/** Hold the real adapter close behind both response and deferred-task completion. */
|
|
2764
4274
|
function coordinateScopedDbLifecycle(scoped) {
|
|
2765
4275
|
if (!scoped.close) return { lifecycle: scoped };
|
|
@@ -3021,12 +4531,6 @@ function createPublicPluginApiRouteHandler(runtime) {
|
|
|
3021
4531
|
//#endregion
|
|
3022
4532
|
//#region src/astro/middleware.ts
|
|
3023
4533
|
/**
|
|
3024
|
-
* EmDash middleware
|
|
3025
|
-
*
|
|
3026
|
-
* Thin wrapper that initializes EmDashRuntime and attaches it to locals.
|
|
3027
|
-
* All heavy lifting happens in EmDashRuntime.
|
|
3028
|
-
*/
|
|
3029
|
-
/**
|
|
3030
4534
|
* Runtime init lock reclaim deadline. Must be strictly larger than the db
|
|
3031
4535
|
* init deadline: this lock wraps EmDashRuntime.create() → getDatabase() →
|
|
3032
4536
|
* the db init lock, and equal deadlines would let this outer lock reclaim
|
|
@@ -3112,10 +4616,11 @@ function getPlugins() {
|
|
|
3112
4616
|
/**
|
|
3113
4617
|
* Build runtime dependencies from virtual modules
|
|
3114
4618
|
*/
|
|
3115
|
-
function buildDependencies(config) {
|
|
4619
|
+
function buildDependencies(config, migrationMode) {
|
|
3116
4620
|
const sandboxModule = virtualSandboxRunnerModule;
|
|
3117
4621
|
return {
|
|
3118
4622
|
config,
|
|
4623
|
+
migrationMode,
|
|
3119
4624
|
plugins: getPlugins(),
|
|
3120
4625
|
createDialect,
|
|
3121
4626
|
createCoalescingDialect,
|
|
@@ -3136,10 +4641,10 @@ function buildDependencies(config) {
|
|
|
3136
4641
|
* cached instance) push nothing — callers should treat an empty array
|
|
3137
4642
|
* as "warm, nothing to report".
|
|
3138
4643
|
*/
|
|
3139
|
-
async function getRuntime(config, initTimings) {
|
|
4644
|
+
async function getRuntime(config, migrationMode, initTimings) {
|
|
3140
4645
|
const holder = getRuntimeHolder();
|
|
3141
4646
|
return initWithLock(holder.lock, () => holder.instance, async (isCurrentClaim) => {
|
|
3142
|
-
const deps = buildDependencies(config);
|
|
4647
|
+
const deps = buildDependencies(config, migrationMode);
|
|
3143
4648
|
const runtime = await EmDashRuntime.create(deps, initTimings);
|
|
3144
4649
|
if (isCurrentClaim()) holder.instance = runtime;
|
|
3145
4650
|
else runtime.stopCron().catch((error) => {
|
|
@@ -3170,6 +4675,15 @@ async function runScheduledTasks(options = {}) {
|
|
|
3170
4675
|
if (!config) return { published: [] };
|
|
3171
4676
|
return runOutsideRequest(config, (runtime) => runtime.runScheduledTasks(options));
|
|
3172
4677
|
}
|
|
4678
|
+
async function runScheduledMediaUsageTasks() {
|
|
4679
|
+
const config = getConfig();
|
|
4680
|
+
if (!config) return {
|
|
4681
|
+
outcome: "inactive",
|
|
4682
|
+
taskClass: null,
|
|
4683
|
+
turn: null
|
|
4684
|
+
};
|
|
4685
|
+
return runOutsideRequest(config, (runtime) => runtime.runScheduledMediaUsageTasks());
|
|
4686
|
+
}
|
|
3173
4687
|
/**
|
|
3174
4688
|
* Run a callback against the EmDash runtime outside any HTTP request — from a
|
|
3175
4689
|
* Cloudflare Queue consumer, a `scheduled()` handler, or any other
|
|
@@ -3213,19 +4727,37 @@ async function withEmDashRuntime(run) {
|
|
|
3213
4727
|
/**
|
|
3214
4728
|
* Shared plumbing for request-free entry points (`runScheduledTasks`,
|
|
3215
4729
|
* `withEmDashRuntime`): resolve the runtime singleton, then run the callback
|
|
3216
|
-
* under an event-scoped db
|
|
4730
|
+
* under an event-scoped db when the adapter needs one.
|
|
3217
4731
|
*
|
|
3218
4732
|
* Connection-backed adapters (e.g. Postgres over Hyperdrive) cannot reuse
|
|
3219
4733
|
* the per-isolate singleton from a platform event: its socket belongs to the
|
|
3220
4734
|
* request that opened it, and workerd rejects cross-event I/O. Open an
|
|
3221
4735
|
* event-scoped connection and run the callback under it in ALS — the
|
|
3222
4736
|
* runtime's db getter, the cron executor, and plugin contexts all resolve
|
|
3223
|
-
* the connection from ALS — then close it
|
|
3224
|
-
*
|
|
3225
|
-
*
|
|
4737
|
+
* the connection from ALS — then close it when required. Stateless adapters
|
|
4738
|
+
* that need primary routing can return a close-less scope; adapters with no
|
|
4739
|
+
* event scoping return null and keep using the singleton.
|
|
3226
4740
|
*/
|
|
3227
4741
|
async function runOutsideRequest(config, fn) {
|
|
3228
|
-
const
|
|
4742
|
+
const migrationMode = resolveConfiguredMigrationMode(config);
|
|
4743
|
+
if (getRequestContext()) return runOutsideRequestWithRuntime(config, await getRuntime(config, migrationMode), fn);
|
|
4744
|
+
const deferredTasks = createDeferredTaskTracker(() => {});
|
|
4745
|
+
return runWithContext({
|
|
4746
|
+
editMode: false,
|
|
4747
|
+
metrics: createRequestMetrics(performance.now()),
|
|
4748
|
+
deferredTasks
|
|
4749
|
+
}, async () => {
|
|
4750
|
+
return runOutsideRequestWithRuntime(config, await (async () => {
|
|
4751
|
+
try {
|
|
4752
|
+
return await getRuntime(config, migrationMode);
|
|
4753
|
+
} finally {
|
|
4754
|
+
deferredTasks.settle();
|
|
4755
|
+
await deferredTasks.settled;
|
|
4756
|
+
}
|
|
4757
|
+
})(), fn);
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4760
|
+
async function runOutsideRequestWithRuntime(config, runtime, fn) {
|
|
3229
4761
|
const scoped = createRequestScopedDb$1({
|
|
3230
4762
|
config: config.database?.config,
|
|
3231
4763
|
isAuthenticated: false,
|
|
@@ -3234,7 +4766,7 @@ async function runOutsideRequest(config, fn) {
|
|
|
3234
4766
|
cookies: NOOP_COOKIE_JAR,
|
|
3235
4767
|
url: CRON_EVENT_URL
|
|
3236
4768
|
});
|
|
3237
|
-
if (!scoped
|
|
4769
|
+
if (!scoped) return fn(runtime);
|
|
3238
4770
|
const { closed, deferredTasks, lifecycle } = coordinateScopedDbLifecycle(scoped);
|
|
3239
4771
|
const parent = getRequestContext();
|
|
3240
4772
|
const ctx = parent ? {
|
|
@@ -3280,6 +4812,24 @@ const NOOP_COOKIE_JAR = {
|
|
|
3280
4812
|
* that path, so the value is never used — it exists to satisfy the contract.
|
|
3281
4813
|
*/
|
|
3282
4814
|
const CRON_EVENT_URL = new URL("https://cron.emdash.internal/");
|
|
4815
|
+
function resolveConfiguredMigrationMode(config) {
|
|
4816
|
+
const processOverride = typeof process !== "undefined" && process.env ? process.env.EMDASH_MIGRATIONS_MODE : void 0;
|
|
4817
|
+
const importMetaOverride = import.meta.env.EMDASH_MIGRATIONS_MODE;
|
|
4818
|
+
return resolveRuntimeMigrationMode(config.migrations, {
|
|
4819
|
+
dev: import.meta.env.DEV,
|
|
4820
|
+
override: processOverride ?? importMetaOverride
|
|
4821
|
+
});
|
|
4822
|
+
}
|
|
4823
|
+
function pendingMigrationsResponse(error) {
|
|
4824
|
+
console.error("[emdash] database migrations are pending:", error.pending.join(", "));
|
|
4825
|
+
return migrationRequiredResponse();
|
|
4826
|
+
}
|
|
4827
|
+
function migrationRequiredResponse() {
|
|
4828
|
+
return new Response("Database migrations are required. Apply the deployment migration manifest and retry.", {
|
|
4829
|
+
status: 503,
|
|
4830
|
+
headers: { "Retry-After": "60" }
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
3283
4833
|
/**
|
|
3284
4834
|
* Baseline security headers applied to all responses.
|
|
3285
4835
|
* Admin routes get additional headers (strict CSP) from auth middleware.
|
|
@@ -3359,15 +4909,44 @@ function createRequestScopedDb$1(opts) {
|
|
|
3359
4909
|
if (typeof createRequestScopedDb !== "function") return null;
|
|
3360
4910
|
return createRequestScopedDb(opts);
|
|
3361
4911
|
}
|
|
4912
|
+
const buildDate = buildTime ? new Date(buildTime) : null;
|
|
4913
|
+
/**
|
|
4914
|
+
* Fold the build timestamp into the route cache validator.
|
|
4915
|
+
*
|
|
4916
|
+
* `CacheHint.lastModified` describes the content, but the response also depends
|
|
4917
|
+
* on the build: `/_astro/*` names are content-hashed, and a deployment only
|
|
4918
|
+
* serves its own. Without the build dimension a code-only deploy answers a
|
|
4919
|
+
* returning visitor's conditional request with 304, leaving them on HTML whose
|
|
4920
|
+
* assets 404.
|
|
4921
|
+
*
|
|
4922
|
+
* Prerendered pages are served by the host's static layer, which manages its
|
|
4923
|
+
* own validators — only on-demand responses need the build dimension.
|
|
4924
|
+
*
|
|
4925
|
+
* Only forward moves are covered. `Last-Modified` expresses newer, not
|
|
4926
|
+
* different, so after a rollback the earlier build still answers a conditional
|
|
4927
|
+
* request with 304 and the browser stays on the newer build's HTML.
|
|
4928
|
+
*
|
|
4929
|
+
* Must run before next(): Astro keeps the later of two dates, so a route's own
|
|
4930
|
+
* hint still wins when content is newer, and a route that opts out with
|
|
4931
|
+
* `Astro.cache.set(false)` stays opted out — calling set() afterwards would
|
|
4932
|
+
* clear that opt-out.
|
|
4933
|
+
*/
|
|
4934
|
+
function applyBuildValidator(context) {
|
|
4935
|
+
if (context.isPrerendered || !buildDate || !context.cache?.enabled) return;
|
|
4936
|
+
context.cache.set({ lastModified: buildDate });
|
|
4937
|
+
}
|
|
3362
4938
|
const onRequest = defineMiddleware(async (context, next) => {
|
|
3363
4939
|
const { request, locals, cookies } = context;
|
|
3364
4940
|
const url = context.url;
|
|
3365
4941
|
if (!url.pathname.startsWith("/_emdash") && virtualConfig?.authProviders) {
|
|
3366
4942
|
if (virtualConfig.authProviders.some((p) => p.routes?.some((r) => r.pattern && url.pathname === r.pattern))) return finalizeResponse(await next());
|
|
3367
4943
|
}
|
|
4944
|
+
applyBuildValidator(context);
|
|
3368
4945
|
const queryRecorder = isInstrumentationEnabled() ? createRecorder(url.pathname, request.method, request.headers.get("x-perf-phase") ?? "default") : void 0;
|
|
3369
4946
|
const metrics = createRequestMetrics(performance.now());
|
|
3370
4947
|
const run = async () => {
|
|
4948
|
+
const config = getConfig();
|
|
4949
|
+
const migrationMode = config ? resolveConfiguredMigrationMode(config) : "auto";
|
|
3371
4950
|
const isEmDashRoute = url.pathname.startsWith("/_emdash");
|
|
3372
4951
|
const isPublicRuntimeRoute = PUBLIC_RUNTIME_ROUTES.has(url.pathname) || SITEMAP_COLLECTION_RE.test(url.pathname);
|
|
3373
4952
|
const hasEditCookie = cookies.get("emdash-edit-mode")?.value === "true";
|
|
@@ -3378,15 +4957,16 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3378
4957
|
const hasBearerAuth = (request.headers.get("authorization") ?? "").toLowerCase().startsWith("bearer ");
|
|
3379
4958
|
const isWrite = request.method !== "GET" && request.method !== "HEAD";
|
|
3380
4959
|
const isAuthenticated = !!sessionUser || hasBearerAuth;
|
|
4960
|
+
const endedAuthenticated = () => requestEndedAuthenticated(isAuthenticated, cookies);
|
|
3381
4961
|
const canUseCachedBinding = !isAuthenticated && !isWrite && !playgroundDb && !isEmDashRoute && !hasEditCookie && !hasPreviewToken;
|
|
3382
4962
|
if (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) {
|
|
3383
4963
|
if (!sessionUser && !playgroundDb) {
|
|
3384
4964
|
const timings = [];
|
|
3385
4965
|
const mwStart = performance.now();
|
|
3386
|
-
if (!isSetupVerified() && !context.isPrerendered) {
|
|
4966
|
+
if (migrationMode === "auto" && !isSetupVerified() && !context.isPrerendered) {
|
|
3387
4967
|
const t0 = performance.now();
|
|
3388
4968
|
try {
|
|
3389
|
-
const { getDb } = await import("../loader-
|
|
4969
|
+
const { getDb } = await import("../loader-CdG_Ci3H.mjs").then((n) => n.c);
|
|
3390
4970
|
await (await getDb()).selectFrom("_emdash_migrations").selectAll().limit(1).execute();
|
|
3391
4971
|
markSetupVerified();
|
|
3392
4972
|
} catch (error) {
|
|
@@ -3399,12 +4979,11 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3399
4979
|
desc: "Setup probe"
|
|
3400
4980
|
});
|
|
3401
4981
|
}
|
|
3402
|
-
const config = getConfig();
|
|
3403
4982
|
if (config) {
|
|
3404
4983
|
const initSubTimings = [];
|
|
3405
4984
|
const t0 = performance.now();
|
|
3406
4985
|
try {
|
|
3407
|
-
const runtime = await getRuntime(config, initSubTimings);
|
|
4986
|
+
const runtime = await getRuntime(config, migrationMode, initSubTimings);
|
|
3408
4987
|
markSetupVerified();
|
|
3409
4988
|
locals.emdash = {
|
|
3410
4989
|
handlePublicPluginApiRoute: createPublicPluginApiRouteHandler(runtime),
|
|
@@ -3414,6 +4993,11 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3414
4993
|
storage: runtime.storage
|
|
3415
4994
|
};
|
|
3416
4995
|
} catch (error) {
|
|
4996
|
+
if (error instanceof PendingMigrationsError) return pendingMigrationsResponse(error);
|
|
4997
|
+
if (migrationMode === "manual" && isMissingTableError(error)) {
|
|
4998
|
+
console.error("[emdash] database schema is unavailable in manual migration mode:", error);
|
|
4999
|
+
return migrationRequiredResponse();
|
|
5000
|
+
}
|
|
3417
5001
|
if (Date.now() - lastRuntimeInitErrorLogAt >= RUNTIME_INIT_ERROR_LOG_INTERVAL_MS) {
|
|
3418
5002
|
lastRuntimeInitErrorLogAt = Date.now();
|
|
3419
5003
|
console.error("[emdash] runtime init failed (page renders without CMS data):", error);
|
|
@@ -3430,6 +5014,7 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3430
5014
|
const anonScoped = createRequestScopedDb$1({
|
|
3431
5015
|
config: config?.database?.config,
|
|
3432
5016
|
isAuthenticated,
|
|
5017
|
+
endedAuthenticated,
|
|
3433
5018
|
isWrite,
|
|
3434
5019
|
canUseCachedBinding,
|
|
3435
5020
|
cookies,
|
|
@@ -3474,7 +5059,6 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3474
5059
|
return runAnon();
|
|
3475
5060
|
}
|
|
3476
5061
|
}
|
|
3477
|
-
const config = getConfig();
|
|
3478
5062
|
if (!config) {
|
|
3479
5063
|
console.error("EmDash: No configuration found");
|
|
3480
5064
|
return finalizeResponse(await next());
|
|
@@ -3485,7 +5069,7 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3485
5069
|
try {
|
|
3486
5070
|
const initSubTimings = [];
|
|
3487
5071
|
let t0 = performance.now();
|
|
3488
|
-
const runtime = await getRuntime(config, initSubTimings);
|
|
5072
|
+
const runtime = await getRuntime(config, migrationMode, initSubTimings);
|
|
3489
5073
|
timings.push({
|
|
3490
5074
|
name: "rt",
|
|
3491
5075
|
dur: performance.now() - t0,
|
|
@@ -3554,12 +5138,18 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3554
5138
|
setPluginStatus: runtime.setPluginStatus.bind(runtime)
|
|
3555
5139
|
};
|
|
3556
5140
|
} catch (error) {
|
|
5141
|
+
if (error instanceof PendingMigrationsError) return pendingMigrationsResponse(error);
|
|
5142
|
+
if (migrationMode === "manual" && isMissingTableError(error)) {
|
|
5143
|
+
console.error("[emdash] database schema is unavailable in manual migration mode:", error);
|
|
5144
|
+
return migrationRequiredResponse();
|
|
5145
|
+
}
|
|
3557
5146
|
console.error("EmDash middleware error:", error);
|
|
3558
5147
|
}
|
|
3559
5148
|
const lastContentWriteAt = canUseCachedBinding && config?.database?.needsLastContentWriteAt ? await getLastContentWriteAt() : void 0;
|
|
3560
5149
|
const scoped = createRequestScopedDb$1({
|
|
3561
5150
|
config: config?.database?.config,
|
|
3562
5151
|
isAuthenticated,
|
|
5152
|
+
endedAuthenticated,
|
|
3563
5153
|
isWrite,
|
|
3564
5154
|
canUseCachedBinding,
|
|
3565
5155
|
cookies: context.cookies,
|
|
@@ -3627,5 +5217,5 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
3627
5217
|
});
|
|
3628
5218
|
|
|
3629
5219
|
//#endregion
|
|
3630
|
-
export { onRequest as default, onRequest, runScheduledTasks, withEmDashRuntime };
|
|
5220
|
+
export { onRequest as default, onRequest, runScheduledMediaUsageTasks, runScheduledTasks, withEmDashRuntime };
|
|
3631
5221
|
//# sourceMappingURL=middleware.mjs.map
|