emdash 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activation-K0kGBWuJ.mjs +382 -0
- package/dist/activation-K0kGBWuJ.mjs.map +1 -0
- package/dist/{adapters-Bb1vG41D.d.mts → adapters-BDQwg236.d.mts} +6 -4
- package/dist/{adapters-Bb1vG41D.d.mts.map → adapters-BDQwg236.d.mts.map} +1 -1
- package/dist/{allowed-origins-DW7sYyqg.mjs → allowed-origins-BGglilcs.mjs} +2 -2
- package/dist/{allowed-origins-DW7sYyqg.mjs.map → allowed-origins-BGglilcs.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +23 -21
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -5
- package/dist/api-Dz6rgoa3.mjs +10040 -0
- package/dist/api-Dz6rgoa3.mjs.map +1 -0
- package/dist/{api-tokens-KO24G1Fd.mjs → api-tokens-BSsQACNe.mjs} +2 -2
- package/dist/{api-tokens-KO24G1Fd.mjs.map → api-tokens-BSsQACNe.mjs.map} +1 -1
- package/dist/{apply-kUDt80-G.mjs → apply-B4SIOxR1.mjs} +34 -35
- package/dist/{apply-kUDt80-G.mjs.map → apply-B4SIOxR1.mjs.map} +1 -1
- package/dist/astro/image-endpoint.mjs +2 -2
- package/dist/astro/image-endpoint.mjs.map +1 -1
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +128 -44
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +10 -7
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware/request-context.mjs +3 -3
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +11 -12
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +237 -1412
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/index.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +16 -16
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +16 -16
- package/dist/astro/routes/api/admin/bylines/index.mjs +16 -16
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -13
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/media-usage/activation.d.mts +9 -0
- package/dist/astro/routes/api/admin/media-usage/activation.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/activation.mjs +46 -0
- package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/progress.d.mts +9 -0
- package/dist/astro/routes/api/admin/media-usage/progress.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/progress.mjs +44 -0
- package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -0
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +16 -11
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +14 -13
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +55 -49
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +55 -49
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +54 -48
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +54 -48
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +54 -48
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +54 -48
- 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 +60 -50
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +179 -139
- 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 +61 -51
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts +8 -0
- package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -0
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +93 -0
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -0
- package/dist/astro/routes/api/admin/plugins/updates.mjs +53 -47
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +53 -47
- 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 +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +53 -47
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
- package/dist/astro/routes/api/admin/users/index.mjs +6 -6
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
- package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
- package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
- package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
- package/dist/astro/routes/api/auth/me.mjs +19 -9
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +2 -2
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
- package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
- package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
- package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -13
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +14 -5
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +13 -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 +17 -15
- 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 +17 -15
- 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 +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +14 -14
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +14 -5
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
- package/dist/astro/routes/api/dashboard.mjs +8 -8
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +17 -17
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +13 -13
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -10
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +17 -17
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +27 -27
- package/dist/astro/routes/api/manifest.mjs +4 -4
- package/dist/astro/routes/api/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +188 -65
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
- package/dist/astro/routes/api/media/_id_/replace.d.mts +8 -0
- package/dist/astro/routes/api/media/_id_/replace.d.mts.map +1 -0
- package/dist/astro/routes/api/media/_id_/replace.mjs +91 -0
- package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -0
- package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
- package/dist/astro/routes/api/media/_id_/usage.mjs +18 -13
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +21 -13
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +3 -2
- package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
- package/dist/astro/routes/api/media/folders/_id_.d.mts +10 -0
- package/dist/astro/routes/api/media/folders/_id_.d.mts.map +1 -0
- package/dist/astro/routes/api/media/folders/_id_.mjs +52 -0
- package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -0
- package/dist/astro/routes/api/media/folders/index.d.mts +9 -0
- package/dist/astro/routes/api/media/folders/index.d.mts.map +1 -0
- package/dist/astro/routes/api/media/folders/index.mjs +41 -0
- package/dist/astro/routes/api/media/folders/index.mjs.map +1 -0
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +16 -14
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.d.mts.map +1 -1
- package/dist/astro/routes/api/media.mjs +59 -27
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +20 -13
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +15 -11
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -11
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +16 -12
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +21 -14
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.d.mts.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +15 -11
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
- package/dist/astro/routes/api/oauth/token.mjs +5 -5
- package/dist/astro/routes/api/openapi.json.mjs +253 -40
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
- package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
- package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
- package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
- package/dist/astro/routes/api/redirects/index.mjs +11 -11
- package/dist/astro/routes/api/relations/_id_/index.mjs +17 -15
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +12 -10
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +17 -15
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +53 -47
- 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 +53 -47
- 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 +53 -47
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +53 -47
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +53 -47
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +53 -47
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +10 -8
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +53 -47
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +53 -47
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +10 -10
- package/dist/astro/routes/api/search/index.mjs +9 -9
- package/dist/astro/routes/api/search/rebuild.mjs +10 -10
- package/dist/astro/routes/api/search/stats.mjs +5 -5
- package/dist/astro/routes/api/search/suggest.mjs +9 -9
- package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
- package/dist/astro/routes/api/sections/index.mjs +9 -9
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +5 -5
- package/dist/astro/routes/api/settings/backups/export.mjs +6 -6
- package/dist/astro/routes/api/settings/backups/index.mjs +7 -7
- package/dist/astro/routes/api/settings/email.mjs +5 -5
- package/dist/astro/routes/api/settings/email.mjs.map +1 -1
- package/dist/astro/routes/api/settings.d.mts.map +1 -1
- package/dist/astro/routes/api/settings.mjs +20 -16
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
- package/dist/astro/routes/api/setup/admin.mjs +11 -11
- package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -28
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +32 -30
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +7 -7
- package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +20 -16
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +21 -17
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -18
- 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 +20 -16
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +14 -14
- package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_.mjs +26 -19
- package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +20 -16
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -8
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +17 -13
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +15 -12
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -6
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.d.mts.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +15 -12
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +7 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
- package/dist/astro/routes/sitemap.xml.mjs +8 -8
- package/dist/astro/types.d.mts +37 -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-DUElxgOQ.mjs → authorize-Bkf_UHhD.mjs} +2 -2
- package/dist/{authorize-DUElxgOQ.mjs.map → authorize-Bkf_UHhD.mjs.map} +1 -1
- package/dist/{backup-DWYpJlLj.mjs → backup-rwVAl9NI.mjs} +5 -5
- package/dist/{backup-DWYpJlLj.mjs.map → backup-rwVAl9NI.mjs.map} +1 -1
- package/dist/{bundle-vn56sLos.mjs → bundle-C8S2Zl6U.mjs} +4 -4
- package/dist/{bundle-vn56sLos.mjs.map → bundle-C8S2Zl6U.mjs.map} +1 -1
- package/dist/{byline-yjvWq4ip.mjs → byline-DuhF7NWl.mjs} +27 -14
- package/dist/byline-DuhF7NWl.mjs.map +1 -0
- package/dist/{byline-fields-txaLGoME.mjs → byline-fields-BovY4KYM.mjs} +2 -2
- package/dist/{byline-fields-txaLGoME.mjs.map → byline-fields-BovY4KYM.mjs.map} +1 -1
- package/dist/{byline-registry-DDun8sxh.mjs → byline-registry-Bpu9-MVs.mjs} +2 -2
- package/dist/{byline-registry-DDun8sxh.mjs.map → byline-registry-Bpu9-MVs.mjs.map} +1 -1
- package/dist/{bylines-DjXS5p0P.mjs → bylines-CJ5nd6Lw.mjs} +7 -7
- package/dist/{bylines-DjXS5p0P.mjs.map → bylines-CJ5nd6Lw.mjs.map} +1 -1
- package/dist/{bylines-Bu7pchQ4.mjs → bylines-RwBAeOF1.mjs} +3 -3
- package/dist/{bylines-Bu7pchQ4.mjs.map → bylines-RwBAeOF1.mjs.map} +1 -1
- package/dist/{cache-C46-3Irl.mjs → cache-BF5Kvn59.mjs} +4 -4
- package/dist/{cache-C46-3Irl.mjs.map → cache-BF5Kvn59.mjs.map} +1 -1
- package/dist/capture-triggers-CNW1q3HK.mjs +362 -0
- package/dist/capture-triggers-CNW1q3HK.mjs.map +1 -0
- package/dist/{challenge-store-SIL5DsF2.mjs → challenge-store-DQN_Yrck.mjs} +5 -3
- package/dist/challenge-store-DQN_Yrck.mjs.map +1 -0
- package/dist/chrome-tags-D8fiZG94.mjs +24 -0
- package/dist/chrome-tags-D8fiZG94.mjs.map +1 -0
- package/dist/{chunks-fiZ8G9RJ.mjs → chunks-C4szrD8o.mjs} +2 -2
- package/dist/{chunks-fiZ8G9RJ.mjs.map → chunks-C4szrD8o.mjs.map} +1 -1
- package/dist/cli/index.mjs +37 -53
- 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 +54 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/{collection-deletion-DQPvMD8R.mjs → collection-deletion-Bj07NHbj.mjs} +13 -356
- package/dist/collection-deletion-Bj07NHbj.mjs.map +1 -0
- package/dist/{comment-reaction-D-2J84UE.mjs → comment-reaction-BhrvSHml.mjs} +2 -2
- package/dist/{comment-reaction-D-2J84UE.mjs.map → comment-reaction-BhrvSHml.mjs.map} +1 -1
- package/dist/{comment-Dxlep6cE.mjs → comment-vxOzWbZN.mjs} +2 -2
- package/dist/{comment-Dxlep6cE.mjs.map → comment-vxOzWbZN.mjs.map} +1 -1
- package/dist/{comments-CTfZtLmM.mjs → comments-Ct4ensnf.mjs} +2 -2
- package/dist/{comments-CTfZtLmM.mjs.map → comments-Ct4ensnf.mjs.map} +1 -1
- package/dist/{components-CQy3lh9x.mjs → components-DJbo1z0S.mjs} +1 -1
- package/dist/{components-CQy3lh9x.mjs.map → components-DJbo1z0S.mjs.map} +1 -1
- package/dist/{config-COQzTe5Q.d.mts → config-B9eZ340f.d.mts} +1 -1
- package/dist/{config-COQzTe5Q.d.mts.map → config-B9eZ340f.d.mts.map} +1 -1
- package/dist/config-it29yDLA.mjs +1163 -0
- package/dist/config-it29yDLA.mjs.map +1 -0
- package/dist/{content-ZHCc1ngN.mjs → content-DjvM0T92.mjs} +86 -45
- package/dist/content-DjvM0T92.mjs.map +1 -0
- package/dist/{content-list-query-wJ2WN8Vp.d.mts → content-list-query-Btqd4uV2.d.mts} +1 -1
- package/dist/{content-list-query-wJ2WN8Vp.d.mts.map → content-list-query-Btqd4uV2.d.mts.map} +1 -1
- package/dist/{content-refresh-B_8SJzuU.mjs → content-refresh-Bwp-8yGy.mjs} +1139 -111
- package/dist/content-refresh-Bwp-8yGy.mjs.map +1 -0
- package/dist/{media-usage-BZCydfvu.mjs → content-repair-Czdp7jz5.mjs} +5 -196
- package/dist/content-repair-Czdp7jz5.mjs.map +1 -0
- package/dist/{context-ZldbZcle.mjs → context-CX6gxOLW.mjs} +55 -20
- package/dist/context-CX6gxOLW.mjs.map +1 -0
- package/dist/{cron-Y9aLtgrA.mjs → cron-C6o7VppJ.mjs} +1 -1
- package/dist/{cron-Y9aLtgrA.mjs.map → cron-C6o7VppJ.mjs.map} +1 -1
- package/dist/{dashboard-DvHbk_cv.mjs → dashboard-C2kTeeRI.mjs} +5 -5
- package/dist/{dashboard-DvHbk_cv.mjs.map → dashboard-C2kTeeRI.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +3 -3
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql-migrations.d.mts +1 -1
- package/dist/db/libsql-migrations.mjs +4 -4
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +1 -1
- package/dist/db/postgres-migrations.mjs +4 -4
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/postgres.d.mts.map +1 -1
- package/dist/db/postgres.mjs +19 -3
- package/dist/db/postgres.mjs.map +1 -1
- package/dist/db/sqlite-migrations.d.mts +1 -1
- package/dist/db/sqlite-migrations.mjs +4 -4
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +3 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{default-DdDl3fxv.mjs → default-Clra32mw.mjs} +1 -1
- package/dist/{default-DdDl3fxv.mjs.map → default-Clra32mw.mjs.map} +1 -1
- package/dist/{deferred-tasks-SDWxrgzJ.d.mts → deferred-tasks-CvDETTJO.d.mts} +1 -1
- package/dist/{deferred-tasks-SDWxrgzJ.d.mts.map → deferred-tasks-CvDETTJO.d.mts.map} +1 -1
- package/dist/dev-typegen-DzOPfsNj.mjs +29 -0
- package/dist/dev-typegen-DzOPfsNj.mjs.map +1 -0
- package/dist/{device-flow-C0GdD_z8.mjs → device-flow-D2oCWvLJ.mjs} +5 -5
- package/dist/{device-flow-C0GdD_z8.mjs.map → device-flow-D2oCWvLJ.mjs.map} +1 -1
- package/dist/{direct-executor-CDXeJCSs.mjs → direct-executor-Bfa5_ToE.mjs} +3 -3
- package/dist/{direct-executor-CDXeJCSs.mjs.map → direct-executor-Bfa5_ToE.mjs.map} +1 -1
- package/dist/{dist-D_6CKR8z.mjs → dist-89xCrGqi.mjs} +1 -1
- package/dist/{dist-D_6CKR8z.mjs.map → dist-89xCrGqi.mjs.map} +1 -1
- package/dist/{email-console-BHNjIwiB.mjs → email-console-Dzb6uGYa.mjs} +1 -1
- package/dist/{email-console-BHNjIwiB.mjs.map → email-console-Dzb6uGYa.mjs.map} +1 -1
- package/dist/{enrich-_ySMEvEE.mjs → enrich-BX0IDp4z.mjs} +3 -3
- package/dist/{enrich-_ySMEvEE.mjs.map → enrich-BX0IDp4z.mjs.map} +1 -1
- package/dist/{error-DupyJZE2.mjs → error-DFrQjppn.mjs} +2 -2
- package/dist/{error-DupyJZE2.mjs.map → error-DFrQjppn.mjs.map} +1 -1
- package/dist/{errors-CLUPm7aD.mjs → errors-DsVoDUjJ.mjs} +24 -2
- package/dist/errors-DsVoDUjJ.mjs.map +1 -0
- package/dist/{escape-DUY2r2Zs.mjs → escape-HmHTC1Is.mjs} +1 -1
- package/dist/{escape-DUY2r2Zs.mjs.map → escape-HmHTC1Is.mjs.map} +1 -1
- package/dist/{field-defs-cache-BzwBlJrR.mjs → field-defs-cache-BM4SIRqH.mjs} +5 -5
- package/dist/{field-defs-cache-BzwBlJrR.mjs.map → field-defs-cache-BM4SIRqH.mjs.map} +1 -1
- package/dist/focal-point-BeWFo6Xx.mjs +23 -0
- package/dist/focal-point-BeWFo6Xx.mjs.map +1 -0
- package/dist/{fts-manager-DyZEohZC.mjs → fts-manager-Do1BlW2r.mjs} +3 -3
- package/dist/{fts-manager-DyZEohZC.mjs.map → fts-manager-Do1BlW2r.mjs.map} +1 -1
- package/dist/{hash-Cg0TXJPV.mjs → hash-3JJPlHF2.mjs} +1 -1
- package/dist/{hash-Cg0TXJPV.mjs.map → hash-3JJPlHF2.mjs.map} +1 -1
- package/dist/hook-result-DCh2WUpe.d.mts +31 -0
- package/dist/hook-result-DCh2WUpe.d.mts.map +1 -0
- package/dist/{hreflang-DwT2E0Sa.d.mts → hreflang-B6hbAuuO.d.mts} +2 -2
- package/dist/{hreflang-DwT2E0Sa.d.mts.map → hreflang-B6hbAuuO.d.mts.map} +1 -1
- package/dist/{hreflang-BBe83kKa.mjs → hreflang-DQbPZliA.mjs} +7 -7
- package/dist/{hreflang-BBe83kKa.mjs.map → hreflang-DQbPZliA.mjs.map} +1 -1
- package/dist/{identity-Ys6Dlc9C.mjs → identity-DksvEazf.mjs} +3 -3
- package/dist/{identity-Ys6Dlc9C.mjs.map → identity-DksvEazf.mjs.map} +1 -1
- package/dist/{import-DPkLmeXO.mjs → import-CJpaBBBT.mjs} +6 -6
- package/dist/{import-DPkLmeXO.mjs.map → import-CJpaBBBT.mjs.map} +1 -1
- package/dist/{index-BVBZj4-7.d.mts → index-BJNMCWmu.d.mts} +267 -82
- package/dist/index-BJNMCWmu.d.mts.map +1 -0
- package/dist/{index-C9KuQyO-.d.mts → index-Du8K7bWa.d.mts} +3 -3
- package/dist/{index-C9KuQyO-.d.mts.map → index-Du8K7bWa.d.mts.map} +1 -1
- package/dist/index.d.mts +22 -21
- package/dist/index.mjs +79 -73
- package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
- package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
- package/dist/{load-YJ2xl3ra.mjs → load-CawHjTOH.mjs} +2 -2
- package/dist/{load-YJ2xl3ra.mjs.map → load-CawHjTOH.mjs.map} +1 -1
- package/dist/{loader-CdG_Ci3H.mjs → loader-Z8vl7zTq.mjs} +48 -13
- package/dist/loader-Z8vl7zTq.mjs.map +1 -0
- package/dist/{manifest-builder-B252yQUf.mjs → manifest-builder-COtzuca2.mjs} +2 -2
- package/dist/{manifest-builder-B252yQUf.mjs.map → manifest-builder-COtzuca2.mjs.map} +1 -1
- package/dist/{manifest-schema-BXbUAHod.mjs → manifest-schema-BNWwR8Vd.mjs} +5 -5
- package/dist/manifest-schema-BNWwR8Vd.mjs.map +1 -0
- package/dist/media/image-endpoint.d.mts +4 -2
- package/dist/media/image-endpoint.d.mts.map +1 -1
- package/dist/media/image-endpoint.mjs +6 -4
- package/dist/media/image-endpoint.mjs.map +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +13 -3
- package/dist/media/index.mjs.map +1 -1
- package/dist/media/local-runtime.d.mts +14 -12
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +12 -6
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-CzhAn2--.mjs → media-BBm8Wg3f.mjs} +69 -12
- package/dist/media-BBm8Wg3f.mjs.map +1 -0
- package/dist/{media-BWWWb8kg.mjs → media-BHjus86Y.mjs} +133 -9
- package/dist/media-BHjus86Y.mjs.map +1 -0
- package/dist/{media-allowlist-D6x-Us02.mjs → media-allowlist-CoTBWoDp.mjs} +3 -2
- package/dist/media-allowlist-CoTBWoDp.mjs.map +1 -0
- package/dist/media-folders-BGkATs8n.mjs +210 -0
- package/dist/media-folders-BGkATs8n.mjs.map +1 -0
- package/dist/{media-upload-CHBVn6m-.mjs → media-upload-BCidR-EY.mjs} +9 -9
- package/dist/{media-upload-CHBVn6m-.mjs.map → media-upload-BCidR-EY.mjs.map} +1 -1
- package/dist/{media-url-BvoX88FA.mjs → media-url-DHoPr0hq.mjs} +1 -1
- package/dist/{media-url-BvoX88FA.mjs.map → media-url-DHoPr0hq.mjs.map} +1 -1
- package/dist/media-usage-CdY5jhSR.mjs +1833 -0
- package/dist/media-usage-CdY5jhSR.mjs.map +1 -0
- package/dist/media-usage-activation-BgeMtK5C.mjs +78 -0
- package/dist/media-usage-activation-BgeMtK5C.mjs.map +1 -0
- package/dist/{media-usage-work-CLo_6tct.mjs → media-usage-work-CnGCdHxx.mjs} +4 -4
- package/dist/{media-usage-work-CLo_6tct.mjs.map → media-usage-work-CnGCdHxx.mjs.map} +1 -1
- package/dist/{media-usage-work-DrCbqBcl.mjs → media-usage-work-D6D9sMCA.mjs} +221 -59
- package/dist/media-usage-work-D6D9sMCA.mjs.map +1 -0
- package/dist/{media-usage-write-fence-aHEgN1zB.mjs → media-usage-write-fence-hjeD4u7s.mjs} +2 -2
- package/dist/{media-usage-write-fence-aHEgN1zB.mjs.map → media-usage-write-fence-hjeD4u7s.mjs.map} +1 -1
- package/dist/{menus-DSo5QeW0.mjs → menus-6qSwTNxG.mjs} +375 -107
- package/dist/menus-6qSwTNxG.mjs.map +1 -0
- package/dist/{menus-TCNSd2vp.mjs → menus-uvPLXMZf.mjs} +4 -4
- package/dist/{menus-TCNSd2vp.mjs.map → menus-uvPLXMZf.mjs.map} +1 -1
- package/dist/migrations/index.d.mts +1 -1
- package/dist/migrations/index.mjs +3 -3
- package/dist/{mime-DfIVjNkr.mjs → mime-CUIG6x84.mjs} +1 -1
- package/dist/mime-CUIG6x84.mjs.map +1 -0
- package/dist/{mode-BDoH_WcF.mjs → mode-DN_WneRx.mjs} +1 -1
- package/dist/{mode-BDoH_WcF.mjs.map → mode-DN_WneRx.mjs.map} +1 -1
- package/dist/node-sqlite-compat-BuJXIeMz.mjs +46 -0
- package/dist/node-sqlite-compat-BuJXIeMz.mjs.map +1 -0
- package/dist/{normalize-Bc-s_r-a.mjs → normalize-C2xNjTSG.mjs} +36 -5
- package/dist/normalize-C2xNjTSG.mjs.map +1 -0
- package/dist/{oauth-authorization-iOikmrJd.mjs → oauth-authorization-Dj6Kwc21.mjs} +5 -5
- package/dist/{oauth-authorization-iOikmrJd.mjs.map → oauth-authorization-Dj6Kwc21.mjs.map} +1 -1
- package/dist/{oauth-clients-CvI0nHMJ.mjs → oauth-clients-FD7I0J8m.mjs} +1 -1
- package/dist/{oauth-clients-CvI0nHMJ.mjs.map → oauth-clients-FD7I0J8m.mjs.map} +1 -1
- package/dist/{oauth-state-store-Ba8KZlwW.mjs → oauth-state-store-DkmDOmHs.mjs} +1 -1
- package/dist/{oauth-state-store-Ba8KZlwW.mjs.map → oauth-state-store-DkmDOmHs.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-C5SQwGYC.mjs → oauth-user-lookup-CvIwGpYd.mjs} +1 -1
- package/dist/{oauth-user-lookup-C5SQwGYC.mjs.map → oauth-user-lookup-CvIwGpYd.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{object-cache-71tPfV4A.mjs → object-cache-DVKnyOfy.mjs} +19 -3
- package/dist/object-cache-DVKnyOfy.mjs.map +1 -0
- package/dist/{options-CBXIHUoT.mjs → options-BHPeIbBd.mjs} +1 -1
- package/dist/{options-CBXIHUoT.mjs.map → options-BHPeIbBd.mjs.map} +1 -1
- package/dist/{options--hhz-jZ1.d.mts → options-CYb4y7hM.d.mts} +24 -11
- package/dist/options-CYb4y7hM.d.mts.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-DFbuc-Fh.mjs → parse-Ckpg-Qfp.mjs} +2 -2
- package/dist/{parse-DFbuc-Fh.mjs.map → parse-Ckpg-Qfp.mjs.map} +1 -1
- package/dist/{passkey-config-DFAmUyuE.mjs → passkey-config-O_lj3y0k.mjs} +1 -1
- package/dist/{passkey-config-DFAmUyuE.mjs.map → passkey-config-O_lj3y0k.mjs.map} +1 -1
- package/dist/{patterns-CiyXeDgr.mjs → patterns-D9iDO3j8.mjs} +1 -1
- package/dist/{patterns-CiyXeDgr.mjs.map → patterns-D9iDO3j8.mjs.map} +1 -1
- package/dist/{placeholder-Dp0K3RkD.d.mts → placeholder-4X9BrRrp.d.mts} +7 -1
- package/dist/placeholder-4X9BrRrp.d.mts.map +1 -0
- package/dist/plugin-types.d.mts +8 -3
- 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 +2 -2
- package/dist/{policy-TluUIxuu.mjs → policy-Dhtwntrt.mjs} +2 -2
- package/dist/{policy-TluUIxuu.mjs.map → policy-Dhtwntrt.mjs.map} +1 -1
- package/dist/{portable-text-imBW5cHK.mjs → portable-text-U85hOafW.mjs} +1 -1
- package/dist/{portable-text-imBW5cHK.mjs.map → portable-text-U85hOafW.mjs.map} +1 -1
- package/dist/{preview-PW-nOnq4.mjs → preview-B_vJLvf8.mjs} +2 -2
- package/dist/{preview-PW-nOnq4.mjs.map → preview-B_vJLvf8.mjs.map} +1 -1
- package/dist/{protocol-DjsJHTlK.d.mts → protocol-JdD2YhY9.d.mts} +2 -2
- package/dist/{protocol-DjsJHTlK.d.mts.map → protocol-JdD2YhY9.d.mts.map} +1 -1
- package/dist/{public-url-CWoFhQT8.mjs → public-url-CarhZJgf.mjs} +1 -1
- package/dist/{public-url-CWoFhQT8.mjs.map → public-url-CarhZJgf.mjs.map} +1 -1
- package/dist/{query-zTVAR1Sl.mjs → query-BZKZcG5b.mjs} +15 -15
- package/dist/{query-zTVAR1Sl.mjs.map → query-BZKZcG5b.mjs.map} +1 -1
- package/dist/{query-BWs7O5Il.mjs → query-BndaFxxp.mjs} +5 -5
- package/dist/{query-BWs7O5Il.mjs.map → query-BndaFxxp.mjs.map} +1 -1
- package/dist/{rate-limit-Bx4g9m3p.mjs → rate-limit-BZmATC6W.mjs} +2 -2
- package/dist/{rate-limit-Bx4g9m3p.mjs.map → rate-limit-BZmATC6W.mjs.map} +1 -1
- package/dist/{redirect-COsS6SWJ.mjs → redirect-CZkbozu9.mjs} +2 -2
- package/dist/{redirect-COsS6SWJ.mjs.map → redirect-CZkbozu9.mjs.map} +1 -1
- package/dist/{redirect-BgAOGHDq.mjs → redirect-CzqnLxK-.mjs} +1 -1
- package/dist/{redirect-BgAOGHDq.mjs.map → redirect-CzqnLxK-.mjs.map} +1 -1
- package/dist/{redirects-DTDHWH31.mjs → redirects-BLNOWQTn.mjs} +64 -29
- package/dist/redirects-BLNOWQTn.mjs.map +1 -0
- package/dist/{redirects-DyChwDkG.mjs → redirects-CqzNM0KM.mjs} +5 -5
- package/dist/{redirects-DyChwDkG.mjs.map → redirects-CqzNM0KM.mjs.map} +1 -1
- package/dist/{registry-Cqa2I749.mjs → registry-AZAsRsor.mjs} +30 -126
- package/dist/registry-AZAsRsor.mjs.map +1 -0
- package/dist/{relation-BnHB0T4J.mjs → relation-04DfKRTO.mjs} +2 -2
- package/dist/{relation-BnHB0T4J.mjs.map → relation-04DfKRTO.mjs.map} +1 -1
- package/dist/{relations-BBApI4e2.mjs → relations-C4htk5sb.mjs} +7 -7
- package/dist/relations-C4htk5sb.mjs.map +1 -0
- package/dist/{relations-D5sM5fW0.mjs → relations-CcFwDJdJ.mjs} +4 -4
- package/dist/{relations-D5sM5fW0.mjs.map → relations-CcFwDJdJ.mjs.map} +1 -1
- package/dist/{relations-BHo5A8Zf.d.mts → relations-DnRK4YIT.d.mts} +347 -54
- package/dist/relations-DnRK4YIT.d.mts.map +1 -0
- package/dist/{request-cache-BSUptuJR.mjs → request-cache-DH9zdCOD.mjs} +1 -1
- package/dist/{request-cache-BSUptuJR.mjs.map → request-cache-DH9zdCOD.mjs.map} +1 -1
- package/dist/request-context.d.mts +1 -1
- package/dist/{request-meta-DgZJh3zG.mjs → request-meta-DYxLlnmz.mjs} +2 -2
- package/dist/{request-meta-DgZJh3zG.mjs.map → request-meta-DYxLlnmz.mjs.map} +1 -1
- package/dist/{resolve-DOhVEvty.mjs → resolve-prZ3mP7l.mjs} +1 -1
- package/dist/{resolve-DOhVEvty.mjs.map → resolve-prZ3mP7l.mjs.map} +1 -1
- package/dist/{runner-C1T11CfC.mjs → runner-BLIDKpmA.mjs} +471 -290
- package/dist/runner-BLIDKpmA.mjs.map +1 -0
- package/dist/{runner-BYHZn3Jy.d.mts → runner-YKfIMnaF.d.mts} +3 -2
- package/dist/runner-YKfIMnaF.d.mts.map +1 -0
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-DZ5YVVZj.mjs → schema-CGnBeLiW.mjs} +3 -3
- package/dist/{schema-DZ5YVVZj.mjs.map → schema-CGnBeLiW.mjs.map} +1 -1
- package/dist/{schema-2AOsSn2v.mjs → schema-DYr-Vd-L.mjs} +36 -6
- package/dist/{schema-2AOsSn2v.mjs.map → schema-DYr-Vd-L.mjs.map} +1 -1
- package/dist/{scopes-8ClPF2bC.mjs → scopes-BhWvBavj.mjs} +3 -3
- package/dist/{scopes-8ClPF2bC.mjs.map → scopes-BhWvBavj.mjs.map} +1 -1
- package/dist/{search-u1QYdOCG.mjs → search-C-qDk4kx.mjs} +4 -4
- package/dist/{search-u1QYdOCG.mjs.map → search-C-qDk4kx.mjs.map} +1 -1
- package/dist/{secrets-BEgsAQEU.mjs → secrets-rEtHt-6z.mjs} +3 -3
- package/dist/{secrets-BEgsAQEU.mjs.map → secrets-rEtHt-6z.mjs.map} +1 -1
- package/dist/{sections-DmHviGyM.mjs → sections-B_nYvlwn.mjs} +2 -2
- package/dist/{sections-DmHviGyM.mjs.map → sections-B_nYvlwn.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +25 -23
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +17 -15
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-Dq4G-RrO.mjs → seo-926NRIgt.mjs} +3 -3
- package/dist/{seo-Dq4G-RrO.mjs.map → seo-926NRIgt.mjs.map} +1 -1
- package/dist/{seo-AFpVvTqr.mjs → seo-CVWkdv6x.mjs} +1 -1
- package/dist/{seo-AFpVvTqr.mjs.map → seo-CVWkdv6x.mjs.map} +1 -1
- package/dist/{service-BpArayPr.mjs → service-CJl3ZAYs.mjs} +3 -3
- package/dist/{service-BpArayPr.mjs.map → service-CJl3ZAYs.mjs.map} +1 -1
- package/dist/{session-user-BEorKVT-.mjs → session-user-BlMgqCHS.mjs} +1 -1
- package/dist/{session-user-BEorKVT-.mjs.map → session-user-BlMgqCHS.mjs.map} +1 -1
- package/dist/{settings-xRdoUZr0.mjs → settings--wobrkkC.mjs} +21 -8
- package/dist/settings--wobrkkC.mjs.map +1 -0
- package/dist/{settings-DE9xtzqd.mjs → settings-Cwm6o2jY.mjs} +3 -3
- package/dist/{settings-DE9xtzqd.mjs.map → settings-Cwm6o2jY.mjs.map} +1 -1
- package/dist/{setup-complete-BnT8pq_C.mjs → setup-complete-DQbClIN2.mjs} +2 -2
- package/dist/{setup-complete-BnT8pq_C.mjs.map → setup-complete-DQbClIN2.mjs.map} +1 -1
- package/dist/{setup-nonce-CBitf_oQ.mjs → setup-nonce-DZCFS2sn.mjs} +1 -1
- package/dist/{setup-nonce-CBitf_oQ.mjs.map → setup-nonce-DZCFS2sn.mjs.map} +1 -1
- package/dist/{single-flight-cache-B0eLKS6K.mjs → single-flight-cache-BPuGPdcN.mjs} +2 -2
- package/dist/{single-flight-cache-B0eLKS6K.mjs.map → single-flight-cache-BPuGPdcN.mjs.map} +1 -1
- package/dist/{site-url-DjpIXMXJ.mjs → site-url-BrxA_E9H.mjs} +3 -3
- package/dist/{site-url-DjpIXMXJ.mjs.map → site-url-BrxA_E9H.mjs.map} +1 -1
- package/dist/{snapshot-BBlGkb2q.mjs → snapshot--OCDTqdb.mjs} +1 -1
- package/dist/{snapshot-BBlGkb2q.mjs.map → snapshot--OCDTqdb.mjs.map} +1 -1
- package/dist/{ssrf-Cpvg_n3A.mjs → ssrf-5sIM5jzl.mjs} +23 -6
- package/dist/{ssrf-Cpvg_n3A.mjs.map → ssrf-5sIM5jzl.mjs.map} +1 -1
- package/dist/{state-DYwPue40.mjs → state-BpFVsJZB.mjs} +12 -4
- package/dist/state-BpFVsJZB.mjs.map +1 -0
- package/dist/{status-CWVYpbD8.mjs → status-B777-dXz.mjs} +1 -1
- package/dist/{status-CWVYpbD8.mjs.map → status-B777-dXz.mjs.map} +1 -1
- package/dist/storage/local.d.mts +2 -1
- package/dist/storage/local.d.mts.map +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/local.mjs.map +1 -1
- package/dist/storage/s3.d.mts +2 -1
- package/dist/storage/s3.d.mts.map +1 -1
- package/dist/storage/s3.mjs +3 -2
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{target-DixqqV5i.mjs → target-BmR0iTwz.mjs} +1 -1
- package/dist/{target-DixqqV5i.mjs.map → target-BmR0iTwz.mjs.map} +1 -1
- package/dist/{taxonomies-OHUIiIpI.mjs → taxonomies-DkAkMupr.mjs} +17 -14
- package/dist/taxonomies-DkAkMupr.mjs.map +1 -0
- package/dist/{taxonomies-BlrncHi9.mjs → taxonomies-Wfy_Lg12.mjs} +43 -17
- package/dist/taxonomies-Wfy_Lg12.mjs.map +1 -0
- package/dist/{taxonomy-BqCZ1TdU.mjs → taxonomy-tJepplLL.mjs} +5 -5
- package/dist/{taxonomy-BqCZ1TdU.mjs.map → taxonomy-tJepplLL.mjs.map} +1 -1
- package/dist/{tokens-C39klX8R.mjs → tokens-BCO1oruF.mjs} +1 -1
- package/dist/{tokens-C39klX8R.mjs.map → tokens-BCO1oruF.mjs.map} +1 -1
- package/dist/{transaction-NQj4VJ7Z.mjs → transaction-0KOpOyMu.mjs} +2 -1
- package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-0KOpOyMu.mjs.map} +1 -1
- package/dist/{transport-DBJwa-Si.mjs → transport-Dr3qtwXC.mjs} +4 -3
- package/dist/{transport-DBJwa-Si.mjs.map → transport-Dr3qtwXC.mjs.map} +1 -1
- package/dist/{transport-RQE36APG.d.mts → transport-xh6YKW2G.d.mts} +1 -1
- package/dist/{transport-RQE36APG.d.mts.map → transport-xh6YKW2G.d.mts.map} +1 -1
- package/dist/{trusted-proxy-CEVekpby.mjs → trusted-proxy-BR5F1Otp.mjs} +1 -1
- package/dist/{trusted-proxy-CEVekpby.mjs.map → trusted-proxy-BR5F1Otp.mjs.map} +1 -1
- package/dist/{types-BW1syrlw.mjs → types-3qNFPlci.mjs} +1 -1
- package/dist/{types-BW1syrlw.mjs.map → types-3qNFPlci.mjs.map} +1 -1
- package/dist/types-75KIqzL9.mjs.map +1 -1
- package/dist/{types-DrJrVCT1.d.mts → types-B6GrPic4.d.mts} +9 -3
- package/dist/{types-DrJrVCT1.d.mts.map → types-B6GrPic4.d.mts.map} +1 -1
- package/dist/{types-DN64_zzZ.d.mts → types-BuHW4xDE.d.mts} +11 -10
- package/dist/types-BuHW4xDE.d.mts.map +1 -0
- package/dist/{types-DWRhk9xZ.d.mts → types-C00oyCZp.d.mts} +1 -1
- package/dist/{types-DWRhk9xZ.d.mts.map → types-C00oyCZp.d.mts.map} +1 -1
- package/dist/{types-BntycPTF.d.mts → types-CDtkNSFQ.d.mts} +1 -1
- package/dist/{types-BntycPTF.d.mts.map → types-CDtkNSFQ.d.mts.map} +1 -1
- package/dist/{types-Dfk8KI5R.d.mts → types-CPeXo1wF.d.mts} +1 -1
- package/dist/{types-Dfk8KI5R.d.mts.map → types-CPeXo1wF.d.mts.map} +1 -1
- package/dist/types-CQPWhPxE.mjs.map +1 -1
- package/dist/{types-BcqdcT-r.d.mts → types-CXFkwEGC.d.mts} +3 -2
- package/dist/types-CXFkwEGC.d.mts.map +1 -0
- package/dist/{types-CtzytVXn.mjs → types-DM3vioBY.mjs} +1 -1
- package/dist/{types-CtzytVXn.mjs.map → types-DM3vioBY.mjs.map} +1 -1
- package/dist/{types-CXnOzDe1.d.mts → types-DczELMop.d.mts} +1 -1
- package/dist/{types-CXnOzDe1.d.mts.map → types-DczELMop.d.mts.map} +1 -1
- package/dist/types-DfuTR6dL.mjs +3 -0
- package/dist/{types-DQIDRKF2.d.mts → types-FV23hEoW.d.mts} +10 -1
- package/dist/types-FV23hEoW.d.mts.map +1 -0
- package/dist/{types-C2pMvO62.d.mts → types-xDMtUFnO.d.mts} +2 -1
- package/dist/types-xDMtUFnO.d.mts.map +1 -0
- package/dist/{upload-attempts-D4FS8LT6.mjs → upload-attempts-BsqoQ1fn.mjs} +1 -1
- package/dist/{upload-attempts-D4FS8LT6.mjs.map → upload-attempts-BsqoQ1fn.mjs.map} +1 -1
- package/dist/{url-pattern-BHS0V5Ng.mjs → url-pattern-D4c2czpJ.mjs} +1 -1
- package/dist/{url-pattern-BHS0V5Ng.mjs.map → url-pattern-D4c2czpJ.mjs.map} +1 -1
- package/dist/{user-DJZfUJVE.mjs → user-BTZ40aq6.mjs} +2 -2
- package/dist/{user-DJZfUJVE.mjs.map → user-BTZ40aq6.mjs.map} +1 -1
- package/dist/{utils-D4vD_Y98.mjs → utils-BKvNjjhY.mjs} +1 -1
- package/dist/{utils-D4vD_Y98.mjs.map → utils-BKvNjjhY.mjs.map} +1 -1
- package/dist/{validate-BMLBvxOC.mjs → validate-DE_LRwo9.mjs} +2 -2
- package/dist/{validate-BMLBvxOC.mjs.map → validate-DE_LRwo9.mjs.map} +1 -1
- package/dist/{validate-CTqt5nuE.d.mts → validate-DUP27OYD.d.mts} +5 -5
- package/dist/{validate-CTqt5nuE.d.mts.map → validate-DUP27OYD.d.mts.map} +1 -1
- package/dist/{validation-npvENUCB.mjs → validation-BMImFJ11.mjs} +10 -8
- package/dist/{validation-npvENUCB.mjs.map → validation-BMImFJ11.mjs.map} +1 -1
- package/dist/version-Bii3suaO.mjs +7 -0
- package/dist/{version-DB_fqCSI.mjs.map → version-Bii3suaO.mjs.map} +1 -1
- package/dist/{widgets-BD3ho7Rq.mjs → widgets-D3gJ10xU.mjs} +18 -5
- package/dist/widgets-D3gJ10xU.mjs.map +1 -0
- package/dist/{wxr-taxonomies-Ca68766D.d.mts → wxr-taxonomies-B2Og_6rU.d.mts} +1 -1
- package/dist/{wxr-taxonomies-Ca68766D.d.mts.map → wxr-taxonomies-B2Og_6rU.d.mts.map} +1 -1
- package/dist/{wxr-taxonomies-B1R3vBoD.mjs → wxr-taxonomies-Cxngsjwo.mjs} +4 -4
- package/dist/{wxr-taxonomies-B1R3vBoD.mjs.map → wxr-taxonomies-Cxngsjwo.mjs.map} +1 -1
- package/dist/{zod-generator-LC-on_Vb.mjs → zod-generator-BuYSQJB7.mjs} +32 -22
- package/dist/zod-generator-BuYSQJB7.mjs.map +1 -0
- package/package.json +20 -9
- package/src/api/errors.ts +22 -0
- package/src/api/handlers/content.ts +49 -11
- package/src/api/handlers/index.ts +18 -0
- package/src/api/handlers/manifest.ts +161 -12
- package/src/api/handlers/media-allowlist.ts +1 -0
- package/src/api/handlers/media-folders.ts +130 -0
- package/src/api/handlers/media-usage-activation.ts +102 -0
- package/src/api/handlers/media-usage.ts +108 -0
- package/src/api/handlers/media.ts +92 -2
- package/src/api/handlers/registry.ts +483 -561
- package/src/api/handlers/taxonomies.ts +16 -9
- package/src/api/openapi/document.ts +277 -7
- package/src/api/parse.ts +1 -1
- package/src/api/schemas/auth.ts +4 -4
- package/src/api/schemas/bylines.ts +3 -3
- package/src/api/schemas/comments.ts +1 -1
- package/src/api/schemas/common.ts +3 -2
- package/src/api/schemas/content.ts +78 -22
- package/src/api/schemas/media-usage.ts +77 -0
- package/src/api/schemas/media.ts +121 -0
- package/src/api/schemas/redirects.ts +6 -1
- package/src/api/schemas/schema.ts +4 -0
- package/src/api/schemas/setup.ts +1 -1
- package/src/api/schemas/taxonomies.ts +2 -2
- package/src/api/schemas/users.ts +1 -1
- package/src/api/types.ts +23 -10
- package/src/astro/dev-typegen.ts +35 -0
- package/src/astro/image-endpoint.ts +2 -2
- package/src/astro/integration/dev-typegen.ts +91 -0
- package/src/astro/integration/index.ts +9 -44
- package/src/astro/integration/routes.ts +23 -0
- package/src/astro/integration/vite-config.ts +23 -9
- package/src/astro/middleware/auth.ts +4 -0
- package/src/astro/middleware.ts +5 -7
- package/src/astro/routes/api/admin/api-tokens/index.ts +4 -1
- package/src/astro/routes/api/admin/media-usage/activation.ts +42 -0
- package/src/astro/routes/api/admin/media-usage/progress.ts +37 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +1 -1
- package/src/astro/routes/api/admin/oauth-clients/index.ts +1 -1
- package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/artifact.ts +247 -101
- package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
- package/src/astro/routes/api/admin/plugins/registry/verify.ts +60 -0
- package/src/astro/routes/api/auth/me.ts +14 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +8 -2
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +2 -1
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +6 -2
- package/src/astro/routes/api/manifest.ts +1 -1
- package/src/astro/routes/api/media/[id]/replace.ts +114 -0
- package/src/astro/routes/api/media/[id].ts +3 -0
- package/src/astro/routes/api/media/file/[...key].ts +4 -1
- package/src/astro/routes/api/media/folders/[id].ts +55 -0
- package/src/astro/routes/api/media/folders/index.ts +37 -0
- package/src/astro/routes/api/media/upload-url.ts +8 -4
- package/src/astro/routes/api/media.ts +66 -10
- package/src/astro/routes/api/menus/[name]/items/[id].ts +7 -2
- package/src/astro/routes/api/menus/[name]/items.ts +4 -1
- package/src/astro/routes/api/menus/[name]/reorder.ts +4 -1
- package/src/astro/routes/api/menus/[name]/translations.ts +4 -1
- package/src/astro/routes/api/menus/[name].ts +7 -2
- package/src/astro/routes/api/menus/index.ts +4 -1
- package/src/astro/routes/api/settings/email.ts +1 -1
- package/src/astro/routes/api/settings.ts +4 -1
- package/src/astro/routes/api/setup/dev-bypass.ts +12 -0
- package/src/astro/routes/api/taxonomies/[name]/reorder.ts +4 -1
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +4 -1
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +7 -2
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +4 -1
- package/src/astro/routes/api/taxonomies/[name].ts +7 -2
- package/src/astro/routes/api/taxonomies/index.ts +4 -1
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +3 -1
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +5 -2
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +3 -1
- package/src/astro/routes/api/widget-areas/[name].ts +3 -1
- package/src/astro/routes/api/widget-areas/index.ts +3 -1
- package/src/astro/types.ts +34 -6
- package/src/auth/challenge-store.ts +3 -1
- package/src/aws-sdk.d.ts +1 -0
- package/src/cache/chrome-tags.ts +24 -0
- package/src/client/index.ts +140 -2
- package/src/client/transport.ts +6 -2
- package/src/components/EmDashImage.astro +156 -51
- package/src/components/EmDashMedia.astro +2 -1
- package/src/components/Gallery.astro +7 -0
- package/src/components/InlinePortableTextEditor.tsx +136 -2
- package/src/components/LiveSearch.astro +23 -0
- package/src/components/inline-code-block.tsx +180 -49
- package/src/components/widgets/Archives.astro +2 -37
- package/src/content/converters/gallery.ts +2 -0
- package/src/content/converters/index.ts +2 -0
- package/src/content/converters/mark-safety.ts +1 -0
- package/src/content/converters/portable-text-identity.ts +139 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +202 -69
- package/src/content/converters/prosemirror-to-portable-text.ts +96 -25
- package/src/content/converters/types.ts +3 -0
- package/src/database/connection.ts +2 -31
- package/src/database/index.ts +1 -1
- package/src/database/json-recordset.ts +16 -0
- package/src/database/migrations/017_authorization_codes.ts +14 -4
- package/src/database/migrations/019_i18n.ts +1 -1
- package/src/database/migrations/071_restore_content_bylines_table.ts +43 -0
- package/src/database/migrations/072_media_folders.ts +72 -0
- package/src/database/migrations/073_media_focal_point.ts +57 -0
- package/src/database/migrations/074_content_deleted_scheduled_index.ts +55 -0
- package/src/database/migrations/runner.ts +11 -3
- package/src/database/repositories/byline.ts +32 -16
- package/src/database/repositories/content.ts +119 -51
- package/src/database/repositories/media-folders.ts +115 -0
- package/src/database/repositories/media-usage-work.ts +395 -111
- package/src/database/repositories/media-usage.ts +1104 -47
- package/src/database/repositories/media.ts +156 -29
- package/src/database/repositories/plugin-storage.ts +74 -9
- package/src/database/repositories/types.ts +8 -7
- package/src/database/transaction.ts +1 -0
- package/src/database/types.ts +13 -0
- package/src/db/adapters.ts +10 -3
- package/src/db/node-sqlite-compat.ts +78 -0
- package/src/db/postgres.ts +23 -0
- package/src/db/sqlite.ts +3 -3
- package/src/emdash-runtime.ts +186 -251
- package/src/fields/boolean.ts +1 -1
- package/src/fields/datetime.ts +1 -1
- package/src/fields/file.ts +1 -1
- package/src/fields/image.ts +7 -1
- package/src/fields/integer.ts +1 -1
- package/src/fields/json.ts +1 -1
- package/src/fields/multiselect.ts +1 -1
- package/src/fields/number.ts +1 -1
- package/src/fields/portable-text.ts +1 -1
- package/src/fields/richtext.ts +1 -1
- package/src/fields/select.ts +1 -1
- package/src/fields/slug.ts +1 -1
- package/src/fields/text.ts +1 -1
- package/src/fields/textarea.ts +1 -1
- package/src/fields/types.ts +8 -4
- package/src/index.ts +30 -3
- package/src/loader.ts +63 -10
- package/src/mcp/server.ts +228 -24
- package/src/media/focal-point.ts +37 -0
- package/src/media/image-endpoint.ts +5 -2
- package/src/media/local-runtime.ts +8 -0
- package/src/media/mime.ts +1 -0
- package/src/media/normalize.ts +25 -4
- package/src/media/types.ts +10 -0
- package/src/media/usage/activation.ts +162 -35
- package/src/media/usage/capture-triggers.ts +19 -3
- package/src/media/usage/cleanup.ts +9 -8
- package/src/media/usage/collection-deletion-processor.ts +7 -3
- package/src/media/usage/collection-deletion.ts +10 -0
- package/src/media/usage/content-refresh.ts +258 -39
- package/src/media/usage/content-snapshots.ts +128 -31
- package/src/media/usage/extractor.ts +32 -13
- package/src/media/usage/maintenance-engine.ts +166 -0
- package/src/media/usage/reconciliation-processor.ts +106 -29
- package/src/media/usage/reconciliation.ts +90 -7
- package/src/media/usage/work-processor.ts +342 -101
- package/src/menus/index.ts +16 -0
- package/src/object-cache/index.ts +44 -2
- package/src/plugin-types.ts +18 -1
- package/src/plugins/hooks.ts +4 -1
- package/src/plugins/index.ts +7 -0
- package/src/plugins/manager.ts +2 -1
- package/src/plugins/manifest-schema.ts +1 -1
- package/src/plugins/routes.ts +3 -1
- package/src/plugins/sandbox/hook-result.ts +52 -0
- package/src/plugins/sandbox/index.ts +10 -0
- package/src/plugins/save-rejection.ts +20 -0
- package/src/plugins/scheduler/node.ts +1 -13
- package/src/plugins/scheduler/types.ts +0 -2
- package/src/plugins/state.ts +13 -3
- package/src/plugins/types.ts +6 -0
- package/src/registry/artifact-checksum.ts +15 -0
- package/src/registry/artifact-fetch.ts +530 -0
- package/src/registry/artifact-verification.ts +85 -0
- package/src/registry/authoritative-records.ts +180 -0
- package/src/registry/config.ts +24 -6
- package/src/registry/record-labels.ts +28 -0
- package/src/registry/types.ts +12 -15
- package/src/schema/registry.ts +26 -3
- package/src/schema/types.ts +1 -0
- package/src/schema/zod-generator.ts +28 -17
- package/src/security/ssrf.ts +26 -4
- package/src/seed/apply.ts +4 -4
- package/src/settings/index.ts +16 -0
- package/src/storage/local.ts +1 -0
- package/src/storage/s3.ts +2 -0
- package/src/storage/types.ts +2 -0
- package/src/taxonomies/index.ts +40 -20
- package/src/ui.ts +2 -0
- package/src/widgets/archives.ts +67 -0
- package/src/widgets/index.ts +16 -0
- package/dist/api-BeBglVxU.mjs +0 -4695
- package/dist/api-BeBglVxU.mjs.map +0 -1
- package/dist/byline-yjvWq4ip.mjs.map +0 -1
- package/dist/challenge-store-SIL5DsF2.mjs.map +0 -1
- package/dist/collection-deletion-DQPvMD8R.mjs.map +0 -1
- package/dist/content-ZHCc1ngN.mjs.map +0 -1
- package/dist/content-refresh-B_8SJzuU.mjs.map +0 -1
- package/dist/context-ZldbZcle.mjs.map +0 -1
- package/dist/errors-CLUPm7aD.mjs.map +0 -1
- package/dist/index-BVBZj4-7.d.mts.map +0 -1
- package/dist/loader-CdG_Ci3H.mjs.map +0 -1
- package/dist/manifest-schema-BXbUAHod.mjs.map +0 -1
- package/dist/media-BWWWb8kg.mjs.map +0 -1
- package/dist/media-CzhAn2--.mjs.map +0 -1
- package/dist/media-allowlist-D6x-Us02.mjs.map +0 -1
- package/dist/media-usage-BZCydfvu.mjs.map +0 -1
- package/dist/media-usage-work-DrCbqBcl.mjs.map +0 -1
- package/dist/menus-DSo5QeW0.mjs.map +0 -1
- package/dist/mime-DfIVjNkr.mjs.map +0 -1
- package/dist/normalize-Bc-s_r-a.mjs.map +0 -1
- package/dist/object-cache-71tPfV4A.mjs.map +0 -1
- package/dist/options--hhz-jZ1.d.mts.map +0 -1
- package/dist/placeholder-Dp0K3RkD.d.mts.map +0 -1
- package/dist/redirects-DTDHWH31.mjs.map +0 -1
- package/dist/registry-Cqa2I749.mjs.map +0 -1
- package/dist/relations-BBApI4e2.mjs.map +0 -1
- package/dist/relations-BHo5A8Zf.d.mts.map +0 -1
- package/dist/runner-BYHZn3Jy.d.mts.map +0 -1
- package/dist/runner-C1T11CfC.mjs.map +0 -1
- package/dist/settings-xRdoUZr0.mjs.map +0 -1
- package/dist/state-DYwPue40.mjs.map +0 -1
- package/dist/taxonomies-BlrncHi9.mjs.map +0 -1
- package/dist/taxonomies-OHUIiIpI.mjs.map +0 -1
- package/dist/types-BcqdcT-r.d.mts.map +0 -1
- package/dist/types-C2pMvO62.d.mts.map +0 -1
- package/dist/types-CP1CflWf.mjs +0 -3
- package/dist/types-DN64_zzZ.d.mts.map +0 -1
- package/dist/types-DQIDRKF2.d.mts.map +0 -1
- package/dist/version-DB_fqCSI.mjs +0 -7
- package/dist/widgets-BD3ho7Rq.mjs.map +0 -1
- package/dist/zod-generator-LC-on_Vb.mjs.map +0 -1
- /package/dist/{api-tokens-rIXC6LdE.mjs → api-tokens-BfYts-sq.mjs} +0 -0
- /package/dist/{ssrf-DpXgKsna.mjs → ssrf-Dg3cIT-b.mjs} +0 -0
|
@@ -1,101 +1,105 @@
|
|
|
1
|
-
import { r as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-
|
|
2
|
-
import {
|
|
3
|
-
import { c as isSqlite, p as tableExists, s as isPostgres, u as listTablesLike } from "../dialect-helpers-BSbfaO3F.mjs";
|
|
1
|
+
import { r as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-BLIDKpmA.mjs";
|
|
2
|
+
import { c as isSqlite, u as listTablesLike } from "../dialect-helpers-BSbfaO3F.mjs";
|
|
4
3
|
import { r as getI18nConfig, s as setI18nConfig } from "../config-C3EBstHi.mjs";
|
|
5
|
-
import {
|
|
4
|
+
import { A as resolveExclusiveHooks, C as parseRouteInput, D as isContentSaveRejection, S as buildRouteMeta, T as EmailPipeline, c as inspectSandboxHookResult, f as getSandboxRouteErrorDetails, j as definePlugin, k as createHookPipeline, t as getMenu, w as toRouteCallerInfo, x as PluginRouteRegistry, z as AuditRepository } from "../menus-6qSwTNxG.mjs";
|
|
6
5
|
import { n as createDeferredTaskTracker, t as after } from "../after-CBU2vkoO.mjs";
|
|
7
6
|
import { createRequestMetrics, getRequestContext, runWithContext } from "../request-context.mjs";
|
|
8
|
-
import { o as getLastContentWriteAt } from "../object-cache-
|
|
9
|
-
import { i as
|
|
10
|
-
import { i as RevisionRepository, t as ContentRepository } from "../content-ZHCc1ngN.mjs";
|
|
7
|
+
import { o as getLastContentWriteAt } from "../object-cache-DVKnyOfy.mjs";
|
|
8
|
+
import { i as RevisionRepository, t as ContentRepository } from "../content-DjvM0T92.mjs";
|
|
11
9
|
import { n as isMissingTableError } from "../db-errors-Cl-sKYTm.mjs";
|
|
12
10
|
import "../slugify-B994zkXC.mjs";
|
|
13
11
|
import "../base64-CvLhUKQv.mjs";
|
|
14
12
|
import { n as ContentMutationConflictError } from "../types-CQPWhPxE.mjs";
|
|
15
|
-
import { t as MediaRepository } from "../media-
|
|
16
|
-
import "../user-
|
|
17
|
-
import
|
|
18
|
-
import "../
|
|
19
|
-
import "../
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import "../
|
|
23
|
-
import "../
|
|
24
|
-
import
|
|
25
|
-
import "../
|
|
26
|
-
import
|
|
27
|
-
import "../
|
|
28
|
-
import "../
|
|
29
|
-
import "../
|
|
30
|
-
import
|
|
31
|
-
import "../
|
|
32
|
-
import "../
|
|
33
|
-
import "../
|
|
34
|
-
import
|
|
35
|
-
import "../
|
|
36
|
-
import
|
|
37
|
-
import "../resolve-DOhVEvty.mjs";
|
|
13
|
+
import { t as MediaRepository } from "../media-BBm8Wg3f.mjs";
|
|
14
|
+
import "../user-BTZ40aq6.mjs";
|
|
15
|
+
import "../taxonomy-tJepplLL.mjs";
|
|
16
|
+
import "../comment-vxOzWbZN.mjs";
|
|
17
|
+
import { t as OptionsRepository } from "../options-BHPeIbBd.mjs";
|
|
18
|
+
import { r as createSiteInfo } from "../context-CX6gxOLW.mjs";
|
|
19
|
+
import "../menus-uvPLXMZf.mjs";
|
|
20
|
+
import "../redirect-CZkbozu9.mjs";
|
|
21
|
+
import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-DH9zdCOD.mjs";
|
|
22
|
+
import "../byline-registry-Bpu9-MVs.mjs";
|
|
23
|
+
import { n as initWithLock, t as createInitLock } from "../init-lock-DJkX6Hto.mjs";
|
|
24
|
+
import "../field-defs-cache-BM4SIRqH.mjs";
|
|
25
|
+
import "../byline-DuhF7NWl.mjs";
|
|
26
|
+
import "../relation-04DfKRTO.mjs";
|
|
27
|
+
import { n as normalizeMediaValue } from "../normalize-C2xNjTSG.mjs";
|
|
28
|
+
import "../dist-89xCrGqi.mjs";
|
|
29
|
+
import "../enrich-BX0IDp4z.mjs";
|
|
30
|
+
import "../seo-926NRIgt.mjs";
|
|
31
|
+
import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-BPuGPdcN.mjs";
|
|
32
|
+
import "../cache-BF5Kvn59.mjs";
|
|
33
|
+
import { t as FTSManager } from "../fts-manager-Do1BlW2r.mjs";
|
|
34
|
+
import "../resolve-prZ3mP7l.mjs";
|
|
38
35
|
import { createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption } from "../database/instrumentation.mjs";
|
|
39
|
-
import { s as getDb } from "../loader-
|
|
40
|
-
import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-
|
|
41
|
-
import { $ as
|
|
42
|
-
import { n as recordSchedulerHeartbeatSafely } from "../dashboard-
|
|
43
|
-
import
|
|
44
|
-
import {
|
|
45
|
-
import
|
|
46
|
-
import "../
|
|
47
|
-
import { n as
|
|
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 "../
|
|
78
|
-
import "../
|
|
79
|
-
import "../
|
|
80
|
-
import "../
|
|
81
|
-
import "../
|
|
82
|
-
import "../
|
|
83
|
-
import { n as
|
|
84
|
-
import "../
|
|
85
|
-
import "../
|
|
86
|
-
import "../
|
|
87
|
-
import "../
|
|
36
|
+
import { s as getDb } from "../loader-Z8vl7zTq.mjs";
|
|
37
|
+
import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-Wfy_Lg12.mjs";
|
|
38
|
+
import { $ as validateRev, A as handleRevisionRestore, B as handleContentDuplicate, C as handleMediaDelete, D as handleMediaUpdate, E as handleMediaReplaceMetadata, F as handleContentCountScheduled, G as handleContentPermanentDelete, H as handleContentGetIncludingTrashed, I as handleContentCountTrashed, J as handleContentSchedule, K as handleContentPublish, L as handleContentCreate, N as handleContentAuthors, O as handleRevisionGet, P as handleContentCompare, Q as handleContentUpdate, R as handleContentDelete, S as handleMediaCreate, T as handleMediaList, U as handleContentList, V as handleContentGet, W as handleContentListTrashed, X as handleContentUnpublish, Y as handleContentTranslations, Z as handleContentUnschedule, j as buildManifestCollections, k as handleRevisionList, m as syncDeclaredStorageIndexes, p as loadBundleFromR2, q as handleContentRestore, w as handleMediaGet, z as handleContentDiscardDraft } from "../api-Dz6rgoa3.mjs";
|
|
39
|
+
import { n as recordSchedulerHeartbeatSafely } from "../dashboard-C2kTeeRI.mjs";
|
|
40
|
+
import "../capture-triggers-CNW1q3HK.mjs";
|
|
41
|
+
import { r as activateMediaUsageCapture } from "../activation-K0kGBWuJ.mjs";
|
|
42
|
+
import "../collection-deletion-Bj07NHbj.mjs";
|
|
43
|
+
import { c as markContentMediaUsageCollectionStale, i as deleteContentMediaUsage, o as findNonTranslatableSiblingContentIds, u as refreshContentMediaUsageAfterWrite, y as MediaUsageRepository } from "../content-refresh-Bwp-8yGy.mjs";
|
|
44
|
+
import { n as SchemaRegistry } from "../registry-AZAsRsor.mjs";
|
|
45
|
+
import { r as hashString } from "../hash-3JJPlHF2.mjs";
|
|
46
|
+
import "../media-folders-BGkATs8n.mjs";
|
|
47
|
+
import "../media-usage-work-D6D9sMCA.mjs";
|
|
48
|
+
import "../content-repair-Czdp7jz5.mjs";
|
|
49
|
+
import { s as processMediaUsageWorkAfterWrite } from "../media-usage-CdY5jhSR.mjs";
|
|
50
|
+
import { t as ErrorCode } from "../errors-DsVoDUjJ.mjs";
|
|
51
|
+
import "../media-usage-activation-BgeMtK5C.mjs";
|
|
52
|
+
import "../media-usage-work-CnGCdHxx.mjs";
|
|
53
|
+
import "../query-BndaFxxp.mjs";
|
|
54
|
+
import "../zod-generator-BuYSQJB7.mjs";
|
|
55
|
+
import "../schema-CGnBeLiW.mjs";
|
|
56
|
+
import "../schema-DYr-Vd-L.mjs";
|
|
57
|
+
import "../seo-CVWkdv6x.mjs";
|
|
58
|
+
import { t as PluginStateRepository } from "../state-BpFVsJZB.mjs";
|
|
59
|
+
import "../sections-B_nYvlwn.mjs";
|
|
60
|
+
import { i as getSiteSettings, s as invalidateSiteSettingsCache } from "../settings--wobrkkC.mjs";
|
|
61
|
+
import "../settings-Cwm6o2jY.mjs";
|
|
62
|
+
import "../taxonomies-DkAkMupr.mjs";
|
|
63
|
+
import { r as normalizeManifestRoute } from "../manifest-schema-BNWwR8Vd.mjs";
|
|
64
|
+
import "../types-DfuTR6dL.mjs";
|
|
65
|
+
import { r as normalizeRegistryConfig } from "../config-it29yDLA.mjs";
|
|
66
|
+
import "../ssrf-5sIM5jzl.mjs";
|
|
67
|
+
import "../error-DFrQjppn.mjs";
|
|
68
|
+
import "../parse-Ckpg-Qfp.mjs";
|
|
69
|
+
import "../media-BHjus86Y.mjs";
|
|
70
|
+
import "../redirects-BLNOWQTn.mjs";
|
|
71
|
+
import "../relations-C4htk5sb.mjs";
|
|
72
|
+
import "../status-B777-dXz.mjs";
|
|
73
|
+
import { a as invalidateUrlPatternCache } from "../query-BZKZcG5b.mjs";
|
|
74
|
+
import "../import-CJpaBBBT.mjs";
|
|
75
|
+
import { t as getTrustedProxyHeaders } from "../trusted-proxy-BR5F1Otp.mjs";
|
|
76
|
+
import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-DYxLlnmz.mjs";
|
|
77
|
+
import "../ssrf-Dg3cIT-b.mjs";
|
|
78
|
+
import { n as CronExecutor } from "../cron-C6o7VppJ.mjs";
|
|
79
|
+
import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "../email-console-Dzb6uGYa.mjs";
|
|
80
|
+
import { n as assertMediaUsageActivationWriteAllowed } from "../media-usage-write-fence-hjeD4u7s.mjs";
|
|
81
|
+
import "../utils-BKvNjjhY.mjs";
|
|
82
|
+
import "../tokens-BCO1oruF.mjs";
|
|
83
|
+
import "../preview-B_vJLvf8.mjs";
|
|
84
|
+
import "../hreflang-DQbPZliA.mjs";
|
|
85
|
+
import "../comment-reaction-BhrvSHml.mjs";
|
|
86
|
+
import "../bylines-CJ5nd6Lw.mjs";
|
|
87
|
+
import { r as getWidgetAreas } from "../widgets-D3gJ10xU.mjs";
|
|
88
|
+
import "../validate-DE_LRwo9.mjs";
|
|
89
|
+
import "../apply-B4SIOxR1.mjs";
|
|
90
|
+
import "../load-CawHjTOH.mjs";
|
|
91
|
+
import "../search-C-qDk4kx.mjs";
|
|
88
92
|
import "../index.mjs";
|
|
89
|
-
import { i as resolveRuntimeMigrationMode, n as enforceRuntimeMigrationPolicy, t as PendingMigrationsError } from "../policy-
|
|
90
|
-
import { n as VERSION, t as COMMIT } from "../version-
|
|
91
|
-
import { t as getAuthMode } from "../mode-
|
|
92
|
-
import "../snapshot
|
|
93
|
-
import { u as maybeRunScheduledBackup } from "../backup-
|
|
94
|
-
import { t as cleanupExpiredChallenges } from "../challenge-store-
|
|
95
|
-
import { t as removeUploadAttempt } from "../upload-attempts-
|
|
96
|
-
import { a as validateEncryptionKeyAtStartup } from "../secrets-
|
|
93
|
+
import { i as resolveRuntimeMigrationMode, n as enforceRuntimeMigrationPolicy, t as PendingMigrationsError } from "../policy-Dhtwntrt.mjs";
|
|
94
|
+
import { n as VERSION, t as COMMIT } from "../version-Bii3suaO.mjs";
|
|
95
|
+
import { t as getAuthMode } from "../mode-DN_WneRx.mjs";
|
|
96
|
+
import "../snapshot--OCDTqdb.mjs";
|
|
97
|
+
import { u as maybeRunScheduledBackup } from "../backup-rwVAl9NI.mjs";
|
|
98
|
+
import { t as cleanupExpiredChallenges } from "../challenge-store-DQN_Yrck.mjs";
|
|
99
|
+
import { t as removeUploadAttempt } from "../upload-attempts-BsqoQ1fn.mjs";
|
|
100
|
+
import { a as validateEncryptionKeyAtStartup } from "../secrets-rEtHt-6z.mjs";
|
|
97
101
|
import { isRecord } from "../plugin-utils.mjs";
|
|
98
|
-
import { t as resolveSessionUser } from "../session-user-
|
|
102
|
+
import { t as resolveSessionUser } from "../session-user-BlMgqCHS.mjs";
|
|
99
103
|
import { Kysely, sql } from "kysely";
|
|
100
104
|
import { ulid } from "ulidx";
|
|
101
105
|
import { z } from "zod";
|
|
@@ -158,1145 +162,6 @@ async function warnAboutUnconfiguredTaxonomyLocales(db, configuredLocales, defin
|
|
|
158
162
|
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
163
|
}
|
|
160
164
|
|
|
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;
|
|
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++;
|
|
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";
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
165
|
//#endregion
|
|
1301
166
|
//#region src/plugins/sandbox/runner-options.ts
|
|
1302
167
|
/**
|
|
@@ -1348,14 +213,15 @@ async function cleanupMediaUsage(db) {
|
|
|
1348
213
|
let sweepComplete = false;
|
|
1349
214
|
try {
|
|
1350
215
|
if (canIssueStatement()) deletedWriteLeases = await repo.deleteExpiredGenerationWriteLeases(MEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT, cleanupLease(leaseToken), canIssueStatement);
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
216
|
+
const cutoff = claim.scanBeforeAt;
|
|
217
|
+
const candidates = await repo.findMediaUsageCleanupCandidates({
|
|
218
|
+
cutoff,
|
|
219
|
+
cursor: claim.cursor,
|
|
220
|
+
limit: MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT,
|
|
221
|
+
cleanupLease: cleanupLease(leaseToken),
|
|
222
|
+
canIssueStatement
|
|
223
|
+
});
|
|
224
|
+
if (candidates) {
|
|
1359
225
|
candidateRows = candidates.length;
|
|
1360
226
|
scanHasMore = candidates.length === MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT;
|
|
1361
227
|
const selected = selectCleanupCandidates(candidates, claim.claimedAt);
|
|
@@ -1786,27 +652,6 @@ function isValidMetadataContribution(c) {
|
|
|
1786
652
|
default: return false;
|
|
1787
653
|
}
|
|
1788
654
|
}
|
|
1789
|
-
/**
|
|
1790
|
-
* Map schema field types to editor field kinds
|
|
1791
|
-
*/
|
|
1792
|
-
const FIELD_TYPE_TO_KIND = {
|
|
1793
|
-
string: "string",
|
|
1794
|
-
slug: "string",
|
|
1795
|
-
url: "url",
|
|
1796
|
-
text: "richText",
|
|
1797
|
-
number: "number",
|
|
1798
|
-
integer: "number",
|
|
1799
|
-
boolean: "boolean",
|
|
1800
|
-
datetime: "datetime",
|
|
1801
|
-
select: "select",
|
|
1802
|
-
multiSelect: "multiSelect",
|
|
1803
|
-
portableText: "portableText",
|
|
1804
|
-
image: "image",
|
|
1805
|
-
file: "file",
|
|
1806
|
-
reference: "reference",
|
|
1807
|
-
json: "json",
|
|
1808
|
-
repeater: "repeater"
|
|
1809
|
-
};
|
|
1810
655
|
const DRAFT_ONLY_UPDATE_KEYS = new Set([
|
|
1811
656
|
"data",
|
|
1812
657
|
"slug",
|
|
@@ -1814,15 +659,28 @@ const DRAFT_ONLY_UPDATE_KEYS = new Set([
|
|
|
1814
659
|
"skipRevision"
|
|
1815
660
|
]);
|
|
1816
661
|
const MAX_DRAFT_STAGE_ATTEMPTS = 32;
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
662
|
+
/**
|
|
663
|
+
* A `ContentSaveRejectedError` carries a message the plugin wrote for the
|
|
664
|
+
* editor; every other exception stays internal and is replaced by a generic
|
|
665
|
+
* message so hook internals cannot leak through the API.
|
|
666
|
+
*/
|
|
667
|
+
function beforeSaveFailure(error) {
|
|
668
|
+
if (isContentSaveRejection(error)) return {
|
|
669
|
+
success: false,
|
|
670
|
+
error: {
|
|
671
|
+
code: ErrorCode.SAVE_REJECTED,
|
|
672
|
+
message: error.message
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
console.error("EmDash: content:beforeSave hook failed:", error);
|
|
676
|
+
return {
|
|
677
|
+
success: false,
|
|
678
|
+
error: {
|
|
679
|
+
code: ErrorCode.CONTENT_HOOK_ERROR,
|
|
680
|
+
message: "A plugin hook failed while saving content"
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
1826
684
|
/**
|
|
1827
685
|
* Convert a ContentItem to Record<string, unknown> for hook consumption.
|
|
1828
686
|
* Hooks receive the full item as a flat record.
|
|
@@ -1906,49 +764,6 @@ const marketplaceManifestCache = /* @__PURE__ */ new Map();
|
|
|
1906
764
|
/** Route metadata for sandboxed plugins: pluginId -> routeName -> RouteMeta */
|
|
1907
765
|
const sandboxedRouteMetaCache = /* @__PURE__ */ new Map();
|
|
1908
766
|
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
|
-
}
|
|
1952
767
|
/**
|
|
1953
768
|
* EmDashRuntime - singleton per worker
|
|
1954
769
|
*/
|
|
@@ -2116,9 +931,6 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2116
931
|
await recordSchedulerHeartbeatSafely(this.db);
|
|
2117
932
|
return { published };
|
|
2118
933
|
}
|
|
2119
|
-
async runScheduledMediaUsageTasks() {
|
|
2120
|
-
return runScheduledMediaUsageLane(this.db);
|
|
2121
|
-
}
|
|
2122
934
|
/**
|
|
2123
935
|
* Materialize plugin-declared storage indexes, once per process.
|
|
2124
936
|
*
|
|
@@ -2519,13 +1331,19 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2519
1331
|
await new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);
|
|
2520
1332
|
});
|
|
2521
1333
|
if (seedGate.collectionCount === 0 && !seedGate.setupDone) {
|
|
1334
|
+
try {
|
|
1335
|
+
if ((await activateMediaUsageCapture(db, { writersDrained: true })).outcome !== "active") throw new Error("Fresh-site media usage activation did not complete");
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
await disposeReadDb();
|
|
1338
|
+
throw error;
|
|
1339
|
+
}
|
|
2522
1340
|
const seedKey = deps.config.database?.entrypoint ?? "default";
|
|
2523
1341
|
const seedHolder = getSeedHolder();
|
|
2524
1342
|
try {
|
|
2525
1343
|
await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
|
|
2526
|
-
const { applySeed } = await import("../apply-
|
|
2527
|
-
const { loadSeed } = await import("../load-
|
|
2528
|
-
const { validateSeed } = await import("../validate-
|
|
1344
|
+
const { applySeed } = await import("../apply-B4SIOxR1.mjs").then((n) => n.n);
|
|
1345
|
+
const { loadSeed } = await import("../load-CawHjTOH.mjs").then((n) => n.r);
|
|
1346
|
+
const { validateSeed } = await import("../validate-DE_LRwo9.mjs").then((n) => n.n);
|
|
2529
1347
|
const seed = await loadSeed();
|
|
2530
1348
|
if (validateSeed(seed).valid) {
|
|
2531
1349
|
await applySeed(db, seed, { onConflict: "skip" });
|
|
@@ -2655,11 +1473,6 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2655
1473
|
if (deps.createScheduler) {
|
|
2656
1474
|
const scheduler = deps.createScheduler(cronExecutor);
|
|
2657
1475
|
cronScheduler = scheduler;
|
|
2658
|
-
const runMediaUsageMaintenance = async () => {
|
|
2659
|
-
const runtime = runtimeRef.current;
|
|
2660
|
-
if (runtime) await runtime.runScheduledMediaUsageTasks();
|
|
2661
|
-
else await runScheduledMediaUsageLane(db);
|
|
2662
|
-
};
|
|
2663
1476
|
scheduler.setSystemCleanup(async () => {
|
|
2664
1477
|
try {
|
|
2665
1478
|
const runtime = runtimeRef.current;
|
|
@@ -2683,13 +1496,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
2683
1496
|
}
|
|
2684
1497
|
await maybeRunScheduledBackup(db, storage ?? void 0);
|
|
2685
1498
|
await recordSchedulerHeartbeatSafely(db);
|
|
2686
|
-
if (!scheduler.setMediaUsageMaintenance) try {
|
|
2687
|
-
await runMediaUsageMaintenance();
|
|
2688
|
-
} catch (error) {
|
|
2689
|
-
console.error("[media-usage] Scheduled maintenance failed:", error);
|
|
2690
|
-
}
|
|
2691
1499
|
});
|
|
2692
|
-
scheduler.setMediaUsageMaintenance?.(runMediaUsageMaintenance);
|
|
2693
1500
|
scheduler.start();
|
|
2694
1501
|
}
|
|
2695
1502
|
} catch (error) {
|
|
@@ -3096,60 +1903,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3096
1903
|
* is two queries in practice; never N+1.
|
|
3097
1904
|
*/
|
|
3098
1905
|
async _buildManifest() {
|
|
3099
|
-
const manifestCollections = {};
|
|
3100
|
-
try {
|
|
3101
|
-
const dbCollections = await new SchemaRegistry(this.db).listCollectionsWithFields();
|
|
3102
|
-
for (const collection of dbCollections) {
|
|
3103
|
-
const fields = {};
|
|
3104
|
-
for (const field of collection.fields) {
|
|
3105
|
-
const entry = {
|
|
3106
|
-
kind: FIELD_TYPE_TO_KIND[field.type] ?? "string",
|
|
3107
|
-
label: field.label,
|
|
3108
|
-
required: field.required
|
|
3109
|
-
};
|
|
3110
|
-
entry.id = field.id;
|
|
3111
|
-
if (field.widget) entry.widget = field.widget;
|
|
3112
|
-
if (field.options) entry.options = field.options;
|
|
3113
|
-
if (field.validation?.options) entry.options = field.validation.options.map((v) => ({
|
|
3114
|
-
value: v,
|
|
3115
|
-
label: v.charAt(0).toUpperCase() + v.slice(1)
|
|
3116
|
-
}));
|
|
3117
|
-
if ((field.type === "repeater" || field.type === "file" || field.type === "image") && field.validation) entry.validation = { ...field.validation };
|
|
3118
|
-
fields[field.slug] = entry;
|
|
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
|
-
}
|
|
3136
|
-
manifestCollections[collection.slug] = {
|
|
3137
|
-
label: collection.label,
|
|
3138
|
-
labelSingular: collection.labelSingular || collection.label,
|
|
3139
|
-
supports: collection.supports || [],
|
|
3140
|
-
hasSeo: collection.hasSeo,
|
|
3141
|
-
urlPattern: collection.urlPattern,
|
|
3142
|
-
routable: collection.routable !== false,
|
|
3143
|
-
titleField: collection.titleField,
|
|
3144
|
-
dateField: collection.dateField,
|
|
3145
|
-
...collection.hidden ? { hidden: true } : {},
|
|
3146
|
-
listColumns: listColumns.length > 0 ? listColumns : void 0,
|
|
3147
|
-
fields
|
|
3148
|
-
};
|
|
3149
|
-
}
|
|
3150
|
-
} catch (error) {
|
|
3151
|
-
console.debug("EmDash: Could not load database collections:", error);
|
|
3152
|
-
}
|
|
1906
|
+
const manifestCollections = await buildManifestCollections({}, this.db);
|
|
3153
1907
|
const manifestPlugins = {};
|
|
3154
1908
|
for (const plugin of this.configuredPlugins) {
|
|
3155
1909
|
const status = this.pluginStates.get(plugin.id);
|
|
@@ -3232,7 +1986,8 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3232
1986
|
const i18nConfig = virtualConfig?.i18n ?? getI18nConfig();
|
|
3233
1987
|
const i18n = i18nConfig && i18nConfig.locales && i18nConfig.locales.length > 1 ? {
|
|
3234
1988
|
defaultLocale: i18nConfig.defaultLocale,
|
|
3235
|
-
locales: i18nConfig.locales
|
|
1989
|
+
locales: i18nConfig.locales,
|
|
1990
|
+
prefixDefaultLocale: i18nConfig.prefixDefaultLocale
|
|
3236
1991
|
} : void 0;
|
|
3237
1992
|
const registry = normalizeRegistryConfig(this.config.experimental?.registry) ?? void 0;
|
|
3238
1993
|
return {
|
|
@@ -3351,10 +2106,16 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3351
2106
|
}
|
|
3352
2107
|
async handleContentCreate(collection, body) {
|
|
3353
2108
|
let processedData = body.data;
|
|
3354
|
-
if (this.hooks.hasHooks("content:beforeSave"))
|
|
3355
|
-
|
|
2109
|
+
if (this.hooks.hasHooks("content:beforeSave")) try {
|
|
2110
|
+
processedData = (await this.hooks.runContentBeforeSave(body.data, collection, true)).content;
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
return beforeSaveFailure(error);
|
|
2113
|
+
}
|
|
2114
|
+
const sandboxResult = await this.runSandboxedBeforeSave(processedData, collection, true);
|
|
2115
|
+
if (!sandboxResult.success) return sandboxResult;
|
|
2116
|
+
processedData = sandboxResult.data;
|
|
3356
2117
|
processedData = await this.normalizeMediaFields(collection, processedData);
|
|
3357
|
-
const { validateContentData } = await import("../validation-
|
|
2118
|
+
const { validateContentData } = await import("../validation-BMImFJ11.mjs");
|
|
3358
2119
|
const validation = await validateContentData(this.db, collection, processedData, { partial: false });
|
|
3359
2120
|
if (!validation.ok) return {
|
|
3360
2121
|
success: false,
|
|
@@ -3394,10 +2155,16 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3394
2155
|
const { _rev: _discardedRev, ...bodyWithoutRev } = body;
|
|
3395
2156
|
let processedData = bodyWithoutRev.data;
|
|
3396
2157
|
if (bodyWithoutRev.data) {
|
|
3397
|
-
if (this.hooks.hasHooks("content:beforeSave"))
|
|
3398
|
-
|
|
2158
|
+
if (this.hooks.hasHooks("content:beforeSave")) try {
|
|
2159
|
+
processedData = (await this.hooks.runContentBeforeSave(bodyWithoutRev.data, collection, false, resolvedItem?.id)).content;
|
|
2160
|
+
} catch (error) {
|
|
2161
|
+
return beforeSaveFailure(error);
|
|
2162
|
+
}
|
|
2163
|
+
const sandboxResult = await this.runSandboxedBeforeSave(processedData, collection, false, resolvedItem?.id);
|
|
2164
|
+
if (!sandboxResult.success) return sandboxResult;
|
|
2165
|
+
processedData = sandboxResult.data;
|
|
3399
2166
|
processedData = await this.normalizeMediaFields(collection, processedData);
|
|
3400
|
-
const { validateContentData } = await import("../validation-
|
|
2167
|
+
const { validateContentData } = await import("../validation-BMImFJ11.mjs");
|
|
3401
2168
|
const validation = await validateContentData(this.db, collection, processedData, { partial: true });
|
|
3402
2169
|
if (!validation.ok) return {
|
|
3403
2170
|
success: false,
|
|
@@ -3543,8 +2310,8 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3543
2310
|
if (result.success) this.runAfterDeleteHooks(id, collection, true);
|
|
3544
2311
|
return result;
|
|
3545
2312
|
}
|
|
3546
|
-
async handleContentCountTrashed(collection) {
|
|
3547
|
-
return handleContentCountTrashed(this.db, collection);
|
|
2313
|
+
async handleContentCountTrashed(collection, params = {}) {
|
|
2314
|
+
return handleContentCountTrashed(this.db, collection, params);
|
|
3548
2315
|
}
|
|
3549
2316
|
async handleContentDuplicate(collection, id, authorId) {
|
|
3550
2317
|
const result = await handleContentDuplicate(this.db, collection, id, authorId);
|
|
@@ -3557,8 +2324,8 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3557
2324
|
if (result.success && result.data) this.runAfterPublishHooks(contentItemToRecord(result.data.item), collection);
|
|
3558
2325
|
return result;
|
|
3559
2326
|
}
|
|
3560
|
-
async handleContentUnpublish(collection, id) {
|
|
3561
|
-
const result = await handleContentUnpublish(this.db, collection, id);
|
|
2327
|
+
async handleContentUnpublish(collection, id, options = {}) {
|
|
2328
|
+
const result = await handleContentUnpublish(this.db, collection, id, options);
|
|
3562
2329
|
if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
|
|
3563
2330
|
if (result.success && result.data) this.runAfterUnpublishHooks(contentItemToRecord(result.data.item), collection);
|
|
3564
2331
|
return result;
|
|
@@ -3578,8 +2345,8 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3578
2345
|
async handleContentCountScheduled(collection) {
|
|
3579
2346
|
return handleContentCountScheduled(this.db, collection);
|
|
3580
2347
|
}
|
|
3581
|
-
async handleContentDiscardDraft(collection, id) {
|
|
3582
|
-
const result = await handleContentDiscardDraft(this.db, collection, id);
|
|
2348
|
+
async handleContentDiscardDraft(collection, id, options = {}) {
|
|
2349
|
+
const result = await handleContentDiscardDraft(this.db, collection, id, options);
|
|
3583
2350
|
if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
|
|
3584
2351
|
return result;
|
|
3585
2352
|
}
|
|
@@ -3630,6 +2397,11 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3630
2397
|
if (result.success) invalidateSiteSettingsCache();
|
|
3631
2398
|
return result;
|
|
3632
2399
|
}
|
|
2400
|
+
async handleMediaReplaceMetadata(id, expectedStorageKey, input) {
|
|
2401
|
+
const result = await handleMediaReplaceMetadata(this.db, id, expectedStorageKey, input);
|
|
2402
|
+
if (result.success) invalidateSiteSettingsCache();
|
|
2403
|
+
return result;
|
|
2404
|
+
}
|
|
3633
2405
|
async handleMediaDelete(id) {
|
|
3634
2406
|
const result = await handleMediaDelete(this.db, id);
|
|
3635
2407
|
if (result.success) invalidateSiteSettingsCache();
|
|
@@ -3713,11 +2485,11 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3713
2485
|
}
|
|
3714
2486
|
async refreshContentUsageAfterSuccessfulWrite(collection, contentIds) {
|
|
3715
2487
|
for (const contentId of new Set(contentIds)) try {
|
|
3716
|
-
if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive")
|
|
2488
|
+
if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") continue;
|
|
3717
2489
|
await refreshContentMediaUsageAfterWrite(this.db, collection, contentId);
|
|
3718
2490
|
} catch (error) {
|
|
3719
2491
|
console.error(`[media-usage] Failed after content write ${collection}/${contentId}:`, error);
|
|
3720
|
-
|
|
2492
|
+
continue;
|
|
3721
2493
|
}
|
|
3722
2494
|
}
|
|
3723
2495
|
async deleteContentUsageAfterSuccessfulPermanentDelete(collection, contentId) {
|
|
@@ -3938,7 +2710,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3938
2710
|
const value = result[field.slug];
|
|
3939
2711
|
if (value == null) continue;
|
|
3940
2712
|
try {
|
|
3941
|
-
const normalized = await normalizeMediaValue(value, getProvider);
|
|
2713
|
+
const normalized = field.type === "image" ? await normalizeImageValue(value, getProvider) : await normalizeMediaValue(value, getProvider);
|
|
3942
2714
|
if (normalized) result[field.slug] = normalized;
|
|
3943
2715
|
} catch {}
|
|
3944
2716
|
}
|
|
@@ -3955,7 +2727,7 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3955
2727
|
const subValue = normalizedItem[slug];
|
|
3956
2728
|
if (subValue == null) continue;
|
|
3957
2729
|
try {
|
|
3958
|
-
const normalized = await
|
|
2730
|
+
const normalized = await normalizeImageValue(subValue, getProvider);
|
|
3959
2731
|
if (normalized) normalizedItem[slug] = normalized;
|
|
3960
2732
|
} catch {}
|
|
3961
2733
|
}
|
|
@@ -3964,27 +2736,61 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
3964
2736
|
}
|
|
3965
2737
|
return result;
|
|
3966
2738
|
}
|
|
3967
|
-
async runSandboxedBeforeSave(content, collection, isNew) {
|
|
2739
|
+
async runSandboxedBeforeSave(content, collection, isNew, contentId) {
|
|
3968
2740
|
let result = content;
|
|
3969
2741
|
for (const [pluginKey, plugin] of this.sandboxedPlugins) {
|
|
3970
2742
|
const [id] = pluginKey.split(":");
|
|
3971
2743
|
if (!id || !this.isPluginEnabled(id)) continue;
|
|
3972
2744
|
try {
|
|
3973
|
-
const
|
|
2745
|
+
const event = {
|
|
3974
2746
|
content: result,
|
|
3975
2747
|
collection,
|
|
3976
2748
|
isNew
|
|
3977
|
-
}
|
|
2749
|
+
};
|
|
2750
|
+
if (contentId !== void 0) event.id = contentId;
|
|
2751
|
+
const hookResult = await plugin.invokeHook("content:beforeSave", event);
|
|
2752
|
+
const inspection = inspectSandboxHookResult(hookResult);
|
|
2753
|
+
if (inspection.kind === "error") return {
|
|
2754
|
+
success: false,
|
|
2755
|
+
error: {
|
|
2756
|
+
code: ErrorCode.SAVE_REJECTED,
|
|
2757
|
+
message: "Save rejected by a sandboxed plugin",
|
|
2758
|
+
details: {
|
|
2759
|
+
pluginId: id,
|
|
2760
|
+
reason: inspection.error.reason
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
if (inspection.kind === "malformed") {
|
|
2765
|
+
console.error(`EmDash: Sandboxed plugin ${id} returned an invalid hook result`);
|
|
2766
|
+
return {
|
|
2767
|
+
success: false,
|
|
2768
|
+
error: {
|
|
2769
|
+
code: ErrorCode.CONTENT_HOOK_ERROR,
|
|
2770
|
+
message: "A plugin hook failed while saving content"
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
3978
2774
|
if (hookResult && typeof hookResult === "object" && !Array.isArray(hookResult)) {
|
|
3979
2775
|
const record = {};
|
|
3980
2776
|
for (const [k, v] of Object.entries(hookResult)) record[k] = v;
|
|
3981
2777
|
result = record;
|
|
3982
2778
|
}
|
|
3983
2779
|
} catch (error) {
|
|
3984
|
-
console.error(`EmDash: Sandboxed plugin ${id} beforeSave hook
|
|
2780
|
+
console.error(`EmDash: Sandboxed plugin ${id} beforeSave hook failed:`, error);
|
|
2781
|
+
return {
|
|
2782
|
+
success: false,
|
|
2783
|
+
error: {
|
|
2784
|
+
code: ErrorCode.CONTENT_HOOK_ERROR,
|
|
2785
|
+
message: "A plugin hook failed while saving content"
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
3985
2788
|
}
|
|
3986
2789
|
}
|
|
3987
|
-
return
|
|
2790
|
+
return {
|
|
2791
|
+
success: true,
|
|
2792
|
+
data: result
|
|
2793
|
+
};
|
|
3988
2794
|
}
|
|
3989
2795
|
async runSandboxedBeforeDelete(id, collection) {
|
|
3990
2796
|
for (const [pluginKey, plugin] of this.sandboxedPlugins) {
|
|
@@ -4214,6 +3020,33 @@ var EmDashRuntime = class EmDashRuntime {
|
|
|
4214
3020
|
return status === void 0 || status === "active";
|
|
4215
3021
|
}
|
|
4216
3022
|
};
|
|
3023
|
+
/**
|
|
3024
|
+
* Normalize an image field value together with its `darkVariant`. The media
|
|
3025
|
+
* normalizer only keeps the media item's own keys, so the variant is normalized
|
|
3026
|
+
* separately and reattached.
|
|
3027
|
+
*/
|
|
3028
|
+
async function normalizeImageValue(value, getProvider) {
|
|
3029
|
+
const primary = await normalizePrimaryImageValue(value, getProvider);
|
|
3030
|
+
if (!primary || !isRecord(value) || value.darkVariant == null) return primary;
|
|
3031
|
+
const darkVariant = await normalizeMediaValue(value.darkVariant, getProvider);
|
|
3032
|
+
return darkVariant ? {
|
|
3033
|
+
...primary,
|
|
3034
|
+
darkVariant
|
|
3035
|
+
} : primary;
|
|
3036
|
+
}
|
|
3037
|
+
/**
|
|
3038
|
+
* Normalize the primary image of an image field value.
|
|
3039
|
+
*
|
|
3040
|
+
* A legacy string URL that the admin upgraded to `{ id: "", src: url }` so it
|
|
3041
|
+
* can carry a dark variant still has to normalize as a string: as an object it
|
|
3042
|
+
* counts as local media, which strips `src` and leaves nothing behind. The
|
|
3043
|
+
* upgrade outlives the variant — an editor can add one and remove it again —
|
|
3044
|
+
* so the shape decides, not the presence of `darkVariant`.
|
|
3045
|
+
*/
|
|
3046
|
+
async function normalizePrimaryImageValue(value, getProvider) {
|
|
3047
|
+
if (isRecord(value) && !value.id && typeof value.src === "string" && !value.provider) return normalizeMediaValue(value.src, getProvider);
|
|
3048
|
+
return normalizeMediaValue(value, getProvider);
|
|
3049
|
+
}
|
|
4217
3050
|
|
|
4218
3051
|
//#endregion
|
|
4219
3052
|
//#region src/media/url.ts
|
|
@@ -4675,15 +3508,6 @@ async function runScheduledTasks(options = {}) {
|
|
|
4675
3508
|
if (!config) return { published: [] };
|
|
4676
3509
|
return runOutsideRequest(config, (runtime) => runtime.runScheduledTasks(options));
|
|
4677
3510
|
}
|
|
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
|
-
}
|
|
4687
3511
|
/**
|
|
4688
3512
|
* Run a callback against the EmDash runtime outside any HTTP request — from a
|
|
4689
3513
|
* Cloudflare Queue consumer, a `scheduled()` handler, or any other
|
|
@@ -4966,7 +3790,7 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
4966
3790
|
if (migrationMode === "auto" && !isSetupVerified() && !context.isPrerendered) {
|
|
4967
3791
|
const t0 = performance.now();
|
|
4968
3792
|
try {
|
|
4969
|
-
const { getDb } = await import("../loader-
|
|
3793
|
+
const { getDb } = await import("../loader-Z8vl7zTq.mjs").then((n) => n.c);
|
|
4970
3794
|
await (await getDb()).selectFrom("_emdash_migrations").selectAll().limit(1).execute();
|
|
4971
3795
|
markSetupVerified();
|
|
4972
3796
|
} catch (error) {
|
|
@@ -5102,6 +3926,7 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
5102
3926
|
handleMediaGet: runtime.handleMediaGet.bind(runtime),
|
|
5103
3927
|
handleMediaCreate: runtime.handleMediaCreate.bind(runtime),
|
|
5104
3928
|
handleMediaUpdate: runtime.handleMediaUpdate.bind(runtime),
|
|
3929
|
+
...runtime.handleMediaReplaceMetadata ? { handleMediaReplaceMetadata: runtime.handleMediaReplaceMetadata.bind(runtime) } : {},
|
|
5105
3930
|
handleMediaDelete: runtime.handleMediaDelete.bind(runtime),
|
|
5106
3931
|
handleRevisionList: runtime.handleRevisionList.bind(runtime),
|
|
5107
3932
|
handleRevisionGet: runtime.handleRevisionGet.bind(runtime),
|
|
@@ -5217,5 +4042,5 @@ const onRequest = defineMiddleware(async (context, next) => {
|
|
|
5217
4042
|
});
|
|
5218
4043
|
|
|
5219
4044
|
//#endregion
|
|
5220
|
-
export { onRequest as default, onRequest,
|
|
4045
|
+
export { onRequest as default, onRequest, runScheduledTasks, withEmDashRuntime };
|
|
5221
4046
|
//# sourceMappingURL=middleware.mjs.map
|