emdash 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-DAAjOID4.d.mts → adapters-hUy8vOB-.d.mts} +1 -1
- package/dist/{adapters-DAAjOID4.d.mts.map → adapters-hUy8vOB-.d.mts.map} +1 -1
- package/dist/{allowed-origins-D5FxMUo8.mjs → allowed-origins-CRsJ6BPM.mjs} +2 -2
- package/dist/{allowed-origins-D5FxMUo8.mjs.map → allowed-origins-CRsJ6BPM.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +17 -16
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +3 -3
- package/dist/{api-BMfveMoB.mjs → api-BFURZn1i.mjs} +104 -38
- package/dist/api-BFURZn1i.mjs.map +1 -0
- package/dist/{api-tokens-D6ppjIVi.mjs → api-tokens-DvqeZmVq.mjs} +2 -2
- package/dist/{api-tokens-D6ppjIVi.mjs.map → api-tokens-DvqeZmVq.mjs.map} +1 -1
- package/dist/{apply--IVNSdXn.mjs → apply-Bu-8nYHL.mjs} +113 -76
- package/dist/apply-Bu-8nYHL.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -11
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +13 -5
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -9
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +52 -12
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.mjs +5 -5
- package/dist/astro/middleware/request-context.mjs +2 -2
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +2 -9
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +125 -71
- 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 +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -14
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
- package/dist/astro/routes/api/admin/bylines/index.mjs +14 -14
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- 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/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +38 -37
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +38 -37
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +37 -36
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +36 -35
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +36 -35
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +37 -36
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +36 -35
- 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 +36 -35
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +36 -35
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +36 -35
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +37 -36
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +36 -35
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +37 -36
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +36 -35
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +36 -35
- 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 +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +36 -35
- 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 +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
- 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 +5 -5
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
- package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +9 -9
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +7 -7
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +11 -11
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
- package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
- package/dist/astro/routes/api/auth/signup/request.mjs +9 -9
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -10
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
- 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 +3 -3
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +19 -11
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -40
- package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -407
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -9
- package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +19 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +97 -79
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +19 -11
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +40 -2
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +519 -39
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.d.mts.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +14 -5
- package/dist/astro/routes/api/manifest.mjs.map +1 -1
- package/dist/astro/routes/api/mcp.mjs +39 -39
- package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
- package/dist/astro/routes/api/media/_id_.mjs +7 -7
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.mjs +8 -8
- package/dist/astro/routes/api/media.mjs +9 -9
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +9 -9
- package/dist/astro/routes/api/menus/_name_/items.mjs +9 -9
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +9 -9
- package/dist/astro/routes/api/menus/_name_/translations.mjs +9 -9
- package/dist/astro/routes/api/menus/_name_.mjs +9 -9
- package/dist/astro/routes/api/menus/index.mjs +9 -9
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
- package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
- package/dist/astro/routes/api/oauth/register.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +4 -4
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
- package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
- package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
- package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
- package/dist/astro/routes/api/redirects/index.mjs +10 -10
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +36 -35
- 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 +36 -35
- 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 +36 -35
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +36 -35
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +36 -35
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +7 -6
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +36 -35
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +36 -35
- 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 +6 -6
- 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/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +12 -12
- package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
- package/dist/astro/routes/api/setup/admin.mjs +10 -10
- package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +28 -25
- 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 +26 -25
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +4 -4
- package/dist/astro/routes/api/snapshot.mjs +6 -6
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
- package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -9
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -9
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/index.mjs +9 -9
- package/dist/astro/routes/api/widget-components.mjs +3 -3
- package/dist/astro/routes/robots.txt.mjs +6 -6
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
- package/dist/astro/routes/sitemap.xml.mjs +7 -7
- package/dist/astro/types.d.mts +14 -12
- 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-BZnOGJFo.mjs → authorize-Dz88F_Fw.mjs} +2 -2
- package/dist/{authorize-BZnOGJFo.mjs.map → authorize-Dz88F_Fw.mjs.map} +1 -1
- package/dist/{byline-LL8lyqdb.mjs → byline-Bm8ZgUP9.mjs} +6 -6
- package/dist/{byline-LL8lyqdb.mjs.map → byline-Bm8ZgUP9.mjs.map} +1 -1
- package/dist/{byline-fields-eyJMblSr.mjs → byline-fields-2Lp5Hbgf.mjs} +18 -3
- package/dist/byline-fields-2Lp5Hbgf.mjs.map +1 -0
- package/dist/{byline-fields-DaMKzkhO.mjs → byline-fields-CIx_GuN4.mjs} +1 -1
- package/dist/{byline-fields-DaMKzkhO.mjs.map → byline-fields-CIx_GuN4.mjs.map} +1 -1
- package/dist/{byline-fields-KD3JFRuH.d.mts → byline-fields-Cgrt9-aL.d.mts} +18 -3
- package/dist/{byline-fields-KD3JFRuH.d.mts.map → byline-fields-Cgrt9-aL.d.mts.map} +1 -1
- package/dist/{bylines-BmJ7Vg7J.mjs → bylines-6tm9mgQb.mjs} +4 -4
- package/dist/{bylines-BmJ7Vg7J.mjs.map → bylines-6tm9mgQb.mjs.map} +1 -1
- package/dist/{bylines-DE47vP8d.mjs → bylines-Woq87129.mjs} +6 -6
- package/dist/{bylines-DE47vP8d.mjs.map → bylines-Woq87129.mjs.map} +1 -1
- package/dist/{cache-D9jF5bYw.mjs → cache-CEBAZjir.mjs} +2 -2
- package/dist/{cache-D9jF5bYw.mjs.map → cache-CEBAZjir.mjs.map} +1 -1
- package/dist/{challenge-store-LhiqMccz.mjs → challenge-store-anhwVER0.mjs} +1 -1
- package/dist/{challenge-store-LhiqMccz.mjs.map → challenge-store-anhwVER0.mjs.map} +1 -1
- package/dist/{chunks-OCG163rN.mjs → chunks-B2isWQhr.mjs} +2 -2
- package/dist/{chunks-OCG163rN.mjs.map → chunks-B2isWQhr.mjs.map} +1 -1
- package/dist/cli/index.mjs +23 -22
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/{comment-D9BLDpZI.mjs → comment-CzeatwBV.mjs} +3 -3
- package/dist/{comment-D9BLDpZI.mjs.map → comment-CzeatwBV.mjs.map} +1 -1
- package/dist/{comment-reaction-Drl9SkNu.mjs → comment-reaction-Cj9QJY7I.mjs} +2 -2
- package/dist/{comment-reaction-Drl9SkNu.mjs.map → comment-reaction-Cj9QJY7I.mjs.map} +1 -1
- package/dist/{comments-BjJ9SKGU.mjs → comments-BLE-jv5k.mjs} +3 -3
- package/dist/{comments-BjJ9SKGU.mjs.map → comments-BLE-jv5k.mjs.map} +1 -1
- package/dist/{components-CYt4uVK9.mjs → components-B_sATNUc.mjs} +1 -1
- package/dist/{components-CYt4uVK9.mjs.map → components-B_sATNUc.mjs.map} +1 -1
- package/dist/{content-ncUWWjna.mjs → content-DhgEth4k.mjs} +4 -4
- package/dist/{content-ncUWWjna.mjs.map → content-DhgEth4k.mjs.map} +1 -1
- package/dist/content-refresh-CA4uKwzH.mjs +1531 -0
- package/dist/content-refresh-CA4uKwzH.mjs.map +1 -0
- package/dist/{context-C_g-iJVA.mjs → context-CUsR9dXL.mjs} +76 -54
- package/dist/context-CUsR9dXL.mjs.map +1 -0
- package/dist/{cron-C5LVoNmP.mjs → cron-v4FTEFR_.mjs} +1 -1
- package/dist/{cron-C5LVoNmP.mjs.map → cron-v4FTEFR_.mjs.map} +1 -1
- package/dist/{dashboard-CotyqQrW.mjs → dashboard-EvEKF5en.mjs} +4 -4
- package/dist/{dashboard-CotyqQrW.mjs.map → dashboard-EvEKF5en.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{default-CeRG-Ot4.mjs → default-CM_D83Mg.mjs} +1 -1
- package/dist/{default-CeRG-Ot4.mjs.map → default-CM_D83Mg.mjs.map} +1 -1
- package/dist/{device-flow-DENDCQ9F.mjs → device-flow-CfGP18Uw.mjs} +4 -4
- package/dist/{device-flow-DENDCQ9F.mjs.map → device-flow-CfGP18Uw.mjs.map} +1 -1
- package/dist/{email-console-DJP32ucW.mjs → email-console-CIf4Lfvv.mjs} +1 -1
- package/dist/{email-console-DJP32ucW.mjs.map → email-console-CIf4Lfvv.mjs.map} +1 -1
- package/dist/{error-rVGiqWDt.mjs → error-CkAseSqZ.mjs} +2 -2
- package/dist/{error-rVGiqWDt.mjs.map → error-CkAseSqZ.mjs.map} +1 -1
- package/dist/{escape-Bjio4ZsM.mjs → escape-DqD0aCKu.mjs} +1 -1
- package/dist/{escape-Bjio4ZsM.mjs.map → escape-DqD0aCKu.mjs.map} +1 -1
- package/dist/{field-defs-cache-ljXzJCfc.mjs → field-defs-cache-WUI88LX5.mjs} +2 -2
- package/dist/{field-defs-cache-ljXzJCfc.mjs.map → field-defs-cache-WUI88LX5.mjs.map} +1 -1
- package/dist/{fts-manager-BbCCbEKA.mjs → fts-manager-BbFaF_V8.mjs} +2 -2
- package/dist/{fts-manager-BbCCbEKA.mjs.map → fts-manager-BbFaF_V8.mjs.map} +1 -1
- package/dist/{import-Bb1T9WJS.mjs → import-fYLf14Co.mjs} +514 -25
- package/dist/import-fYLf14Co.mjs.map +1 -0
- package/dist/{index-BRNabKrx.d.mts → index-B9sIxwe4.d.mts} +2420 -2379
- package/dist/index-B9sIxwe4.d.mts.map +1 -0
- package/dist/{index-Dt0OKJB4.d.mts → index-Bm-Gy8Mh.d.mts} +25 -4
- package/dist/index-Bm-Gy8Mh.d.mts.map +1 -0
- package/dist/index.d.mts +18 -17
- package/dist/index.mjs +54 -53
- package/dist/{load-DMjXghbZ.mjs → load-Djn3HWQS.mjs} +2 -2
- package/dist/{load-DMjXghbZ.mjs.map → load-Djn3HWQS.mjs.map} +1 -1
- package/dist/{loader-BA_zhkS_.mjs → loader-CB6YHeul.mjs} +6 -5
- package/dist/loader-CB6YHeul.mjs.map +1 -0
- package/dist/{manifest-schema-kPGX7VS-.mjs → manifest-schema-BHlgZM1W.mjs} +1 -1
- package/dist/{manifest-schema-kPGX7VS-.mjs.map → manifest-schema-BHlgZM1W.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/local-runtime.d.mts +12 -11
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +5 -5
- package/dist/{media-C5t_eCnk.mjs → media-BrXIaMFN.mjs} +2 -2
- package/dist/{media-C5t_eCnk.mjs.map → media-BrXIaMFN.mjs.map} +1 -1
- package/dist/{media-allowlist-Du8t1u6a.mjs → media-allowlist-CRHEeJ_i.mjs} +1 -1
- package/dist/{media-allowlist-Du8t1u6a.mjs.map → media-allowlist-CRHEeJ_i.mjs.map} +1 -1
- package/dist/{media-url-O4rm9-SQ.mjs → media-url-CKi5XSw4.mjs} +1 -1
- package/dist/{media-url-O4rm9-SQ.mjs.map → media-url-CKi5XSw4.mjs.map} +1 -1
- package/dist/{menus-CBb6UrMU.mjs → menus-Cfivc2Ad.mjs} +3 -3
- package/dist/{menus-CBb6UrMU.mjs.map → menus-Cfivc2Ad.mjs.map} +1 -1
- package/dist/{menus-CYH09a41.mjs → menus-CsZ2ZEK2.mjs} +16 -12
- package/dist/{menus-CYH09a41.mjs.map → menus-CsZ2ZEK2.mjs.map} +1 -1
- package/dist/{mode-BB0F8xTC.mjs → mode-BM9QgEmt.mjs} +1 -1
- package/dist/{mode-BB0F8xTC.mjs.map → mode-BM9QgEmt.mjs.map} +1 -1
- package/dist/{oauth-authorization-CsvzIp_F.mjs → oauth-authorization-CXWkIGT4.mjs} +4 -4
- package/dist/{oauth-authorization-CsvzIp_F.mjs.map → oauth-authorization-CXWkIGT4.mjs.map} +1 -1
- package/dist/{oauth-clients-C9SYwEbZ.mjs → oauth-clients-BmTI5rsl.mjs} +1 -1
- package/dist/{oauth-clients-C9SYwEbZ.mjs.map → oauth-clients-BmTI5rsl.mjs.map} +1 -1
- package/dist/{oauth-state-store---zrApfB.mjs → oauth-state-store-YLFLcH5F.mjs} +1 -1
- package/dist/{oauth-state-store---zrApfB.mjs.map → oauth-state-store-YLFLcH5F.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-SHsWRlG9.mjs → oauth-user-lookup-Bsc_GsX-.mjs} +1 -1
- package/dist/{oauth-user-lookup-SHsWRlG9.mjs.map → oauth-user-lookup-Bsc_GsX-.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{object-cache-CHbHv83-.mjs → object-cache-Bok5j2ae.mjs} +43 -3
- package/dist/object-cache-Bok5j2ae.mjs.map +1 -0
- package/dist/{options-41nCWqi9.d.mts → options-iMpRRxIe.d.mts} +3 -3
- package/dist/{options-41nCWqi9.d.mts.map → options-iMpRRxIe.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-BwAdS7FZ.mjs → parse-GmjecGct.mjs} +2 -2
- package/dist/{parse-BwAdS7FZ.mjs.map → parse-GmjecGct.mjs.map} +1 -1
- package/dist/{passkey-config-C0YfSBko.mjs → passkey-config-De0ahnRg.mjs} +1 -1
- package/dist/{passkey-config-C0YfSBko.mjs.map → passkey-config-De0ahnRg.mjs.map} +1 -1
- package/dist/{placeholder-Cuce9U-m.d.mts → placeholder-BSKwzRe4.d.mts} +1 -1
- package/dist/{placeholder-Cuce9U-m.d.mts.map → placeholder-BSKwzRe4.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +10 -9
- package/dist/plugin-utils.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{portable-text-BICg8bTk.mjs → portable-text-cCa0cGV6.mjs} +1 -1
- package/dist/{portable-text-BICg8bTk.mjs.map → portable-text-cCa0cGV6.mjs.map} +1 -1
- package/dist/{preview-DKGCt2_p.mjs → preview-SSXNVPAC.mjs} +2 -2
- package/dist/{preview-DKGCt2_p.mjs.map → preview-SSXNVPAC.mjs.map} +1 -1
- package/dist/{public-url-CTVqgMmg.mjs → public-url-Danv47cS.mjs} +1 -1
- package/dist/{public-url-CTVqgMmg.mjs.map → public-url-Danv47cS.mjs.map} +1 -1
- package/dist/{query-BpQb-blG.mjs → query-ByGq6ver.mjs} +14 -14
- package/dist/{query-BpQb-blG.mjs.map → query-ByGq6ver.mjs.map} +1 -1
- package/dist/{rate-limit-Dap6HMNM.mjs → rate-limit-C_Lf63Dd.mjs} +2 -2
- package/dist/{rate-limit-Dap6HMNM.mjs.map → rate-limit-C_Lf63Dd.mjs.map} +1 -1
- package/dist/{redirect-CS-PHtNh.mjs → redirect-CoW3G-0U.mjs} +1 -1
- package/dist/{redirect-CS-PHtNh.mjs.map → redirect-CoW3G-0U.mjs.map} +1 -1
- package/dist/{redirect-_M0trWoo.mjs → redirect-IyfNirgS.mjs} +2 -2
- package/dist/{redirect-_M0trWoo.mjs.map → redirect-IyfNirgS.mjs.map} +1 -1
- package/dist/{redirects-w0V9vj_j.mjs → redirects-Bs1g42Ri.mjs} +4 -4
- package/dist/{redirects-w0V9vj_j.mjs.map → redirects-Bs1g42Ri.mjs.map} +1 -1
- package/dist/{redirects-Dcbuisoj.mjs → redirects-DXEXBAII.mjs} +2 -2
- package/dist/{redirects-Dcbuisoj.mjs.map → redirects-DXEXBAII.mjs.map} +1 -1
- package/dist/{registry-g8yYt4cC.mjs → registry-B65mUtPa.mjs} +122 -79
- package/dist/registry-B65mUtPa.mjs.map +1 -0
- package/dist/{request-meta-CmS1tDFf.mjs → request-meta-DbvXaAvU.mjs} +2 -2
- package/dist/{request-meta-CmS1tDFf.mjs.map → request-meta-DbvXaAvU.mjs.map} +1 -1
- package/dist/{resolve-C7I0qiR0.mjs → resolve-DR3wqMTy.mjs} +1 -1
- package/dist/{resolve-C7I0qiR0.mjs.map → resolve-DR3wqMTy.mjs.map} +1 -1
- package/dist/{runner-BbR3DfrL.d.mts → runner-DvFWoz8-.d.mts} +2 -2
- package/dist/{runner-BbR3DfrL.d.mts.map → runner-DvFWoz8-.d.mts.map} +1 -1
- package/dist/{runner-DPdt_Wpa.mjs → runner-K8XK101B.mjs} +351 -199
- package/dist/runner-K8XK101B.mjs.map +1 -0
- package/dist/runtime.d.mts +11 -10
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/scheduled-publish-Drffrf2a.d.mts +11 -0
- package/dist/scheduled-publish-Drffrf2a.d.mts.map +1 -0
- package/dist/{schema-DoLd3j2e.mjs → schema-Blhoxp3P.mjs} +5 -5
- package/dist/{schema-DoLd3j2e.mjs.map → schema-Blhoxp3P.mjs.map} +1 -1
- package/dist/{search-BR0ZeZmh.mjs → search-DZN2HZ-V.mjs} +6 -4
- package/dist/search-DZN2HZ-V.mjs.map +1 -0
- package/dist/{secrets-BSf9pRRY.mjs → secrets-kp760LlD.mjs} +2 -2
- package/dist/{secrets-BSf9pRRY.mjs.map → secrets-kp760LlD.mjs.map} +1 -1
- package/dist/{sections-5J2YJPqJ.mjs → sections-CpF5SaGr.mjs} +3 -3
- package/dist/{sections-5J2YJPqJ.mjs.map → sections-CpF5SaGr.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +19 -18
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.mjs +1 -1
- package/dist/{seo-BkhuuaaE.mjs → seo-89-53DDs.mjs} +1 -1
- package/dist/{seo-BkhuuaaE.mjs.map → seo-89-53DDs.mjs.map} +1 -1
- package/dist/{seo-DKeXHkzE.mjs → seo-DXu2UQnC.mjs} +3 -3
- package/dist/{seo-DKeXHkzE.mjs.map → seo-DXu2UQnC.mjs.map} +1 -1
- package/dist/{service-BbO2s8gT.mjs → service-DQmiBMzt.mjs} +3 -3
- package/dist/{service-BbO2s8gT.mjs.map → service-DQmiBMzt.mjs.map} +1 -1
- package/dist/{session-user-B8aLtKAH.mjs → session-user-Cascbax7.mjs} +1 -1
- package/dist/{session-user-B8aLtKAH.mjs.map → session-user-Cascbax7.mjs.map} +1 -1
- package/dist/{settings-BMNr9Dmi.mjs → settings-rZyHEcNT.mjs} +3 -3
- package/dist/{settings-BMNr9Dmi.mjs.map → settings-rZyHEcNT.mjs.map} +1 -1
- package/dist/{settings-DJeki-97.mjs → settings-zhmdKyJQ.mjs} +5 -5
- package/dist/{settings-DJeki-97.mjs.map → settings-zhmdKyJQ.mjs.map} +1 -1
- package/dist/{setup-complete-gEiySUc-.mjs → setup-complete-Cb3LRyzy.mjs} +1 -1
- package/dist/{setup-complete-gEiySUc-.mjs.map → setup-complete-Cb3LRyzy.mjs.map} +1 -1
- package/dist/{setup-nonce-DzS50zme.mjs → setup-nonce-Z8x0tHf8.mjs} +1 -1
- package/dist/{setup-nonce-DzS50zme.mjs.map → setup-nonce-Z8x0tHf8.mjs.map} +1 -1
- package/dist/{single-flight-cache-Cdfkic3t.mjs → single-flight-cache-BG3B_GhN.mjs} +1 -1
- package/dist/{single-flight-cache-Cdfkic3t.mjs.map → single-flight-cache-BG3B_GhN.mjs.map} +1 -1
- package/dist/{site-url-BLebyON8.mjs → site-url-BBfDTgZB.mjs} +1 -1
- package/dist/{site-url-BLebyON8.mjs.map → site-url-BBfDTgZB.mjs.map} +1 -1
- package/dist/{ssrf-BRKb343l.mjs → ssrf-1g7usGBg.mjs} +1 -1
- package/dist/{ssrf-BRKb343l.mjs.map → ssrf-1g7usGBg.mjs.map} +1 -1
- package/dist/{status-vUK0SA17.mjs → status-CqJQX1QN.mjs} +1 -1
- package/dist/{status-vUK0SA17.mjs.map → status-CqJQX1QN.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-CM_fUqLU.mjs → taxonomies-BGBd16Eu.mjs} +4 -4
- package/dist/{taxonomies-CM_fUqLU.mjs.map → taxonomies-BGBd16Eu.mjs.map} +1 -1
- package/dist/{taxonomies-D13i2ctt.mjs → taxonomies-Cnb4F-Pb.mjs} +7 -7
- package/dist/{taxonomies-D13i2ctt.mjs.map → taxonomies-Cnb4F-Pb.mjs.map} +1 -1
- package/dist/{taxonomy-BU0LjdVb.mjs → taxonomy-BxGxZZdu.mjs} +4 -4
- package/dist/{taxonomy-BU0LjdVb.mjs.map → taxonomy-BxGxZZdu.mjs.map} +1 -1
- package/dist/{tokens-LeuXF9gG.mjs → tokens-BGT0TUUo.mjs} +1 -1
- package/dist/{tokens-LeuXF9gG.mjs.map → tokens-BGT0TUUo.mjs.map} +1 -1
- package/dist/{transport-BZ-03aFT.d.mts → transport-BMDWHSgZ.d.mts} +1 -1
- package/dist/{transport-BZ-03aFT.d.mts.map → transport-BMDWHSgZ.d.mts.map} +1 -1
- package/dist/{transport-_2nBz7e9.mjs → transport-DPefPMcz.mjs} +1 -1
- package/dist/{transport-_2nBz7e9.mjs.map → transport-DPefPMcz.mjs.map} +1 -1
- package/dist/{trusted-proxy-DZY5WCn2.mjs → trusted-proxy-VTPRiX2s.mjs} +1 -1
- package/dist/{trusted-proxy-DZY5WCn2.mjs.map → trusted-proxy-VTPRiX2s.mjs.map} +1 -1
- package/dist/{types-ETmO_jQr.d.mts → types-BcLUUbbR.d.mts} +2 -2
- package/dist/{types-ETmO_jQr.d.mts.map → types-BcLUUbbR.d.mts.map} +1 -1
- package/dist/{types-BYnDBVwe.d.mts → types-CGbYowGQ.d.mts} +2 -2
- package/dist/{types-BYnDBVwe.d.mts.map → types-CGbYowGQ.d.mts.map} +1 -1
- package/dist/{types-BWqGVhFn.d.mts → types-CgvhU-hF.d.mts} +1 -1
- package/dist/{types-BWqGVhFn.d.mts.map → types-CgvhU-hF.d.mts.map} +1 -1
- package/dist/{types-fS7-VTdR.d.mts → types-ClUkdbWb.d.mts} +1 -1
- package/dist/{types-fS7-VTdR.d.mts.map → types-ClUkdbWb.d.mts.map} +1 -1
- package/dist/{types-CZgK3EMW.mjs → types-CuW0zWDA.mjs} +2 -2
- package/dist/{types-CZgK3EMW.mjs.map → types-CuW0zWDA.mjs.map} +1 -1
- package/dist/{types-BG8_XTLj.d.mts → types-DmHk7vAt.d.mts} +1 -1
- package/dist/{types-BG8_XTLj.d.mts.map → types-DmHk7vAt.d.mts.map} +1 -1
- package/dist/{types-CQAugunJ.mjs → types-Jo3UYd6b.mjs} +1 -1
- package/dist/{types-CQAugunJ.mjs.map → types-Jo3UYd6b.mjs.map} +1 -1
- package/dist/{types-xIfVRNLp.d.mts → types-_HFLRzTn.d.mts} +22 -1
- package/dist/types-_HFLRzTn.d.mts.map +1 -0
- package/dist/{types-DrYMm9s5.d.mts → types-ea9sAPck.d.mts} +1 -1
- package/dist/{types-DrYMm9s5.d.mts.map → types-ea9sAPck.d.mts.map} +1 -1
- package/dist/{types-CNlaBFzx.d.mts → types-g_aFIdW0.d.mts} +1 -1
- package/dist/{types-CNlaBFzx.d.mts.map → types-g_aFIdW0.d.mts.map} +1 -1
- package/dist/{types-CUsXGRVj.d.mts → types-vgEWF18A.d.mts} +19 -1
- package/dist/{types-CUsXGRVj.d.mts.map → types-vgEWF18A.d.mts.map} +1 -1
- package/dist/{user-DkG2jKCp.mjs → user-BjAOvU3Z.mjs} +3 -3
- package/dist/{user-DkG2jKCp.mjs.map → user-BjAOvU3Z.mjs.map} +1 -1
- package/dist/{utils-B7A57fm9.mjs → utils-BJXElxdw.mjs} +156 -7
- package/dist/utils-BJXElxdw.mjs.map +1 -0
- package/dist/{validate-CuJ1Ps9j.mjs → validate-CWSnKVIJ.mjs} +2 -2
- package/dist/{validate-CuJ1Ps9j.mjs.map → validate-CWSnKVIJ.mjs.map} +1 -1
- package/dist/{validate-CG0V0wNR.d.mts → validate-Z1b86_3B.d.mts} +11 -6
- package/dist/{validate-CG0V0wNR.d.mts.map → validate-Z1b86_3B.d.mts.map} +1 -1
- package/dist/{validation-CwUfTdGF.mjs → validation-c8WjhH8z.mjs} +8 -5
- package/dist/{validation-CwUfTdGF.mjs.map → validation-c8WjhH8z.mjs.map} +1 -1
- package/dist/version-CaccU7DM.mjs +7 -0
- package/dist/{version-BoNBItP_.mjs.map → version-CaccU7DM.mjs.map} +1 -1
- package/dist/{widgets-BfOZ-ajY.mjs → widgets-hWcIicX8.mjs} +3 -3
- package/dist/{widgets-BfOZ-ajY.mjs.map → widgets-hWcIicX8.mjs.map} +1 -1
- package/dist/wxr-taxonomies-0_uxSrmf.d.mts +34 -0
- package/dist/wxr-taxonomies-0_uxSrmf.d.mts.map +1 -0
- package/dist/wxr-taxonomies-BC3OYpvx.mjs +425 -0
- package/dist/wxr-taxonomies-BC3OYpvx.mjs.map +1 -0
- package/dist/{zod-generator-gFsRuNNy.mjs → zod-generator-CacrR-i8.mjs} +2 -2
- package/dist/{zod-generator-gFsRuNNy.mjs.map → zod-generator-CacrR-i8.mjs.map} +1 -1
- package/package.json +6 -6
- package/src/api/handlers/content.ts +69 -23
- package/src/api/handlers/plugins.ts +77 -10
- package/src/api/schemas/import.ts +19 -0
- package/src/astro/integration/runtime.ts +3 -6
- package/src/astro/integration/vite-config.ts +19 -1
- package/src/astro/middleware/auth.ts +10 -3
- package/src/astro/middleware/csp.ts +54 -3
- package/src/astro/middleware.ts +1 -0
- package/src/astro/routes/admin.astro +9 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +6 -1
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +6 -1
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +1 -0
- package/src/astro/routes/api/admin/plugins/index.ts +1 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +122 -98
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +794 -44
- package/src/astro/routes/api/manifest.ts +20 -1
- package/src/astro/routes/api/setup/dev-bypass.ts +7 -2
- package/src/astro/types.ts +4 -0
- package/src/cli/commands/seed.ts +1 -1
- package/src/components/EmDashImage.astro +59 -23
- package/src/components/Image.astro +36 -35
- package/src/components/LiveSearch.astro +40 -4
- package/src/components/live-search-routing.ts +30 -0
- package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +23 -18
- package/src/database/migrations/048_restore_content_taxonomies_term_index.ts +32 -0
- package/src/database/migrations/049_taxonomies_name_locale_index.ts +42 -0
- package/src/database/migrations/050_media_usage_index_status.ts +142 -0
- package/src/database/migrations/runner.ts +6 -0
- package/src/database/repositories/media-usage.ts +932 -58
- package/src/database/types.ts +22 -0
- package/src/emdash-runtime.ts +123 -3
- package/src/import/comments.ts +159 -0
- package/src/import/index.ts +8 -1
- package/src/import/settings.ts +65 -189
- package/src/import/sources/wordpress-plugin.ts +338 -42
- package/src/import/sources/wxr.ts +4 -1
- package/src/import/types.ts +12 -0
- package/src/import/utils.ts +207 -0
- package/src/import/wxr-taxonomies.ts +39 -0
- package/src/loader.ts +13 -2
- package/src/media/usage/content-fields.ts +119 -0
- package/src/media/usage/content-refresh.ts +524 -0
- package/src/media/usage/content-repair.ts +491 -0
- package/src/media/usage/content-snapshots.ts +435 -0
- package/src/media/usage/extractor.ts +4 -4
- package/src/media/usage/source-key.ts +22 -0
- package/src/menus/index.ts +17 -1
- package/src/object-cache/index.ts +82 -7
- package/src/plugins/context.ts +100 -71
- package/src/schema/registry.ts +240 -154
- package/src/search/query.ts +3 -0
- package/src/search/types.ts +2 -0
- package/src/seed/apply.ts +158 -101
- package/src/seed/types.ts +6 -1
- package/dist/api-BMfveMoB.mjs.map +0 -1
- package/dist/apply--IVNSdXn.mjs.map +0 -1
- package/dist/byline-fields-eyJMblSr.mjs.map +0 -1
- package/dist/context-C_g-iJVA.mjs.map +0 -1
- package/dist/import-Bb1T9WJS.mjs.map +0 -1
- package/dist/index-BRNabKrx.d.mts.map +0 -1
- package/dist/index-Dt0OKJB4.d.mts.map +0 -1
- package/dist/loader-BA_zhkS_.mjs.map +0 -1
- package/dist/object-cache-CHbHv83-.mjs.map +0 -1
- package/dist/registry-g8yYt4cC.mjs.map +0 -1
- package/dist/runner-DPdt_Wpa.mjs.map +0 -1
- package/dist/search-BR0ZeZmh.mjs.map +0 -1
- package/dist/types-xIfVRNLp.d.mts.map +0 -1
- package/dist/utils-B7A57fm9.mjs.map +0 -1
- package/dist/version-BoNBItP_.mjs +0 -7
- /package/dist/{api-tokens-B4BQybOp.mjs → api-tokens-DsvvCtdh.mjs} +0 -0
- /package/dist/{ssrf-CcX9zvMK.mjs → ssrf-CmuGgCq3.mjs} +0 -0
- /package/dist/{types-BvbeGEtr.mjs → types-DROWRxYV.mjs} +0 -0
|
@@ -0,0 +1,1531 @@
|
|
|
1
|
+
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
2
|
+
import { r as isI18nEnabled } from "./config-CVssduLe.mjs";
|
|
3
|
+
import { a as encodeCursor, i as decodeCursor } from "./types-CuW0zWDA.mjs";
|
|
4
|
+
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-B2isWQhr.mjs";
|
|
5
|
+
import { t as withTransaction } from "./transaction-qfqpPVpu.mjs";
|
|
6
|
+
import { t as INTERNAL_MEDIA_PREFIX } from "./normalize-CeglrBT9.mjs";
|
|
7
|
+
import { n as normalizeMime } from "./mime-CCEzze7W.mjs";
|
|
8
|
+
import { n as hashString } from "./hash-DrvzIXcz.mjs";
|
|
9
|
+
import { sql } from "kysely";
|
|
10
|
+
import { ulid } from "ulidx";
|
|
11
|
+
|
|
12
|
+
//#region src/database/repositories/media-usage.ts
|
|
13
|
+
const OCCURRENCE_INSERT_BATCH_SIZE = Math.max(1, Math.floor(SQL_BATCH_SIZE / 13));
|
|
14
|
+
/** Persistence-only repository for the internal media usage projection tables. */
|
|
15
|
+
var MediaUsageRepository = class {
|
|
16
|
+
constructor(db) {
|
|
17
|
+
this.db = db;
|
|
18
|
+
}
|
|
19
|
+
async replaceSource(source, occurrences) {
|
|
20
|
+
const generation = ulid();
|
|
21
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22
|
+
await withTransaction(this.db, async (trx) => {
|
|
23
|
+
await this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);
|
|
24
|
+
await this.upsertSource(trx, source, generation, now);
|
|
25
|
+
});
|
|
26
|
+
const replaced = await this.findSource(source.sourceKey);
|
|
27
|
+
if (!replaced) throw new Error(`Media usage source ${source.sourceKey} was not persisted`);
|
|
28
|
+
return replaced;
|
|
29
|
+
}
|
|
30
|
+
async replaceSourceIfCurrent(source, occurrences, expectedCurrentGeneration) {
|
|
31
|
+
const generation = ulid();
|
|
32
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
33
|
+
const row = this.buildSourceRow(source, generation, now);
|
|
34
|
+
let replaced = false;
|
|
35
|
+
await withTransaction(this.db, async (trx) => {
|
|
36
|
+
await this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);
|
|
37
|
+
if (expectedCurrentGeneration === null) {
|
|
38
|
+
replaced = await this.insertSourceIfAbsent(trx, row);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
replaced = await this.updateSourceIfGeneration(trx, row, expectedCurrentGeneration);
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
replaced,
|
|
45
|
+
source: replaced ? null : await this.findSource(source.sourceKey)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async findSource(sourceKey) {
|
|
49
|
+
const row = await this.db.selectFrom("_emdash_media_usage_sources").selectAll().where("source_key", "=", sourceKey).executeTakeFirst();
|
|
50
|
+
return row ? rowToSource(row) : null;
|
|
51
|
+
}
|
|
52
|
+
async findSources(sourceKeys) {
|
|
53
|
+
const uniqueSourceKeys = [...new Set(sourceKeys)];
|
|
54
|
+
const sources = /* @__PURE__ */ new Map();
|
|
55
|
+
if (uniqueSourceKeys.length === 0) return sources;
|
|
56
|
+
for (const sourceKeyBatch of chunks(uniqueSourceKeys, SQL_BATCH_SIZE)) {
|
|
57
|
+
const rows = await this.db.selectFrom("_emdash_media_usage_sources").selectAll().where("source_key", "in", sourceKeyBatch).execute();
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
const source = rowToSource(row);
|
|
60
|
+
sources.set(source.sourceKey, source);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return sources;
|
|
64
|
+
}
|
|
65
|
+
async replaceSourceIfMatching(source, occurrences, expectedSource) {
|
|
66
|
+
const generation = ulid();
|
|
67
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
68
|
+
const row = this.buildSourceRow(source, generation, now);
|
|
69
|
+
let replaced = false;
|
|
70
|
+
await withTransaction(this.db, async (trx) => {
|
|
71
|
+
await this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);
|
|
72
|
+
if (expectedSource === null) {
|
|
73
|
+
replaced = await this.insertSourceIfAbsent(trx, row);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
replaced = await this.updateSourceIfMatching(trx, row, expectedSource);
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
replaced,
|
|
80
|
+
source: replaced ? null : await this.findSource(source.sourceKey)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async markSourceAttempted(source) {
|
|
84
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
85
|
+
const row = this.buildAttemptedSourceRow(source, now);
|
|
86
|
+
const updates = this.attemptedSourceUpdateSet(source, row);
|
|
87
|
+
await this.db.insertInto("_emdash_media_usage_sources").values(row).onConflict((oc) => oc.column("source_key").doUpdateSet(updates)).execute();
|
|
88
|
+
const attempted = await this.findSource(source.sourceKey);
|
|
89
|
+
if (!attempted) throw new Error(`Media usage source ${source.sourceKey} was not persisted`);
|
|
90
|
+
return attempted;
|
|
91
|
+
}
|
|
92
|
+
async markSourceAttemptedIfMatching(source, expectedSource) {
|
|
93
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
94
|
+
const row = this.buildAttemptedSourceRow(source, now);
|
|
95
|
+
let attempted = false;
|
|
96
|
+
if (expectedSource === null) attempted = await this.insertSourceIfAbsent(this.db, row);
|
|
97
|
+
else attempted = await this.updateAttemptedSourceIfMatching(this.db, source, row, expectedSource);
|
|
98
|
+
return {
|
|
99
|
+
attempted,
|
|
100
|
+
source: attempted ? null : await this.findSource(source.sourceKey)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async findCurrentUsageByMediaId(mediaId) {
|
|
104
|
+
return (await this.db.selectFrom("_emdash_media_usage_sources as s").innerJoin("_emdash_media_usage as u", (join) => join.onRef("u.source_key", "=", "s.source_key").onRef("u.generation", "=", "s.current_generation")).select(currentUsageSelect).where("u.media_id", "=", mediaId).orderBy("s.source_key", "asc").orderBy("u.field_path", "asc").orderBy("u.occurrence_index", "asc").execute()).map(rowToUsageRecord);
|
|
105
|
+
}
|
|
106
|
+
async findCurrentUsageByProviderAsset(provider, providerAssetId) {
|
|
107
|
+
return (await this.db.selectFrom("_emdash_media_usage_sources as s").innerJoin("_emdash_media_usage as u", (join) => join.onRef("u.source_key", "=", "s.source_key").onRef("u.generation", "=", "s.current_generation")).select(currentUsageSelect).where("u.provider", "=", provider).where("u.provider_asset_id", "=", providerAssetId).orderBy("s.source_key", "asc").orderBy("u.field_path", "asc").orderBy("u.occurrence_index", "asc").execute()).map(rowToUsageRecord);
|
|
108
|
+
}
|
|
109
|
+
async findCurrentUsagePageByMediaId(mediaId, options = {}) {
|
|
110
|
+
return this.findCurrentUsagePage((query) => query.where("u.media_id", "=", mediaId), options);
|
|
111
|
+
}
|
|
112
|
+
async findCurrentUsagePageByProviderAsset(provider, providerAssetId, options = {}) {
|
|
113
|
+
return this.findCurrentUsagePage((query) => query.where("u.provider", "=", provider).where("u.provider_asset_id", "=", providerAssetId), options);
|
|
114
|
+
}
|
|
115
|
+
async deleteSource(sourceKey) {
|
|
116
|
+
return this.deleteSources([sourceKey]);
|
|
117
|
+
}
|
|
118
|
+
async deleteSourceIfCurrent(sourceKey, expectedCurrentGeneration) {
|
|
119
|
+
let deleted = false;
|
|
120
|
+
await withTransaction(this.db, async (trx) => {
|
|
121
|
+
const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where("current_generation", "=", expectedCurrentGeneration).executeTakeFirst();
|
|
122
|
+
deleted = Number(result.numDeletedRows ?? 0) > 0;
|
|
123
|
+
if (!deleted) return;
|
|
124
|
+
await trx.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).execute();
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
deleted,
|
|
128
|
+
source: await this.findSource(sourceKey)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async deleteSourceIfMatching(sourceKey, expectedSource) {
|
|
132
|
+
let deleted = false;
|
|
133
|
+
await withTransaction(this.db, async (trx) => {
|
|
134
|
+
const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where(this.sourceMatchExpression(expectedSource)).executeTakeFirst();
|
|
135
|
+
deleted = Number(result.numDeletedRows ?? 0) > 0;
|
|
136
|
+
if (!deleted) return;
|
|
137
|
+
await trx.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).execute();
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
deleted,
|
|
141
|
+
source: await this.findSource(sourceKey)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async deleteSourceIfMatchingContentAbsent(sourceKey, expectedSource, collectionSlug, contentId) {
|
|
145
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
146
|
+
const tableName = `ec_${collectionSlug}`;
|
|
147
|
+
let deleted = false;
|
|
148
|
+
await withTransaction(this.db, async (trx) => {
|
|
149
|
+
const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "=", sourceKey).where(this.sourceMatchExpression(expectedSource)).where(sql`NOT EXISTS (SELECT 1 FROM ${sql.ref(tableName)} WHERE id = ${contentId})`).executeTakeFirst();
|
|
150
|
+
deleted = Number(result.numDeletedRows ?? 0) > 0;
|
|
151
|
+
if (!deleted) return;
|
|
152
|
+
await trx.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).execute();
|
|
153
|
+
});
|
|
154
|
+
const contentPresent = deleted ? false : await this.contentRowExists(tableName, contentId);
|
|
155
|
+
return {
|
|
156
|
+
deleted,
|
|
157
|
+
contentPresent,
|
|
158
|
+
source: deleted || contentPresent ? null : await this.findSource(sourceKey)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async deleteSources(sourceKeys) {
|
|
162
|
+
return this.deleteSourceKeys(sourceKeys);
|
|
163
|
+
}
|
|
164
|
+
async deleteContentSources(collectionSlug, contentId) {
|
|
165
|
+
const sourceKeys = (await this.db.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_type", "=", "content").where("collection_slug", "=", collectionSlug).where("content_id", "=", contentId).execute()).map((row) => row.source_key);
|
|
166
|
+
return this.deleteSourceKeys(sourceKeys);
|
|
167
|
+
}
|
|
168
|
+
async deleteCollectionSources(collectionSlug) {
|
|
169
|
+
let deleted = 0;
|
|
170
|
+
while (true) {
|
|
171
|
+
const sourceRows = await this.db.selectFrom("_emdash_media_usage_sources").select("source_key").where("source_type", "=", "content").where("collection_slug", "=", collectionSlug).orderBy("source_key", "asc").limit(SQL_BATCH_SIZE).execute();
|
|
172
|
+
if (sourceRows.length === 0) break;
|
|
173
|
+
deleted += await this.deleteSourceKeys(sourceRows.map((row) => row.source_key));
|
|
174
|
+
}
|
|
175
|
+
return deleted;
|
|
176
|
+
}
|
|
177
|
+
async findCollectionContentSources(collectionSlug) {
|
|
178
|
+
return (await this.db.selectFrom("_emdash_media_usage_sources").selectAll().where("source_type", "=", "content").where("collection_slug", "=", collectionSlug).orderBy("source_key", "asc").execute()).map((row) => rowToSource(row));
|
|
179
|
+
}
|
|
180
|
+
async deleteOrphanOccurrencesOlderThan(cutoff, limit) {
|
|
181
|
+
const batchLimit = Math.floor(limit);
|
|
182
|
+
if (batchLimit <= 0) return 0;
|
|
183
|
+
const rows = await this.db.selectFrom("_emdash_media_usage as u").leftJoin("_emdash_media_usage_sources as s", (join) => join.onRef("s.source_key", "=", "u.source_key")).select("u.id").where("s.source_key", "is", null).where("u.created_at", "<", cutoff).orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(batchLimit).execute();
|
|
184
|
+
let deleted = 0;
|
|
185
|
+
for (const idBatch of chunks(rows.map((row) => row.id), SQL_BATCH_SIZE)) {
|
|
186
|
+
const result = await this.db.deleteFrom("_emdash_media_usage").where("id", "in", idBatch).where("created_at", "<", cutoff).where(sql`NOT EXISTS (SELECT 1 FROM _emdash_media_usage_sources s WHERE s.source_key = _emdash_media_usage.source_key)`).executeTakeFirst();
|
|
187
|
+
deleted += Number(result.numDeletedRows ?? 0);
|
|
188
|
+
}
|
|
189
|
+
return deleted;
|
|
190
|
+
}
|
|
191
|
+
async deleteStaleGenerationsOlderThan(cutoff, limit) {
|
|
192
|
+
const batchLimit = Math.floor(limit);
|
|
193
|
+
if (batchLimit <= 0) return 0;
|
|
194
|
+
const ids = (await this.db.selectFrom("_emdash_media_usage as u").innerJoin("_emdash_media_usage_sources as s", (join) => join.onRef("s.source_key", "=", "u.source_key")).select("u.id").where("u.created_at", "<", cutoff).whereRef("u.generation", "!=", "s.current_generation").whereRef("u.created_at", "<", "s.indexed_at").orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(batchLimit).execute()).map((row) => row.id);
|
|
195
|
+
if (ids.length === 0) return 0;
|
|
196
|
+
let deleted = 0;
|
|
197
|
+
for (const idBatch of chunks(ids, SQL_BATCH_SIZE)) {
|
|
198
|
+
const result = await this.db.deleteFrom("_emdash_media_usage").where("id", "in", idBatch).where("created_at", "<", cutoff).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_sources as s").select("s.source_key").whereRef("s.source_key", "=", "_emdash_media_usage.source_key").whereRef("s.current_generation", "!=", "_emdash_media_usage.generation").whereRef("_emdash_media_usage.created_at", "<", "s.indexed_at"))).executeTakeFirst();
|
|
199
|
+
deleted += Number(result.numDeletedRows ?? 0);
|
|
200
|
+
}
|
|
201
|
+
return deleted;
|
|
202
|
+
}
|
|
203
|
+
async deleteAbandonedGenerationsOlderThan(cutoff, limit) {
|
|
204
|
+
const batchLimit = Math.floor(limit);
|
|
205
|
+
if (batchLimit <= 0) return 0;
|
|
206
|
+
const rows = await this.db.selectFrom("_emdash_media_usage as u").innerJoin("_emdash_media_usage_sources as s", (join) => join.onRef("s.source_key", "=", "u.source_key")).select("u.id").where("u.created_at", "<", cutoff).whereRef("u.generation", "!=", "s.current_generation").whereRef("u.created_at", ">=", "s.indexed_at").orderBy("u.created_at", "asc").orderBy("u.id", "asc").limit(batchLimit).execute();
|
|
207
|
+
let deleted = 0;
|
|
208
|
+
for (const idBatch of chunks(rows.map((row) => row.id), SQL_BATCH_SIZE)) {
|
|
209
|
+
const result = await this.db.deleteFrom("_emdash_media_usage").where("id", "in", idBatch).where("created_at", "<", cutoff).where((eb) => eb.exists(eb.selectFrom("_emdash_media_usage_sources as s").select("s.source_key").whereRef("s.source_key", "=", "_emdash_media_usage.source_key").whereRef("s.current_generation", "!=", "_emdash_media_usage.generation").whereRef("_emdash_media_usage.created_at", ">=", "s.indexed_at"))).executeTakeFirst();
|
|
210
|
+
deleted += Number(result.numDeletedRows ?? 0);
|
|
211
|
+
}
|
|
212
|
+
return deleted;
|
|
213
|
+
}
|
|
214
|
+
async upsertIndexStatus(input) {
|
|
215
|
+
const now = input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
216
|
+
const row = {
|
|
217
|
+
adapter_id: input.adapterId,
|
|
218
|
+
scope_type: input.scopeType,
|
|
219
|
+
scope_key: input.scopeKey,
|
|
220
|
+
status: input.status,
|
|
221
|
+
schema_version: input.schemaVersion ?? 1,
|
|
222
|
+
started_at: input.startedAt ?? null,
|
|
223
|
+
completed_at: input.completedAt ?? null,
|
|
224
|
+
cursor: input.cursor ?? null,
|
|
225
|
+
indexed_source_count: input.indexedSourceCount ?? 0,
|
|
226
|
+
failed_source_count: input.failedSourceCount ?? 0,
|
|
227
|
+
last_error_code: input.lastErrorCode ?? null,
|
|
228
|
+
updated_at: now
|
|
229
|
+
};
|
|
230
|
+
await this.db.insertInto("_emdash_media_usage_index_status").values(row).onConflict((oc) => oc.columns([
|
|
231
|
+
"adapter_id",
|
|
232
|
+
"scope_type",
|
|
233
|
+
"scope_key"
|
|
234
|
+
]).doUpdateSet({
|
|
235
|
+
status: row.status,
|
|
236
|
+
schema_version: row.schema_version,
|
|
237
|
+
started_at: row.started_at,
|
|
238
|
+
completed_at: row.completed_at,
|
|
239
|
+
cursor: row.cursor,
|
|
240
|
+
indexed_source_count: row.indexed_source_count,
|
|
241
|
+
failed_source_count: row.failed_source_count,
|
|
242
|
+
last_error_code: row.last_error_code,
|
|
243
|
+
updated_at: row.updated_at
|
|
244
|
+
})).execute();
|
|
245
|
+
const status = await this.findIndexStatus(input);
|
|
246
|
+
if (!status) throw new Error(`Media usage index status ${input.adapterId}:${input.scopeType}:${input.scopeKey} was not persisted`);
|
|
247
|
+
return status;
|
|
248
|
+
}
|
|
249
|
+
async beginIndexStatusRepair(input) {
|
|
250
|
+
return this.upsertIndexStatus({
|
|
251
|
+
adapterId: input.adapterId,
|
|
252
|
+
scopeType: input.scopeType,
|
|
253
|
+
scopeKey: input.scopeKey,
|
|
254
|
+
status: "running",
|
|
255
|
+
schemaVersion: input.schemaVersion,
|
|
256
|
+
startedAt: input.startedAt,
|
|
257
|
+
completedAt: null,
|
|
258
|
+
cursor: input.runToken,
|
|
259
|
+
indexedSourceCount: 0,
|
|
260
|
+
failedSourceCount: 0,
|
|
261
|
+
lastErrorCode: null,
|
|
262
|
+
updatedAt: input.updatedAt
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async finalizeIndexStatusRepairIfRunning(input) {
|
|
266
|
+
const updates = {
|
|
267
|
+
status: input.status,
|
|
268
|
+
completed_at: input.completedAt,
|
|
269
|
+
cursor: null,
|
|
270
|
+
indexed_source_count: input.indexedSourceCount ?? 0,
|
|
271
|
+
failed_source_count: input.failedSourceCount ?? 0,
|
|
272
|
+
last_error_code: input.lastErrorCode ?? null,
|
|
273
|
+
updated_at: input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
274
|
+
};
|
|
275
|
+
if (input.schemaVersion !== void 0) updates.schema_version = input.schemaVersion;
|
|
276
|
+
const result = await this.db.updateTable("_emdash_media_usage_index_status").set(updates).where("adapter_id", "=", input.adapterId).where("scope_type", "=", input.scopeType).where("scope_key", "=", input.scopeKey).where("status", "=", "running").where("cursor", "=", input.runToken).executeTakeFirst();
|
|
277
|
+
return {
|
|
278
|
+
finalized: Number(result.numUpdatedRows ?? 0) > 0,
|
|
279
|
+
status: await this.findIndexStatus(input)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
async findIndexStatus(identity) {
|
|
283
|
+
const row = await this.db.selectFrom("_emdash_media_usage_index_status").selectAll().where("adapter_id", "=", identity.adapterId).where("scope_type", "=", identity.scopeType).where("scope_key", "=", identity.scopeKey).executeTakeFirst();
|
|
284
|
+
return row ? rowToIndexStatus(row) : null;
|
|
285
|
+
}
|
|
286
|
+
async deleteIndexStatus(identity) {
|
|
287
|
+
const result = await this.db.deleteFrom("_emdash_media_usage_index_status").where("adapter_id", "=", identity.adapterId).where("scope_type", "=", identity.scopeType).where("scope_key", "=", identity.scopeKey).executeTakeFirst();
|
|
288
|
+
return Number(result.numDeletedRows ?? 0);
|
|
289
|
+
}
|
|
290
|
+
async findCurrentUsagePage(applyFilter, options) {
|
|
291
|
+
const limit = Math.min(Math.max(1, options.limit ?? 50), 100);
|
|
292
|
+
let query = applyFilter(this.currentUsageBaseQuery()).orderBy("u.id", "asc").limit(limit + 1);
|
|
293
|
+
if (options.cursor) {
|
|
294
|
+
const { id } = decodeCursor(options.cursor);
|
|
295
|
+
query = query.where("u.id", ">", id);
|
|
296
|
+
}
|
|
297
|
+
const rows = await query.execute();
|
|
298
|
+
const items = rows.slice(0, limit).map(rowToUsageRecord);
|
|
299
|
+
const result = { items };
|
|
300
|
+
if (rows.length > limit && items.length > 0) {
|
|
301
|
+
const last = items.at(-1);
|
|
302
|
+
result.nextCursor = encodeCursor(last.occurrence.id, last.occurrence.id);
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
currentUsageBaseQuery() {
|
|
307
|
+
return this.db.selectFrom("_emdash_media_usage_sources as s").innerJoin("_emdash_media_usage as u", (join) => join.onRef("u.source_key", "=", "s.source_key").onRef("u.generation", "=", "s.current_generation")).select(currentUsageSelect);
|
|
308
|
+
}
|
|
309
|
+
async deleteSourceKeys(sourceKeys) {
|
|
310
|
+
const uniqueSourceKeys = [...new Set(sourceKeys)];
|
|
311
|
+
if (uniqueSourceKeys.length === 0) return 0;
|
|
312
|
+
return withTransaction(this.db, async (trx) => {
|
|
313
|
+
let deleted = 0;
|
|
314
|
+
for (const sourceKeyBatch of chunks(uniqueSourceKeys, SQL_BATCH_SIZE)) {
|
|
315
|
+
const result = await trx.deleteFrom("_emdash_media_usage_sources").where("source_key", "in", sourceKeyBatch).executeTakeFirst();
|
|
316
|
+
deleted += Number(result.numDeletedRows ?? 0);
|
|
317
|
+
await trx.deleteFrom("_emdash_media_usage").where("source_key", "in", sourceKeyBatch).execute();
|
|
318
|
+
}
|
|
319
|
+
return deleted;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async insertOccurrences(db, sourceKey, generation, occurrences, now) {
|
|
323
|
+
if (occurrences.length === 0) return;
|
|
324
|
+
const rows = occurrences.map((occurrence) => ({
|
|
325
|
+
id: ulid(),
|
|
326
|
+
source_key: sourceKey,
|
|
327
|
+
generation,
|
|
328
|
+
field_slug: occurrence.fieldSlug,
|
|
329
|
+
field_path: occurrence.fieldPath,
|
|
330
|
+
occurrence_index: occurrence.occurrenceIndex ?? 0,
|
|
331
|
+
reference_type: occurrence.referenceType,
|
|
332
|
+
media_id: occurrence.mediaId,
|
|
333
|
+
provider: occurrence.provider,
|
|
334
|
+
provider_asset_id: occurrence.providerAssetId,
|
|
335
|
+
media_kind: occurrence.mediaKind ?? null,
|
|
336
|
+
mime_type: occurrence.mimeType ?? null,
|
|
337
|
+
created_at: now
|
|
338
|
+
}));
|
|
339
|
+
for (const rowBatch of chunks(rows, OCCURRENCE_INSERT_BATCH_SIZE)) await db.insertInto("_emdash_media_usage").values(rowBatch).execute();
|
|
340
|
+
}
|
|
341
|
+
async upsertSource(db, source, generation, now) {
|
|
342
|
+
const row = this.buildSourceRow(source, generation, now);
|
|
343
|
+
await db.insertInto("_emdash_media_usage_sources").values(row).onConflict((oc) => oc.column("source_key").doUpdateSet(this.sourceUpdateSet(row))).execute();
|
|
344
|
+
}
|
|
345
|
+
async insertSourceIfAbsent(db, row) {
|
|
346
|
+
return ((await db.insertInto("_emdash_media_usage_sources").values(row).onConflict((oc) => oc.column("source_key").doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows ?? 0n) > 0n;
|
|
347
|
+
}
|
|
348
|
+
async updateSourceIfGeneration(db, row, expectedCurrentGeneration) {
|
|
349
|
+
const result = await db.updateTable("_emdash_media_usage_sources").set(this.sourceUpdateSet(row)).where("source_key", "=", row.source_key).where("current_generation", "=", expectedCurrentGeneration).executeTakeFirst();
|
|
350
|
+
return Number(result.numUpdatedRows ?? 0) > 0;
|
|
351
|
+
}
|
|
352
|
+
async updateSourceIfMatching(db, row, expectedSource) {
|
|
353
|
+
const result = await db.updateTable("_emdash_media_usage_sources").set(this.sourceUpdateSet(row)).where("source_key", "=", row.source_key).where(this.sourceMatchExpression(expectedSource)).executeTakeFirst();
|
|
354
|
+
return Number(result.numUpdatedRows ?? 0) > 0;
|
|
355
|
+
}
|
|
356
|
+
async updateAttemptedSourceIfMatching(db, source, row, expectedSource) {
|
|
357
|
+
const result = await db.updateTable("_emdash_media_usage_sources").set(this.attemptedSourceUpdateSet(source, row)).where("source_key", "=", row.source_key).where(this.sourceMatchExpression(expectedSource)).executeTakeFirst();
|
|
358
|
+
return Number(result.numUpdatedRows ?? 0) > 0;
|
|
359
|
+
}
|
|
360
|
+
sourceMatchExpression(expectedSource) {
|
|
361
|
+
return (eb) => eb.and([
|
|
362
|
+
eb("current_generation", "=", expectedSource.currentGeneration),
|
|
363
|
+
eb("source_completeness", "=", expectedSource.sourceCompleteness),
|
|
364
|
+
this.nullableStringExpression(eb, "updated_at", expectedSource.updatedAt),
|
|
365
|
+
this.nullableStringExpression(eb, "source_fingerprint", expectedSource.sourceFingerprint),
|
|
366
|
+
this.nullableStringExpression(eb, "source_updated_at", expectedSource.sourceUpdatedAt),
|
|
367
|
+
this.nullableNumberExpression(eb, "source_version", expectedSource.sourceVersion),
|
|
368
|
+
this.nullableStringExpression(eb, "revision_id", expectedSource.revisionId),
|
|
369
|
+
this.nullableStringExpression(eb, "last_attempted_at", expectedSource.lastAttemptedAt),
|
|
370
|
+
this.nullableStringExpression(eb, "last_error_code", expectedSource.lastErrorCode)
|
|
371
|
+
]);
|
|
372
|
+
}
|
|
373
|
+
nullableStringExpression(eb, column, value) {
|
|
374
|
+
return value === null ? eb(column, "is", null) : eb(column, "=", value);
|
|
375
|
+
}
|
|
376
|
+
nullableNumberExpression(eb, column, value) {
|
|
377
|
+
return value === null ? eb(column, "is", null) : eb(column, "=", value);
|
|
378
|
+
}
|
|
379
|
+
async contentRowExists(tableName, contentId) {
|
|
380
|
+
return (await sql`
|
|
381
|
+
SELECT id
|
|
382
|
+
FROM ${sql.ref(tableName)}
|
|
383
|
+
WHERE id = ${contentId}
|
|
384
|
+
LIMIT 1
|
|
385
|
+
`.execute(this.db)).rows.length > 0;
|
|
386
|
+
}
|
|
387
|
+
buildSourceRow(source, generation, now) {
|
|
388
|
+
return {
|
|
389
|
+
source_key: source.sourceKey,
|
|
390
|
+
source_type: source.sourceType,
|
|
391
|
+
collection_slug: source.collectionSlug ?? null,
|
|
392
|
+
content_id: source.contentId ?? null,
|
|
393
|
+
source_variant: source.sourceVariant,
|
|
394
|
+
locale: source.locale ?? null,
|
|
395
|
+
translation_group: source.translationGroup ?? null,
|
|
396
|
+
content_slug: source.contentSlug ?? null,
|
|
397
|
+
content_title: source.contentTitle ?? null,
|
|
398
|
+
content_status: source.contentStatus ?? null,
|
|
399
|
+
content_scheduled_at: source.contentScheduledAt ?? null,
|
|
400
|
+
content_deleted_at: source.contentDeletedAt ?? null,
|
|
401
|
+
revision_id: source.revisionId ?? null,
|
|
402
|
+
current_generation: generation,
|
|
403
|
+
schema_version: source.schemaVersion ?? 1,
|
|
404
|
+
source_updated_at: source.sourceUpdatedAt ?? null,
|
|
405
|
+
source_version: source.sourceVersion ?? null,
|
|
406
|
+
source_fingerprint: source.sourceFingerprint ?? null,
|
|
407
|
+
source_completeness: source.sourceCompleteness ?? "complete",
|
|
408
|
+
last_attempted_at: source.lastAttemptedAt ?? now,
|
|
409
|
+
last_error_code: null,
|
|
410
|
+
indexed_at: now,
|
|
411
|
+
updated_at: now
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
buildAttemptedSourceRow(source, now) {
|
|
415
|
+
return {
|
|
416
|
+
source_key: source.sourceKey,
|
|
417
|
+
source_type: source.sourceType,
|
|
418
|
+
collection_slug: source.collectionSlug ?? null,
|
|
419
|
+
content_id: source.contentId ?? null,
|
|
420
|
+
source_variant: source.sourceVariant,
|
|
421
|
+
locale: source.locale ?? null,
|
|
422
|
+
translation_group: source.translationGroup ?? null,
|
|
423
|
+
content_slug: source.contentSlug ?? null,
|
|
424
|
+
content_title: source.contentTitle ?? null,
|
|
425
|
+
content_status: source.contentStatus ?? null,
|
|
426
|
+
content_scheduled_at: source.contentScheduledAt ?? null,
|
|
427
|
+
content_deleted_at: source.contentDeletedAt ?? null,
|
|
428
|
+
revision_id: source.revisionId ?? null,
|
|
429
|
+
current_generation: ulid(),
|
|
430
|
+
schema_version: source.schemaVersion ?? 1,
|
|
431
|
+
source_updated_at: source.sourceUpdatedAt ?? null,
|
|
432
|
+
source_version: source.sourceVersion ?? null,
|
|
433
|
+
source_fingerprint: source.sourceFingerprint ?? null,
|
|
434
|
+
source_completeness: source.sourceCompleteness ?? (source.lastErrorCode ? "failed" : "unknown"),
|
|
435
|
+
last_attempted_at: source.lastAttemptedAt ?? now,
|
|
436
|
+
last_error_code: source.lastErrorCode ?? null,
|
|
437
|
+
indexed_at: now,
|
|
438
|
+
updated_at: now
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
attemptedSourceUpdateSet(source, row) {
|
|
442
|
+
const updates = {
|
|
443
|
+
source_type: row.source_type,
|
|
444
|
+
source_variant: row.source_variant,
|
|
445
|
+
source_completeness: row.source_completeness,
|
|
446
|
+
last_attempted_at: row.last_attempted_at,
|
|
447
|
+
last_error_code: row.last_error_code,
|
|
448
|
+
updated_at: row.updated_at
|
|
449
|
+
};
|
|
450
|
+
if (source.collectionSlug !== void 0) updates.collection_slug = row.collection_slug;
|
|
451
|
+
if (source.contentId !== void 0) updates.content_id = row.content_id;
|
|
452
|
+
if (source.locale !== void 0) updates.locale = row.locale;
|
|
453
|
+
if (source.translationGroup !== void 0) updates.translation_group = row.translation_group;
|
|
454
|
+
if (source.contentSlug !== void 0) updates.content_slug = row.content_slug;
|
|
455
|
+
if (source.contentTitle !== void 0) updates.content_title = row.content_title;
|
|
456
|
+
if (source.contentStatus !== void 0) updates.content_status = row.content_status;
|
|
457
|
+
if (source.contentScheduledAt !== void 0) updates.content_scheduled_at = row.content_scheduled_at;
|
|
458
|
+
if (source.contentDeletedAt !== void 0) updates.content_deleted_at = row.content_deleted_at;
|
|
459
|
+
if (source.revisionId !== void 0) updates.revision_id = row.revision_id;
|
|
460
|
+
if (source.schemaVersion !== void 0) updates.schema_version = row.schema_version;
|
|
461
|
+
if (source.sourceUpdatedAt !== void 0) updates.source_updated_at = row.source_updated_at;
|
|
462
|
+
if (source.sourceVersion !== void 0) updates.source_version = row.source_version;
|
|
463
|
+
if (source.sourceFingerprint !== void 0) updates.source_fingerprint = row.source_fingerprint;
|
|
464
|
+
return updates;
|
|
465
|
+
}
|
|
466
|
+
sourceUpdateSet(row) {
|
|
467
|
+
return {
|
|
468
|
+
source_type: row.source_type,
|
|
469
|
+
collection_slug: row.collection_slug,
|
|
470
|
+
content_id: row.content_id,
|
|
471
|
+
source_variant: row.source_variant,
|
|
472
|
+
locale: row.locale,
|
|
473
|
+
translation_group: row.translation_group,
|
|
474
|
+
content_slug: row.content_slug,
|
|
475
|
+
content_title: row.content_title,
|
|
476
|
+
content_status: row.content_status,
|
|
477
|
+
content_scheduled_at: row.content_scheduled_at,
|
|
478
|
+
content_deleted_at: row.content_deleted_at,
|
|
479
|
+
revision_id: row.revision_id,
|
|
480
|
+
current_generation: row.current_generation,
|
|
481
|
+
schema_version: row.schema_version,
|
|
482
|
+
source_updated_at: row.source_updated_at,
|
|
483
|
+
source_version: row.source_version,
|
|
484
|
+
source_fingerprint: row.source_fingerprint,
|
|
485
|
+
source_completeness: row.source_completeness,
|
|
486
|
+
last_attempted_at: row.last_attempted_at,
|
|
487
|
+
last_error_code: row.last_error_code,
|
|
488
|
+
indexed_at: row.indexed_at,
|
|
489
|
+
updated_at: row.updated_at
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
const currentUsageSelect = [
|
|
494
|
+
"s.source_key as source_key",
|
|
495
|
+
"s.source_type as source_type",
|
|
496
|
+
"s.collection_slug as collection_slug",
|
|
497
|
+
"s.content_id as content_id",
|
|
498
|
+
"s.source_variant as source_variant",
|
|
499
|
+
"s.locale as locale",
|
|
500
|
+
"s.translation_group as translation_group",
|
|
501
|
+
"s.content_slug as content_slug",
|
|
502
|
+
"s.content_title as content_title",
|
|
503
|
+
"s.content_status as content_status",
|
|
504
|
+
"s.content_scheduled_at as content_scheduled_at",
|
|
505
|
+
"s.content_deleted_at as content_deleted_at",
|
|
506
|
+
"s.revision_id as revision_id",
|
|
507
|
+
"s.current_generation as current_generation",
|
|
508
|
+
"s.schema_version as schema_version",
|
|
509
|
+
"s.source_updated_at as source_updated_at",
|
|
510
|
+
"s.source_version as source_version",
|
|
511
|
+
"s.source_fingerprint as source_fingerprint",
|
|
512
|
+
"s.source_completeness as source_completeness",
|
|
513
|
+
"s.last_attempted_at as last_attempted_at",
|
|
514
|
+
"s.last_error_code as last_error_code",
|
|
515
|
+
"s.indexed_at as indexed_at",
|
|
516
|
+
"s.created_at as source_created_at",
|
|
517
|
+
"s.updated_at as source_row_updated_at",
|
|
518
|
+
"u.id as occurrence_id",
|
|
519
|
+
"u.generation as generation",
|
|
520
|
+
"u.field_slug as field_slug",
|
|
521
|
+
"u.field_path as field_path",
|
|
522
|
+
"u.occurrence_index as occurrence_index",
|
|
523
|
+
"u.reference_type as reference_type",
|
|
524
|
+
"u.media_id as media_id",
|
|
525
|
+
"u.provider as provider",
|
|
526
|
+
"u.provider_asset_id as provider_asset_id",
|
|
527
|
+
"u.media_kind as media_kind",
|
|
528
|
+
"u.mime_type as mime_type",
|
|
529
|
+
"u.created_at as occurrence_created_at"
|
|
530
|
+
];
|
|
531
|
+
function rowToSource(row) {
|
|
532
|
+
return {
|
|
533
|
+
sourceKey: row.source_key,
|
|
534
|
+
sourceType: row.source_type,
|
|
535
|
+
collectionSlug: row.collection_slug,
|
|
536
|
+
contentId: row.content_id,
|
|
537
|
+
sourceVariant: row.source_variant,
|
|
538
|
+
locale: row.locale,
|
|
539
|
+
translationGroup: row.translation_group,
|
|
540
|
+
contentSlug: row.content_slug,
|
|
541
|
+
contentTitle: row.content_title,
|
|
542
|
+
contentStatus: row.content_status,
|
|
543
|
+
contentScheduledAt: row.content_scheduled_at,
|
|
544
|
+
contentDeletedAt: row.content_deleted_at,
|
|
545
|
+
revisionId: row.revision_id,
|
|
546
|
+
currentGeneration: row.current_generation,
|
|
547
|
+
schemaVersion: Number(row.schema_version),
|
|
548
|
+
sourceUpdatedAt: row.source_updated_at,
|
|
549
|
+
sourceVersion: row.source_version === null ? null : Number(row.source_version),
|
|
550
|
+
sourceFingerprint: row.source_fingerprint,
|
|
551
|
+
sourceCompleteness: row.source_completeness,
|
|
552
|
+
lastAttemptedAt: row.last_attempted_at,
|
|
553
|
+
lastErrorCode: row.last_error_code,
|
|
554
|
+
indexedAt: row.indexed_at,
|
|
555
|
+
createdAt: row.created_at,
|
|
556
|
+
updatedAt: row.updated_at
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function rowToOccurrence(row) {
|
|
560
|
+
return {
|
|
561
|
+
id: row.id,
|
|
562
|
+
sourceKey: row.source_key,
|
|
563
|
+
generation: row.generation,
|
|
564
|
+
fieldSlug: row.field_slug,
|
|
565
|
+
fieldPath: row.field_path,
|
|
566
|
+
occurrenceIndex: Number(row.occurrence_index),
|
|
567
|
+
referenceType: row.reference_type,
|
|
568
|
+
mediaId: row.media_id,
|
|
569
|
+
provider: row.provider,
|
|
570
|
+
providerAssetId: row.provider_asset_id,
|
|
571
|
+
mediaKind: row.media_kind,
|
|
572
|
+
mimeType: row.mime_type,
|
|
573
|
+
createdAt: row.created_at
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function rowToUsageRecord(row) {
|
|
577
|
+
return {
|
|
578
|
+
source: rowToSource({
|
|
579
|
+
source_key: row.source_key,
|
|
580
|
+
source_type: row.source_type,
|
|
581
|
+
collection_slug: row.collection_slug,
|
|
582
|
+
content_id: row.content_id,
|
|
583
|
+
source_variant: row.source_variant,
|
|
584
|
+
locale: row.locale,
|
|
585
|
+
translation_group: row.translation_group,
|
|
586
|
+
content_slug: row.content_slug,
|
|
587
|
+
content_title: row.content_title,
|
|
588
|
+
content_status: row.content_status,
|
|
589
|
+
content_scheduled_at: row.content_scheduled_at,
|
|
590
|
+
content_deleted_at: row.content_deleted_at,
|
|
591
|
+
revision_id: row.revision_id,
|
|
592
|
+
current_generation: row.current_generation,
|
|
593
|
+
schema_version: row.schema_version,
|
|
594
|
+
source_updated_at: row.source_updated_at,
|
|
595
|
+
source_version: row.source_version,
|
|
596
|
+
source_fingerprint: row.source_fingerprint,
|
|
597
|
+
source_completeness: row.source_completeness,
|
|
598
|
+
last_attempted_at: row.last_attempted_at,
|
|
599
|
+
last_error_code: row.last_error_code,
|
|
600
|
+
indexed_at: row.indexed_at,
|
|
601
|
+
created_at: row.source_created_at,
|
|
602
|
+
updated_at: row.source_row_updated_at
|
|
603
|
+
}),
|
|
604
|
+
occurrence: rowToOccurrence({
|
|
605
|
+
id: row.occurrence_id,
|
|
606
|
+
source_key: row.source_key,
|
|
607
|
+
generation: row.generation,
|
|
608
|
+
field_slug: row.field_slug,
|
|
609
|
+
field_path: row.field_path,
|
|
610
|
+
occurrence_index: row.occurrence_index,
|
|
611
|
+
reference_type: row.reference_type,
|
|
612
|
+
media_id: row.media_id,
|
|
613
|
+
provider: row.provider,
|
|
614
|
+
provider_asset_id: row.provider_asset_id,
|
|
615
|
+
media_kind: row.media_kind,
|
|
616
|
+
mime_type: row.mime_type,
|
|
617
|
+
created_at: row.occurrence_created_at
|
|
618
|
+
})
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function rowToIndexStatus(row) {
|
|
622
|
+
return {
|
|
623
|
+
adapterId: row.adapter_id,
|
|
624
|
+
scopeType: row.scope_type,
|
|
625
|
+
scopeKey: row.scope_key,
|
|
626
|
+
status: row.status,
|
|
627
|
+
schemaVersion: Number(row.schema_version),
|
|
628
|
+
startedAt: row.started_at,
|
|
629
|
+
completedAt: row.completed_at,
|
|
630
|
+
cursor: row.cursor,
|
|
631
|
+
indexedSourceCount: Number(row.indexed_source_count),
|
|
632
|
+
failedSourceCount: Number(row.failed_source_count),
|
|
633
|
+
lastErrorCode: row.last_error_code,
|
|
634
|
+
updatedAt: row.updated_at
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region src/media/usage/content-fields.ts
|
|
640
|
+
var MediaUsageFieldDiscoveryError = class extends Error {
|
|
641
|
+
constructor(message, code) {
|
|
642
|
+
super(message);
|
|
643
|
+
this.code = code;
|
|
644
|
+
this.name = "MediaUsageFieldDiscoveryError";
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
const DISPLAY_FIELD_SLUGS = ["title", "name"];
|
|
648
|
+
const SUPPORTED_TOP_LEVEL_TYPES = [
|
|
649
|
+
"file",
|
|
650
|
+
"image",
|
|
651
|
+
"portableText"
|
|
652
|
+
];
|
|
653
|
+
async function loadContentMediaUsageFields(db, collectionSlug) {
|
|
654
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
655
|
+
const rows = await db.selectFrom("_emdash_fields").innerJoin("_emdash_collections", "_emdash_collections.id", "_emdash_fields.collection_id").select([
|
|
656
|
+
"_emdash_fields.slug",
|
|
657
|
+
"_emdash_fields.type",
|
|
658
|
+
"_emdash_fields.validation"
|
|
659
|
+
]).where("_emdash_collections.slug", "=", collectionSlug).execute();
|
|
660
|
+
const extractionFields = [];
|
|
661
|
+
const rowBySlug = /* @__PURE__ */ new Map();
|
|
662
|
+
for (const row of rows) {
|
|
663
|
+
rowBySlug.set(row.slug, row);
|
|
664
|
+
if (isSupportedTopLevelType(row.type)) {
|
|
665
|
+
validateIdentifier(row.slug, "media usage field slug");
|
|
666
|
+
extractionFields.push({
|
|
667
|
+
slug: row.slug,
|
|
668
|
+
type: row.type
|
|
669
|
+
});
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (row.type === "repeater") {
|
|
673
|
+
validateIdentifier(row.slug, "media usage field slug");
|
|
674
|
+
const subFields = normalizeRepeaterImageSubFields(row.validation);
|
|
675
|
+
if (subFields.length > 0) extractionFields.push({
|
|
676
|
+
slug: row.slug,
|
|
677
|
+
type: "repeater",
|
|
678
|
+
validation: { subFields }
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
extractionFields.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
683
|
+
return {
|
|
684
|
+
extractionFields,
|
|
685
|
+
displayFieldSlugs: DISPLAY_FIELD_SLUGS.filter((slug) => {
|
|
686
|
+
if (!rowBySlug.has(slug)) return false;
|
|
687
|
+
validateIdentifier(slug, "media usage display field slug");
|
|
688
|
+
return true;
|
|
689
|
+
})
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function normalizeRepeaterImageSubFields(rawValidation) {
|
|
693
|
+
const validation = parseValidation(rawValidation);
|
|
694
|
+
if (!isRecord$2(validation) || !Array.isArray(validation.subFields)) return [];
|
|
695
|
+
const subFields = [];
|
|
696
|
+
for (const subField of validation.subFields) {
|
|
697
|
+
if (!isRecord$2(subField) || subField.type !== "image") continue;
|
|
698
|
+
if (typeof subField.slug !== "string") continue;
|
|
699
|
+
validateIdentifier(subField.slug, "media usage repeater sub-field slug");
|
|
700
|
+
subFields.push({
|
|
701
|
+
slug: subField.slug,
|
|
702
|
+
type: "image"
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return subFields.toSorted((a, b) => a.slug.localeCompare(b.slug));
|
|
706
|
+
}
|
|
707
|
+
function parseValidation(rawValidation) {
|
|
708
|
+
if (!rawValidation) return null;
|
|
709
|
+
try {
|
|
710
|
+
return JSON.parse(rawValidation);
|
|
711
|
+
} catch {
|
|
712
|
+
throw new MediaUsageFieldDiscoveryError("Repeater field validation must be valid JSON before media usage can be discovered", "INVALID_REPEATER_VALIDATION");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function isSupportedTopLevelType(value) {
|
|
716
|
+
return SUPPORTED_TOP_LEVEL_TYPES.includes(value);
|
|
717
|
+
}
|
|
718
|
+
function isRecord$2(value) {
|
|
719
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/media/usage/extractor.ts
|
|
724
|
+
const URL_LIKE_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
725
|
+
function extractMediaUsageOccurrences({ fields, data }) {
|
|
726
|
+
const occurrences = [];
|
|
727
|
+
const seen = /* @__PURE__ */ new Set();
|
|
728
|
+
for (const field of fields) {
|
|
729
|
+
const value = data[field.slug];
|
|
730
|
+
if (field.type === "image") {
|
|
731
|
+
addOccurrence(occurrences, seen, {
|
|
732
|
+
fieldSlug: field.slug,
|
|
733
|
+
fieldPath: field.slug,
|
|
734
|
+
referenceType: "image_field",
|
|
735
|
+
value,
|
|
736
|
+
fallbackKind: "image"
|
|
737
|
+
});
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (field.type === "file") {
|
|
741
|
+
addOccurrence(occurrences, seen, {
|
|
742
|
+
fieldSlug: field.slug,
|
|
743
|
+
fieldPath: field.slug,
|
|
744
|
+
referenceType: "file_field",
|
|
745
|
+
value,
|
|
746
|
+
fallbackKind: null
|
|
747
|
+
});
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (field.type === "repeater") {
|
|
751
|
+
extractRepeaterOccurrences(occurrences, seen, field.slug, value, field.validation?.subFields);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (field.type === "portableText") extractPortableTextOccurrences(occurrences, seen, field.slug, value);
|
|
755
|
+
}
|
|
756
|
+
return occurrences;
|
|
757
|
+
}
|
|
758
|
+
function extractRepeaterOccurrences(occurrences, seen, fieldSlug, value, subFields) {
|
|
759
|
+
if (!Array.isArray(value) || !Array.isArray(subFields)) return;
|
|
760
|
+
for (const [itemIndex, item] of value.entries()) {
|
|
761
|
+
if (!isRecord$1(item)) continue;
|
|
762
|
+
for (const subField of subFields) {
|
|
763
|
+
if (subField.type !== "image") continue;
|
|
764
|
+
addOccurrence(occurrences, seen, {
|
|
765
|
+
fieldSlug,
|
|
766
|
+
fieldPath: `${fieldSlug}[${itemIndex}].${subField.slug}`,
|
|
767
|
+
referenceType: "image_field",
|
|
768
|
+
value: item[subField.slug],
|
|
769
|
+
fallbackKind: "image"
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function extractPortableTextOccurrences(occurrences, seen, fieldSlug, value) {
|
|
775
|
+
if (!Array.isArray(value)) return;
|
|
776
|
+
for (const [blockIndex, block] of value.entries()) {
|
|
777
|
+
if (!isRecord$1(block) || block._type !== "image" || !isRecord$1(block.asset)) continue;
|
|
778
|
+
const provider = normalizeProvider(block.asset.provider);
|
|
779
|
+
const ref = readPortableTextAssetRef(block.asset, provider);
|
|
780
|
+
if (!ref) continue;
|
|
781
|
+
addRefOccurrence(occurrences, seen, {
|
|
782
|
+
fieldSlug,
|
|
783
|
+
fieldPath: `${fieldSlug}[${blockIndex}].asset.${ref.key}`,
|
|
784
|
+
referenceType: "portable_text_image",
|
|
785
|
+
ref: buildMediaRef({
|
|
786
|
+
id: ref.id,
|
|
787
|
+
provider,
|
|
788
|
+
mimeType: normalizeMimeValue(block.asset.mimeType),
|
|
789
|
+
fallbackKind: "image"
|
|
790
|
+
})
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function addOccurrence(occurrences, seen, input) {
|
|
795
|
+
const ref = readMediaRef(input.value, input.fallbackKind);
|
|
796
|
+
if (!ref) return;
|
|
797
|
+
addRefOccurrence(occurrences, seen, {
|
|
798
|
+
fieldSlug: input.fieldSlug,
|
|
799
|
+
fieldPath: input.fieldPath,
|
|
800
|
+
referenceType: input.referenceType,
|
|
801
|
+
ref
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function addRefOccurrence(occurrences, seen, input) {
|
|
805
|
+
if (!input.ref) return;
|
|
806
|
+
const occurrence = {
|
|
807
|
+
fieldSlug: input.fieldSlug,
|
|
808
|
+
fieldPath: input.fieldPath,
|
|
809
|
+
occurrenceIndex: 0,
|
|
810
|
+
referenceType: input.referenceType,
|
|
811
|
+
mediaId: input.ref.mediaId,
|
|
812
|
+
provider: input.ref.provider,
|
|
813
|
+
providerAssetId: input.ref.providerAssetId,
|
|
814
|
+
mediaKind: input.ref.mediaKind,
|
|
815
|
+
mimeType: input.ref.mimeType
|
|
816
|
+
};
|
|
817
|
+
const key = [
|
|
818
|
+
occurrence.fieldSlug,
|
|
819
|
+
occurrence.fieldPath,
|
|
820
|
+
occurrence.occurrenceIndex,
|
|
821
|
+
occurrence.referenceType,
|
|
822
|
+
occurrence.provider,
|
|
823
|
+
occurrence.providerAssetId,
|
|
824
|
+
occurrence.mediaId ?? ""
|
|
825
|
+
].join("\0");
|
|
826
|
+
if (seen.has(key)) return;
|
|
827
|
+
seen.add(key);
|
|
828
|
+
occurrences.push(occurrence);
|
|
829
|
+
}
|
|
830
|
+
function readMediaRef(value, fallbackKind) {
|
|
831
|
+
if (typeof value === "string") {
|
|
832
|
+
const id = normalizeLocalMediaId(value);
|
|
833
|
+
return id ? buildMediaRef({
|
|
834
|
+
id,
|
|
835
|
+
provider: "local",
|
|
836
|
+
mimeType: null,
|
|
837
|
+
fallbackKind
|
|
838
|
+
}) : null;
|
|
839
|
+
}
|
|
840
|
+
if (!isRecord$1(value)) return null;
|
|
841
|
+
const provider = normalizeProvider(value.provider);
|
|
842
|
+
const id = provider === "local" ? normalizeLocalMediaId(value.id) : normalizeStableId(value.id);
|
|
843
|
+
if (!id) return null;
|
|
844
|
+
return buildMediaRef({
|
|
845
|
+
id,
|
|
846
|
+
provider,
|
|
847
|
+
mimeType: normalizeMimeValue(value.mimeType),
|
|
848
|
+
fallbackKind
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
function buildMediaRef(input) {
|
|
852
|
+
const provider = normalizeProvider(input.provider);
|
|
853
|
+
if (provider === "external") return null;
|
|
854
|
+
return {
|
|
855
|
+
mediaId: provider === "local" ? input.id : null,
|
|
856
|
+
provider,
|
|
857
|
+
providerAssetId: input.id,
|
|
858
|
+
mediaKind: mediaKindFromMime(input.mimeType) ?? input.fallbackKind,
|
|
859
|
+
mimeType: input.mimeType
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function readPortableTextAssetRef(asset, provider) {
|
|
863
|
+
const normalizeId = provider === "local" ? normalizeLocalMediaId : normalizeStableId;
|
|
864
|
+
const ref = normalizeId(asset._ref);
|
|
865
|
+
if (ref) return {
|
|
866
|
+
key: "_ref",
|
|
867
|
+
id: ref
|
|
868
|
+
};
|
|
869
|
+
const id = normalizeId(asset.id);
|
|
870
|
+
if (id) return {
|
|
871
|
+
key: "id",
|
|
872
|
+
id
|
|
873
|
+
};
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
function normalizeProvider(value) {
|
|
877
|
+
return readString$1(value)?.trim() || "local";
|
|
878
|
+
}
|
|
879
|
+
function normalizeLocalMediaId(value) {
|
|
880
|
+
const id = normalizeStableId(value);
|
|
881
|
+
if (!id) return null;
|
|
882
|
+
return id.includes("/") ? null : id;
|
|
883
|
+
}
|
|
884
|
+
function normalizeStableId(value) {
|
|
885
|
+
if (typeof value !== "string") return null;
|
|
886
|
+
const trimmed = value.trim();
|
|
887
|
+
if (!trimmed) return null;
|
|
888
|
+
if (URL_LIKE_RE.test(trimmed)) return null;
|
|
889
|
+
if (trimmed.startsWith(INTERNAL_MEDIA_PREFIX)) return null;
|
|
890
|
+
return trimmed;
|
|
891
|
+
}
|
|
892
|
+
function normalizeMimeValue(value) {
|
|
893
|
+
if (typeof value !== "string") return null;
|
|
894
|
+
const normalized = normalizeMime(value);
|
|
895
|
+
return normalized.includes("/") ? normalized : null;
|
|
896
|
+
}
|
|
897
|
+
function mediaKindFromMime(mimeType) {
|
|
898
|
+
if (!mimeType) return null;
|
|
899
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
900
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
901
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
902
|
+
if (mimeType.startsWith("font/") || mimeType.startsWith("application/font-")) return "font";
|
|
903
|
+
if (mimeType.startsWith("text/")) return "text";
|
|
904
|
+
if (isDocumentMime(mimeType)) return "document";
|
|
905
|
+
if (isArchiveMime(mimeType)) return "archive";
|
|
906
|
+
return "other";
|
|
907
|
+
}
|
|
908
|
+
function isDocumentMime(mimeType) {
|
|
909
|
+
return mimeType === "application/pdf" || mimeType === "application/msword" || mimeType === "application/rtf" || mimeType === "application/vnd.ms-excel" || mimeType === "application/vnd.ms-powerpoint" || mimeType.startsWith("application/vnd.openxmlformats-officedocument.");
|
|
910
|
+
}
|
|
911
|
+
function isArchiveMime(mimeType) {
|
|
912
|
+
return mimeType === "application/zip" || mimeType === "application/gzip" || mimeType === "application/x-tar" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-rar-compressed" || mimeType === "application/vnd.rar";
|
|
913
|
+
}
|
|
914
|
+
function readString$1(value) {
|
|
915
|
+
return typeof value === "string" ? value : null;
|
|
916
|
+
}
|
|
917
|
+
function isRecord$1(value) {
|
|
918
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
//#endregion
|
|
922
|
+
//#region src/media/usage/source-key.ts
|
|
923
|
+
const MEDIA_USAGE_CONTENT_SOURCE_VARIANTS = ["columns", "draft_overlay"];
|
|
924
|
+
function buildContentMediaUsageSourceKey(input) {
|
|
925
|
+
return `content:${input.collectionSlug}:${input.contentId}:${input.sourceVariant}`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/media/usage/content-snapshots.ts
|
|
930
|
+
const CONTENT_SOURCE_SCHEMA_VERSION = 1;
|
|
931
|
+
const CONTENT_SYSTEM_COLUMNS = [
|
|
932
|
+
"id",
|
|
933
|
+
"slug",
|
|
934
|
+
"status",
|
|
935
|
+
"created_at",
|
|
936
|
+
"updated_at",
|
|
937
|
+
"published_at",
|
|
938
|
+
"scheduled_at",
|
|
939
|
+
"deleted_at",
|
|
940
|
+
"version",
|
|
941
|
+
"live_revision_id",
|
|
942
|
+
"draft_revision_id",
|
|
943
|
+
"locale",
|
|
944
|
+
"translation_group"
|
|
945
|
+
];
|
|
946
|
+
async function loadContentMediaUsageSnapshots(db, collectionSlug, contentId, fieldDiscovery) {
|
|
947
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
948
|
+
const discovery = fieldDiscovery ?? await loadContentMediaUsageFields(db, collectionSlug);
|
|
949
|
+
const row = await loadContentRow(db, collectionSlug, contentId, [...discovery.extractionFields.map((field) => field.slug), ...discovery.displayFieldSlugs]);
|
|
950
|
+
if (!row) return {
|
|
951
|
+
success: false,
|
|
952
|
+
error: "CONTENT_NOT_FOUND"
|
|
953
|
+
};
|
|
954
|
+
const columnsData = projectData(row, discovery.extractionFields.map((field) => field.slug));
|
|
955
|
+
const displayData = projectRawData(row, discovery.displayFieldSlugs);
|
|
956
|
+
const occurrences = extractMediaUsageOccurrences({
|
|
957
|
+
fields: discovery.extractionFields,
|
|
958
|
+
data: columnsData
|
|
959
|
+
});
|
|
960
|
+
const columnsRevisionId = readNullableString(row.live_revision_id);
|
|
961
|
+
const snapshots = [{
|
|
962
|
+
source: buildContentSource({
|
|
963
|
+
collectionSlug,
|
|
964
|
+
row,
|
|
965
|
+
displayData,
|
|
966
|
+
sourceVariant: "columns",
|
|
967
|
+
revisionId: columnsRevisionId,
|
|
968
|
+
sourceFingerprint: await buildSourceFingerprint({
|
|
969
|
+
collectionSlug,
|
|
970
|
+
sourceVariant: "columns",
|
|
971
|
+
revisionId: columnsRevisionId,
|
|
972
|
+
fields: discovery.extractionFields,
|
|
973
|
+
data: columnsData
|
|
974
|
+
})
|
|
975
|
+
}),
|
|
976
|
+
occurrences,
|
|
977
|
+
fields: discovery.extractionFields
|
|
978
|
+
}];
|
|
979
|
+
const draftRevisionId = readNullableString(row.draft_revision_id);
|
|
980
|
+
if (draftRevisionId) {
|
|
981
|
+
const attemptedDraftSource = buildContentSource({
|
|
982
|
+
collectionSlug,
|
|
983
|
+
row,
|
|
984
|
+
displayData,
|
|
985
|
+
sourceVariant: "draft_overlay",
|
|
986
|
+
revisionId: draftRevisionId
|
|
987
|
+
});
|
|
988
|
+
const revisionResult = await loadRevisionRow(db, draftRevisionId);
|
|
989
|
+
if (!revisionResult) return {
|
|
990
|
+
success: false,
|
|
991
|
+
error: "DRAFT_REVISION_NOT_FOUND",
|
|
992
|
+
source: attemptedDraftSource
|
|
993
|
+
};
|
|
994
|
+
if (!revisionResult.success) return {
|
|
995
|
+
success: false,
|
|
996
|
+
error: "DRAFT_REVISION_INVALID",
|
|
997
|
+
source: attemptedDraftSource
|
|
998
|
+
};
|
|
999
|
+
const revision = revisionResult.revision;
|
|
1000
|
+
if (revision.collection !== collectionSlug || revision.entryId !== row.id) return {
|
|
1001
|
+
success: false,
|
|
1002
|
+
error: "DRAFT_REVISION_MISMATCH",
|
|
1003
|
+
source: attemptedDraftSource
|
|
1004
|
+
};
|
|
1005
|
+
const revisionData = stripRevisionMetadata(revision.data);
|
|
1006
|
+
const draftOverlayData = {
|
|
1007
|
+
...columnsData,
|
|
1008
|
+
...revisionData
|
|
1009
|
+
};
|
|
1010
|
+
const draftDisplayData = {
|
|
1011
|
+
...displayData,
|
|
1012
|
+
...projectPresentData(revisionData, discovery.displayFieldSlugs)
|
|
1013
|
+
};
|
|
1014
|
+
const draftContentSlug = readNullableString(revision.data._slug) ?? readNullableString(row.slug);
|
|
1015
|
+
const draftFingerprint = await buildSourceFingerprint({
|
|
1016
|
+
collectionSlug,
|
|
1017
|
+
sourceVariant: "draft_overlay",
|
|
1018
|
+
revisionId: draftRevisionId,
|
|
1019
|
+
fields: discovery.extractionFields,
|
|
1020
|
+
data: draftOverlayData
|
|
1021
|
+
});
|
|
1022
|
+
snapshots.push({
|
|
1023
|
+
source: buildContentSource({
|
|
1024
|
+
collectionSlug,
|
|
1025
|
+
row,
|
|
1026
|
+
displayData: draftDisplayData,
|
|
1027
|
+
sourceVariant: "draft_overlay",
|
|
1028
|
+
revisionId: draftRevisionId,
|
|
1029
|
+
contentSlug: draftContentSlug,
|
|
1030
|
+
sourceFingerprint: draftFingerprint
|
|
1031
|
+
}),
|
|
1032
|
+
occurrences: extractMediaUsageOccurrences({
|
|
1033
|
+
fields: discovery.extractionFields,
|
|
1034
|
+
data: draftOverlayData
|
|
1035
|
+
}),
|
|
1036
|
+
fields: discovery.extractionFields
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
success: true,
|
|
1041
|
+
snapshots
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async function loadContentRow(db, collectionSlug, contentId, fieldSlugs) {
|
|
1045
|
+
const tableName = getContentTableName(collectionSlug);
|
|
1046
|
+
const columnRefs = uniqueColumns([...CONTENT_SYSTEM_COLUMNS, ...fieldSlugs]).map((column) => sql.ref(column));
|
|
1047
|
+
return (await sql`
|
|
1048
|
+
SELECT ${sql.join(columnRefs, sql`, `)}
|
|
1049
|
+
FROM ${sql.ref(tableName)}
|
|
1050
|
+
WHERE id = ${contentId}
|
|
1051
|
+
LIMIT 1
|
|
1052
|
+
`.execute(db)).rows[0] ?? null;
|
|
1053
|
+
}
|
|
1054
|
+
async function loadRevisionRow(db, revisionId) {
|
|
1055
|
+
const row = await db.selectFrom("revisions").select([
|
|
1056
|
+
"id",
|
|
1057
|
+
"collection",
|
|
1058
|
+
"entry_id",
|
|
1059
|
+
"data"
|
|
1060
|
+
]).where("id", "=", revisionId).executeTakeFirst();
|
|
1061
|
+
if (!row) return null;
|
|
1062
|
+
const data = parseRevisionData(row.data);
|
|
1063
|
+
if (!data) return { success: false };
|
|
1064
|
+
return {
|
|
1065
|
+
success: true,
|
|
1066
|
+
revision: {
|
|
1067
|
+
id: row.id,
|
|
1068
|
+
collection: row.collection,
|
|
1069
|
+
entryId: row.entry_id,
|
|
1070
|
+
data
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function buildContentSource(input) {
|
|
1075
|
+
const { collectionSlug, row, displayData, sourceVariant, revisionId } = input;
|
|
1076
|
+
const contentId = readString(row.id) ?? "";
|
|
1077
|
+
const contentSlug = input.contentSlug ?? readNullableString(row.slug);
|
|
1078
|
+
const source = {
|
|
1079
|
+
sourceKey: buildContentMediaUsageSourceKey({
|
|
1080
|
+
collectionSlug,
|
|
1081
|
+
contentId,
|
|
1082
|
+
sourceVariant
|
|
1083
|
+
}),
|
|
1084
|
+
sourceType: "content",
|
|
1085
|
+
collectionSlug,
|
|
1086
|
+
contentId,
|
|
1087
|
+
sourceVariant,
|
|
1088
|
+
locale: readNullableString(row.locale),
|
|
1089
|
+
translationGroup: readNullableString(row.translation_group),
|
|
1090
|
+
contentSlug,
|
|
1091
|
+
contentTitle: deriveContentTitle(displayData, contentSlug, contentId),
|
|
1092
|
+
contentStatus: readNullableString(row.status),
|
|
1093
|
+
contentScheduledAt: readNullableString(row.scheduled_at),
|
|
1094
|
+
contentDeletedAt: readNullableString(row.deleted_at),
|
|
1095
|
+
revisionId,
|
|
1096
|
+
schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,
|
|
1097
|
+
sourceUpdatedAt: readNullableString(row.updated_at),
|
|
1098
|
+
sourceVersion: readNumber(row.version)
|
|
1099
|
+
};
|
|
1100
|
+
if (input.sourceFingerprint !== void 0) source.sourceFingerprint = input.sourceFingerprint;
|
|
1101
|
+
return source;
|
|
1102
|
+
}
|
|
1103
|
+
async function buildSourceFingerprint(input) {
|
|
1104
|
+
return hashString(canonicalJson({
|
|
1105
|
+
schemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,
|
|
1106
|
+
collectionSlug: input.collectionSlug,
|
|
1107
|
+
sourceVariant: input.sourceVariant,
|
|
1108
|
+
fields: normalizeFingerprintFields(input.fields),
|
|
1109
|
+
values: projectFingerprintData(input.data, input.fields),
|
|
1110
|
+
revisionId: input.sourceVariant === "draft_overlay" ? input.revisionId : null
|
|
1111
|
+
}));
|
|
1112
|
+
}
|
|
1113
|
+
function normalizeFingerprintFields(fields) {
|
|
1114
|
+
return fields.map((field) => {
|
|
1115
|
+
if (field.type !== "repeater") return {
|
|
1116
|
+
slug: field.slug,
|
|
1117
|
+
type: field.type
|
|
1118
|
+
};
|
|
1119
|
+
return {
|
|
1120
|
+
slug: field.slug,
|
|
1121
|
+
type: field.type,
|
|
1122
|
+
subFields: (field.validation?.subFields ?? []).map((subField) => ({
|
|
1123
|
+
slug: subField.slug,
|
|
1124
|
+
type: subField.type
|
|
1125
|
+
})).toSorted((a, b) => a.slug.localeCompare(b.slug))
|
|
1126
|
+
};
|
|
1127
|
+
}).toSorted((a, b) => String(a.slug).localeCompare(String(b.slug)));
|
|
1128
|
+
}
|
|
1129
|
+
function projectFingerprintData(data, fields) {
|
|
1130
|
+
const projected = {};
|
|
1131
|
+
for (const field of fields) projected[field.slug] = Object.hasOwn(data, field.slug) ? data[field.slug] : null;
|
|
1132
|
+
return projected;
|
|
1133
|
+
}
|
|
1134
|
+
function canonicalJson(value) {
|
|
1135
|
+
return JSON.stringify(canonicalize(value));
|
|
1136
|
+
}
|
|
1137
|
+
function canonicalize(value) {
|
|
1138
|
+
if (value === void 0) return null;
|
|
1139
|
+
if (typeof value === "bigint") return value.toString();
|
|
1140
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
1141
|
+
if (Array.isArray(value)) return value.map((item) => canonicalize(item));
|
|
1142
|
+
if (!isRecord(value)) return value;
|
|
1143
|
+
const canonical = {};
|
|
1144
|
+
for (const key of Object.keys(value).toSorted()) canonical[key] = canonicalize(value[key]);
|
|
1145
|
+
return canonical;
|
|
1146
|
+
}
|
|
1147
|
+
function projectData(row, fieldSlugs) {
|
|
1148
|
+
const data = {};
|
|
1149
|
+
for (const fieldSlug of fieldSlugs) data[fieldSlug] = deserializeValue(row[fieldSlug] ?? null);
|
|
1150
|
+
return data;
|
|
1151
|
+
}
|
|
1152
|
+
function projectRawData(row, fieldSlugs) {
|
|
1153
|
+
const data = {};
|
|
1154
|
+
for (const fieldSlug of fieldSlugs) data[fieldSlug] = row[fieldSlug] ?? null;
|
|
1155
|
+
return data;
|
|
1156
|
+
}
|
|
1157
|
+
function projectPresentData(row, fieldSlugs) {
|
|
1158
|
+
const data = {};
|
|
1159
|
+
for (const fieldSlug of fieldSlugs) if (Object.hasOwn(row, fieldSlug)) data[fieldSlug] = row[fieldSlug];
|
|
1160
|
+
return data;
|
|
1161
|
+
}
|
|
1162
|
+
function uniqueColumns(columns) {
|
|
1163
|
+
const unique = [...new Set(columns)];
|
|
1164
|
+
for (const column of unique) validateIdentifier(column, "content media usage column");
|
|
1165
|
+
return unique;
|
|
1166
|
+
}
|
|
1167
|
+
function getContentTableName(collectionSlug) {
|
|
1168
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
1169
|
+
return `ec_${collectionSlug}`;
|
|
1170
|
+
}
|
|
1171
|
+
function deserializeValue(value) {
|
|
1172
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) try {
|
|
1173
|
+
return JSON.parse(value);
|
|
1174
|
+
} catch {
|
|
1175
|
+
return value;
|
|
1176
|
+
}
|
|
1177
|
+
return value;
|
|
1178
|
+
}
|
|
1179
|
+
function parseRevisionData(value) {
|
|
1180
|
+
if (typeof value === "string") try {
|
|
1181
|
+
const parsed = JSON.parse(value);
|
|
1182
|
+
return isRecord(parsed) ? parsed : null;
|
|
1183
|
+
} catch {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
return isRecord(value) ? value : null;
|
|
1187
|
+
}
|
|
1188
|
+
function stripRevisionMetadata(data) {
|
|
1189
|
+
const stripped = {};
|
|
1190
|
+
for (const [key, value] of Object.entries(data)) if (!key.startsWith("_")) stripped[key] = value;
|
|
1191
|
+
return stripped;
|
|
1192
|
+
}
|
|
1193
|
+
function deriveContentTitle(displayData, contentSlug, contentId) {
|
|
1194
|
+
for (const fieldSlug of ["title", "name"]) {
|
|
1195
|
+
const value = displayData[fieldSlug];
|
|
1196
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
1197
|
+
}
|
|
1198
|
+
return contentSlug ?? contentId;
|
|
1199
|
+
}
|
|
1200
|
+
function readString(value) {
|
|
1201
|
+
return typeof value === "string" ? value : null;
|
|
1202
|
+
}
|
|
1203
|
+
function readNullableString(value) {
|
|
1204
|
+
return value === null || value === void 0 ? null : readString(value);
|
|
1205
|
+
}
|
|
1206
|
+
function readNumber(value) {
|
|
1207
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1208
|
+
if (typeof value === "bigint") return Number(value);
|
|
1209
|
+
if (typeof value === "string" && value) {
|
|
1210
|
+
const parsed = Number(value);
|
|
1211
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1212
|
+
}
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
function isRecord(value) {
|
|
1216
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
//#endregion
|
|
1220
|
+
//#region src/media/usage/content-refresh.ts
|
|
1221
|
+
const CONTENT_MEDIA_USAGE_ADAPTER_ID = "content-media";
|
|
1222
|
+
const CONTENT_MEDIA_USAGE_COLLECTION_SCOPE = "collection";
|
|
1223
|
+
const CONTENT_USAGE_LOCKS_KEY = Symbol.for("emdash.mediaUsage.contentLocks");
|
|
1224
|
+
const CONTENT_USAGE_COLLECTION_LOCKS_KEY = Symbol.for("emdash.mediaUsage.collectionLocks");
|
|
1225
|
+
const CONTENT_USAGE_REFRESH_MAX_ATTEMPTS = 2;
|
|
1226
|
+
const ZERO_RESULT = {
|
|
1227
|
+
success: true,
|
|
1228
|
+
refreshedSourceCount: 0,
|
|
1229
|
+
deletedSourceCount: 0,
|
|
1230
|
+
failedSourceCount: 0
|
|
1231
|
+
};
|
|
1232
|
+
async function refreshContentMediaUsage(db, collectionSlug, contentId) {
|
|
1233
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
1234
|
+
return withContentUsageCollectionLock(collectionSlug, () => withContentUsageLock(collectionSlug, contentId, () => refreshContentMediaUsageUnlocked(db, collectionSlug, contentId)));
|
|
1235
|
+
}
|
|
1236
|
+
async function refreshContentMediaUsageUnlocked(db, collectionSlug, contentId) {
|
|
1237
|
+
try {
|
|
1238
|
+
let conflictResult = null;
|
|
1239
|
+
for (let attempt = 0; attempt < CONTENT_USAGE_REFRESH_MAX_ATTEMPTS; attempt++) {
|
|
1240
|
+
const result = await refreshContentMediaUsageAttempt(db, collectionSlug, contentId);
|
|
1241
|
+
if (result.errorCode !== "CONTENT_USAGE_GENERATION_CONFLICT") return result;
|
|
1242
|
+
conflictResult = result;
|
|
1243
|
+
}
|
|
1244
|
+
return markGenerationConflict(db, collectionSlug, {
|
|
1245
|
+
refreshedSourceCount: conflictResult?.refreshedSourceCount ?? 0,
|
|
1246
|
+
deletedSourceCount: conflictResult?.deletedSourceCount ?? 0
|
|
1247
|
+
});
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
console.error(`[media-usage] Failed to refresh ${collectionSlug}/${contentId}:`, error);
|
|
1250
|
+
await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_REFRESH_ERROR");
|
|
1251
|
+
return {
|
|
1252
|
+
success: false,
|
|
1253
|
+
refreshedSourceCount: 0,
|
|
1254
|
+
deletedSourceCount: 0,
|
|
1255
|
+
failedSourceCount: 0,
|
|
1256
|
+
errorCode: "CONTENT_USAGE_REFRESH_ERROR"
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
async function refreshContentMediaUsageAttempt(db, collectionSlug, contentId) {
|
|
1261
|
+
const repo = new MediaUsageRepository(db);
|
|
1262
|
+
const observedGenerations = await loadObservedContentSourceGenerations(db, collectionSlug, contentId);
|
|
1263
|
+
const snapshotsResult = await loadContentMediaUsageSnapshots(db, collectionSlug, contentId);
|
|
1264
|
+
if (!snapshotsResult.success) return markSnapshotFailure(db, collectionSlug, snapshotsResult);
|
|
1265
|
+
if (!await contentCollectionExists(db, collectionSlug)) {
|
|
1266
|
+
const deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);
|
|
1267
|
+
return {
|
|
1268
|
+
...ZERO_RESULT,
|
|
1269
|
+
deletedSourceCount
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
let refreshedSourceCount = 0;
|
|
1273
|
+
for (const snapshot of snapshotsResult.snapshots) {
|
|
1274
|
+
if (!(await repo.replaceSourceIfCurrent(snapshot.source, snapshot.occurrences, observedGenerations.get(snapshot.source.sourceKey) ?? null)).replaced) return generationConflictResult({
|
|
1275
|
+
refreshedSourceCount,
|
|
1276
|
+
deletedSourceCount: 0
|
|
1277
|
+
});
|
|
1278
|
+
refreshedSourceCount++;
|
|
1279
|
+
}
|
|
1280
|
+
if (!await contentCollectionExists(db, collectionSlug)) {
|
|
1281
|
+
const deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);
|
|
1282
|
+
return {
|
|
1283
|
+
...ZERO_RESULT,
|
|
1284
|
+
deletedSourceCount
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
const expectedSourceKeys = new Set(snapshotsResult.snapshots.map((snapshot) => snapshot.source.sourceKey));
|
|
1288
|
+
const absentSourceKeys = MEDIA_USAGE_CONTENT_SOURCE_VARIANTS.map((sourceVariant) => buildContentMediaUsageSourceKey({
|
|
1289
|
+
collectionSlug,
|
|
1290
|
+
contentId,
|
|
1291
|
+
sourceVariant
|
|
1292
|
+
})).filter((sourceKey) => !expectedSourceKeys.has(sourceKey));
|
|
1293
|
+
let deletedSourceCount = 0;
|
|
1294
|
+
for (const sourceKey of absentSourceKeys) {
|
|
1295
|
+
const expectedGeneration = observedGenerations.get(sourceKey) ?? null;
|
|
1296
|
+
if (expectedGeneration === null) continue;
|
|
1297
|
+
const result = await repo.deleteSourceIfCurrent(sourceKey, expectedGeneration);
|
|
1298
|
+
if (result.deleted) {
|
|
1299
|
+
deletedSourceCount++;
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
if (result.source) return generationConflictResult({
|
|
1303
|
+
refreshedSourceCount,
|
|
1304
|
+
deletedSourceCount
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
return {
|
|
1308
|
+
success: true,
|
|
1309
|
+
refreshedSourceCount,
|
|
1310
|
+
deletedSourceCount,
|
|
1311
|
+
failedSourceCount: 0
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
async function loadObservedContentSourceGenerations(db, collectionSlug, contentId) {
|
|
1315
|
+
const generations = /* @__PURE__ */ new Map();
|
|
1316
|
+
const sourceKeys = MEDIA_USAGE_CONTENT_SOURCE_VARIANTS.map((sourceVariant) => buildContentMediaUsageSourceKey({
|
|
1317
|
+
collectionSlug,
|
|
1318
|
+
contentId,
|
|
1319
|
+
sourceVariant
|
|
1320
|
+
}));
|
|
1321
|
+
for (const sourceKey of sourceKeys) generations.set(sourceKey, null);
|
|
1322
|
+
const rows = await db.selectFrom("_emdash_media_usage_sources").select(["source_key", "current_generation"]).where("source_key", "in", sourceKeys).execute();
|
|
1323
|
+
for (const row of rows) generations.set(row.source_key, row.current_generation);
|
|
1324
|
+
return generations;
|
|
1325
|
+
}
|
|
1326
|
+
async function markGenerationConflict(db, collectionSlug, counts) {
|
|
1327
|
+
await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_GENERATION_CONFLICT");
|
|
1328
|
+
return {
|
|
1329
|
+
success: false,
|
|
1330
|
+
refreshedSourceCount: counts.refreshedSourceCount,
|
|
1331
|
+
deletedSourceCount: counts.deletedSourceCount,
|
|
1332
|
+
failedSourceCount: 0,
|
|
1333
|
+
errorCode: "CONTENT_USAGE_GENERATION_CONFLICT"
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function generationConflictResult(counts) {
|
|
1337
|
+
return {
|
|
1338
|
+
success: false,
|
|
1339
|
+
refreshedSourceCount: counts.refreshedSourceCount,
|
|
1340
|
+
deletedSourceCount: counts.deletedSourceCount,
|
|
1341
|
+
failedSourceCount: 0,
|
|
1342
|
+
errorCode: "CONTENT_USAGE_GENERATION_CONFLICT"
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
async function contentCollectionExists(db, collectionSlug) {
|
|
1346
|
+
return await db.selectFrom("_emdash_collections").select("id").where("slug", "=", collectionSlug).executeTakeFirst() !== void 0;
|
|
1347
|
+
}
|
|
1348
|
+
async function deleteContentMediaUsage(db, collectionSlug, contentId) {
|
|
1349
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
1350
|
+
return withContentUsageCollectionLock(collectionSlug, () => withContentUsageLock(collectionSlug, contentId, () => deleteContentMediaUsageUnlocked(db, collectionSlug, contentId)));
|
|
1351
|
+
}
|
|
1352
|
+
async function deleteContentMediaUsageUnlocked(db, collectionSlug, contentId) {
|
|
1353
|
+
try {
|
|
1354
|
+
const deletedSourceCount = await new MediaUsageRepository(db).deleteContentSources(collectionSlug, contentId);
|
|
1355
|
+
return {
|
|
1356
|
+
...ZERO_RESULT,
|
|
1357
|
+
deletedSourceCount
|
|
1358
|
+
};
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
console.error(`[media-usage] Failed to delete usage for ${collectionSlug}/${contentId}:`, error);
|
|
1361
|
+
await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_DELETE_ERROR");
|
|
1362
|
+
return {
|
|
1363
|
+
success: false,
|
|
1364
|
+
refreshedSourceCount: 0,
|
|
1365
|
+
deletedSourceCount: 0,
|
|
1366
|
+
failedSourceCount: 0,
|
|
1367
|
+
errorCode: "CONTENT_USAGE_DELETE_ERROR"
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
async function deleteContentMediaUsageCollection(db, collectionSlug) {
|
|
1372
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
1373
|
+
return withContentUsageCollectionLock(collectionSlug, () => deleteContentMediaUsageCollectionUnlocked(db, collectionSlug));
|
|
1374
|
+
}
|
|
1375
|
+
async function deleteContentMediaUsageCollectionUnlocked(db, collectionSlug) {
|
|
1376
|
+
try {
|
|
1377
|
+
const repo = new MediaUsageRepository(db);
|
|
1378
|
+
const deletedSourceCount = await repo.deleteCollectionSources(collectionSlug);
|
|
1379
|
+
await repo.deleteIndexStatus({
|
|
1380
|
+
adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
|
|
1381
|
+
scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,
|
|
1382
|
+
scopeKey: collectionSlug
|
|
1383
|
+
});
|
|
1384
|
+
return {
|
|
1385
|
+
...ZERO_RESULT,
|
|
1386
|
+
deletedSourceCount
|
|
1387
|
+
};
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
console.error(`[media-usage] Failed to delete usage for collection ${collectionSlug}:`, error);
|
|
1390
|
+
try {
|
|
1391
|
+
await new MediaUsageRepository(db).deleteIndexStatus({
|
|
1392
|
+
adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
|
|
1393
|
+
scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,
|
|
1394
|
+
scopeKey: collectionSlug
|
|
1395
|
+
});
|
|
1396
|
+
} catch (statusError) {
|
|
1397
|
+
console.error(`[media-usage] Failed to clear usage status for deleted collection ${collectionSlug}:`, statusError);
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
success: false,
|
|
1401
|
+
refreshedSourceCount: 0,
|
|
1402
|
+
deletedSourceCount: 0,
|
|
1403
|
+
failedSourceCount: 0,
|
|
1404
|
+
errorCode: "CONTENT_USAGE_DELETE_ERROR"
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
async function refreshContentMediaUsageAfterWrite(db, collectionSlug, contentId) {
|
|
1409
|
+
const result = await refreshContentMediaUsage(db, collectionSlug, contentId);
|
|
1410
|
+
if (!result.success) console.error(`[media-usage] Usage refresh for ${collectionSlug}/${contentId} finished with ${result.errorCode}`);
|
|
1411
|
+
}
|
|
1412
|
+
async function markContentMediaUsageCollectionStale(db, collectionSlug, lastErrorCode) {
|
|
1413
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
1414
|
+
const repo = new MediaUsageRepository(db);
|
|
1415
|
+
const identity = {
|
|
1416
|
+
adapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,
|
|
1417
|
+
scopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,
|
|
1418
|
+
scopeKey: collectionSlug
|
|
1419
|
+
};
|
|
1420
|
+
const existing = await repo.findIndexStatus(identity);
|
|
1421
|
+
await repo.upsertIndexStatus({
|
|
1422
|
+
...identity,
|
|
1423
|
+
status: "stale",
|
|
1424
|
+
schemaVersion: existing?.schemaVersion ?? CONTENT_SOURCE_SCHEMA_VERSION,
|
|
1425
|
+
startedAt: existing?.startedAt ?? null,
|
|
1426
|
+
completedAt: existing?.completedAt ?? null,
|
|
1427
|
+
cursor: existing?.cursor ?? null,
|
|
1428
|
+
indexedSourceCount: existing?.indexedSourceCount ?? 0,
|
|
1429
|
+
failedSourceCount: existing?.failedSourceCount ?? 0,
|
|
1430
|
+
lastErrorCode
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
async function findNonTranslatableSiblingContentIds(db, collectionSlug, updatedContentId, translationGroup, updatedData) {
|
|
1434
|
+
if (!isI18nEnabled() || !updatedData || !translationGroup) return [];
|
|
1435
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
1436
|
+
const collection = await db.selectFrom("_emdash_collections").select("id").where("slug", "=", collectionSlug).executeTakeFirst();
|
|
1437
|
+
if (!collection) return [];
|
|
1438
|
+
const touchedNonTranslatableSlugs = (await db.selectFrom("_emdash_fields").select("slug").where("collection_id", "=", collection.id).where("translatable", "=", 0).execute()).filter((field) => field.slug in updatedData).map((field) => field.slug);
|
|
1439
|
+
if (touchedNonTranslatableSlugs.length === 0) return [];
|
|
1440
|
+
const usageFields = await loadContentMediaUsageFields(db, collectionSlug);
|
|
1441
|
+
const usageRelevantSlugs = new Set([...usageFields.extractionFields.map((field) => field.slug), ...usageFields.displayFieldSlugs]);
|
|
1442
|
+
if (!touchedNonTranslatableSlugs.some((slug) => usageRelevantSlugs.has(slug))) return [];
|
|
1443
|
+
const tableName = `ec_${collectionSlug}`;
|
|
1444
|
+
return (await sql`
|
|
1445
|
+
SELECT id
|
|
1446
|
+
FROM ${sql.ref(tableName)}
|
|
1447
|
+
WHERE translation_group = ${translationGroup}
|
|
1448
|
+
AND id != ${updatedContentId}
|
|
1449
|
+
ORDER BY id ASC
|
|
1450
|
+
`.execute(db)).rows.map((row) => row.id);
|
|
1451
|
+
}
|
|
1452
|
+
async function markSnapshotFailure(db, collectionSlug, result) {
|
|
1453
|
+
const repo = new MediaUsageRepository(db);
|
|
1454
|
+
if (result.source) await repo.markSourceAttempted({
|
|
1455
|
+
...result.source,
|
|
1456
|
+
sourceCompleteness: "failed",
|
|
1457
|
+
lastErrorCode: result.error
|
|
1458
|
+
});
|
|
1459
|
+
await markContentMediaUsageCollectionStale(db, collectionSlug, result.error);
|
|
1460
|
+
return {
|
|
1461
|
+
success: false,
|
|
1462
|
+
refreshedSourceCount: 0,
|
|
1463
|
+
deletedSourceCount: 0,
|
|
1464
|
+
failedSourceCount: result.source ? 1 : 0,
|
|
1465
|
+
errorCode: result.error
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
async function markContentMediaUsageCollectionStaleSafely(db, collectionSlug, lastErrorCode) {
|
|
1469
|
+
try {
|
|
1470
|
+
await markContentMediaUsageCollectionStale(db, collectionSlug, lastErrorCode);
|
|
1471
|
+
return true;
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
console.error(`[media-usage] Failed to mark ${collectionSlug} stale:`, error);
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async function withContentUsageLock(collectionSlug, contentId, fn) {
|
|
1478
|
+
const locks = getContentUsageLocks();
|
|
1479
|
+
const lockKey = `${collectionSlug}\0${contentId}`;
|
|
1480
|
+
const previous = locks.get(lockKey) ?? Promise.resolve();
|
|
1481
|
+
let releaseCurrent;
|
|
1482
|
+
const current = new Promise((resolve) => {
|
|
1483
|
+
releaseCurrent = resolve;
|
|
1484
|
+
});
|
|
1485
|
+
const next = previous.catch(() => {}).then(() => current);
|
|
1486
|
+
locks.set(lockKey, next);
|
|
1487
|
+
try {
|
|
1488
|
+
await previous.catch(() => {});
|
|
1489
|
+
return await fn();
|
|
1490
|
+
} finally {
|
|
1491
|
+
releaseCurrent();
|
|
1492
|
+
if (locks.get(lockKey) === next) locks.delete(lockKey);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
async function withContentUsageCollectionLock(collectionSlug, fn) {
|
|
1496
|
+
const locks = getContentUsageCollectionLocks();
|
|
1497
|
+
const previous = locks.get(collectionSlug) ?? Promise.resolve();
|
|
1498
|
+
let releaseCurrent;
|
|
1499
|
+
const current = new Promise((resolve) => {
|
|
1500
|
+
releaseCurrent = resolve;
|
|
1501
|
+
});
|
|
1502
|
+
const next = previous.catch(() => {}).then(() => current);
|
|
1503
|
+
locks.set(collectionSlug, next);
|
|
1504
|
+
try {
|
|
1505
|
+
await previous.catch(() => {});
|
|
1506
|
+
return await fn();
|
|
1507
|
+
} finally {
|
|
1508
|
+
releaseCurrent();
|
|
1509
|
+
if (locks.get(collectionSlug) === next) locks.delete(collectionSlug);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function getContentUsageLocks() {
|
|
1513
|
+
const global = globalThis;
|
|
1514
|
+
const existing = global[CONTENT_USAGE_LOCKS_KEY];
|
|
1515
|
+
if (existing instanceof Map) return existing;
|
|
1516
|
+
const locks = /* @__PURE__ */ new Map();
|
|
1517
|
+
global[CONTENT_USAGE_LOCKS_KEY] = locks;
|
|
1518
|
+
return locks;
|
|
1519
|
+
}
|
|
1520
|
+
function getContentUsageCollectionLocks() {
|
|
1521
|
+
const global = globalThis;
|
|
1522
|
+
const existing = global[CONTENT_USAGE_COLLECTION_LOCKS_KEY];
|
|
1523
|
+
if (existing instanceof Map) return existing;
|
|
1524
|
+
const locks = /* @__PURE__ */ new Map();
|
|
1525
|
+
global[CONTENT_USAGE_COLLECTION_LOCKS_KEY] = locks;
|
|
1526
|
+
return locks;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
//#endregion
|
|
1530
|
+
export { markContentMediaUsageCollectionStaleSafely as a, markContentMediaUsageCollectionStale as i, deleteContentMediaUsageCollection as n, refreshContentMediaUsageAfterWrite as o, findNonTranslatableSiblingContentIds as r, deleteContentMediaUsage as t };
|
|
1531
|
+
//# sourceMappingURL=content-refresh-CA4uKwzH.mjs.map
|