emdash 0.27.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-u037EnTR.d.mts → adapters-hUy8vOB-.d.mts} +1 -1
- package/dist/{adapters-u037EnTR.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-80kycR9o.mjs → api-BFURZn1i.mjs} +33 -23
- 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-DSypk6Iu.mjs → apply-Bu-8nYHL.mjs} +111 -74
- package/dist/apply-Bu-8nYHL.mjs.map +1 -0
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.mjs +3 -3
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +50 -11
- 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 -2
- package/dist/astro/middleware.mjs +124 -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 +37 -36
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +37 -36
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +36 -35
- 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 +36 -35
- 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 -41
- 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 -10
- 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.mjs +4 -4
- 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.mjs +25 -24
- 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 +13 -13
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-C3a_zGsM.mjs → authorize-Dz88F_Fw.mjs} +2 -2
- package/dist/{authorize-C3a_zGsM.mjs.map → authorize-Dz88F_Fw.mjs.map} +1 -1
- package/dist/{byline-DEKSVPTy.mjs → byline-Bm8ZgUP9.mjs} +6 -6
- package/dist/{byline-DEKSVPTy.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-CtQ6blXd.d.mts → byline-fields-Cgrt9-aL.d.mts} +49 -34
- package/dist/{byline-fields-CtQ6blXd.d.mts.map → byline-fields-Cgrt9-aL.d.mts.map} +1 -1
- package/dist/{bylines-Ds4-otXl.mjs → bylines-6tm9mgQb.mjs} +4 -4
- package/dist/{bylines-Ds4-otXl.mjs.map → bylines-6tm9mgQb.mjs.map} +1 -1
- package/dist/{bylines-CiqgTHWF.mjs → bylines-Woq87129.mjs} +6 -6
- package/dist/{bylines-CiqgTHWF.mjs.map → bylines-Woq87129.mjs.map} +1 -1
- package/dist/{cache-Ciu3e3AF.mjs → cache-CEBAZjir.mjs} +2 -2
- package/dist/{cache-Ciu3e3AF.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-CZFiauig.mjs → chunks-B2isWQhr.mjs} +2 -2
- package/dist/{chunks-CZFiauig.mjs.map → chunks-B2isWQhr.mjs.map} +1 -1
- package/dist/cli/index.mjs +22 -21
- 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-CH1IrFFs.mjs → comment-CzeatwBV.mjs} +3 -3
- package/dist/{comment-CH1IrFFs.mjs.map → comment-CzeatwBV.mjs.map} +1 -1
- package/dist/{comment-reaction-kruNY-kP.mjs → comment-reaction-Cj9QJY7I.mjs} +2 -2
- package/dist/{comment-reaction-kruNY-kP.mjs.map → comment-reaction-Cj9QJY7I.mjs.map} +1 -1
- package/dist/{comments-BUJyXbgq.mjs → comments-BLE-jv5k.mjs} +3 -3
- package/dist/{comments-BUJyXbgq.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-RmfHoWqK.mjs → content-DhgEth4k.mjs} +4 -4
- package/dist/{content-RmfHoWqK.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-CzUU_WzL.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-CVyGhift.mjs → dashboard-EvEKF5en.mjs} +4 -4
- package/dist/{dashboard-CVyGhift.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-BHkLZk-w.mjs → error-CkAseSqZ.mjs} +2 -2
- package/dist/{error-BHkLZk-w.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-BA-8ZPiT.mjs → field-defs-cache-WUI88LX5.mjs} +2 -2
- package/dist/{field-defs-cache-BA-8ZPiT.mjs.map → field-defs-cache-WUI88LX5.mjs.map} +1 -1
- package/dist/{fts-manager-Db9jGavf.mjs → fts-manager-BbFaF_V8.mjs} +2 -2
- package/dist/{fts-manager-Db9jGavf.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-CDZZjrLT.d.mts → index-B9sIxwe4.d.mts} +21 -22
- package/dist/{index-CDZZjrLT.d.mts.map → index-B9sIxwe4.d.mts.map} +1 -1
- package/dist/{index-DGIjmUXQ.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 -18
- package/dist/index.mjs +54 -53
- package/dist/{load-DpHICVPh.mjs → load-Djn3HWQS.mjs} +2 -2
- package/dist/{load-DpHICVPh.mjs.map → load-Djn3HWQS.mjs.map} +1 -1
- package/dist/{loader-4K37qA-y.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 -12
- package/dist/media/local-runtime.mjs +5 -5
- package/dist/{media-C0nRRfs1.mjs → media-BrXIaMFN.mjs} +2 -2
- package/dist/{media-C0nRRfs1.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-SL2K0xq2.mjs → menus-Cfivc2Ad.mjs} +3 -3
- package/dist/{menus-SL2K0xq2.mjs.map → menus-Cfivc2Ad.mjs.map} +1 -1
- package/dist/{menus-DzRyC1ID.mjs → menus-CsZ2ZEK2.mjs} +16 -12
- package/dist/{menus-DzRyC1ID.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-CqyZj3Hp.mjs → parse-GmjecGct.mjs} +2 -2
- package/dist/{parse-CqyZj3Hp.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 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
- 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-B0M-9LYR.mjs → query-ByGq6ver.mjs} +14 -14
- package/dist/{query-B0M-9LYR.mjs.map → query-ByGq6ver.mjs.map} +1 -1
- package/dist/{rate-limit-BwoUdIpC.mjs → rate-limit-C_Lf63Dd.mjs} +2 -2
- package/dist/{rate-limit-BwoUdIpC.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-lMOzG04S.mjs → redirect-IyfNirgS.mjs} +2 -2
- package/dist/{redirect-lMOzG04S.mjs.map → redirect-IyfNirgS.mjs.map} +1 -1
- package/dist/{redirects-9218MmYl.mjs → redirects-Bs1g42Ri.mjs} +4 -4
- package/dist/{redirects-9218MmYl.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-DP-iEtdR.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-CFngB4WZ.mjs → runner-K8XK101B.mjs} +309 -190
- package/dist/runner-K8XK101B.mjs.map +1 -0
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +2 -2
- package/dist/{scheduled-publish-BMljtY5G.d.mts → scheduled-publish-Drffrf2a.d.mts} +1 -1
- package/dist/scheduled-publish-Drffrf2a.d.mts.map +1 -0
- package/dist/{schema-BfJ7bIXH.mjs → schema-Blhoxp3P.mjs} +5 -5
- package/dist/{schema-BfJ7bIXH.mjs.map → schema-Blhoxp3P.mjs.map} +1 -1
- package/dist/{search-C7t3PH1J.mjs → search-DZN2HZ-V.mjs} +4 -4
- package/dist/{search-C7t3PH1J.mjs.map → search-DZN2HZ-V.mjs.map} +1 -1
- 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-Dm0CVX23.mjs → sections-CpF5SaGr.mjs} +3 -3
- package/dist/{sections-Dm0CVX23.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-Dd3tbY_k.mjs → seo-DXu2UQnC.mjs} +3 -3
- package/dist/{seo-Dd3tbY_k.mjs.map → seo-DXu2UQnC.mjs.map} +1 -1
- package/dist/{service-CTrqp_Jy.mjs → service-DQmiBMzt.mjs} +3 -3
- package/dist/{service-CTrqp_Jy.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-CZ7ZP9cW.mjs → settings-rZyHEcNT.mjs} +3 -3
- package/dist/{settings-CZ7ZP9cW.mjs.map → settings-rZyHEcNT.mjs.map} +1 -1
- package/dist/{settings-BX8cWBus.mjs → settings-zhmdKyJQ.mjs} +5 -5
- package/dist/{settings-BX8cWBus.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-DChKcVxf.mjs → taxonomies-BGBd16Eu.mjs} +4 -4
- package/dist/{taxonomies-DChKcVxf.mjs.map → taxonomies-BGBd16Eu.mjs.map} +1 -1
- package/dist/{taxonomies-ckPjz-6d.mjs → taxonomies-Cnb4F-Pb.mjs} +7 -7
- package/dist/{taxonomies-ckPjz-6d.mjs.map → taxonomies-Cnb4F-Pb.mjs.map} +1 -1
- package/dist/{taxonomy-Cru-he6s.mjs → taxonomy-BxGxZZdu.mjs} +4 -4
- package/dist/{taxonomy-Cru-he6s.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-Blrl2k_o.d.mts → transport-BMDWHSgZ.d.mts} +1 -1
- package/dist/{transport-Blrl2k_o.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-CvuKO5Pn.d.mts → types-CgvhU-hF.d.mts} +1 -1
- package/dist/{types-CvuKO5Pn.d.mts.map → types-CgvhU-hF.d.mts.map} +1 -1
- package/dist/{types-Y09-wtyU.d.mts → types-ClUkdbWb.d.mts} +1 -1
- package/dist/{types-Y09-wtyU.d.mts.map → types-ClUkdbWb.d.mts.map} +1 -1
- package/dist/{types-DU4TSFS5.mjs → types-CuW0zWDA.mjs} +2 -2
- package/dist/{types-DU4TSFS5.mjs.map → types-CuW0zWDA.mjs.map} +1 -1
- package/dist/{types-BkZ8DUEI.d.mts → types-DmHk7vAt.d.mts} +1 -1
- package/dist/{types-BkZ8DUEI.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-ByChcBgE.d.mts → types-ea9sAPck.d.mts} +1 -1
- package/dist/{types-ByChcBgE.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-Dbqff978.d.mts → types-vgEWF18A.d.mts} +19 -1
- package/dist/{types-Dbqff978.d.mts.map → types-vgEWF18A.d.mts.map} +1 -1
- package/dist/{user-DgGbKL9s.mjs → user-BjAOvU3Z.mjs} +3 -3
- package/dist/{user-DgGbKL9s.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-CRkcYZAR.mjs → validate-CWSnKVIJ.mjs} +2 -2
- package/dist/{validate-CRkcYZAR.mjs.map → validate-CWSnKVIJ.mjs.map} +1 -1
- package/dist/{validate-9ECmtEpJ.d.mts → validate-Z1b86_3B.d.mts} +5 -5
- package/dist/{validate-9ECmtEpJ.d.mts.map → validate-Z1b86_3B.d.mts.map} +1 -1
- package/dist/{validation-sR26SOwD.mjs → validation-c8WjhH8z.mjs} +8 -5
- package/dist/{validation-sR26SOwD.mjs.map → validation-c8WjhH8z.mjs.map} +1 -1
- package/dist/version-CaccU7DM.mjs +7 -0
- package/dist/{version-CrqhKGWn.mjs.map → version-CaccU7DM.mjs.map} +1 -1
- package/dist/{widgets-DRgtn7Cl.mjs → widgets-hWcIicX8.mjs} +3 -3
- package/dist/{widgets-DRgtn7Cl.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-C5JaV--i.mjs → zod-generator-CacrR-i8.mjs} +2 -2
- package/dist/{zod-generator-C5JaV--i.mjs.map → zod-generator-CacrR-i8.mjs.map} +1 -1
- package/package.json +7 -7
- package/src/api/handlers/content.ts +10 -7
- package/src/api/schemas/import.ts +19 -0
- package/src/astro/integration/runtime.ts +3 -6
- package/src/astro/middleware/auth.ts +9 -3
- package/src/astro/middleware/csp.ts +54 -3
- package/src/astro/routes/admin.astro +9 -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/components/EmDashImage.astro +59 -23
- package/src/components/Image.astro +36 -35
- 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 +4 -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/seed/apply.ts +156 -99
- package/dist/api-80kycR9o.mjs.map +0 -1
- package/dist/apply-DSypk6Iu.mjs.map +0 -1
- package/dist/byline-fields-eyJMblSr.mjs.map +0 -1
- package/dist/context-CzUU_WzL.mjs.map +0 -1
- package/dist/import-Bb1T9WJS.mjs.map +0 -1
- package/dist/index-DGIjmUXQ.d.mts.map +0 -1
- package/dist/loader-4K37qA-y.mjs.map +0 -1
- package/dist/object-cache-CHbHv83-.mjs.map +0 -1
- package/dist/registry-DP-iEtdR.mjs.map +0 -1
- package/dist/runner-CFngB4WZ.mjs.map +0 -1
- package/dist/scheduled-publish-BMljtY5G.d.mts.map +0 -1
- package/dist/types-xIfVRNLp.d.mts.map +0 -1
- package/dist/utils-B7A57fm9.mjs.map +0 -1
- package/dist/version-CrqhKGWn.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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"import-Bb1T9WJS.mjs","names":["normalizeUrl"],"sources":["../src/cli/wxr/parser.ts","../src/import/sections.ts","../src/import/registry.ts","../src/import/sources/wxr.ts","../src/import/sources/wordpress-rest.ts","../src/import/sources/wordpress-plugin.ts","../src/import/index.ts"],"sourcesContent":["/**\n * WordPress WXR (WordPress eXtended RSS) parser\n *\n * Uses SAX streaming parser to handle large export files efficiently.\n * WXR is an RSS extension containing WordPress content exports.\n *\n * @see https://developer.wordpress.org/plugins/data-storage/wp-xml-rpc/\n */\n\nimport type { Readable } from \"node:stream\";\n\nimport sax from \"sax\";\n\n// Regex patterns for WXR parsing\nconst PHP_SERIALIZED_STRING_PATTERN = /s:\\d+:\"([^\"]+)\"/g;\nconst PHP_SERIALIZED_STRING_MATCH_PATTERN = /s:\\d+:\"([^\"]+)\"/;\n\n/**\n * Parsed WordPress export data\n */\nexport interface WxrData {\n\t/** Site metadata */\n\tsite: WxrSite;\n\t/** Posts (including custom post types) */\n\tposts: WxrPost[];\n\t/** Media attachments */\n\tattachments: WxrAttachment[];\n\t/** Categories */\n\tcategories: WxrCategory[];\n\t/** Tags */\n\ttags: WxrTag[];\n\t/** Authors */\n\tauthors: WxrAuthor[];\n\t/** All taxonomy terms (including custom taxonomies and nav_menu) */\n\tterms: WxrTerm[];\n\t/** Parsed navigation menus */\n\tnavMenus: WxrNavMenu[];\n}\n\nexport interface WxrSite {\n\ttitle?: string;\n\tlink?: string;\n\tdescription?: string;\n\tlanguage?: string;\n\tbaseSiteUrl?: string;\n\tbaseBlogUrl?: string;\n}\n\nexport interface WxrPost {\n\tid?: number;\n\ttitle?: string;\n\tlink?: string;\n\tpubDate?: string;\n\tcreator?: string;\n\tguid?: string;\n\tdescription?: string;\n\tcontent?: string;\n\texcerpt?: string;\n\tpostDate?: string;\n\tpostDateGmt?: string;\n\tpostModified?: string;\n\tpostModifiedGmt?: string;\n\tcommentStatus?: string;\n\tpingStatus?: string;\n\tstatus?: string;\n\tpostType?: string;\n\tpostName?: string;\n\tpostPassword?: string;\n\tisSticky?: boolean;\n\t/** Parent post ID for hierarchical content (pages) */\n\tpostParent?: number;\n\t/** Menu order for sorting */\n\tmenuOrder?: number;\n\tcategories: string[];\n\ttags: string[];\n\t/** Custom taxonomy assignments beyond categories/tags */\n\tcustomTaxonomies?: Map<string, string[]>;\n\t/**\n\t * Display labels for per-item category/tag/custom-taxonomy assignments,\n\t * captured from `<category domain=\"...\" nicename=\"...\">Label</category>`\n\t * text content. Used by the importer to back-fill term labels when a\n\t * `<wp:category>` or `<wp:tag>` block wasn't present at the top of the\n\t * WXR (older / hand-edited exports). Keyed by\n\t * `${normalisedTaxonomy}\\u0000${slug}` so categories and tags don't\n\t * collide.\n\t */\n\ttaxonomyLabels?: Map<string, string>;\n\tmeta: Map<string, string>;\n\t/**\n\t * BCP 47 locale code extracted from a detected multilingual plugin\n\t * (WPML's `_icl_lang_code` or Polylang's per-post language taxonomy).\n\t * Absent when no per-post locale could be determined.\n\t */\n\tlocale?: string;\n\t/**\n\t * Source-side translation group ID extracted from a detected multilingual\n\t * plugin (WPML's `trid` / `_icl_translation_id`, or a synthesized id derived\n\t * from Polylang's `_translations` meta). Posts sharing a `translationGroup`\n\t * are translations of one another. The string is opaque -- consumers use it\n\t * only as a key to link posts.\n\t */\n\ttranslationGroup?: string;\n}\n\n/**\n * WPML stores per-post language in postmeta as `_icl_lang_code`. The shared\n * translation id is `trid` (this is the group ID -- every translation of the\n * same content shares it). `_icl_translation_id` exists on some exports too\n * but is a per-translation row id from `wp_icl_translations`, NOT the group\n * id, so it must NOT be used as the group key. We accept it only when `trid`\n * is absent and trust the export to be internally consistent (the only case\n * where that's reasonable is single-post exports with no real grouping).\n *\n * See `wpml_element_trid` in the WPML hook docs: \"the ID of the translation\n * group\".\n */\nconst WPML_LOCALE_META_KEYS = [\"_icl_lang_code\"] as const;\nconst WPML_TRID_META_KEYS = [\"trid\", \"_icl_translation_id\"] as const;\n\n/**\n * Polylang stores per-post language in postmeta as `_locale` on newer\n * exports. The actual language taxonomy assignment lives on\n * `customTaxonomies.language`, which we use as a fallback. Translation\n * grouping is encoded in `_translations` as a serialized PHP map of\n * `{ lang_code => post_id }`; we synthesize a stable group key from the\n * sorted IDs so every member of the group resolves to the same string.\n */\nconst POLYLANG_LOCALE_META_KEY = \"_locale\";\nconst POLYLANG_TRANSLATIONS_META_KEY = \"_translations\";\nconst POLYLANG_LANG_TAXONOMY = \"language\";\n\n/**\n * Extract a list of post-IDs from Polylang's `_translations` PHP-serialized\n * value. The format we care about is roughly:\n *\n * a:2:{s:2:\"en\";i:1;s:2:\"ar\";i:7;}\n *\n * We don't need to round-trip the PHP value -- we just need a stable group\n * key shared by every translation of the same content. Concatenating the\n * sorted IDs gives us exactly that: every post in the group derives the\n * same key from its own copy of `_translations`.\n *\n * Naïve `/i:(\\d+);/g` would also match `i:N;` literals embedded INSIDE\n * string values (e.g. `s:11:\"i:42;hello\";`), which would silently corrupt\n * the group key. We walk the serialized blob token-by-token instead.\n *\n * PHP serializes `s:LEN:\"...\"` with LEN counted in BYTES, not characters\n * (UTF-8 byte length). JS string positions are UTF-16 code units, so we\n * encode to bytes via `TextEncoder` and walk byte offsets. Single-byte-only\n * inputs (the common case for Polylang's `_translations` which only stores\n * ASCII locale codes) take the same path; the encoder is cheap.\n */\nfunction polylangTranslationGroupFromMeta(serialized: string): string | undefined {\n\t// Operate on the UTF-8 byte view so `s:LEN:` advances the right number\n\t// of bytes when the payload contains multibyte characters.\n\tconst encoder = new TextEncoder();\n\tconst bytes = encoder.encode(serialized);\n\tconst decoder = new TextDecoder(\"utf-8\");\n\n\tconst ids: number[] = [];\n\tlet i = 0;\n\tconst n = bytes.length;\n\tconst CHAR_S = 0x73; // 's'\n\tconst CHAR_I = 0x69; // 'i'\n\tconst CHAR_COLON = 0x3a; // ':'\n\tconst CHAR_SEMI = 0x3b; // ';'\n\tconst CHAR_QUOTE = 0x22; // '\"'\n\n\tconst indexOf = (byte: number, from: number): number => {\n\t\tfor (let k = from; k < n; k++) {\n\t\t\tif (bytes[k] === byte) return k;\n\t\t}\n\t\treturn -1;\n\t};\n\n\twhile (i < n) {\n\t\tconst ch = bytes[i];\n\t\tif (ch === CHAR_S && bytes[i + 1] === CHAR_COLON) {\n\t\t\t// s:LEN:\"...\"; — skip the entire string value: the LEN digits,\n\t\t\t// the opening quote, the payload of exactly LEN bytes, the\n\t\t\t// closing quote, and the trailing semicolon.\n\t\t\tconst lenStart = i + 2;\n\t\t\tconst lenEnd = indexOf(CHAR_COLON, lenStart);\n\t\t\tif (lenEnd === -1) break;\n\t\t\tconst lenText = decoder.decode(bytes.slice(lenStart, lenEnd));\n\t\t\tconst len = Number.parseInt(lenText, 10);\n\t\t\tif (!Number.isFinite(len) || len < 0) {\n\t\t\t\ti = lenEnd + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// lenEnd+1 should be `\"`. Defensively check before advancing\n\t\t\t// over the payload -- a malformed input shouldn't crash the\n\t\t\t// import; just skip past this token.\n\t\t\tif (bytes[lenEnd + 1] !== CHAR_QUOTE) {\n\t\t\t\ti = lenEnd + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst payloadStart = lenEnd + 2;\n\t\t\tconst afterPayload = payloadStart + len;\n\t\t\t// afterPayload should point at the closing `\"`; +2 past `\";`.\n\t\t\ti = afterPayload + 2;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === CHAR_I && bytes[i + 1] === CHAR_COLON) {\n\t\t\tconst valStart = i + 2;\n\t\t\tconst valEnd = indexOf(CHAR_SEMI, valStart);\n\t\t\tif (valEnd === -1) break;\n\t\t\tconst idText = decoder.decode(bytes.slice(valStart, valEnd));\n\t\t\tconst id = Number.parseInt(idText, 10);\n\t\t\tif (Number.isFinite(id)) ids.push(id);\n\t\t\ti = valEnd + 1;\n\t\t\tcontinue;\n\t\t}\n\t\t// Any other token (a:LEN:{...}, b:0;, N;, {, }, :, ;, etc.) -- just\n\t\t// advance one byte. We only care about integer literals; everything\n\t\t// else is structural.\n\t\ti++;\n\t}\n\tif (ids.length === 0) return undefined;\n\tconst sorted = [...new Set(ids)].toSorted((a, b) => a - b);\n\treturn `pll:${sorted.join(\",\")}`;\n}\n\n/**\n * Promote multilingual-plugin metadata from `post.meta` and\n * `post.customTaxonomies` into `post.locale` / `post.translationGroup`.\n *\n * Called once per `<item>` after all of its `<wp:postmeta>` and per-item\n * `<category>` entries have been parsed. Safe to call on posts that have no\n * multilingual metadata -- it's a no-op in that case.\n *\n * WPML wins over Polylang when both are present (they shouldn't co-exist on\n * the same site, but defensive precedence avoids ambiguity).\n */\nfunction promoteI18nMetadata(post: WxrPost): void {\n\t// WPML\n\tfor (const key of WPML_LOCALE_META_KEYS) {\n\t\tconst value = post.meta.get(key);\n\t\tif (value) {\n\t\t\tpost.locale = value;\n\t\t\tbreak;\n\t\t}\n\t}\n\tfor (const key of WPML_TRID_META_KEYS) {\n\t\tconst value = post.meta.get(key);\n\t\tif (value) {\n\t\t\tpost.translationGroup = `wpml:${value}`;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Polylang fallbacks (only fill what WPML didn't already provide)\n\tif (!post.locale) {\n\t\tconst pllLocale = post.meta.get(POLYLANG_LOCALE_META_KEY);\n\t\tif (pllLocale) {\n\t\t\tpost.locale = pllLocale;\n\t\t} else {\n\t\t\t// Polylang's primary language signal is a taxonomy assignment\n\t\t\t// on the `language` custom taxonomy. The nicename is the locale\n\t\t\t// code (e.g. \"en\", \"ar\").\n\t\t\tconst langTaxonomy = post.customTaxonomies?.get(POLYLANG_LANG_TAXONOMY);\n\t\t\tconst firstLang = langTaxonomy?.[0];\n\t\t\tif (firstLang) post.locale = firstLang;\n\t\t}\n\t}\n\n\tif (!post.translationGroup) {\n\t\tconst pllTranslations = post.meta.get(POLYLANG_TRANSLATIONS_META_KEY);\n\t\tif (pllTranslations) {\n\t\t\tconst group = polylangTranslationGroupFromMeta(pllTranslations);\n\t\t\tif (group) post.translationGroup = group;\n\t\t}\n\t}\n}\n\nexport interface WxrAttachment {\n\tid?: number;\n\ttitle?: string;\n\turl?: string;\n\tpostDate?: string;\n\tmeta: Map<string, string>;\n}\n\nexport interface WxrCategory {\n\tid?: number;\n\tnicename?: string;\n\tname?: string;\n\tparent?: string;\n\tdescription?: string;\n}\n\nexport interface WxrTag {\n\tid?: number;\n\tslug?: string;\n\tname?: string;\n\tdescription?: string;\n}\n\n/**\n * Generic taxonomy term (categories, tags, nav_menu, custom taxonomies)\n */\nexport interface WxrTerm {\n\tid: number;\n\ttaxonomy: string; // 'category', 'post_tag', 'nav_menu', 'genre', etc.\n\tslug: string;\n\tname: string;\n\tparent?: string;\n\tdescription?: string;\n}\n\n/**\n * Navigation menu structure\n */\nexport interface WxrNavMenu {\n\tid: number;\n\tname: string; // Menu slug\n\tlabel: string; // Menu name\n\titems: WxrNavMenuItem[];\n}\n\n/**\n * Navigation menu item\n */\nexport interface WxrNavMenuItem {\n\tid: number;\n\tmenuId: number;\n\tparentId?: number;\n\tsortOrder: number;\n\ttype: \"custom\" | \"post_type\" | \"taxonomy\";\n\tobjectType?: string; // 'page', 'post', 'category'\n\tobjectId?: number;\n\turl?: string;\n\ttitle: string;\n\ttarget?: string;\n\tclasses?: string;\n}\n\nexport interface WxrAuthor {\n\tid?: number;\n\tlogin?: string;\n\temail?: string;\n\tdisplayName?: string;\n\tfirstName?: string;\n\tlastName?: string;\n}\n\n/** Extract string value from a SAX attribute (handles both Tag and QualifiedTag) */\nfunction attrStr(attr: string | { value: string } | undefined): string {\n\tif (typeof attr === \"string\") return attr;\n\tif (attr && typeof attr === \"object\" && \"value\" in attr) return attr.value;\n\treturn \"\";\n}\n\n/**\n * Normalise a `<category domain=\"...\">` value to the matching EmDash\n * taxonomy name so per-item label captures can be retrieved later using\n * the same key.\n */\nfunction normaliseDomain(domain: string): string {\n\tif (domain === \"post_tag\") return \"tag\";\n\treturn domain;\n}\n\n/**\n * Persist the human label of a `<category>` text body keyed by the\n * normalised `(taxonomy, slug)` pair. Skips trivial labels that equal the\n * slug (no information vs. just storing the slug).\n */\nfunction captureItemCategoryLabel(\n\titem: WxrPost,\n\tpair: { domain: string; nicename: string },\n\tlabel: string,\n): void {\n\tif (!label || label === pair.nicename) return;\n\tif (!item.taxonomyLabels) item.taxonomyLabels = new Map();\n\tconst key = `${normaliseDomain(pair.domain)}\\u0000${pair.nicename}`;\n\tif (!item.taxonomyLabels.has(key)) item.taxonomyLabels.set(key, label);\n}\n\n/** Type guard for complete WxrTerm (all required fields present) */\nfunction isCompleteWxrTerm(term: Partial<WxrTerm>): term is WxrTerm {\n\treturn (\n\t\tterm.id !== undefined &&\n\t\tterm.taxonomy !== undefined &&\n\t\tterm.slug !== undefined &&\n\t\tterm.name !== undefined\n\t);\n}\n\n/**\n * Parse a WordPress WXR export file\n */\nexport function parseWxr(stream: Readable): Promise<WxrData> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst parser = sax.createStream(true, { trim: true });\n\n\t\tconst data: WxrData = {\n\t\t\tsite: {},\n\t\t\tposts: [],\n\t\t\tattachments: [],\n\t\t\tcategories: [],\n\t\t\ttags: [],\n\t\t\tauthors: [],\n\t\t\tterms: [],\n\t\t\tnavMenus: [],\n\t\t};\n\n\t\t// Parser state\n\t\tlet currentPath: string[] = [];\n\t\tlet currentText = \"\";\n\t\tlet currentItem: WxrPost | null = null;\n\t\tlet currentAttachment: WxrAttachment | null = null;\n\t\tlet currentCategory: WxrCategory | null = null;\n\t\tlet currentTag: WxrTag | null = null;\n\t\tlet currentAuthor: WxrAuthor | null = null;\n\t\tlet currentTerm: Partial<WxrTerm> | null = null;\n\t\tlet currentMetaKey = \"\";\n\t\t// Per-item category element currently open. Captured at opentag so\n\t\t// we can pair the text body (the human label) with the slug when\n\t\t// closetag fires. WXR per-item category elements look like:\n\t\t// <category domain=\"category\" nicename=\"hello-world\">Hello World</category>\n\t\tlet pendingItemCategory: { domain: string; nicename: string } | null = null;\n\n\t\t// Track nav_menu_item posts for post-processing\n\t\tconst navMenuItemPosts: WxrPost[] = [];\n\t\t// Track menu term IDs by slug for linking items to menus\n\t\tconst menuTermsBySlug = new Map<string, number>();\n\n\t\tparser.on(\"opentag\", (node) => {\n\t\t\tconst tagName = node.name.toLowerCase();\n\t\t\tcurrentPath.push(tagName);\n\t\t\tcurrentText = \"\";\n\n\t\t\t// Start new item\n\t\t\tif (tagName === \"item\") {\n\t\t\t\tcurrentItem = {\n\t\t\t\t\tcategories: [],\n\t\t\t\t\ttags: [],\n\t\t\t\t\tcustomTaxonomies: new Map(),\n\t\t\t\t\tmeta: new Map(),\n\t\t\t\t};\n\t\t\t} else if (tagName === \"wp:category\") {\n\t\t\t\tcurrentCategory = {};\n\t\t\t} else if (tagName === \"wp:tag\") {\n\t\t\t\tcurrentTag = {};\n\t\t\t} else if (tagName === \"wp:author\") {\n\t\t\t\tcurrentAuthor = {};\n\t\t\t} else if (tagName === \"wp:term\") {\n\t\t\t\tcurrentTerm = {};\n\t\t\t}\n\n\t\t\t// Handle category/tag/custom taxonomy assignment in items\n\t\t\tif (tagName === \"category\" && currentItem && node.attributes) {\n\t\t\t\tconst domain = attrStr(node.attributes.domain);\n\t\t\t\tconst nicename = attrStr(node.attributes.nicename);\n\t\t\t\tif (domain === \"category\" && nicename) {\n\t\t\t\t\tcurrentItem.categories.push(nicename);\n\t\t\t\t\tpendingItemCategory = { domain, nicename };\n\t\t\t\t} else if (domain === \"post_tag\" && nicename) {\n\t\t\t\t\tcurrentItem.tags.push(nicename);\n\t\t\t\t\tpendingItemCategory = { domain, nicename };\n\t\t\t\t} else if (domain && nicename && domain !== \"category\" && domain !== \"post_tag\") {\n\t\t\t\t\t// Custom taxonomy (including nav_menu)\n\t\t\t\t\tif (!currentItem.customTaxonomies) {\n\t\t\t\t\t\tcurrentItem.customTaxonomies = new Map();\n\t\t\t\t\t}\n\t\t\t\t\tconst existing = currentItem.customTaxonomies.get(domain) || [];\n\t\t\t\t\texisting.push(nicename);\n\t\t\t\t\tcurrentItem.customTaxonomies.set(domain, existing);\n\t\t\t\t\tpendingItemCategory = { domain, nicename };\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tparser.on(\"text\", (text) => {\n\t\t\tcurrentText += text;\n\t\t});\n\n\t\tparser.on(\"cdata\", (cdata) => {\n\t\t\tcurrentText += cdata;\n\t\t});\n\n\t\tparser.on(\"closetag\", (tagName) => {\n\t\t\tconst tag = tagName.toLowerCase();\n\t\t\tconst text = currentText.trim();\n\n\t\t\t// Site-level metadata (in channel)\n\t\t\tif (currentPath.includes(\"channel\") && !currentItem) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tif (!data.site.title) data.site.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tif (!data.site.link) data.site.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tif (!data.site.description) data.site.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"language\":\n\t\t\t\t\t\tdata.site.language = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_site_url\":\n\t\t\t\t\t\tdata.site.baseSiteUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_blog_url\":\n\t\t\t\t\t\tdata.site.baseBlogUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Item (post/page/attachment) parsing\n\t\t\tif (currentItem) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tcurrentItem.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tcurrentItem.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"pubdate\":\n\t\t\t\t\t\tcurrentItem.pubDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"dc:creator\":\n\t\t\t\t\t\tcurrentItem.creator = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"guid\":\n\t\t\t\t\t\tcurrentItem.guid = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tcurrentItem.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"content:encoded\":\n\t\t\t\t\t\tcurrentItem.content = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"excerpt:encoded\":\n\t\t\t\t\t\tcurrentItem.excerpt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_id\":\n\t\t\t\t\t\tcurrentItem.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date\":\n\t\t\t\t\t\tcurrentItem.postDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date_gmt\":\n\t\t\t\t\t\tcurrentItem.postDateGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified\":\n\t\t\t\t\t\tcurrentItem.postModified = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified_gmt\":\n\t\t\t\t\t\tcurrentItem.postModifiedGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:comment_status\":\n\t\t\t\t\t\tcurrentItem.commentStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:ping_status\":\n\t\t\t\t\t\tcurrentItem.pingStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:status\":\n\t\t\t\t\t\tcurrentItem.status = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_type\":\n\t\t\t\t\t\tcurrentItem.postType = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_name\":\n\t\t\t\t\t\tcurrentItem.postName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_parent\":\n\t\t\t\t\t\tcurrentItem.postParent = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:menu_order\":\n\t\t\t\t\t\tcurrentItem.menuOrder = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_password\":\n\t\t\t\t\t\tcurrentItem.postPassword = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:is_sticky\":\n\t\t\t\t\t\tcurrentItem.isSticky = text === \"1\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_key\":\n\t\t\t\t\t\tcurrentMetaKey = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_value\":\n\t\t\t\t\t\tif (currentMetaKey) {\n\t\t\t\t\t\t\tcurrentItem.meta.set(currentMetaKey, text);\n\t\t\t\t\t\t\tcurrentMetaKey = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:attachment_url\":\n\t\t\t\t\t\tif (currentItem.postType === \"attachment\") {\n\t\t\t\t\t\t\t// This is actually an attachment\n\t\t\t\t\t\t\tcurrentAttachment = {\n\t\t\t\t\t\t\t\tid: currentItem.id,\n\t\t\t\t\t\t\t\ttitle: currentItem.title,\n\t\t\t\t\t\t\t\turl: text,\n\t\t\t\t\t\t\t\tpostDate: currentItem.postDate,\n\t\t\t\t\t\t\t\tmeta: currentItem.meta,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"category\":\n\t\t\t\t\t\t// Per-item category text body = the human label for\n\t\t\t\t\t\t// the term (`<category nicename=\"hello-world\">Hello\n\t\t\t\t\t\t// World</category>`). Backfilling from per-item\n\t\t\t\t\t\t// elements (older / hand-edited exports without top-\n\t\t\t\t\t\t// level `<wp:category>` blocks) lands the right label\n\t\t\t\t\t\t// instead of slug-cased nonsense.\n\t\t\t\t\t\tif (pendingItemCategory && text) {\n\t\t\t\t\t\t\tcaptureItemCategoryLabel(currentItem, pendingItemCategory, text);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingItemCategory = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"item\":\n\t\t\t\t\t\t// End of item - categorize and store\n\t\t\t\t\t\tif (currentAttachment) {\n\t\t\t\t\t\t\tdata.attachments.push(currentAttachment);\n\t\t\t\t\t\t\tcurrentAttachment = null;\n\t\t\t\t\t\t} else if (currentItem.postType === \"nav_menu_item\") {\n\t\t\t\t\t\t\t// Track nav_menu_item posts for post-processing into menus\n\t\t\t\t\t\t\tnavMenuItemPosts.push(currentItem);\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t} else if (currentItem.postType !== \"attachment\") {\n\t\t\t\t\t\t\t// Promote multilingual plugin metadata before storing.\n\t\t\t\t\t\t\t// All postmeta and per-item categories are parsed by the time\n\t\t\t\t\t\t\t// the closing </item> tag fires, so it's safe to inspect them.\n\t\t\t\t\t\t\tpromoteI18nMetadata(currentItem);\n\t\t\t\t\t\t\t// Store all non-attachment post types (posts, pages, custom post types)\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentItem = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Category parsing\n\t\t\tif (currentCategory) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentCategory.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_nicename\":\n\t\t\t\t\t\tcurrentCategory.nicename = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:cat_name\":\n\t\t\t\t\t\tcurrentCategory.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_parent\":\n\t\t\t\t\t\tcurrentCategory.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_description\":\n\t\t\t\t\t\tcurrentCategory.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category\":\n\t\t\t\t\t\tif (currentCategory.name) {\n\t\t\t\t\t\t\tdata.categories.push(currentCategory);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentCategory = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Tag parsing\n\t\t\tif (currentTag) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTag.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_slug\":\n\t\t\t\t\t\tcurrentTag.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_name\":\n\t\t\t\t\t\tcurrentTag.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_description\":\n\t\t\t\t\t\tcurrentTag.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag\":\n\t\t\t\t\t\tif (currentTag.name) {\n\t\t\t\t\t\t\tdata.tags.push(currentTag);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTag = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Author parsing\n\t\t\tif (currentAuthor) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:author_id\":\n\t\t\t\t\t\tcurrentAuthor.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_login\":\n\t\t\t\t\t\tcurrentAuthor.login = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_email\":\n\t\t\t\t\t\tcurrentAuthor.email = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_display_name\":\n\t\t\t\t\t\tcurrentAuthor.displayName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_first_name\":\n\t\t\t\t\t\tcurrentAuthor.firstName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_last_name\":\n\t\t\t\t\t\tcurrentAuthor.lastName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author\":\n\t\t\t\t\t\tif (currentAuthor.login) {\n\t\t\t\t\t\t\tdata.authors.push(currentAuthor);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentAuthor = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Generic term parsing (wp:term elements - custom taxonomies, nav_menu, etc.)\n\t\t\tif (currentTerm) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTerm.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_taxonomy\":\n\t\t\t\t\t\tcurrentTerm.taxonomy = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_slug\":\n\t\t\t\t\t\tcurrentTerm.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_name\":\n\t\t\t\t\t\tcurrentTerm.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_parent\":\n\t\t\t\t\t\tcurrentTerm.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_description\":\n\t\t\t\t\t\tcurrentTerm.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term\":\n\t\t\t\t\t\tif (isCompleteWxrTerm(currentTerm)) {\n\t\t\t\t\t\t\tdata.terms.push(currentTerm);\n\t\t\t\t\t\t\t// Track nav_menu terms for building menus\n\t\t\t\t\t\t\tif (currentTerm.taxonomy === \"nav_menu\") {\n\t\t\t\t\t\t\t\tmenuTermsBySlug.set(currentTerm.slug, currentTerm.id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTerm = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentPath.pop();\n\t\t\tcurrentText = \"\";\n\t\t});\n\n\t\tparser.on(\"error\", (err) => {\n\t\t\treject(new Error(`XML parsing error: ${err.message}`));\n\t\t});\n\n\t\tparser.on(\"end\", () => {\n\t\t\t// Post-process nav_menu_item posts into structured menus\n\t\t\tdata.navMenus = buildNavMenus(navMenuItemPosts, menuTermsBySlug);\n\t\t\tresolve(data);\n\t\t});\n\n\t\t// Pipe the stream through the parser\n\t\tstream.pipe(parser);\n\t});\n}\n\n/**\n * Parse a WordPress WXR export from a string\n *\n * Uses the non-streaming SAX parser API for compatibility with\n * environments that don't have Node.js streams (e.g., Cloudflare Workers).\n */\nexport function parseWxrString(xml: string): Promise<WxrData> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst parser = sax.parser(true, { trim: false, normalize: false });\n\n\t\tconst data: WxrData = {\n\t\t\tsite: {},\n\t\t\tposts: [],\n\t\t\tattachments: [],\n\t\t\tcategories: [],\n\t\t\ttags: [],\n\t\t\tauthors: [],\n\t\t\tterms: [],\n\t\t\tnavMenus: [],\n\t\t};\n\n\t\tlet currentPath: string[] = [];\n\t\tlet currentText = \"\";\n\t\tlet currentItem: WxrPost | null = null;\n\t\tlet currentAttachment: WxrAttachment | null = null;\n\t\tlet currentCategory: WxrCategory | null = null;\n\t\tlet currentTag: WxrTag | null = null;\n\t\tlet currentAuthor: WxrAuthor | null = null;\n\t\tlet currentTerm: Partial<WxrTerm> | null = null;\n\t\tlet currentMetaKey = \"\";\n\t\t// Per-item category element currently open (see streaming-parser\n\t\t// counterpart above for rationale).\n\t\tlet pendingItemCategory: { domain: string; nicename: string } | null = null;\n\n\t\t// Track nav_menu_item posts for post-processing\n\t\tconst navMenuItemPosts: WxrPost[] = [];\n\t\t// Track menu term IDs by slug for linking items to menus\n\t\tconst menuTermsBySlug = new Map<string, number>();\n\n\t\tparser.onopentag = (node) => {\n\t\t\tconst tag = node.name.toLowerCase();\n\t\t\tcurrentPath.push(tag);\n\t\t\tcurrentText = \"\";\n\n\t\t\t// Start new elements\n\t\t\tif (tag === \"item\") {\n\t\t\t\tcurrentItem = {\n\t\t\t\t\tcategories: [],\n\t\t\t\t\ttags: [],\n\t\t\t\t\tcustomTaxonomies: new Map(),\n\t\t\t\t\tmeta: new Map(),\n\t\t\t\t};\n\t\t\t} else if (tag === \"wp:category\") {\n\t\t\t\tcurrentCategory = {};\n\t\t\t} else if (tag === \"wp:tag\") {\n\t\t\t\tcurrentTag = {};\n\t\t\t} else if (tag === \"wp:author\") {\n\t\t\t\tcurrentAuthor = {};\n\t\t\t} else if (tag === \"wp:term\") {\n\t\t\t\tcurrentTerm = {};\n\t\t\t}\n\n\t\t\t// Handle category/tag/custom taxonomy assignment in items\n\t\t\tif (tag === \"category\" && currentItem && node.attributes) {\n\t\t\t\tconst domain = attrStr(node.attributes.domain);\n\t\t\t\tconst nicename = attrStr(node.attributes.nicename);\n\t\t\t\tif (domain === \"category\" && nicename) {\n\t\t\t\t\tcurrentItem.categories.push(nicename);\n\t\t\t\t\tpendingItemCategory = { domain, nicename };\n\t\t\t\t} else if (domain === \"post_tag\" && nicename) {\n\t\t\t\t\tcurrentItem.tags.push(nicename);\n\t\t\t\t\tpendingItemCategory = { domain, nicename };\n\t\t\t\t} else if (domain && nicename && domain !== \"category\" && domain !== \"post_tag\") {\n\t\t\t\t\t// Custom taxonomy (including nav_menu)\n\t\t\t\t\tif (!currentItem.customTaxonomies) {\n\t\t\t\t\t\tcurrentItem.customTaxonomies = new Map();\n\t\t\t\t\t}\n\t\t\t\t\tconst existing = currentItem.customTaxonomies.get(domain) || [];\n\t\t\t\t\texisting.push(nicename);\n\t\t\t\t\tcurrentItem.customTaxonomies.set(domain, existing);\n\t\t\t\t\tpendingItemCategory = { domain, nicename };\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tparser.ontext = (text) => {\n\t\t\tcurrentText += text;\n\t\t};\n\n\t\tparser.oncdata = (cdata) => {\n\t\t\tcurrentText += cdata;\n\t\t};\n\n\t\tparser.onclosetag = (tagName) => {\n\t\t\tconst tag = tagName.toLowerCase();\n\t\t\tconst text = currentText.trim();\n\n\t\t\t// Site metadata\n\t\t\tif (currentPath.length === 2 && currentPath[0] === \"rss\") {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tdata.site.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tdata.site.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tdata.site.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"language\":\n\t\t\t\t\t\tdata.site.language = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_site_url\":\n\t\t\t\t\t\tdata.site.baseSiteUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_blog_url\":\n\t\t\t\t\t\tdata.site.baseBlogUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Item (post/page/attachment) parsing\n\t\t\tif (currentItem) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tcurrentItem.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tcurrentItem.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"pubdate\":\n\t\t\t\t\t\tcurrentItem.pubDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"dc:creator\":\n\t\t\t\t\t\tcurrentItem.creator = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"guid\":\n\t\t\t\t\t\tcurrentItem.guid = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tcurrentItem.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"content:encoded\":\n\t\t\t\t\t\tcurrentItem.content = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"excerpt:encoded\":\n\t\t\t\t\t\tcurrentItem.excerpt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_id\":\n\t\t\t\t\t\tcurrentItem.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date\":\n\t\t\t\t\t\tcurrentItem.postDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date_gmt\":\n\t\t\t\t\t\tcurrentItem.postDateGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified\":\n\t\t\t\t\t\tcurrentItem.postModified = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified_gmt\":\n\t\t\t\t\t\tcurrentItem.postModifiedGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:comment_status\":\n\t\t\t\t\t\tcurrentItem.commentStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:ping_status\":\n\t\t\t\t\t\tcurrentItem.pingStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_name\":\n\t\t\t\t\t\tcurrentItem.postName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:status\":\n\t\t\t\t\t\tcurrentItem.status = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_parent\":\n\t\t\t\t\t\tcurrentItem.postParent = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:menu_order\":\n\t\t\t\t\t\tcurrentItem.menuOrder = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_type\":\n\t\t\t\t\t\tcurrentItem.postType = text;\n\t\t\t\t\t\t// If it's an attachment, convert to attachment type\n\t\t\t\t\t\tif (text === \"attachment\") {\n\t\t\t\t\t\t\tcurrentAttachment = {\n\t\t\t\t\t\t\t\tid: currentItem.id,\n\t\t\t\t\t\t\t\ttitle: currentItem.title,\n\t\t\t\t\t\t\t\turl: currentItem.link,\n\t\t\t\t\t\t\t\tpostDate: currentItem.postDate,\n\t\t\t\t\t\t\t\tmeta: new Map(),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_password\":\n\t\t\t\t\t\tcurrentItem.postPassword = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:is_sticky\":\n\t\t\t\t\t\tcurrentItem.isSticky = text === \"1\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:attachment_url\":\n\t\t\t\t\t\tif (currentAttachment) {\n\t\t\t\t\t\t\tcurrentAttachment.url = text;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_key\":\n\t\t\t\t\t\tcurrentMetaKey = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_value\":\n\t\t\t\t\t\tif (currentMetaKey && currentItem.meta) {\n\t\t\t\t\t\t\tcurrentItem.meta.set(currentMetaKey, text);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"category\":\n\t\t\t\t\t\t// Per-item category text body = human label. See\n\t\t\t\t\t\t// streaming-parser counterpart for rationale.\n\t\t\t\t\t\tif (pendingItemCategory && text) {\n\t\t\t\t\t\t\tcaptureItemCategoryLabel(currentItem, pendingItemCategory, text);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingItemCategory = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"item\":\n\t\t\t\t\t\t// End of item - categorize and store\n\t\t\t\t\t\tif (currentAttachment) {\n\t\t\t\t\t\t\tdata.attachments.push(currentAttachment);\n\t\t\t\t\t\t\tcurrentAttachment = null;\n\t\t\t\t\t\t} else if (currentItem.postType === \"nav_menu_item\") {\n\t\t\t\t\t\t\t// Track nav_menu_item posts for post-processing into menus\n\t\t\t\t\t\t\tnavMenuItemPosts.push(currentItem);\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t} else if (currentItem.postType !== \"attachment\") {\n\t\t\t\t\t\t\t// Promote multilingual plugin metadata before storing.\n\t\t\t\t\t\t\t// All postmeta and per-item categories are parsed by the time\n\t\t\t\t\t\t\t// the closing </item> tag fires, so it's safe to inspect them.\n\t\t\t\t\t\t\tpromoteI18nMetadata(currentItem);\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentItem = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Category parsing\n\t\t\tif (currentCategory) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentCategory.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_nicename\":\n\t\t\t\t\t\tcurrentCategory.nicename = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:cat_name\":\n\t\t\t\t\t\tcurrentCategory.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_parent\":\n\t\t\t\t\t\tcurrentCategory.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_description\":\n\t\t\t\t\t\tcurrentCategory.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category\":\n\t\t\t\t\t\tif (currentCategory.name) {\n\t\t\t\t\t\t\tdata.categories.push(currentCategory);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentCategory = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Tag parsing\n\t\t\tif (currentTag) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTag.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_slug\":\n\t\t\t\t\t\tcurrentTag.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_name\":\n\t\t\t\t\t\tcurrentTag.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_description\":\n\t\t\t\t\t\tcurrentTag.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag\":\n\t\t\t\t\t\tif (currentTag.name) {\n\t\t\t\t\t\t\tdata.tags.push(currentTag);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTag = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Author parsing\n\t\t\tif (currentAuthor) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:author_id\":\n\t\t\t\t\t\tcurrentAuthor.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_login\":\n\t\t\t\t\t\tcurrentAuthor.login = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_email\":\n\t\t\t\t\t\tcurrentAuthor.email = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_display_name\":\n\t\t\t\t\t\tcurrentAuthor.displayName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_first_name\":\n\t\t\t\t\t\tcurrentAuthor.firstName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_last_name\":\n\t\t\t\t\t\tcurrentAuthor.lastName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author\":\n\t\t\t\t\t\tif (currentAuthor.login) {\n\t\t\t\t\t\t\tdata.authors.push(currentAuthor);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentAuthor = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Generic term parsing (wp:term elements - custom taxonomies, nav_menu, etc.)\n\t\t\tif (currentTerm) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTerm.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_taxonomy\":\n\t\t\t\t\t\tcurrentTerm.taxonomy = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_slug\":\n\t\t\t\t\t\tcurrentTerm.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_name\":\n\t\t\t\t\t\tcurrentTerm.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_parent\":\n\t\t\t\t\t\tcurrentTerm.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_description\":\n\t\t\t\t\t\tcurrentTerm.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term\":\n\t\t\t\t\t\tif (isCompleteWxrTerm(currentTerm)) {\n\t\t\t\t\t\t\tdata.terms.push(currentTerm);\n\t\t\t\t\t\t\t// Track nav_menu terms for building menus\n\t\t\t\t\t\t\tif (currentTerm.taxonomy === \"nav_menu\") {\n\t\t\t\t\t\t\t\tmenuTermsBySlug.set(currentTerm.slug, currentTerm.id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTerm = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentPath.pop();\n\t\t\tcurrentText = \"\";\n\t\t};\n\n\t\tparser.onerror = (err) => {\n\t\t\treject(new Error(`XML parsing error: ${err.message}`));\n\t\t};\n\n\t\tparser.onend = () => {\n\t\t\t// Post-process nav_menu_item posts into structured menus\n\t\t\tdata.navMenus = buildNavMenus(navMenuItemPosts, menuTermsBySlug);\n\t\t\tresolve(data);\n\t\t};\n\n\t\t// Parse the string (non-streaming)\n\t\tparser.write(xml).close();\n\t});\n}\n\n/**\n * Build structured navigation menus from nav_menu_item posts\n */\nfunction buildNavMenus(\n\tnavMenuItemPosts: WxrPost[],\n\tmenuTermsBySlug: Map<string, number>,\n): WxrNavMenu[] {\n\t// Group menu items by menu slug\n\tconst menuItemsByMenu = new Map<string, WxrPost[]>();\n\n\tfor (const post of navMenuItemPosts) {\n\t\t// Get the nav_menu taxonomy assignment to find which menu this item belongs to\n\t\tconst navMenuSlugs = post.customTaxonomies?.get(\"nav_menu\");\n\t\tif (!navMenuSlugs || navMenuSlugs.length === 0) continue;\n\n\t\tconst menuSlug = navMenuSlugs[0];\n\t\tif (!menuSlug) continue;\n\n\t\tconst items = menuItemsByMenu.get(menuSlug) || [];\n\t\titems.push(post);\n\t\tmenuItemsByMenu.set(menuSlug, items);\n\t}\n\n\t// Build structured menus\n\tconst menus: WxrNavMenu[] = [];\n\n\tfor (const [menuSlug, posts] of menuItemsByMenu) {\n\t\tconst menuId = menuTermsBySlug.get(menuSlug) || 0;\n\n\t\t// Convert posts to menu items\n\t\tconst items: WxrNavMenuItem[] = posts.map((post) => {\n\t\t\tconst meta = post.meta;\n\t\t\tconst menuItemTypeRaw = meta.get(\"_menu_item_type\") || \"custom\";\n\t\t\tconst menuItemType: WxrNavMenuItem[\"type\"] =\n\t\t\t\tmenuItemTypeRaw === \"post_type\" || menuItemTypeRaw === \"taxonomy\"\n\t\t\t\t\t? menuItemTypeRaw\n\t\t\t\t\t: \"custom\";\n\t\t\tconst objectType = meta.get(\"_menu_item_object\");\n\t\t\tconst objectIdStr = meta.get(\"_menu_item_object_id\");\n\t\t\tconst url = meta.get(\"_menu_item_url\");\n\t\t\tconst parentIdStr = meta.get(\"_menu_item_menu_item_parent\");\n\t\t\tconst target = meta.get(\"_menu_item_target\");\n\t\t\tconst classesStr = meta.get(\"_menu_item_classes\");\n\n\t\t\t// Parse classes (stored as serialized PHP array)\n\t\t\tlet classes: string | undefined;\n\t\t\tif (classesStr) {\n\t\t\t\t// Simple extraction of class names from serialized PHP\n\t\t\t\tconst matches = classesStr.match(PHP_SERIALIZED_STRING_PATTERN);\n\t\t\t\tif (matches) {\n\t\t\t\t\tclasses = matches\n\t\t\t\t\t\t.map((m) => m.match(PHP_SERIALIZED_STRING_MATCH_PATTERN)?.[1])\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t.join(\" \");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: post.id || 0,\n\t\t\t\tmenuId,\n\t\t\t\tparentId: parentIdStr ? parseInt(parentIdStr, 10) || undefined : undefined,\n\t\t\t\tsortOrder: post.menuOrder || 0,\n\t\t\t\ttype: menuItemType,\n\t\t\t\tobjectType: objectType || undefined,\n\t\t\t\tobjectId: objectIdStr ? parseInt(objectIdStr, 10) : undefined,\n\t\t\t\turl: url || undefined,\n\t\t\t\ttitle: post.title || \"\",\n\t\t\t\ttarget: target || undefined,\n\t\t\t\tclasses: classes || undefined,\n\t\t\t};\n\t\t});\n\n\t\t// Sort items by menu_order\n\t\titems.sort((a, b) => a.sortOrder - b.sortOrder);\n\n\t\t// Find the menu name from the terms\n\t\t// For now, use the slug as both name and label; we could enhance this\n\t\t// by looking up the actual term name from data.terms\n\t\tmenus.push({\n\t\t\tid: menuId,\n\t\t\tname: menuSlug,\n\t\t\tlabel: menuSlug, // Will be enhanced when we have term data\n\t\t\titems,\n\t\t});\n\t}\n\n\treturn menus;\n}\n","/**\n * Sections import functions\n *\n * Import reusable blocks from WordPress WXR exports as EmDash sections.\n */\n\nimport type { PortableTextBlock } from \"@emdash-cms/gutenberg-to-portable-text\";\nimport { gutenbergToPortableText } from \"@emdash-cms/gutenberg-to-portable-text\";\nimport type { Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { WxrPost } from \"../cli/wxr/parser.js\";\nimport type { Database } from \"../database/types.js\";\nimport { slugify } from \"../utils/slugify.js\";\n\n/**\n * Result of sections import operation\n */\nexport interface SectionsImportResult {\n\t/** Number of sections created */\n\tsectionsCreated: number;\n\t/** Number of sections skipped (already exist) */\n\tsectionsSkipped: number;\n\t/** Errors encountered during import */\n\terrors: Array<{ title: string; error: string }>;\n}\n\n/**\n * Import reusable blocks (wp_block post type) from WXR as sections\n *\n * @param posts - All posts from WXR (will filter to wp_block)\n * @param db - Database connection\n * @returns Import result with counts\n */\nexport async function importReusableBlocksAsSections(\n\tposts: WxrPost[],\n\tdb: Kysely<Database>,\n): Promise<SectionsImportResult> {\n\tconst result: SectionsImportResult = {\n\t\tsectionsCreated: 0,\n\t\tsectionsSkipped: 0,\n\t\terrors: [],\n\t};\n\n\t// Filter to only wp_block posts\n\tconst reusableBlocks = posts.filter((post) => post.postType === \"wp_block\");\n\n\tif (reusableBlocks.length === 0) {\n\t\treturn result;\n\t}\n\n\tfor (const block of reusableBlocks) {\n\t\ttry {\n\t\t\tconst slug = block.postName || slugify(block.title || `block-${block.id || Date.now()}`);\n\n\t\t\t// Check if section already exists\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"_emdash_sections\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (existing) {\n\t\t\t\tresult.sectionsSkipped++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Convert Gutenberg content to Portable Text\n\t\t\tconst content: PortableTextBlock[] = block.content\n\t\t\t\t? gutenbergToPortableText(block.content)\n\t\t\t\t: [];\n\n\t\t\tconst id = ulid();\n\t\t\tconst now = new Date().toISOString();\n\n\t\t\tawait db\n\t\t\t\t.insertInto(\"_emdash_sections\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\n\t\t\t\t\tslug,\n\t\t\t\t\ttitle: block.title || \"Untitled Block\",\n\t\t\t\t\tdescription: null,\n\t\t\t\t\tkeywords: null,\n\t\t\t\t\tcontent: JSON.stringify(content),\n\t\t\t\t\tpreview_media_id: null,\n\t\t\t\t\tsource: \"import\",\n\t\t\t\t\ttheme_id: null,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tresult.sectionsCreated++;\n\t\t} catch (error) {\n\t\t\tresult.errors.push({\n\t\t\t\ttitle: block.title || \"Untitled Block\",\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * Import source registry\n *\n * Manages available import sources and provides URL probing.\n */\n\nimport { resolveAndValidateExternalUrl } from \"./ssrf.js\";\nimport type { ImportSource, ProbeResult, SourceProbeResult } from \"./types.js\";\n\n// Regex pattern for URL normalization\nconst TRAILING_SLASHES_PATTERN = /\\/+$/;\n\n/** Registered import sources */\nconst sources = new Map<string, ImportSource>();\n\n/**\n * Register an import source\n */\nexport function registerSource(source: ImportSource): void {\n\tsources.set(source.id, source);\n}\n\n/**\n * Get a source by ID\n */\nexport function getSource(id: string): ImportSource | undefined {\n\treturn sources.get(id);\n}\n\n/**\n * Get all registered sources\n */\nexport function getAllSources(): ImportSource[] {\n\treturn [...sources.values()];\n}\n\n/**\n * Get sources that can handle file uploads\n */\nexport function getFileSources(): ImportSource[] {\n\treturn getAllSources().filter((s) => s.requiresFile);\n}\n\n/**\n * Get sources that can probe URLs\n */\nexport function getUrlSources(): ImportSource[] {\n\treturn getAllSources().filter((s) => s.canProbe);\n}\n\n/**\n * Probe a URL against all registered sources\n *\n * Returns probe results sorted by confidence (definite > likely > possible)\n */\nexport async function probeUrl(url: string): Promise<ProbeResult> {\n\t// Normalize URL\n\tlet normalizedUrl = url.trim();\n\tif (!normalizedUrl.startsWith(\"http\")) {\n\t\tnormalizedUrl = `https://${normalizedUrl}`;\n\t}\n\n\t// Remove trailing slash for consistency\n\tnormalizedUrl = normalizedUrl.replace(TRAILING_SLASHES_PATTERN, \"\");\n\n\t// SSRF: reject internal/private network targets. DNS resolution\n\t// catches hostnames that resolve to private addresses.\n\tawait resolveAndValidateExternalUrl(normalizedUrl);\n\n\tconst results: SourceProbeResult[] = [];\n\tconst urlSources = getUrlSources();\n\n\t// Probe all sources in parallel\n\tconst probePromises = urlSources.map(async (source) => {\n\t\ttry {\n\t\t\tconst result = await source.probe?.(normalizedUrl);\n\t\t\tif (result) {\n\t\t\t\treturn result;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Probe failed, skip this source\n\t\t\tconsole.debug(`Probe failed for ${source.id}:`, error);\n\t\t}\n\t\treturn null;\n\t});\n\n\tconst probeResults = await Promise.allSettled(probePromises);\n\n\tfor (const result of probeResults) {\n\t\tif (result.status === \"fulfilled\" && result.value) {\n\t\t\tresults.push(result.value);\n\t\t}\n\t}\n\n\t// Sort by confidence\n\tconst confidenceOrder = { definite: 0, likely: 1, possible: 2 };\n\tresults.sort((a, b) => confidenceOrder[a.confidence] - confidenceOrder[b.confidence]);\n\n\treturn {\n\t\turl: normalizedUrl,\n\t\tisWordPress: results.length > 0,\n\t\tbestMatch: results[0] ?? null,\n\t\tallMatches: results,\n\t};\n}\n\n/**\n * Clear all registered sources (useful for testing)\n */\nexport function clearSources(): void {\n\tsources.clear();\n}\n","/**\n * WXR (WordPress eXtended RSS) import source\n *\n * Handles WordPress export file uploads (.xml).\n * This wraps the existing WXR parsing and analysis logic.\n */\n\nimport { gutenbergToPortableText } from \"@emdash-cms/gutenberg-to-portable-text\";\n\nimport { parseWxrString, type WxrData, type WxrPost } from \"../../cli/wxr/parser.js\";\nimport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tFetchOptions,\n\tNormalizedItem,\n\tPostTypeAnalysis,\n\tAttachmentInfo,\n\tNavMenuAnalysis,\n\tTaxonomyAnalysis,\n\tReusableBlockAnalysis,\n} from \"../types.js\";\nimport {\n\tBASE_REQUIRED_FIELDS,\n\tFEATURED_IMAGE_FIELD,\n\tisInternalPostType,\n\tisInternalMetaKey,\n\tmapWpStatus,\n\tmapPostTypeToCollection,\n\tmapMetaKeyToField,\n\tinferMetaType,\n\tslugify,\n\tbuildAttachmentMap,\n\tgetFilenameFromUrl,\n\tguessMimeType,\n\tcheckSchemaCompatibility,\n} from \"../utils.js\";\n\nexport const wxrSource: ImportSource = {\n\tid: \"wxr\",\n\tname: \"WordPress Export File\",\n\tdescription: \"Upload a WordPress export file (.xml)\",\n\ticon: \"upload\",\n\trequiresFile: true,\n\tcanProbe: false,\n\n\tasync analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis> {\n\t\tif (input.type !== \"file\") {\n\t\t\tthrow new Error(\"WXR source requires a file input\");\n\t\t}\n\n\t\tconst text = await input.file.text();\n\t\tconst wxr = await parseWxrString(text);\n\n\t\t// Get existing collections for schema compatibility check\n\t\tconst existingCollections = context.getExistingCollections\n\t\t\t? await context.getExistingCollections()\n\t\t\t: new Map();\n\n\t\treturn analyzeWxrData(wxr, existingCollections);\n\t},\n\n\tasync *fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem> {\n\t\tif (input.type !== \"file\") {\n\t\t\tthrow new Error(\"WXR source requires a file input\");\n\t\t}\n\n\t\tconst text = await input.file.text();\n\t\tconst wxr = await parseWxrString(text);\n\n\t\t// Build attachment ID -> URL map for resolving featured images\n\t\tconst attachmentMap = buildAttachmentMap(wxr.attachments);\n\n\t\tlet count = 0;\n\t\tfor (const post of wxr.posts) {\n\t\t\tconst postType = post.postType || \"post\";\n\n\t\t\t// Skip if not in requested post types\n\t\t\tif (!options.postTypes.includes(postType)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip internal post types\n\t\t\tif (isInternalPostType(postType)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip drafts if not requested\n\t\t\tif (!options.includeDrafts && post.status !== \"publish\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Convert to normalized item\n\t\t\tyield wxrPostToNormalizedItem(post, attachmentMap);\n\n\t\t\tcount++;\n\t\t\tif (options.limit && count >= options.limit) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t},\n};\n\n/**\n * Analyze WXR data and return normalized ImportAnalysis\n */\nfunction analyzeWxrData(\n\twxr: WxrData,\n\texistingCollections: Map<string, { slug: string; fields: Map<string, { type: string }> }>,\n): ImportAnalysis {\n\t// Count post types and track which have featured images\n\tconst postTypeCounts = new Map<string, number>();\n\tconst postTypesWithThumbnails = new Set<string>();\n\tconst metaKeys = new Map<string, { count: number; samples: string[]; isInternal: boolean }>();\n\tconst authorPostCounts = new Map<string, number>();\n\n\tfor (const post of wxr.posts) {\n\t\tconst type = post.postType || \"post\";\n\t\tpostTypeCounts.set(type, (postTypeCounts.get(type) || 0) + 1);\n\n\t\t// Count posts per author (by login)\n\t\tif (post.creator) {\n\t\t\tauthorPostCounts.set(post.creator, (authorPostCounts.get(post.creator) || 0) + 1);\n\t\t}\n\n\t\t// Track if this post type has featured images\n\t\tif (post.meta.has(\"_thumbnail_id\")) {\n\t\t\tpostTypesWithThumbnails.add(type);\n\t\t}\n\n\t\t// Analyze meta keys\n\t\tfor (const [key, value] of post.meta) {\n\t\t\tconst existing = metaKeys.get(key);\n\t\t\tif (existing) {\n\t\t\t\texisting.count++;\n\t\t\t\tif (existing.samples.length < 3 && value) {\n\t\t\t\t\texisting.samples.push(value.slice(0, 100));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmetaKeys.set(key, {\n\t\t\t\t\tcount: 1,\n\t\t\t\t\tsamples: value ? [value.slice(0, 100)] : [],\n\t\t\t\t\tisInternal: isInternalMetaKey(key),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Map meta keys to fields (for custom fields analysis)\n\tconst customFields = [...metaKeys.entries()]\n\t\t.filter(([_, info]) => !info.isInternal)\n\t\t.map(([key, info]) => ({\n\t\t\tkey,\n\t\t\tcount: info.count,\n\t\t\tsamples: info.samples,\n\t\t\tsuggestedField: mapMetaKeyToField(key),\n\t\t\tsuggestedType: inferMetaType(key, info.samples[0]),\n\t\t\tisInternal: info.isInternal,\n\t\t}))\n\t\t.toSorted((a, b) => b.count - a.count);\n\n\t// Build post type analysis with schema compatibility\n\tconst postTypes: PostTypeAnalysis[] = [...postTypeCounts.entries()]\n\t\t.filter(([type]) => !isInternalPostType(type))\n\t\t.map(([name, count]) => {\n\t\t\tconst suggestedCollection = mapPostTypeToCollection(name);\n\t\t\tconst existingCollection = existingCollections.get(suggestedCollection);\n\n\t\t\t// Build required fields - add featured_image only if posts have thumbnails\n\t\t\tconst requiredFields = [...BASE_REQUIRED_FIELDS];\n\t\t\tif (postTypesWithThumbnails.has(name)) {\n\t\t\t\trequiredFields.push(FEATURED_IMAGE_FIELD);\n\t\t\t}\n\n\t\t\tconst schemaStatus = checkSchemaCompatibility(requiredFields, existingCollection);\n\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\tcount,\n\t\t\t\tsuggestedCollection,\n\t\t\t\trequiredFields,\n\t\t\t\tschemaStatus,\n\t\t\t};\n\t\t})\n\t\t.toSorted((a, b) => b.count - a.count);\n\n\t// Build attachment info list\n\tconst attachmentItems: AttachmentInfo[] = wxr.attachments.map((att) => {\n\t\tconst filename = att.url ? getFilenameFromUrl(att.url) : undefined;\n\t\tconst mimeType = filename ? guessMimeType(filename) : undefined;\n\t\treturn {\n\t\t\tid: att.id,\n\t\t\ttitle: att.title,\n\t\t\turl: att.url,\n\t\t\tfilename,\n\t\t\tmimeType,\n\t\t};\n\t});\n\n\t// Analyze navigation menus\n\tconst navMenus: NavMenuAnalysis[] = wxr.navMenus.map((menu) => ({\n\t\tname: menu.name,\n\t\tlabel: menu.label,\n\t\titemCount: menu.items.length,\n\t}));\n\n\t// Analyze custom taxonomies (from wp:term elements, excluding category/post_tag/nav_menu)\n\tconst taxonomyMap = new Map<string, { count: number; samples: string[] }>();\n\tfor (const term of wxr.terms) {\n\t\tif (\n\t\t\tterm.taxonomy === \"category\" ||\n\t\t\tterm.taxonomy === \"post_tag\" ||\n\t\t\tterm.taxonomy === \"nav_menu\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst existing = taxonomyMap.get(term.taxonomy);\n\t\tif (existing) {\n\t\t\texisting.count++;\n\t\t\tif (existing.samples.length < 3) {\n\t\t\t\texisting.samples.push(term.name);\n\t\t\t}\n\t\t} else {\n\t\t\ttaxonomyMap.set(term.taxonomy, {\n\t\t\t\tcount: 1,\n\t\t\t\tsamples: [term.name],\n\t\t\t});\n\t\t}\n\t}\n\n\tconst customTaxonomies: TaxonomyAnalysis[] = Array.from(\n\t\ttaxonomyMap.entries(),\n\t\t([slug, info]) => ({\n\t\t\tslug,\n\t\t\ttermCount: info.count,\n\t\t\tsampleTerms: info.samples,\n\t\t}),\n\t).toSorted((a, b) => b.termCount - a.termCount);\n\n\t// Analyze reusable blocks (wp_block post type)\n\tconst reusableBlocks: ReusableBlockAnalysis[] = wxr.posts\n\t\t.filter((post) => post.postType === \"wp_block\")\n\t\t.map((post) => ({\n\t\t\tid: post.id || 0,\n\t\t\ttitle: post.title || \"Untitled Block\",\n\t\t\tslug: post.postName || slugify(post.title || `block-${post.id || Date.now()}`),\n\t\t}));\n\n\treturn {\n\t\tsourceId: \"wxr\",\n\t\tsite: {\n\t\t\ttitle: wxr.site.title || \"WordPress Site\",\n\t\t\turl: wxr.site.link || \"\",\n\t\t},\n\t\tpostTypes,\n\t\tattachments: {\n\t\t\tcount: wxr.attachments.length,\n\t\t\titems: attachmentItems,\n\t\t},\n\t\tcategories: wxr.categories.length,\n\t\ttags: wxr.tags.length,\n\t\tauthors: wxr.authors.map((a) => ({\n\t\t\tid: a.id,\n\t\t\tlogin: a.login,\n\t\t\temail: a.email,\n\t\t\tdisplayName: a.displayName || a.login || \"Unknown\",\n\t\t\tpostCount: a.login ? authorPostCounts.get(a.login) || 0 : 0,\n\t\t})),\n\t\tnavMenus: navMenus.length > 0 ? navMenus : undefined,\n\t\tcustomTaxonomies: customTaxonomies.length > 0 ? customTaxonomies : undefined,\n\t\treusableBlocks: reusableBlocks.length > 0 ? reusableBlocks : undefined,\n\t\tcustomFields,\n\t};\n}\n\n/**\n * Convert a WXR post to a normalized item\n */\nfunction wxrPostToNormalizedItem(\n\tpost: WxrPost,\n\tattachmentMap: Map<string, string>,\n): NormalizedItem {\n\tconst content = post.content ? gutenbergToPortableText(post.content) : [];\n\n\t// Resolve featured image: _thumbnail_id is the attachment ID, look up the URL\n\tconst thumbnailId = post.meta.get(\"_thumbnail_id\");\n\tconst featuredImage = thumbnailId ? attachmentMap.get(String(thumbnailId)) : undefined;\n\n\t// Convert custom taxonomies Map to Record.\n\t// Strip Polylang's `language` taxonomy -- the parser already promoted it\n\t// to `post.locale`, and exposing it as a content taxonomy here would\n\t// (a) try to insert a `language` taxonomy on EmDash that doesn't exist\n\t// and (b) duplicate the locale signal in two places.\n\tlet customTaxonomies: Record<string, string[]> | undefined;\n\tif (post.customTaxonomies && post.customTaxonomies.size > 0) {\n\t\tconst filtered = Object.fromEntries(\n\t\t\t[...post.customTaxonomies].filter(([taxonomy]) => taxonomy !== \"language\"),\n\t\t);\n\t\tif (Object.keys(filtered).length > 0) {\n\t\t\tcustomTaxonomies = filtered;\n\t\t}\n\t}\n\n\treturn {\n\t\tsourceId: post.id || 0,\n\t\tpostType: post.postType || \"post\",\n\t\tstatus: mapWpStatus(post.status),\n\t\tslug: post.postName || slugify(post.title || `post-${post.id || Date.now()}`),\n\t\ttitle: post.title || \"Untitled\",\n\t\tcontent,\n\t\texcerpt: post.excerpt,\n\t\tdate: parseWxrDate(post.postDateGmt, post.pubDate, post.postDate) ?? new Date(),\n\t\tmodified: parseWxrDate(post.postModifiedGmt, undefined, post.postModified),\n\t\tauthor: post.creator,\n\t\tcategories: post.categories,\n\t\ttags: post.tags,\n\t\tmeta: Object.fromEntries(post.meta),\n\t\tfeaturedImage,\n\t\t// Hierarchical content support\n\t\tparentId: post.postParent && post.postParent !== 0 ? post.postParent : undefined,\n\t\tmenuOrder: post.menuOrder,\n\t\t// Custom taxonomy assignments\n\t\tcustomTaxonomies,\n\t\t// Multilingual plugin metadata (WPML / Polylang) promoted by the parser.\n\t\t// When absent, the caller (e.g. execute route) falls back to the\n\t\t// upload-wide locale.\n\t\tlocale: post.locale,\n\t\ttranslationGroup: post.translationGroup,\n\t};\n}\n\n/**\n * WordPress uses \"0000-00-00 00:00:00\" as a sentinel for missing GMT dates\n * (e.g. unpublished drafts). This must be treated as absent.\n */\nexport const WXR_ZERO_DATE = \"0000-00-00 00:00:00\";\n\n/**\n * Parse a WXR date with the correct fallback chain:\n * 1. GMT date (always UTC, most reliable)\n * 2. pubDate (RFC 2822, includes timezone offset)\n * 3. Site-local date (MySQL datetime without timezone, imprecise but best available)\n *\n * Returns undefined when none of the inputs yield a valid date.\n * Callers that need a guaranteed Date should use `?? new Date()`.\n */\nexport function parseWxrDate(\n\tgmtDate: string | undefined,\n\tpubDate: string | undefined,\n\tlocalDate: string | undefined,\n): Date | undefined {\n\tif (gmtDate && gmtDate !== WXR_ZERO_DATE) {\n\t\t// GMT dates from WordPress are \"YYYY-MM-DD HH:MM:SS\" in UTC.\n\t\t// Append \"Z\" so the JS Date constructor treats them as UTC.\n\t\treturn new Date(gmtDate.replace(\" \", \"T\") + \"Z\");\n\t}\n\n\tif (pubDate) {\n\t\t// RFC 2822 format includes timezone offset, JS Date parses it correctly\n\t\tconst d = new Date(pubDate);\n\t\tif (!isNaN(d.getTime())) return d;\n\t}\n\n\tif (localDate) {\n\t\t// Site-local time without timezone. Normalize to ISO-like form so\n\t\t// runtimes that reject \"YYYY-MM-DD HH:MM:SS\" can still parse it as\n\t\t// local time. If parsing still fails, return undefined.\n\t\tconst d = new Date(localDate.replace(\" \", \"T\"));\n\t\tif (!isNaN(d.getTime())) return d;\n\t}\n\n\treturn undefined;\n}\n\n// Export for use in other sources\nexport { analyzeWxrData, wxrPostToNormalizedItem };\n\n// Re-export shared utilities that other sources may need\nexport {\n\tBASE_REQUIRED_FIELDS,\n\tFEATURED_IMAGE_FIELD,\n\tmapPostTypeToCollection,\n\tisInternalPostType,\n\tcheckSchemaCompatibility,\n} from \"../utils.js\";\n","/**\n * WordPress REST API probe\n *\n * Probes self-hosted WordPress sites to detect capabilities.\n * This source is probe-only - it tells users what's available\n * and suggests next steps (usually: upload WXR file).\n */\n\nimport { ssrfSafeFetch, validateExternalUrl } from \"../ssrf.js\";\nimport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tSourceProbeResult,\n\tFetchOptions,\n\tNormalizedItem,\n} from \"../types.js\";\n\nconst TRAILING_SLASHES = /\\/+$/;\nconst WP_JSON_SUFFIX = /\\/wp-json\\/?$/;\n\n/** WordPress REST API discovery response */\ninterface WpApiDiscovery {\n\tname?: string;\n\tdescription?: string;\n\turl?: string;\n\thome?: string;\n\tgmt_offset?: number;\n\ttimezone_string?: string;\n\tnamespaces?: string[];\n\tauthentication?: Record<string, unknown>;\n\troutes?: Record<string, unknown>;\n}\n\nexport const wordpressRestSource: ImportSource = {\n\tid: \"wordpress-rest\",\n\tname: \"WordPress Site\",\n\tdescription: \"Connect to a self-hosted WordPress site\",\n\ticon: \"globe\",\n\trequiresFile: false,\n\tcanProbe: true,\n\n\tasync probe(url: string): Promise<SourceProbeResult | null> {\n\t\ttry {\n\t\t\tconst siteUrl = normalizeUrl(url);\n\n\t\t\t// SSRF protection: validate URL before any outbound requests\n\t\t\tvalidateExternalUrl(siteUrl);\n\n\t\t\t// Try to fetch the WP REST API root\n\t\t\tconst apiUrl = `${siteUrl}/wp-json/`;\n\t\t\tconst response = await ssrfSafeFetch(apiUrl, {\n\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\tsignal: AbortSignal.timeout(10000),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\t// Try alternate location (some sites use different prefix)\n\t\t\t\tconst altResponse = await ssrfSafeFetch(`${siteUrl}/?rest_route=/`, {\n\t\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\t\tsignal: AbortSignal.timeout(10000),\n\t\t\t\t});\n\n\t\t\t\tif (!altResponse.ok) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data: WpApiDiscovery = await response.json();\n\n\t\t\t// Check if this looks like WordPress\n\t\t\tif (!data.namespaces?.includes(\"wp/v2\")) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Get content counts (unauthenticated - published only)\n\t\t\tconst preview = await getPublicContentCounts(siteUrl);\n\n\t\t\t// Check for authentication methods\n\t\t\tconst hasAppPasswords = !!data.authentication?.[\"application-passwords\"];\n\n\t\t\treturn {\n\t\t\t\tsourceId: \"wordpress-rest\",\n\t\t\t\tconfidence: \"definite\",\n\t\t\t\tdetected: {\n\t\t\t\t\tplatform: \"wordpress\",\n\t\t\t\t\tsiteTitle: data.name,\n\t\t\t\t\tsiteUrl: data.url || data.home || siteUrl,\n\t\t\t\t},\n\t\t\t\tcapabilities: {\n\t\t\t\t\tpublicContent: true,\n\t\t\t\t\tprivateContent: false, // Would need auth\n\t\t\t\t\tcustomPostTypes: false, // Only if show_in_rest: true\n\t\t\t\t\tallMeta: false, // Only if registered for REST\n\t\t\t\t\tmediaStream: true,\n\t\t\t\t},\n\t\t\t\tauth: hasAppPasswords\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: \"password\",\n\t\t\t\t\t\t\tinstructions:\n\t\t\t\t\t\t\t\t\"To import drafts and private content, create an Application Password in WordPress → Users → Your Profile → Application Passwords\",\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t\tpreview,\n\t\t\t\tsuggestedAction: {\n\t\t\t\t\ttype: \"upload\",\n\t\t\t\t\tinstructions:\n\t\t\t\t\t\t\"For a complete import including drafts, custom post types, and all metadata, export your content from WordPress (Tools → Export) and upload the file here.\",\n\t\t\t\t},\n\t\t\t};\n\t\t} catch {\n\t\t\t// Probe failed - not a WordPress site or not accessible\n\t\t\treturn null;\n\t\t}\n\t},\n\n\tasync analyze(_input: SourceInput, _context: ImportContext): Promise<ImportAnalysis> {\n\t\t// REST-only import not implemented - we use this for probe only\n\t\t// and suggest WXR upload for actual import\n\t\tthrow new Error(\"Direct REST API import not implemented. Please upload a WXR export file.\");\n\t},\n\n\t// eslint-disable-next-line require-yield\n\tasync *fetchContent(_input: SourceInput, _options: FetchOptions): AsyncGenerator<NormalizedItem> {\n\t\tthrow new Error(\"Direct REST API import not implemented. Please upload a WXR export file.\");\n\t},\n};\n\n/**\n * Normalize a URL for API requests\n */\nfunction normalizeUrl(url: string): string {\n\tlet normalized = url.trim();\n\n\t// Add protocol if missing\n\tif (!normalized.startsWith(\"http\")) {\n\t\tnormalized = `https://${normalized}`;\n\t}\n\n\t// Remove trailing slash\n\tnormalized = normalized.replace(TRAILING_SLASHES, \"\");\n\n\t// Remove /wp-json if included\n\tnormalized = normalized.replace(WP_JSON_SUFFIX, \"\");\n\n\treturn normalized;\n}\n\n/**\n * Get public content counts from REST API\n */\nasync function getPublicContentCounts(\n\tsiteUrl: string,\n): Promise<{ posts?: number; pages?: number; media?: number }> {\n\tconst result: { posts?: number; pages?: number; media?: number } = {};\n\n\ttry {\n\t\t// Fetch with per_page=1 to get total from headers\n\t\tconst [postsRes, pagesRes, mediaRes] = await Promise.allSettled([\n\t\t\tssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/posts?per_page=1`, {\n\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t}),\n\t\t\tssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/pages?per_page=1`, {\n\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t}),\n\t\t\tssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/media?per_page=1`, {\n\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t}),\n\t\t]);\n\n\t\tif (postsRes.status === \"fulfilled\" && postsRes.value.ok) {\n\t\t\tconst total = postsRes.value.headers.get(\"X-WP-Total\");\n\t\t\tif (total) result.posts = parseInt(total, 10);\n\t\t}\n\n\t\tif (pagesRes.status === \"fulfilled\" && pagesRes.value.ok) {\n\t\t\tconst total = pagesRes.value.headers.get(\"X-WP-Total\");\n\t\t\tif (total) result.pages = parseInt(total, 10);\n\t\t}\n\n\t\tif (mediaRes.status === \"fulfilled\" && mediaRes.value.ok) {\n\t\t\tconst total = mediaRes.value.headers.get(\"X-WP-Total\");\n\t\t\tif (total) result.media = parseInt(total, 10);\n\t\t}\n\t} catch {\n\t\t// Counts are optional, continue without them\n\t}\n\n\treturn result;\n}\n","/**\n * WordPress Plugin (EmDash Exporter) import source\n *\n * Connects to self-hosted WordPress sites running the EmDash Exporter plugin.\n * Provides full access to all content including drafts, custom post types, and ACF fields.\n */\n\nimport { gutenbergToPortableText } from \"@emdash-cms/gutenberg-to-portable-text\";\n\nimport { encodeBase64 } from \"../../utils/base64.js\";\nimport { ssrfSafeFetch, validateExternalUrl } from \"../ssrf.js\";\nimport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tSourceProbeResult,\n\tI18nDetection,\n\tFetchOptions,\n\tNormalizedItem,\n\tPostTypeAnalysis,\n\tAttachmentInfo,\n} from \"../types.js\";\nimport {\n\tBASE_REQUIRED_FIELDS,\n\tFEATURED_IMAGE_FIELD,\n\tmapPostTypeToCollection,\n\tmapWpStatus,\n\tnormalizeUrl,\n\tcheckSchemaCompatibility,\n} from \"../utils.js\";\n\n// =============================================================================\n// API Response Types\n// =============================================================================\n\n/** Detected i18n plugin info from the WordPress site */\ninterface PluginI18nInfo {\n\t/** Which multilingual plugin is active */\n\tplugin: \"wpml\" | \"polylang\";\n\t/** BCP 47 default locale */\n\tdefault_locale: string;\n\t/** All configured locales */\n\tlocales: string[];\n}\n\n/** Probe response from /emdash/v1/probe */\ninterface PluginProbeResponse {\n\temdash_exporter: string;\n\twordpress_version: string;\n\tsite: {\n\t\ttitle: string;\n\t\tdescription: string;\n\t\turl: string;\n\t\thome: string;\n\t\tlanguage: string;\n\t\ttimezone: string;\n\t};\n\tcapabilities: {\n\t\tapplication_passwords: boolean;\n\t\tacf: boolean;\n\t\tyoast: boolean;\n\t\trankmath: boolean;\n\t};\n\tpost_types: Array<{\n\t\tname: string;\n\t\tlabel: string;\n\t\tcount: number;\n\t}>;\n\tmedia_count: number;\n\tendpoints: Record<string, string>;\n\tauth_instructions: {\n\t\tmethod: string;\n\t\tinstructions: string;\n\t\turl?: string;\n\t};\n\t/** Detected multilingual plugin (WPML or Polylang). Absent when neither is active. */\n\ti18n?: PluginI18nInfo;\n}\n\n/** Analyze response from /emdash/v1/analyze */\ninterface PluginAnalyzeResponse {\n\tsite: {\n\t\ttitle: string;\n\t\turl: string;\n\t};\n\tpost_types: Array<{\n\t\tname: string;\n\t\tlabel: string;\n\t\tlabel_singular: string;\n\t\ttotal: number;\n\t\tby_status: Record<string, number>;\n\t\tsupports: Record<string, unknown>;\n\t\ttaxonomies: string[];\n\t\tcustom_fields: Array<{\n\t\t\tkey: string;\n\t\t\tcount: number;\n\t\t\tinferred_type: string;\n\t\t\tsample: string | null;\n\t\t}>;\n\t\thierarchical: boolean;\n\t\thas_archive: boolean;\n\t}>;\n\ttaxonomies: Array<{\n\t\tname: string;\n\t\tlabel: string;\n\t\thierarchical: boolean;\n\t\tterm_count: number;\n\t\tobject_types: string[];\n\t}>;\n\tauthors: Array<{\n\t\tid: number;\n\t\tlogin: string;\n\t\temail: string;\n\t\tdisplay_name: string;\n\t\tpost_count: number;\n\t}>;\n\tattachments: {\n\t\tcount: number;\n\t\tby_type: Record<string, number>;\n\t};\n\tacf?: Array<{\n\t\tkey: string;\n\t\ttitle: string;\n\t\tfields: Array<{\n\t\t\tkey: string;\n\t\t\tname: string;\n\t\t\tlabel: string;\n\t\t\ttype: string;\n\t\t\trequired: boolean;\n\t\t}>;\n\t}>;\n\t/** Detected multilingual plugin (WPML or Polylang). Absent when neither is active. */\n\ti18n?: PluginI18nInfo;\n}\n\n/** Content response from /emdash/v1/content */\ninterface PluginContentResponse {\n\titems: PluginPost[];\n\ttotal: number;\n\tpages: number;\n\tpage: number;\n\tper_page: number;\n}\n\n/** Single post from plugin API */\ninterface PluginPost {\n\tid: number;\n\tpost_type: string;\n\tstatus: string;\n\tslug: string;\n\ttitle: string;\n\tcontent: string;\n\texcerpt: string;\n\tdate: string;\n\tdate_gmt: string;\n\tmodified: string;\n\tmodified_gmt: string;\n\tauthor: {\n\t\tid: number;\n\t\tlogin: string;\n\t\temail: string;\n\t\tdisplay_name: string;\n\t} | null;\n\tparent: number | null;\n\tmenu_order: number;\n\ttaxonomies: Record<string, Array<{ id: number; name: string; slug: string }>>;\n\tfeatured_image?: {\n\t\tid: number;\n\t\turl: string;\n\t\tfilename: string;\n\t\tmime_type: string;\n\t\talt: string;\n\t\ttitle: string;\n\t\tcaption: string;\n\t\twidth: number | null;\n\t\theight: number | null;\n\t};\n\tmeta: Record<string, unknown>;\n\tacf?: Record<string, unknown>;\n\tyoast?: Record<string, string>;\n\trankmath?: Record<string, string>;\n\t/** BCP 47 locale from WPML/Polylang (when detected) */\n\tlocale?: string;\n\t/** Translation group ID from WPML trid or Polylang (when detected) */\n\ttranslation_group?: string;\n}\n\n/** Media response from /emdash/v1/media */\ninterface PluginMediaResponse {\n\titems: PluginMediaItem[];\n\ttotal: number;\n\tpages: number;\n\tpage: number;\n\tper_page: number;\n}\n\ninterface PluginMediaItem {\n\tid: number;\n\turl: string;\n\tfilename: string;\n\tmime_type: string;\n\ttitle: string;\n\talt: string;\n\tcaption: string;\n\tdescription: string;\n\twidth?: number;\n\theight?: number;\n\tfilesize?: number;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Pattern to remove spaces from application passwords */\nconst SPACE_PATTERN = /\\s/g;\n\n// =============================================================================\n// Import Source\n// =============================================================================\n\nexport const wordpressPluginSource: ImportSource = {\n\tid: \"wordpress-plugin\",\n\tname: \"WordPress (EmDash Exporter)\",\n\tdescription: \"Import from WordPress sites with the EmDash Exporter plugin installed\",\n\ticon: \"plug\",\n\trequiresFile: false,\n\tcanProbe: true,\n\n\tasync probe(url: string): Promise<SourceProbeResult | null> {\n\t\ttry {\n\t\t\tconst siteUrl = normalizeUrl(url);\n\n\t\t\t// SSRF protection: validate URL before any outbound requests\n\t\t\tvalidateExternalUrl(siteUrl);\n\n\t\t\tconst probeUrl = `${siteUrl}/wp-json/emdash/v1/probe`;\n\n\t\t\tconst response = await ssrfSafeFetch(probeUrl, {\n\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\tsignal: AbortSignal.timeout(10000),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst data: PluginProbeResponse = await response.json();\n\n\t\t\t// Verify it's actually our plugin\n\t\t\tif (!data.emdash_exporter) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tsourceId: \"wordpress-plugin\",\n\t\t\t\tconfidence: \"definite\",\n\t\t\t\tdetected: {\n\t\t\t\t\tplatform: \"wordpress\",\n\t\t\t\t\tversion: data.wordpress_version,\n\t\t\t\t\tsiteTitle: data.site.title,\n\t\t\t\t\tsiteUrl: data.site.url,\n\t\t\t\t},\n\t\t\t\tcapabilities: {\n\t\t\t\t\tpublicContent: true,\n\t\t\t\t\tprivateContent: true, // Full access with auth\n\t\t\t\t\tcustomPostTypes: true,\n\t\t\t\t\tallMeta: true,\n\t\t\t\t\tmediaStream: true,\n\t\t\t\t},\n\t\t\t\tauth: data.capabilities.application_passwords\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: \"password\",\n\t\t\t\t\t\t\tinstructions: data.auth_instructions.instructions,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t\tpreview: {\n\t\t\t\t\tposts: data.post_types.find((p) => p.name === \"post\")?.count,\n\t\t\t\t\tpages: data.post_types.find((p) => p.name === \"page\")?.count,\n\t\t\t\t\tmedia: data.media_count,\n\t\t\t\t},\n\t\t\t\tsuggestedAction: {\n\t\t\t\t\ttype: \"proceed\",\n\t\t\t\t},\n\t\t\t\ti18n: pluginI18nToDetection(data.i18n),\n\t\t\t};\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t},\n\n\tasync analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis> {\n\t\tconst { siteUrl, headers } = getRequestConfig(input);\n\n\t\tconst response = await ssrfSafeFetch(`${siteUrl}/wp-json/emdash/v1/analyze`, {\n\t\t\theaders,\n\t\t\tsignal: AbortSignal.timeout(30000),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst body: unknown = await response.json().catch(() => undefined);\n\t\t\tconst message =\n\t\t\t\ttypeof body === \"object\" &&\n\t\t\t\tbody !== null &&\n\t\t\t\t\"message\" in body &&\n\t\t\t\ttypeof body.message === \"string\"\n\t\t\t\t\t? body.message\n\t\t\t\t\t: \"\";\n\t\t\tthrow new Error(message || `Failed to analyze site: ${response.statusText}`);\n\t\t}\n\n\t\tconst data: PluginAnalyzeResponse = await response.json();\n\n\t\t// Get existing collections for schema check\n\t\tconst existingCollections = context.getExistingCollections\n\t\t\t? await context.getExistingCollections()\n\t\t\t: new Map();\n\n\t\t// Build post type analysis\n\t\tconst postTypes: PostTypeAnalysis[] = data.post_types\n\t\t\t.filter((pt) => pt.total > 0)\n\t\t\t.map((pt) => {\n\t\t\t\tconst suggestedCollection = mapPostTypeToCollection(pt.name);\n\t\t\t\tconst existingCollection = existingCollections.get(suggestedCollection);\n\n\t\t\t\t// Include featured_image if post type supports thumbnails\n\t\t\t\tconst supportsThumbnail = pt.supports && \"thumbnail\" in pt.supports;\n\t\t\t\tconst requiredFields = supportsThumbnail\n\t\t\t\t\t? [...BASE_REQUIRED_FIELDS, FEATURED_IMAGE_FIELD]\n\t\t\t\t\t: [...BASE_REQUIRED_FIELDS];\n\n\t\t\t\treturn {\n\t\t\t\t\tname: pt.name,\n\t\t\t\t\tcount: pt.total,\n\t\t\t\t\tsuggestedCollection,\n\t\t\t\t\trequiredFields,\n\t\t\t\t\tschemaStatus: checkSchemaCompatibility(requiredFields, existingCollection),\n\t\t\t\t};\n\t\t\t});\n\n\t\t// Fetch media list for attachment info\n\t\tconst attachments: AttachmentInfo[] = [];\n\t\tif (data.attachments.count > 0) {\n\t\t\ttry {\n\t\t\t\t// Fetch first page of media to populate attachment info\n\t\t\t\tconst mediaResponse = await ssrfSafeFetch(\n\t\t\t\t\t`${siteUrl}/wp-json/emdash/v1/media?per_page=500`,\n\t\t\t\t\t{\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(30000),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (mediaResponse.ok) {\n\t\t\t\t\tconst mediaData: PluginMediaResponse = await mediaResponse.json();\n\t\t\t\t\tfor (const item of mediaData.items) {\n\t\t\t\t\t\tattachments.push({\n\t\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\t\turl: item.url,\n\t\t\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\t\t\tmimeType: item.mime_type,\n\t\t\t\t\t\t\ttitle: item.title,\n\t\t\t\t\t\t\talt: item.alt,\n\t\t\t\t\t\t\tcaption: item.caption,\n\t\t\t\t\t\t\twidth: item.width,\n\t\t\t\t\t\t\theight: item.height,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tconsole.warn(\"Failed to fetch media list:\", e);\n\t\t\t}\n\t\t}\n\n\t\t// Count categories and tags\n\t\tconst categoryTaxonomy = data.taxonomies.find((t) => t.name === \"category\");\n\t\tconst tagTaxonomy = data.taxonomies.find((t) => t.name === \"post_tag\");\n\n\t\treturn {\n\t\t\tsourceId: \"wordpress-plugin\",\n\t\t\tsite: {\n\t\t\t\ttitle: data.site.title,\n\t\t\t\turl: data.site.url,\n\t\t\t},\n\t\t\tpostTypes,\n\t\t\tattachments: {\n\t\t\t\tcount: data.attachments.count,\n\t\t\t\titems: attachments,\n\t\t\t},\n\t\t\tcategories: categoryTaxonomy?.term_count ?? 0,\n\t\t\ttags: tagTaxonomy?.term_count ?? 0,\n\t\t\tauthors: data.authors.map((a) => ({\n\t\t\t\tid: a.id,\n\t\t\t\tlogin: a.login,\n\t\t\t\temail: a.email,\n\t\t\t\tdisplayName: a.display_name,\n\t\t\t\tpostCount: a.post_count,\n\t\t\t})),\n\t\t\ti18n: pluginI18nToDetection(data.i18n),\n\t\t};\n\t},\n\n\tasync *fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem> {\n\t\tconst { siteUrl, headers } = getRequestConfig(input);\n\n\t\tfor (const postType of options.postTypes) {\n\t\t\tlet page = 1;\n\t\t\tlet totalPages = 1;\n\t\t\tlet yielded = 0;\n\n\t\t\twhile (page <= totalPages) {\n\t\t\t\tconst status = options.includeDrafts ? \"any\" : \"publish\";\n\t\t\t\tconst url = `${siteUrl}/wp-json/emdash/v1/content?post_type=${postType}&status=${status}&per_page=100&page=${page}`;\n\n\t\t\t\tconst response = await ssrfSafeFetch(url, {\n\t\t\t\t\theaders,\n\t\t\t\t\tsignal: AbortSignal.timeout(60000),\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Failed to fetch ${postType}: ${response.statusText}`);\n\t\t\t\t}\n\n\t\t\t\tconst data: PluginContentResponse = await response.json();\n\t\t\t\ttotalPages = data.pages;\n\n\t\t\t\tfor (const post of data.items) {\n\t\t\t\t\tyield pluginPostToNormalizedItem(post);\n\t\t\t\t\tyielded++;\n\n\t\t\t\t\tif (options.limit && yielded >= options.limit) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tpage++;\n\t\t\t}\n\t\t}\n\t},\n\n\tasync fetchMedia(url: string, _input: SourceInput): Promise<Blob> {\n\t\t// SSRF protection: validate media URL before fetching\n\t\tvalidateExternalUrl(url);\n\n\t\t// Media URLs are publicly accessible on WP (ssrfSafeFetch validates redirects)\n\t\tconst response = await ssrfSafeFetch(url);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch media: ${response.statusText}`);\n\t\t}\n\t\treturn response.blob();\n\t},\n};\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Convert plugin i18n info to the shared I18nDetection type.\n * Returns undefined when no multilingual plugin is detected.\n */\nfunction pluginI18nToDetection(i18n: PluginI18nInfo | undefined): I18nDetection | undefined {\n\tif (!i18n) return undefined;\n\treturn {\n\t\tplugin: i18n.plugin,\n\t\tdefaultLocale: i18n.default_locale,\n\t\tlocales: i18n.locales,\n\t};\n}\n\n/**\n * Get request configuration from input\n */\nfunction getRequestConfig(input: SourceInput): {\n\tsiteUrl: string;\n\theaders: HeadersInit;\n} {\n\tif (input.type === \"url\") {\n\t\tconst siteUrl = normalizeUrl(input.url);\n\n\t\t// SSRF protection: validate URL before any outbound requests\n\t\tvalidateExternalUrl(siteUrl);\n\t\tconst headers: HeadersInit = {\n\t\t\tAccept: \"application/json\",\n\t\t};\n\n\t\tif (input.token) {\n\t\t\t// Token format: \"username:password\" base64 encoded\n\t\t\theaders[\"Authorization\"] = `Basic ${input.token}`;\n\t\t}\n\n\t\treturn { siteUrl, headers };\n\t}\n\n\tif (input.type === \"oauth\") {\n\t\tconst oauthSiteUrl = normalizeUrl(input.url);\n\n\t\t// SSRF protection: validate URL before any outbound requests\n\t\tvalidateExternalUrl(oauthSiteUrl);\n\n\t\treturn {\n\t\t\tsiteUrl: oauthSiteUrl,\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${input.accessToken}`,\n\t\t\t},\n\t\t};\n\t}\n\n\tthrow new Error(\"WordPress plugin source requires URL or OAuth input\");\n}\n\n/**\n * Convert plugin post to normalized item\n */\nfunction pluginPostToNormalizedItem(post: PluginPost): NormalizedItem {\n\tconst content = post.content ? gutenbergToPortableText(post.content) : [];\n\n\t// Extract categories and tags from taxonomies\n\tconst categories =\n\t\tpost.taxonomies?.category?.map((c) => c.slug) ??\n\t\tpost.taxonomies?.categories?.map((c) => c.slug) ??\n\t\t[];\n\tconst tags =\n\t\tpost.taxonomies?.post_tag?.map((t) => t.slug) ??\n\t\tpost.taxonomies?.tags?.map((t) => t.slug) ??\n\t\t[];\n\n\t// Build meta from various sources\n\tconst meta: Record<string, unknown> = { ...post.meta };\n\n\t// Include ACF fields in meta\n\tif (post.acf) {\n\t\tmeta._acf = post.acf;\n\t}\n\n\t// Include SEO data in meta\n\tif (post.yoast) {\n\t\tmeta._yoast = post.yoast;\n\t}\n\tif (post.rankmath) {\n\t\tmeta._rankmath = post.rankmath;\n\t}\n\n\treturn {\n\t\tsourceId: post.id,\n\t\tpostType: post.post_type,\n\t\tstatus: mapWpStatus(post.status),\n\t\tslug: post.slug,\n\t\ttitle: post.title,\n\t\tcontent,\n\t\texcerpt: post.excerpt || undefined,\n\t\tdate: new Date(post.date_gmt || post.date),\n\t\tmodified: post.modified_gmt ? new Date(post.modified_gmt) : new Date(post.modified),\n\t\tauthor: post.author?.login,\n\t\tcategories,\n\t\ttags,\n\t\tmeta,\n\t\tfeaturedImage: post.featured_image?.url,\n\t\tlocale: post.locale,\n\t\ttranslationGroup: post.translation_group,\n\t};\n}\n\n// =============================================================================\n// Utility Functions for External Use\n// =============================================================================\n\n/**\n * Create a Basic Auth token from username and password\n */\nexport function createBasicAuthToken(username: string, password: string): string {\n\t// Remove spaces from application password (WP formats them with spaces)\n\tconst cleanPassword = password.replace(SPACE_PATTERN, \"\");\n\treturn encodeBase64(`${username}:${cleanPassword}`);\n}\n\n/**\n * Fetch media list from plugin API\n */\nexport async function fetchPluginMedia(\n\tsiteUrl: string,\n\tauthToken: string,\n\tpage = 1,\n\tperPage = 100,\n): Promise<PluginMediaResponse> {\n\tconst normalizedSiteUrl = normalizeUrl(siteUrl);\n\n\t// SSRF protection: validate URL before any outbound requests\n\tvalidateExternalUrl(normalizedSiteUrl);\n\n\tconst url = `${normalizedSiteUrl}/wp-json/emdash/v1/media?per_page=${perPage}&page=${page}`;\n\n\tconst response = await ssrfSafeFetch(url, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Basic ${authToken}`,\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch media: ${response.statusText}`);\n\t}\n\n\treturn response.json();\n}\n\n/**\n * Fetch taxonomies from plugin API\n */\nexport async function fetchPluginTaxonomies(\n\tsiteUrl: string,\n\tauthToken: string,\n): Promise<\n\tArray<{\n\t\tname: string;\n\t\tlabel: string;\n\t\thierarchical: boolean;\n\t\tterms: Array<{\n\t\t\tid: number;\n\t\t\tname: string;\n\t\t\tslug: string;\n\t\t\tdescription: string;\n\t\t\tparent: number | null;\n\t\t\tcount: number;\n\t\t}>;\n\t}>\n> {\n\tconst normalizedSiteUrl = normalizeUrl(siteUrl);\n\n\t// SSRF protection: validate URL before any outbound requests\n\tvalidateExternalUrl(normalizedSiteUrl);\n\n\tconst url = `${normalizedSiteUrl}/wp-json/emdash/v1/taxonomies`;\n\n\tconst response = await ssrfSafeFetch(url, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Basic ${authToken}`,\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch taxonomies: ${response.statusText}`);\n\t}\n\n\treturn response.json();\n}\n","/**\n * Import system\n *\n * Provides a pluggable system for importing content from various sources.\n */\n\n// Core types\nexport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tFileInput,\n\tUrlInput,\n\tOAuthInput,\n\tSourceProbeResult,\n\tProbeResult,\n\tSourceAuth,\n\tSourceCapabilities,\n\tSuggestedAction,\n\tPostTypeAnalysis,\n\tImportFieldDef,\n\tFieldCompatibility,\n\tCollectionSchemaStatus,\n\tAttachmentInfo,\n\tNormalizedItem,\n\tImportConfig,\n\tImportResult,\n\tFetchOptions,\n\tPostTypeMapping,\n\tNavMenuAnalysis,\n\tTaxonomyAnalysis,\n} from \"./types.js\";\n\n// Menu import\nexport {\n\timportMenusFromWxr,\n\timportMenusFromPlugin,\n\ttype MenuImportResult,\n\ttype PluginMenu,\n\ttype PluginMenuItem,\n} from \"./menus.js\";\n\n// Sections import\nexport { importReusableBlocksAsSections, type SectionsImportResult } from \"./sections.js\";\n\n// Site settings import\nexport {\n\timportSiteSettings,\n\tparseSiteSettingsFromPlugin,\n\ttype SiteSettingsAnalysis,\n\ttype SettingsImportResult,\n\ttype WidgetAreaAnalysis,\n} from \"./settings.js\";\n\n// Registry\nexport {\n\tregisterSource,\n\tgetSource,\n\tgetAllSources,\n\tgetFileSources,\n\tgetUrlSources,\n\tprobeUrl,\n\tclearSources,\n} from \"./registry.js\";\n\n// SSRF protection\nexport { validateExternalUrl, ssrfSafeFetch, SsrfError } from \"./ssrf.js\";\n\n// Sources\nexport { wxrSource, parseWxrDate } from \"./sources/wxr.js\";\nexport { wordpressRestSource } from \"./sources/wordpress-rest.js\";\nexport {\n\twordpressPluginSource,\n\tcreateBasicAuthToken,\n\tfetchPluginMedia,\n\tfetchPluginTaxonomies,\n} from \"./sources/wordpress-plugin.js\";\n\n// Auto-register built-in sources\nimport { registerSource } from \"./registry.js\";\nimport { wordpressPluginSource } from \"./sources/wordpress-plugin.js\";\nimport { wordpressRestSource } from \"./sources/wordpress-rest.js\";\nimport { wxrSource } from \"./sources/wxr.js\";\n\n// Register in priority order (most specific first)\n// Plugin source first - if they have our plugin, use it\nregisterSource(wordpressPluginSource);\nregisterSource(wordpressRestSource);\nregisterSource(wxrSource);\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-DGIjmUXQ.d.mts","names":[],"sources":["../src/database/repositories/byline.ts","../src/utils/slugify.ts","../src/cli/wxr/parser.ts","../src/import/sections.ts","../src/import/registry.ts","../src/import/sources/wxr.ts","../src/import/sources/wordpress-rest.ts"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loader-4K37qA-y.mjs","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Astro Live Collections loader for EmDash\n *\n * This loader implements the Astro LiveLoader interface to fetch content\n * at runtime from the database, enabling live editing without rebuilds.\n *\n * Architecture:\n * - Single `_emdash` Astro collection handles all content types\n * - Dialect comes from virtual module (configured in astro.config.mjs)\n * - Each content type maps to its own database table: ec_posts, ec_products, etc.\n * - `getEmDashCollection()` / `getEmDashEntry()` wrap Astro's live collection API\n */\n\nimport type { LiveLoader } from \"astro/loaders\";\nimport { Kysely, type RawBuilder, sql, type Dialect } from \"kysely\";\n\nimport { currentTimestampValue, isPostgres } from \"./database/dialect-helpers.js\";\nimport { kyselyLogOption } from \"./database/instrumentation.js\";\nimport { decodeCursor, encodeCursor } from \"./database/repositories/types.js\";\nimport { validateIdentifier } from \"./database/validate.js\";\nimport type { Database } from \"./index.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport { isMissingColumnError, isMissingTableError } from \"./utils/db-errors.js\";\n\nconst FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * SEO columns folded into the single-entry query as a single JSON column\n * (`_emdash_seo` in the result set), then expanded onto the row under these\n * aliases for `extractSeo()`. Surfacing SEO as one aggregated column keeps the\n * result-set width bounded regardless of how many fields the collection has,\n * which matters for D1: a flat `LEFT JOIN _emdash_seo` adds 5 alias columns to\n * every row and pushes wide collections (common after WordPress / ACF imports)\n * past D1's per-result-set column limit, surfacing as a silent null entry.\n * One JSON column is one column, so the join stays safe at any schema width.\n *\n * The aliases mirror the strategy used by `foldedHydrationSelects` for byline\n * and taxonomy hydration: aggregate in SQL, expand in JS. SEO is 1:1 with\n * content, so the subquery uses `json_object` (not the array aggregator).\n *\n * The `_emdash_` prefix on the aliases guarantees they can never collide with\n * a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can\n * legitimately define a `seo_title` field; surfacing the SEO column under its\n * bare name would shadow that field in the result set and drop the user's\n * value. The prefix (illegal as a leading slug char) sidesteps this entirely.\n */\nconst SEO_COLUMN_ALIASES: Record<string, string> = {\n\tseo_title: \"_emdash_seo_title\",\n\tseo_description: \"_emdash_seo_description\",\n\tseo_image: \"_emdash_seo_image\",\n\tseo_canonical: \"_emdash_seo_canonical\",\n\tseo_no_index: \"_emdash_seo_no_index\",\n};\n\n/** Aliased SEO result keys — excluded from generic field mapping. */\nconst SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);\n\n/** Folded SEO JSON column name in the result set (expanded onto aliases in JS). */\nconst SEO_FOLDED_COLUMN = \"_emdash_seo\";\n\n/**\n * System columns excluded from entry.data\n * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates\n */\nconst SYSTEM_COLUMNS = new Set([\n\t\"id\",\n\t// \"slug\" - kept in data for template access\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n\t// Aliased SEO columns expanded from the folded _emdash_seo JSON column on\n\t// the single-entry path. Surfaced as a nested data.seo object (see\n\t// extractSeo), never as flat fields. The aliases are _emdash_-prefixed so\n\t// they can't shadow a user field named e.g. `seo_title`.\n\t...SEO_ALIAS_COLUMNS,\n\t// Folded hydration JSON columns (see foldedHydrationSelects and\n\t// foldedSeoSelect) — surfaced via the FOLDED_* markers or expanded onto\n\t// SEO_ALIAS_COLUMNS, never as flat fields.\n\t\"_emdash_terms\",\n\t\"_emdash_bylines\",\n\tSEO_FOLDED_COLUMN,\n]);\n\n/** Markers for byline/taxonomy hydration folded into the content query. */\nexport const FOLDED_TERMS = Symbol.for(\"emdash:foldedTerms\");\nexport const FOLDED_BYLINES = Symbol.for(\"emdash:foldedBylines\");\n\n/**\n * Correlated JSON-array subqueries that fold taxonomy-term and byline hydration\n * into the content query, removing the two separate hydration round trips per\n * fetch. `outer` is the content table's alias/name; each subquery correlates on\n * `<outer>.id`, so the base query stays one row per entry (no join fan-out, no\n * duplicated content payload). Order is NOT applied in the aggregate (it differs\n * across dialects) — the consumer sorts terms by label and credits by sortOrder.\n *\n * Dialect-specific aggregation: SQLite `json_group_array`/`json_object` returns\n * a JSON *string*; Postgres `json_agg`/`json_build_object` (coalesced to `[]`)\n * returns parsed JSON. {@link stashFolded} handles both.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance\nfunction foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {\n\tconst o = sql.ref(outer);\n\tconst pg = isPostgres(db);\n\tconst obj = (pairs: string) =>\n\t\tpg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);\n\tconst agg = (inner: RawBuilder<unknown>) =>\n\t\tpg ? sql`coalesce(json_agg(${inner}), '[]'::json)` : sql`json_group_array(${inner})`;\n\n\tconst termObj = obj(\n\t\t\"'id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group\",\n\t);\n\t// Filter terms to the entry's own locale (matches #1441: terms render in the\n\t// entry's resolved locale, not all locale variants of the attached group).\n\tconst terms = sql`(SELECT ${agg(termObj)} FROM ${sql.ref(\"content_taxonomies\")} AS ct JOIN ${sql.ref(\"taxonomies\")} AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ${type} AND ct.entry_id = ${o}.id AND t.locale = ${o}.locale) AS ${sql.ref(\"_emdash_terms\")}`;\n\n\tconst bylineInner = obj(\n\t\t\"'id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group\",\n\t);\n\tconst creditObj = pg\n\t\t? sql.raw(\n\t\t\t\t\"json_build_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', \",\n\t\t\t)\n\t\t: sql.raw(\"json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', \");\n\tconst credit = sql`${creditObj}${bylineInner})`;\n\tconst bylines = sql`(SELECT ${agg(credit)} FROM ${sql.ref(\"_emdash_content_bylines\")} AS cb JOIN ${sql.ref(\"_emdash_bylines\")} AS b ON b.translation_group = cb.byline_id LEFT JOIN ${sql.ref(\"media\")} AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ${type} AND cb.content_id = ${o}.id AND b.locale = ${o}.locale) AS ${sql.ref(\"_emdash_bylines\")}`;\n\treturn { terms, bylines };\n}\n\n/**\n * Correlated JSON-object subquery that folds per-entry SEO into the content\n * query without widening the result set: 1 row of `_emdash_seo` becomes 1 JSON\n * column rather than 5 flat columns. The JSON column is expanded onto the row\n * via {@link expandFoldedSeo} after the query runs, preserving the alias keys\n * that {@link extractSeo} reads. Missing SEO row (no entry in `_emdash_seo`)\n * yields NULL, which {@link expandFoldedSeo} treats as \"no SEO\" - identical to\n * the prior LEFT JOIN miss behavior.\n *\n * Dialect-specific aggregation mirrors {@link foldedHydrationSelects}: SQLite\n * `json_object` returns a JSON *string*, Postgres `json_build_object` returns\n * parsed JSON; both branches are handled in expansion.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance\nfunction foldedSeoSelect(db: Kysely<any>, type: string, outer: string) {\n\tconst o = sql.ref(outer);\n\tconst pg = isPostgres(db);\n\t// Use raw column names (not aliases) as JSON keys: the JSON is expanded back\n\t// onto SEO_COLUMN_ALIASES in JS, and keeping the keys matched to the\n\t// underlying columns makes the SQL readable and the expansion 1-to-1.\n\tconst pairs =\n\t\t\"'seo_title', s.seo_title, 'seo_description', s.seo_description, 'seo_image', s.seo_image, 'seo_canonical', s.seo_canonical, 'seo_no_index', s.seo_no_index\";\n\tconst obj = pg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);\n\treturn sql`(SELECT ${obj} FROM ${sql.ref(\"_emdash_seo\")} AS s WHERE s.collection = ${type} AND s.content_id = ${o}.id LIMIT 1) AS ${sql.ref(SEO_FOLDED_COLUMN)}`;\n}\n\n/**\n * Expand the folded `_emdash_seo` JSON column onto the row using SEO_COLUMN_ALIASES,\n * so {@link extractSeo} reads it transparently. SQLite returns a JSON string\n * (parse it); Postgres returns already-parsed JSON. Missing/malformed/null is\n * a no-op: {@link extractSeo} returns null when the aliases are absent.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction expandFoldedSeo(row: Record<string, unknown>): void {\n\tconst raw = row[SEO_FOLDED_COLUMN];\n\tdelete row[SEO_FOLDED_COLUMN];\n\tlet parsed: Record<string, unknown> | null = null;\n\tif (typeof raw === \"string\") {\n\t\ttry {\n\t\t\tconst candidate: unknown = JSON.parse(raw);\n\t\t\tif (isPlainObject(candidate)) parsed = candidate;\n\t\t} catch {\n\t\t\treturn; // malformed JSON: leave the row without SEO aliases (extractSeo returns null)\n\t\t}\n\t} else if (isPlainObject(raw)) {\n\t\tparsed = raw;\n\t}\n\tif (!parsed) return;\n\tfor (const [col, alias] of Object.entries(SEO_COLUMN_ALIASES)) {\n\t\trow[alias] = parsed[col] ?? null;\n\t}\n}\n\n/**\n * Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.\n * SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.\n */\nfunction stashFolded(data: Record<string, unknown>, row: Record<string, unknown>): void {\n\tfor (const [col, sym] of [\n\t\t[\"_emdash_terms\", FOLDED_TERMS],\n\t\t[\"_emdash_bylines\", FOLDED_BYLINES],\n\t] as const) {\n\t\tconst raw = row[col];\n\t\tlet value: unknown;\n\t\tif (typeof raw === \"string\") {\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(raw);\n\t\t\t} catch {\n\t\t\t\tcontinue; // malformed: fall back to the query path\n\t\t\t}\n\t\t} else if (Array.isArray(raw)) {\n\t\t\tvalue = raw; // Postgres json/jsonb already parsed by the driver\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\t\tObject.defineProperty(data, sym, { value, enumerable: false, configurable: true });\n\t}\n}\n\n/** Resolved SEO shape attached to `entry.data.seo`. Mirrors `ContentSeo`. */\ninterface EntrySeo {\n\ttitle: string | null;\n\tdescription: string | null;\n\timage: string | null;\n\tcanonical: string | null;\n\tnoIndex: boolean;\n}\n\n/**\n * Build a `data.seo` object from the joined `_emdash_seo` columns on a row.\n *\n * Returns `null` when no SEO row exists (LEFT JOIN miss → `seo_no_index` is\n * NULL, since the column is `NOT NULL DEFAULT 0` whenever a row is present).\n * Returning null keeps the `seo` key off entries that have none, so\n * `getSeoMeta()` falls back to its defaults exactly as before.\n */\nfunction extractSeo(row: Record<string, unknown>): EntrySeo | null {\n\tconst noIndex = row[SEO_COLUMN_ALIASES.seo_no_index];\n\tif (noIndex === null || noIndex === undefined) return null;\n\tconst title = row[SEO_COLUMN_ALIASES.seo_title];\n\tconst description = row[SEO_COLUMN_ALIASES.seo_description];\n\tconst image = row[SEO_COLUMN_ALIASES.seo_image];\n\tconst canonical = row[SEO_COLUMN_ALIASES.seo_canonical];\n\treturn {\n\t\ttitle: typeof title === \"string\" ? title : null,\n\t\tdescription: typeof description === \"string\" ? description : null,\n\t\timage: typeof image === \"string\" ? image : null,\n\t\tcanonical: typeof canonical === \"string\" ? canonical : null,\n\t\tnoIndex: noIndex === 1,\n\t};\n}\n\n/**\n * Get the table name for a collection type\n */\nfunction getTableName(type: string): string {\n\tvalidateIdentifier(type, \"collection type\");\n\treturn `ec_${type}`;\n}\n\n/**\n * Cache for taxonomy names (only used for the primary database).\n * Skipped when a per-request DB override is active (e.g. preview mode)\n * because the override DB may have different taxonomies.\n */\nlet taxonomyNames: Set<string> | null = null;\n\n/**\n * Get all taxonomy names (cached for the primary DB, bypassed only when\n * the per-request DB is an isolated instance — playground / DO preview).\n * Plain D1 Sessions routing shares schema with the singleton, so the\n * module-scoped cache stays valid.\n */\nasync function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {\n\tconst hasIsolatedDb = getRequestContext()?.dbIsIsolated === true;\n\n\tif (!hasIsolatedDb && taxonomyNames) {\n\t\treturn taxonomyNames;\n\t}\n\n\ttry {\n\t\tconst defs = await db.selectFrom(\"_emdash_taxonomy_defs\").select(\"name\").execute();\n\t\tconst names = new Set(defs.map((d) => d.name));\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = names;\n\t\t}\n\t\treturn names;\n\t} catch {\n\t\t// Table doesn't exist yet, return empty set\n\t\tconst empty = new Set<string>();\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = empty;\n\t\t}\n\t\treturn empty;\n\t}\n}\n\n/**\n * Reset the module-scoped taxonomy-names cache.\n *\n * Called from `invalidateTaxonomyDefsCache()` so that creating or seeding a\n * taxonomy definition is reflected within the current isolate instead of\n * waiting for the isolate to recycle. Keeps this cache consistent with the\n * isolate-wide taxonomy-defs cache in `taxonomies/index.ts`.\n */\nexport function resetTaxonomyNamesCache(): void {\n\ttaxonomyNames = null;\n}\n\n/**\n * System columns to include in data (mapped to camelCase where needed)\n */\nconst INCLUDE_IN_DATA: Record<string, string> = {\n\tid: \"id\",\n\tstatus: \"status\",\n\tauthor_id: \"authorId\",\n\tprimary_byline_id: \"primaryBylineId\",\n\tcreated_at: \"createdAt\",\n\tupdated_at: \"updatedAt\",\n\tpublished_at: \"publishedAt\",\n\tscheduled_at: \"scheduledAt\",\n\tdraft_revision_id: \"draftRevisionId\",\n\tlive_revision_id: \"liveRevisionId\",\n\tlocale: \"locale\",\n\ttranslation_group: \"translationGroup\",\n};\n\n/** System date columns that should be converted to Date objects */\nconst DATE_COLUMNS = new Set([\"created_at\", \"updated_at\", \"published_at\", \"scheduled_at\"]);\n\n/**\n * Hidden, symbol-keyed property on each mapped data record carrying the raw\n * DB string for every date column. Lets cursor encoders downstream reproduce\n * the loader's exact `nextCursor` format without round-tripping through\n * `new Date()`, which loses precision for stored values that aren't already\n * ISO-with-milliseconds (e.g. `2026-01-01T00:00:00Z` becomes\n * `2026-01-01T00:00:00.000Z`).\n */\nexport const CURSOR_RAW_VALUES: unique symbol = Symbol(\"emdash:cursorRawValues\");\n\nconst LOCAL_MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\nconst URL_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\n\n/** Safely extract a string value from a record, returning fallback if not a string */\nfunction rowStr(row: Record<string, unknown>, key: string, fallback = \"\"): string {\n\tconst val = row[key];\n\treturn typeof val === \"string\" ? val : fallback;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBareMediaKey(src: string): boolean {\n\treturn !src.startsWith(\"/\") && !URL_SCHEME_PATTERN.test(src);\n}\n\nfunction normalizeLocalMediaValue(value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\treturn value.map(normalizeLocalMediaValue);\n\t}\n\n\tif (!isRecord(value)) {\n\t\treturn value;\n\t}\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, child] of Object.entries(value)) {\n\t\tnormalized[key] = normalizeLocalMediaValue(child);\n\t}\n\n\tif (\n\t\tnormalized.provider === \"local\" &&\n\t\ttypeof normalized.src === \"string\" &&\n\t\tnormalized.src.length > 0\n\t) {\n\t\tconst src = normalized.src;\n\t\tif (src.startsWith(LOCAL_MEDIA_FILE_PREFIX)) {\n\t\t\tconst id = src.slice(LOCAL_MEDIA_FILE_PREFIX.length);\n\t\t\tif (!normalized.id && id) {\n\t\t\t\tnormalized.id = id;\n\t\t\t}\n\t\t} else if (isBareMediaKey(src)) {\n\t\t\tif (!normalized.id) {\n\t\t\t\tnormalized.id = src;\n\t\t\t}\n\t\t\tnormalized.src = `${LOCAL_MEDIA_FILE_PREFIX}${src}`;\n\t\t}\n\t}\n\n\treturn normalized;\n}\n\n/**\n * Map a database row to entry data\n * Extracts content fields (non-system columns) and parses JSON where needed.\n * System columns needed for templates (id, status, dates) are included with camelCase names.\n */\nfunction mapRowToData(row: Record<string, unknown>): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tconst rawDateValues: Record<string, string> = {};\n\n\tfor (const [key, value] of Object.entries(row)) {\n\t\t// Include certain system columns (mapped to camelCase where needed)\n\t\tif (key in INCLUDE_IN_DATA) {\n\t\t\t// Convert date columns from ISO strings to Date objects\n\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\trawDateValues[key] = value;\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = new Date(value);\n\t\t\t\t} else {\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = null;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata[INCLUDE_IN_DATA[key]] = value;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (SYSTEM_COLUMNS.has(key)) continue;\n\n\t\t// Try to parse JSON strings (for portableText, json fields, etc.)\n\t\tif (typeof value === \"string\") {\n\t\t\ttry {\n\t\t\t\t// Only parse if it looks like JSON (starts with { or [)\n\t\t\t\tif (value.startsWith(\"{\") || value.startsWith(\"[\")) {\n\t\t\t\t\tdata[key] = normalizeLocalMediaValue(JSON.parse(value));\n\t\t\t\t} else {\n\t\t\t\t\tdata[key] = value;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tdata[key] = value;\n\t\t\t}\n\t\t} else {\n\t\t\tdata[key] = value;\n\t\t}\n\t}\n\n\tObject.defineProperty(data, CURSOR_RAW_VALUES, {\n\t\tvalue: rawDateValues,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t});\n\n\treturn data;\n}\n\n/**\n * Map revision data (already-parsed JSON object) to entry data.\n * Strips _-prefixed metadata keys (e.g. _slug) used internally by revisions.\n */\nfunction mapRevisionData(data: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (key.startsWith(\"_\")) continue; // revision metadata\n\t\tresult[key] = normalizeLocalMediaValue(value);\n\t}\n\treturn result;\n}\n\n// Virtual module imports are lazy-loaded to avoid errors when importing\n// emdash outside of Astro/Vite context (e.g., in astro.config.mjs)\nlet virtualConfig:\n\t| {\n\t\t\tdatabase?: { config: unknown };\n\t\t\ti18n?: { defaultLocale: string; locales: string[]; prefixDefaultLocale?: boolean } | null;\n\t }\n\t| undefined;\nlet virtualCreateDialect: ((config: unknown) => Dialect) | undefined;\n\nasync function loadVirtualModules() {\n\tif (virtualConfig === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst configModule = await import(\"virtual:emdash/config\");\n\t\tvirtualConfig = configModule.default;\n\t}\n\tif (virtualCreateDialect === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst dialectModule = await import(\"virtual:emdash/dialect\");\n\t\tvirtualCreateDialect = dialectModule.createDialect;\n\t\t// dialectType is no longer needed here — dialect detection is\n\t\t// done via the db adapter instance in dialect-helpers.ts\n\t}\n}\n\n/**\n * Entry data type - generic object\n */\nexport type EntryData = Record<string, unknown>;\n\n/**\n * Sort direction\n */\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Order by specification - field name to direction\n * @example { created_at: \"desc\" } - Sort by created_at descending\n * @example { title: \"asc\" } - Sort by title ascending\n */\nexport type OrderBySpec = Record<string, SortDirection>;\n\n/**\n * Build WHERE clause for status filtering.\n * When filtering for 'published' status, also include scheduled content\n * whose scheduled_at time has passed (treating it as effectively published).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nfunction buildStatusCondition(\n\tdb: Kysely<any>,\n\tstatus: string,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst statusField = tablePrefix ? `${tablePrefix}.status` : \"status\";\n\tconst scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : \"scheduled_at\";\n\n\tif (status === \"published\") {\n\t\t// Include both published content AND scheduled content past its publish time.\n\t\t// scheduled_at is stored as text (ISO 8601). On Postgres, we must cast it\n\t\t// to timestamptz for the comparison with CURRENT_TIMESTAMP to work.\n\t\tconst scheduledAtExpr = isPostgres(db)\n\t\t\t? sql`${sql.ref(scheduledAtField)}::timestamptz`\n\t\t\t: sql.ref(scheduledAtField);\n\t\tconst nowExpr = isPostgres(db)\n\t\t\t? currentTimestampValue(db)\n\t\t\t: sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n\t\treturn sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${nowExpr}))`;\n\t}\n\n\treturn sql`${sql.ref(statusField)} = ${status}`;\n}\n\n/**\n * Resolved primary sort field and direction (used for cursor pagination).\n */\ninterface PrimarySort {\n\tfield: string;\n\tdirection: SortDirection;\n}\n\n/**\n * Get the primary sort field from an orderBy spec (first valid field, or default).\n */\nfunction getPrimarySort(orderBy: OrderBySpec | undefined, tablePrefix?: string): PrimarySort {\n\tif (orderBy) {\n\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\tif (FIELD_NAME_PATTERN.test(field)) {\n\t\t\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\t\t\treturn { field: fullField, direction };\n\t\t\t}\n\t\t}\n\t}\n\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\treturn { field: defaultField, direction: \"desc\" };\n}\n\n/**\n * Build ORDER BY clause from orderBy spec\n * Validates field names to prevent SQL injection (alphanumeric + underscore only)\n * Supports multiple sort fields in object key order\n */\nfunction buildOrderByClause(\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\t// Default to created_at DESC\n\tif (!orderBy || Object.keys(orderBy).length === 0) {\n\t\tconst field = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(field)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\tconst sortParts: ReturnType<typeof sql>[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t// Validate field name (alphanumeric + underscore only)\n\t\tif (!FIELD_NAME_PATTERN.test(field)) {\n\t\t\tcontinue; // Skip invalid field names\n\t\t}\n\n\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\tconst dir = direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\t\tsortParts.push(sql`${sql.ref(fullField)} ${dir}`);\n\t}\n\n\t// If no valid sort fields, fall back to default\n\tif (sortParts.length === 0) {\n\t\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(defaultField)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\t// Add id as tiebreaker to ensure stable cursor ordering\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\tconst idDir = primary.direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\tsortParts.push(sql`${sql.ref(idField)} ${idDir}`);\n\n\treturn sql`ORDER BY ${sql.join(sortParts, sql`, `)}`;\n}\n\n/**\n * Build a cursor WHERE condition for keyset pagination.\n * Uses the primary sort field + id as tiebreaker for stable ordering.\n *\n * Throws `InvalidCursorError` if the cursor is malformed; callers should\n * let this propagate so users see a real error rather than silently\n * falling back to the first page.\n */\nfunction buildCursorCondition(\n\tcursor: string,\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst { orderValue, id: cursorId } = decodeCursor(cursor);\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\n\tif (primary.direction === \"desc\") {\n\t\treturn sql`(${sql.ref(primary.field)} < ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} < ${cursorId}))`;\n\t}\n\treturn sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;\n}\n\n/** Type guard: is the where value a range object (not a string or array)? */\nfunction isWhereRange(value: WhereValue): value is WhereRange {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Build AND conditions for non-taxonomy field filters.\n * Returns an array of sql fragments; empty if no field filters apply.\n * Field names are validated against FIELD_NAME_PATTERN to prevent injection.\n */\nfunction buildFieldConditions(\n\tfields: Record<string, WhereValue>,\n\ttablePrefix?: string,\n): ReturnType<typeof sql>[] {\n\tconst conditions: ReturnType<typeof sql>[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (!FIELD_NAME_PATTERN.test(key)) {\n\t\t\tconsole.warn(`[emdash] where filter: invalid field name \"${key}\" ignored`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (value == null) continue;\n\t\tconst ref = tablePrefix ? sql.ref(`${tablePrefix}.${key}`) : sql.ref(key);\n\n\t\tif (isWhereRange(value)) {\n\t\t\tif (value.gt !== undefined) conditions.push(sql`${ref} > ${value.gt}`);\n\t\t\tif (value.gte !== undefined) conditions.push(sql`${ref} >= ${value.gte}`);\n\t\t\tif (value.lt !== undefined) conditions.push(sql`${ref} < ${value.lt}`);\n\t\t\tif (value.lte !== undefined) conditions.push(sql`${ref} <= ${value.lte}`);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tif (value.length > 0) {\n\t\t\t\tconditions.push(sql`${ref} IN (${sql.join(value.map((v) => sql`${v}`))})`);\n\t\t\t}\n\t\t} else {\n\t\t\tconditions.push(sql`${ref} = ${value}`);\n\t\t}\n\t}\n\n\treturn conditions;\n}\n\n/**\n * Range filter for comparison operators on field values.\n * Values are compared as strings in the database. This works correctly for\n * ISO 8601 dates (e.g. \"2024-01-01T00:00:00Z\") because lexicographic ordering\n * matches chronological ordering. Ensure date values use a consistent format.\n */\nexport interface WhereRange {\n\tgt?: string;\n\tgte?: string;\n\tlt?: string;\n\tlte?: string;\n}\n\n/**\n * A where clause value: exact match, multi-value match, or range comparison.\n */\nexport type WhereValue = string | string[] | WhereRange;\n\n/**\n * Fields shared by every collection filter, independent of pagination mode.\n *\n * Cursor and offset pagination are mutually exclusive, so they live on the\n * `CursorCollectionFilter` / `OffsetCollectionFilter` variants rather than\n * here. Use the {@link CollectionFilter} union for any value that may be\n * either.\n */\nexport interface CollectionFilterBase {\n\ttype: string;\n\tstatus?: \"draft\" | \"published\" | \"archived\";\n\tlimit?: number;\n\t/**\n\t * Filter by field values, taxonomy terms, byline credits, or ranges.\n\t *\n\t * Taxonomy names are detected automatically and filtered via JOIN.\n\t * The reserved `byline` key filters by byline credit (any credit, not\n\t * just the primary one) via the `_emdash_content_bylines` junction\n\t * table; its value is one or more byline translation groups.\n\t * Other keys are treated as column filters on the content table.\n\t *\n\t * @example { category: 'news' } - taxonomy term\n\t * @example { byline: '01HXYZ...' } - entries credited to a byline (any position)\n\t * @example { series: 'main' } - exact match on a content field\n\t * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range\n\t */\n\twhere?: Record<string, WhereValue>;\n\t/**\n\t * Order results by field(s)\n\t * @default { created_at: \"desc\" }\n\t */\n\torderBy?: OrderBySpec;\n\t/**\n\t * Filter by locale (e.g. 'en', 'fr').\n\t * When set, only returns content in this locale.\n\t */\n\tlocale?: string;\n}\n\n/** Keyset-paginated collection filter. Cannot also carry an `offset`. */\nexport interface CursorCollectionFilter extends CollectionFilterBase {\n\t/**\n\t * Opaque cursor for keyset pagination.\n\t * Pass the `nextCursor` value from a previous result to fetch the next page.\n\t */\n\tcursor?: string;\n\toffset?: never;\n}\n\n/** Offset-paginated collection filter. Cannot also carry a `cursor`. */\nexport interface OffsetCollectionFilter extends CollectionFilterBase {\n\t/**\n\t * Skip this many rows before returning results (offset pagination).\n\t * Use with `limit` for numbered archive routes (`/page/2`):\n\t * `offset = (page - 1) * perPage`. Ignored unless it is a positive\n\t * integer.\n\t */\n\toffset?: number;\n\tcursor?: never;\n}\n\n/**\n * Filter for loadCollection - type is required.\n *\n * A union of the cursor and offset pagination variants: supplying both\n * `cursor` and `offset` is a compile-time error, since they are mutually\n * exclusive ways to express \"the next page\" (cursor wins at runtime).\n */\nexport type CollectionFilter = CursorCollectionFilter | OffsetCollectionFilter;\n\n/**\n * Filter for loadEntry - type and id are required\n */\nexport interface EntryFilter {\n\ttype: string;\n\tid: string;\n\t/**\n\t * When set, fetch content data from this revision instead of the content table.\n\t * Used by preview mode to serve draft revision data.\n\t */\n\trevisionId?: string;\n\t/**\n\t * Locale to scope slug lookup. Only affects slug resolution;\n\t * IDs are globally unique and always resolve regardless of locale.\n\t */\n\tlocale?: string;\n}\n\n// Cached database instance (shared across calls)\nlet dbInstance: Kysely<Database> | null = null;\n\n/**\n * Get the database instance. Used by query wrapper functions and middleware.\n *\n * Checks the ALS request context first — if a per-request DB override is set\n * (e.g. by DO preview middleware), it takes precedence over the module-level\n * cached instance. This allows preview mode to route queries to an isolated\n * Durable Object database without modifying any calling code.\n *\n * Initializes the default database on first call using config from virtual module.\n */\nexport async function getDb(): Promise<Kysely<Database>> {\n\t// Per-request DB override via ALS (normal mode)\n\tconst ctx = getRequestContext();\n\tif (ctx?.db) {\n\t\treturn ctx.db as Kysely<Database>; // eslint-disable-line typescript/no-unsafe-type-assertion -- db is typed as unknown in RequestContext to avoid circular deps\n\t}\n\n\tif (!dbInstance) {\n\t\tawait loadVirtualModules();\n\t\tif (!virtualConfig?.database || typeof virtualCreateDialect !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"EmDash database not configured. Add database config to emdash() in astro.config.mjs\",\n\t\t\t);\n\t\t}\n\t\tconst dialect = virtualCreateDialect(virtualConfig.database.config);\n\t\tdbInstance = new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\t}\n\treturn dbInstance;\n}\n\n/**\n * Create an EmDash Live Collections loader\n *\n * This loader handles ALL content types in a single Astro collection.\n * Use `getEmDashCollection()` and `getEmDashEntry()` to query\n * specific content types.\n *\n * Database is configured in astro.config.mjs via the emdash() integration.\n *\n * @example\n * ```ts\n * // src/live.config.ts\n * import { defineLiveCollection } from \"astro:content\";\n * import { emdashLoader } from \"emdash\";\n *\n * export const collections = {\n * emdash: defineLiveCollection({\n * loader: emdashLoader(),\n * }),\n * };\n * ```\n */\nexport function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter> {\n\treturn {\n\t\tname: \"emdash\",\n\n\t\t/**\n\t\t * Load all entries for a content type\n\t\t */\n\t\tasync loadCollection({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance (initializes on first use)\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Type filter is required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tif (!type) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type filter is required. Use getEmDashCollection() instead of getLiveCollection() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table (ec_posts, ec_products, etc.)\n\t\t\t\tconst tableName = getTableName(type);\n\n\t\t\t\t// Build query with dynamic table name\n\t\t\t\tconst status = filter?.status || \"published\";\n\t\t\t\tconst limit = filter?.limit;\n\t\t\t\tconst cursor = filter?.cursor;\n\t\t\t\tconst where = filter?.where;\n\t\t\t\tconst orderBy = filter?.orderBy;\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Cursor pagination: over-fetch by 1 to detect next page\n\t\t\t\tconst fetchLimit = limit ? limit + 1 : undefined;\n\n\t\t\t\t// Offset pagination (numbered archive routes). Keyset (cursor)\n\t\t\t\t// and offset are mutually exclusive ways to express \"the next\n\t\t\t\t// page\" — when both are supplied, cursor wins and offset is\n\t\t\t\t// dropped so the two don't stack into a double skip. Only a\n\t\t\t\t// positive integer applies; 0 / negative / fractional are no-ops.\n\t\t\t\tconst rawOffset = cursor ? undefined : filter?.offset;\n\t\t\t\tconst offset =\n\t\t\t\t\ttypeof rawOffset === \"number\" && Number.isInteger(rawOffset) && rawOffset > 0\n\t\t\t\t\t\t? rawOffset\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t// Build cursor condition if cursor is provided\n\t\t\t\tconst cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;\n\n\t\t\t\t// Separate taxonomy / byline filters from field filters\n\t\t\t\tlet result: { rows: Record<string, unknown>[] };\n\t\t\t\t// Taxonomy filters AND together: each entry constrains the base\n\t\t\t\t// row to match at least one of its slugs *within that taxonomy*.\n\t\t\t\t// Term slugs are unique only within a taxonomy, so every filter\n\t\t\t\t// keeps its own `name` and emits its own `EXISTS` clause rather\n\t\t\t\t// than pooling slugs into one `IN`.\n\t\t\t\tconst taxonomyFilters: { name: string; slugs: string[] }[] = [];\n\t\t\t\t// A byline filter matches entries credited to any of the given\n\t\t\t\t// byline translation groups via the `_emdash_content_bylines`\n\t\t\t\t// junction table. `null` means no byline filter; an empty\n\t\t\t\t// `groups` array means the filter was requested but matches\n\t\t\t\t// nothing (short-circuited to an empty result below).\n\t\t\t\tlet bylineFilter: { groups: string[] } | null = null;\n\t\t\t\tconst fieldFilters: Record<string, WhereValue> = {};\n\n\t\t\t\tif (where && Object.keys(where).length > 0) {\n\t\t\t\t\tconst taxNames = await getTaxonomyNames(db);\n\n\t\t\t\t\tfor (const [key, value] of Object.entries(where)) {\n\t\t\t\t\t\tif (value == null) continue;\n\t\t\t\t\t\tif (key === \"byline\") {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on \"byline\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst groups = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\tbylineFilter = { groups };\n\t\t\t\t\t\t} else if (taxNames.has(key)) {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on taxonomy \"${key}\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst slugs = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\ttaxonomyFilters.push({ name: key, slugs });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfieldFilters[key] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A byline or taxonomy filter with no values matches nothing —\n\t\t\t\t// short-circuit before building SQL (an empty `IN ()` is invalid\n\t\t\t\t// SQL on both dialects).\n\t\t\t\tif (\n\t\t\t\t\t(bylineFilter && bylineFilter.groups.length === 0) ||\n\t\t\t\t\ttaxonomyFilters.some((f) => f.slugs.length === 0)\n\t\t\t\t) {\n\t\t\t\t\treturn { entries: [], cacheHint: { tags: [type] } };\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\t// Taxonomy and byline filters are applied as correlated\n\t\t\t\t\t// `EXISTS` semi-joins rather than `INNER JOIN ... DISTINCT`.\n\t\t\t\t\t// A join fan-out would force `SELECT DISTINCT table.*`, and\n\t\t\t\t\t// Postgres cannot apply DISTINCT to a row containing a `json`\n\t\t\t\t\t// column (no equality operator), so the join approach throws\n\t\t\t\t\t// there. EXISTS matches \"credited/tagged at least once\"\n\t\t\t\t\t// without duplicating rows, needs no DISTINCT, and works on\n\t\t\t\t\t// both SQLite and Postgres. The base query stays a single-\n\t\t\t\t\t// table `SELECT *`, so all field/status/locale/cursor/order\n\t\t\t\t\t// conditions reference unprefixed columns as before.\n\t\t\t\t\tconst orderByClause = buildOrderByClause(orderBy);\n\t\t\t\t\tconst statusCondition = buildStatusCondition(db, status);\n\t\t\t\t\tconst localeFilter = locale ? sql`AND locale = ${locale}` : sql``;\n\t\t\t\t\tconst cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;\n\t\t\t\t\tconst fieldConds = buildFieldConditions(fieldFilters);\n\t\t\t\t\tconst fieldCondsSQL =\n\t\t\t\t\t\tfieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;\n\n\t\t\t\t\t// One `EXISTS` per taxonomy, AND'd together: an entry must be\n\t\t\t\t\t// tagged with a matching term in *every* requested taxonomy.\n\t\t\t\t\t// Each clause pins its own `t.name`, so slugs never pool\n\t\t\t\t\t// across taxonomies (they're only unique within one).\n\t\t\t\t\tconst taxonomyCond =\n\t\t\t\t\t\ttaxonomyFilters.length > 0\n\t\t\t\t\t\t\t? sql`${sql.join(\n\t\t\t\t\t\t\t\t\ttaxonomyFilters.map(\n\t\t\t\t\t\t\t\t\t\t(f) => sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM content_taxonomies ct\n\t\t\t\t\t\t\tINNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id\n\t\t\t\t\t\t\tWHERE ct.collection = ${type}\n\t\t\t\t\t\t\t\tAND ct.entry_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND t.name = ${f.name}\n\t\t\t\t\t\t\t\tAND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})\n\t\t\t\t\t\t\t${locale ? sql`AND t.locale = ${locale}` : sql``}\n\t\t\t\t\t\t)`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tsql` `,\n\t\t\t\t\t\t\t\t)}`\n\t\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\t// `_emdash_content_bylines.byline_id` stores the byline's\n\t\t\t\t\t// translation_group (migration 040), so a credit spans every\n\t\t\t\t\t// locale variant of the byline and we match the group directly.\n\t\t\t\t\tconst bylineCond = bylineFilter\n\t\t\t\t\t\t? sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM _emdash_content_bylines cb\n\t\t\t\t\t\t\tWHERE cb.collection_slug = ${type}\n\t\t\t\t\t\t\t\tAND cb.content_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND cb.byline_id IN (${sql.join(bylineFilter.groups.map((g) => sql`${g}`))})\n\t\t\t\t\t\t)`\n\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\t// Fold byline + taxonomy hydration into the list query.\n\t\t\t\t\tconst { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\ttableName,\n\t\t\t\t\t);\n\n\t\t\t\t\t// LIMIT/OFFSET clause. SQLite only accepts OFFSET when a\n\t\t\t\t\t// LIMIT is present, so a bare offset uses `LIMIT -1`\n\t\t\t\t\t// (unbounded); Postgres takes a standalone OFFSET.\n\t\t\t\t\tlet limitOffsetClause = sql``;\n\t\t\t\t\tif (fetchLimit != null && offset != null) {\n\t\t\t\t\t\tlimitOffsetClause = sql`LIMIT ${fetchLimit} OFFSET ${offset}`;\n\t\t\t\t\t} else if (fetchLimit != null) {\n\t\t\t\t\t\tlimitOffsetClause = sql`LIMIT ${fetchLimit}`;\n\t\t\t\t\t} else if (offset != null) {\n\t\t\t\t\t\tlimitOffsetClause = isPostgres(db)\n\t\t\t\t\t\t\t? sql`OFFSET ${offset}`\n\t\t\t\t\t\t\t: sql`LIMIT -1 OFFSET ${offset}`;\n\t\t\t\t\t}\n\t\t\t\t\tresult = await sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT *, ${termsSelect}, ${bylinesSelect} FROM ${sql.ref(tableName)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND ${statusCondition}\n\t\t\t\t\t\t${localeFilter}\n\t\t\t\t\t\t${cursorCond}\n\t\t\t\t\t\t${taxonomyCond}\n\t\t\t\t\t\t${bylineCond}\n\t\t\t\t\t\t${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}\n\t\t\t\t\t\t${orderByClause}\n\t\t\t\t\t\t${limitOffsetClause}\n\t\t\t\t\t`.execute(db);\n\t\t\t\t}\n\n\t\t\t\t// Detect whether there are more results (over-fetched by 1)\n\t\t\t\tconst hasMore = limit ? result.rows.length > limit : false;\n\t\t\t\tconst rows = hasMore ? result.rows.slice(0, limit) : result.rows;\n\n\t\t\t\t// Map rows to entries\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entries = rows.map((row) => {\n\t\t\t\t\tconst slug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\t\tconst rowLocale = rowStr(row, \"locale\");\n\t\t\t\t\tconst shouldPrefix =\n\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\trowLocale !== \"\" &&\n\t\t\t\t\t\t(rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\tconst id = shouldPrefix ? `${rowLocale}/${slug}` : slug;\n\t\t\t\t\tconst data = mapRowToData(row);\n\t\t\t\t\tstashFolded(data, row);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Encode nextCursor from the last row if there are more results\n\t\t\t\tlet nextCursor: string | undefined;\n\t\t\t\tif (hasMore && rows.length > 0) {\n\t\t\t\t\tconst lastRow = rows.at(-1)!;\n\t\t\t\t\tconst primary = getPrimarySort(orderBy);\n\t\t\t\t\t// Strip table prefix from field name for row lookup\n\t\t\t\t\tconst fieldName = primary.field.includes(\".\")\n\t\t\t\t\t\t? primary.field.split(\".\").pop()!\n\t\t\t\t\t\t: primary.field;\n\t\t\t\t\tconst lastOrderValue = lastRow[fieldName];\n\t\t\t\t\tconst orderStr =\n\t\t\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tnextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t\t\t}\n\n\t\t\t\t// Collection-level cache hint uses the most recent updated_at\n\t\t\t\tlet collectionLastModified: Date | undefined;\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tif (row.updated_at) {\n\t\t\t\t\t\tconst d = new Date(rowStr(row, \"updated_at\"));\n\t\t\t\t\t\tif (!collectionLastModified || d > collectionLastModified) {\n\t\t\t\t\t\t\tcollectionLastModified = d;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tentries,\n\t\t\t\t\tnextCursor,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [type],\n\t\t\t\t\t\tlastModified: collectionLastModified,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table/column gracefully - return empty collection.\n\t\t\t\t// Missing table happens before migrations have run.\n\t\t\t\t// Missing column happens when a where filter references a non-existent field.\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (isMissingTableError(error) || isMissingColumnError(error)) {\n\t\t\t\t\tif (isMissingColumnError(error)) {\n\t\t\t\t\t\tconsole.warn(`[emdash] where filter: ${message}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn { entries: [] };\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load collection: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load a single entry by type and ID/slug\n\t\t *\n\t\t * When filter.revisionId is set (preview mode), the entry's data\n\t\t * comes from the revisions table instead of the content table columns.\n\t\t */\n\t\tasync loadEntry({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Both type and id are required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tconst id = filter?.id;\n\n\t\t\t\tif (!type || !id) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type and id filters are required. Use getEmDashEntry() instead of getLiveEntry() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table\n\t\t\t\tconst tableName = getTableName(type);\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Use raw SQL for dynamic table name, match by slug or id\n\t\t\t\t// When locale is specified, prefer locale-scoped slug match,\n\t\t\t\t// but IDs are globally unique so always check id without locale scope.\n\t\t\t\t//\n\t\t\t\t// Byline + taxonomy hydration (foldedHydrationSelects) and per-entry\n\t\t\t\t// SEO (foldedSeoSelect) are each surfaced as a single aggregated JSON\n\t\t\t\t// column rather than flat columns. This keeps the result-set width\n\t\t\t\t// bounded at any collection schema width: a flat `LEFT JOIN _emdash_seo`\n\t\t\t\t// adds 5 alias columns to every row and pushes wide flat-schema\n\t\t\t\t// collections (common after WordPress / ACF imports) past D1's\n\t\t\t\t// per-result-set column limit, surfacing as a silent null entry. One\n\t\t\t\t// JSON column is one column, so the join stays safe at any width and\n\t\t\t\t// we keep the single round trip.\n\t\t\t\tconst { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(\n\t\t\t\t\tdb,\n\t\t\t\t\ttype,\n\t\t\t\t\t\"c\",\n\t\t\t\t);\n\t\t\t\tconst seoSelect = foldedSeoSelect(db, type, \"c\");\n\t\t\t\tconst result = locale\n\t\t\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db)\n\t\t\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND (c.slug = ${id} OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db);\n\n\t\t\t\tconst row = result.rows[0];\n\t\t\t\tif (!row) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\t// Expand the folded SEO JSON column onto SEO_COLUMN_ALIASES so\n\t\t\t\t// extractSeo() reads it transparently. Missing/null SEO is a\n\t\t\t\t// no-op: extractSeo() returns null when the aliases are absent.\n\t\t\t\texpandFoldedSeo(row);\n\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entrySlug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\tconst entryLocale = rowStr(row, \"locale\");\n\t\t\t\tconst shouldPrefixEntry =\n\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\tentryLocale !== \"\" &&\n\t\t\t\t\t(entryLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\tconst entryId = shouldPrefixEntry ? `${entryLocale}/${entrySlug}` : entrySlug;\n\n\t\t\t\t// Preview mode: override content fields with revision data,\n\t\t\t\t// keeping system metadata from the content table row.\n\t\t\t\tconst revisionId = filter?.revisionId;\n\t\t\t\tif (revisionId) {\n\t\t\t\t\tconst revRow = await sql<{ data: string }>`\n\t\t\t\t\t\tSELECT data FROM revisions\n\t\t\t\t\t\tWHERE id = ${revisionId}\n\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t`.execute(db);\n\n\t\t\t\t\tconst revData = revRow.rows[0];\n\t\t\t\t\tif (revData) {\n\t\t\t\t\t\tconst parsed: Record<string, unknown> = JSON.parse(revData.data);\n\t\t\t\t\t\t// System metadata from content table + content fields from revision\n\t\t\t\t\t\tconst systemData: Record<string, unknown> = {};\n\t\t\t\t\t\tfor (const [key, mappedKey] of Object.entries(INCLUDE_IN_DATA)) {\n\t\t\t\t\t\t\tif (key in row) {\n\t\t\t\t\t\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = typeof row[key] === \"string\" ? new Date(row[key]) : null;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = row[key];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Use slug from revision metadata if present, else from content table\n\t\t\t\t\t\tconst slug = typeof parsed._slug === \"string\" ? parsed._slug : rowStr(row, \"slug\");\n\t\t\t\t\t\tconst revSlug = slug || rowStr(row, \"id\");\n\t\t\t\t\t\tconst revLocale = rowStr(row, \"locale\");\n\t\t\t\t\t\tconst shouldPrefixRev =\n\t\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\t\trevLocale !== \"\" &&\n\t\t\t\t\t\t\t(revLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\t\tconst revId = shouldPrefixRev ? `${revLocale}/${revSlug}` : revSlug;\n\t\t\t\t\t\t// SEO is not revisioned — it comes from the content row's\n\t\t\t\t\t\t// joined _emdash_seo columns, not the revision snapshot.\n\t\t\t\t\t\tconst revEntryData: Record<string, unknown> = {\n\t\t\t\t\t\t\t...systemData,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\t...mapRevisionData(parsed),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst revSeo = extractSeo(row);\n\t\t\t\t\t\tif (revSeo) revEntryData.seo = revSeo;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: revId,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\t\tdata: revEntryData,\n\t\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst entryData = mapRowToData(row);\n\t\t\t\tconst entrySeo = extractSeo(row);\n\t\t\t\tif (entrySeo) entryData.seo = entrySeo;\n\t\t\t\tstashFolded(entryData, row);\n\t\t\t\treturn {\n\t\t\t\t\tid: entryId,\n\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\tdata: entryData,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table gracefully - return undefined (not found).\n\t\t\t\t// This happens before migrations have run.\n\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load entry: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;AAsB3B,MAAM,qBAA6C;CAClD,WAAW;CACX,iBAAiB;CACjB,WAAW;CACX,eAAe;CACf,cAAc;CACd;;AAGD,MAAM,oBAAoB,OAAO,OAAO,mBAAmB;;AAG3D,MAAM,oBAAoB;;;;;AAM1B,MAAM,iBAAiB,IAAI,IAAI;CAC9B;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAKA,GAAG;CAIH;CACA;CACA;CACA,CAAC;;AAGF,MAAa,eAAe,OAAO,IAAI,qBAAqB;AAC5D,MAAa,iBAAiB,OAAO,IAAI,uBAAuB;;;;;;;;;;;;;AAehE,SAAS,uBAAuB,IAAiB,MAAc,OAAe;CAC7E,MAAM,IAAI,IAAI,IAAI,MAAM;CACxB,MAAM,KAAK,WAAW,GAAG;CACzB,MAAM,OAAO,UACZ,KAAK,IAAI,IAAI,qBAAqB,MAAM,GAAG,GAAG,IAAI,IAAI,eAAe,MAAM,GAAG;CAC/E,MAAM,OAAO,UACZ,KAAK,GAAG,qBAAqB,MAAM,kBAAkB,GAAG,oBAAoB,MAAM;CAOnF,MAAM,QAAQ,GAAG,WAAW,IALZ,IACf,uJACA,CAGuC,CAAC,QAAQ,IAAI,IAAI,qBAAqB,CAAC,cAAc,IAAI,IAAI,aAAa,CAAC,sEAAsE,KAAK,qBAAqB,EAAE,qBAAqB,EAAE,cAAc,IAAI,IAAI,gBAAgB;CAElR,MAAM,cAAc,IACnB,waACA;AAQD,QAAO;EAAE;EAAO,SADA,GAAG,WAAW,IADf,GAAG,GALA,KACf,IAAI,IACJ,uFACA,GACA,IAAI,IAAI,iFAAiF,GAC3D,YAAY,GACJ,CAAC,QAAQ,IAAI,IAAI,0BAA0B,CAAC,cAAc,IAAI,IAAI,kBAAkB,CAAC,wDAAwD,IAAI,IAAI,QAAQ,CAAC,+DAA+D,KAAK,uBAAuB,EAAE,qBAAqB,EAAE,cAAc,IAAI,IAAI,kBAAkB;EAC1U;;;;;;;;;;;;;;;AAiB1B,SAAS,gBAAgB,IAAiB,MAAc,OAAe;CACtE,MAAM,IAAI,IAAI,IAAI,MAAM;CACxB,MAAM,KAAK,WAAW,GAAG;CAIzB,MAAM,QACL;AAED,QAAO,GAAG,WADE,KAAK,IAAI,IAAI,qBAAqB,MAAM,GAAG,GAAG,IAAI,IAAI,eAAe,MAAM,GAAG,CACjE,QAAQ,IAAI,IAAI,cAAc,CAAC,6BAA6B,KAAK,sBAAsB,EAAE,kBAAkB,IAAI,IAAI,kBAAkB;;;;;;;;AAS/J,SAAS,cAAc,OAAkD;AACxE,QAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;AAGrE,SAAS,gBAAgB,KAAoC;CAC5D,MAAM,MAAM,IAAI;AAChB,QAAO,IAAI;CACX,IAAI,SAAyC;AAC7C,KAAI,OAAO,QAAQ,SAClB,KAAI;EACH,MAAM,YAAqB,KAAK,MAAM,IAAI;AAC1C,MAAI,cAAc,UAAU,CAAE,UAAS;SAChC;AACP;;UAES,cAAc,IAAI,CAC5B,UAAS;AAEV,KAAI,CAAC,OAAQ;AACb,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,mBAAmB,CAC5D,KAAI,SAAS,OAAO,QAAQ;;;;;;AAQ9B,SAAS,YAAY,MAA+B,KAAoC;AACvF,MAAK,MAAM,CAAC,KAAK,QAAQ,CACxB,CAAC,iBAAiB,aAAa,EAC/B,CAAC,mBAAmB,eAAe,CACnC,EAAW;EACX,MAAM,MAAM,IAAI;EAChB,IAAI;AACJ,MAAI,OAAO,QAAQ,SAClB,KAAI;AACH,WAAQ,KAAK,MAAM,IAAI;UAChB;AACP;;WAES,MAAM,QAAQ,IAAI,CAC5B,SAAQ;MAER;AAED,SAAO,eAAe,MAAM,KAAK;GAAE;GAAO,YAAY;GAAO,cAAc;GAAM,CAAC;;;;;;;;;;;AAqBpF,SAAS,WAAW,KAA+C;CAClE,MAAM,UAAU,IAAI,mBAAmB;AACvC,KAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;CACtD,MAAM,QAAQ,IAAI,mBAAmB;CACrC,MAAM,cAAc,IAAI,mBAAmB;CAC3C,MAAM,QAAQ,IAAI,mBAAmB;CACrC,MAAM,YAAY,IAAI,mBAAmB;AACzC,QAAO;EACN,OAAO,OAAO,UAAU,WAAW,QAAQ;EAC3C,aAAa,OAAO,gBAAgB,WAAW,cAAc;EAC7D,OAAO,OAAO,UAAU,WAAW,QAAQ;EAC3C,WAAW,OAAO,cAAc,WAAW,YAAY;EACvD,SAAS,YAAY;EACrB;;;;;AAMF,SAAS,aAAa,MAAsB;AAC3C,oBAAmB,MAAM,kBAAkB;AAC3C,QAAO,MAAM;;;;;;;AAQd,IAAI,gBAAoC;;;;;;;AAQxC,eAAe,iBAAiB,IAA4C;CAC3E,MAAM,gBAAgB,mBAAmB,EAAE,iBAAiB;AAE5D,KAAI,CAAC,iBAAiB,cACrB,QAAO;AAGR,KAAI;EACH,MAAM,OAAO,MAAM,GAAG,WAAW,wBAAwB,CAAC,OAAO,OAAO,CAAC,SAAS;EAClF,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,KAAK,CAAC;AAC9C,MAAI,CAAC,cACJ,iBAAgB;AAEjB,SAAO;SACA;EAEP,MAAM,wBAAQ,IAAI,KAAa;AAC/B,MAAI,CAAC,cACJ,iBAAgB;AAEjB,SAAO;;;;;;;;;;;AAYT,SAAgB,0BAAgC;AAC/C,iBAAgB;;;;;AAMjB,MAAM,kBAA0C;CAC/C,IAAI;CACJ,QAAQ;CACR,WAAW;CACX,mBAAmB;CACnB,YAAY;CACZ,YAAY;CACZ,cAAc;CACd,cAAc;CACd,mBAAmB;CACnB,kBAAkB;CAClB,QAAQ;CACR,mBAAmB;CACnB;;AAGD,MAAM,eAAe,IAAI,IAAI;CAAC;CAAc;CAAc;CAAgB;CAAe,CAAC;;;;;;;;;AAU1F,MAAa,oBAAmC,OAAO,yBAAyB;AAEhF,MAAM,0BAA0B;AAChC,MAAM,qBAAqB;;AAG3B,SAAS,OAAO,KAA8B,KAAa,WAAW,IAAY;CACjF,MAAM,MAAM,IAAI;AAChB,QAAO,OAAO,QAAQ,WAAW,MAAM;;AAGxC,SAAS,SAAS,OAAkD;AACnE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG5E,SAAS,eAAe,KAAsB;AAC7C,QAAO,CAAC,IAAI,WAAW,IAAI,IAAI,CAAC,mBAAmB,KAAK,IAAI;;AAG7D,SAAS,yBAAyB,OAAyB;AAC1D,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO,MAAM,IAAI,yBAAyB;AAG3C,KAAI,CAAC,SAAS,MAAM,CACnB,QAAO;CAGR,MAAM,aAAsC,EAAE;AAC9C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC/C,YAAW,OAAO,yBAAyB,MAAM;AAGlD,KACC,WAAW,aAAa,WACxB,OAAO,WAAW,QAAQ,YAC1B,WAAW,IAAI,SAAS,GACvB;EACD,MAAM,MAAM,WAAW;AACvB,MAAI,IAAI,WAAW,wBAAwB,EAAE;GAC5C,MAAM,KAAK,IAAI,MAAM,GAA+B;AACpD,OAAI,CAAC,WAAW,MAAM,GACrB,YAAW,KAAK;aAEP,eAAe,IAAI,EAAE;AAC/B,OAAI,CAAC,WAAW,GACf,YAAW,KAAK;AAEjB,cAAW,MAAM,GAAG,0BAA0B;;;AAIhD,QAAO;;;;;;;AAQR,SAAS,aAAa,KAAuD;CAC5E,MAAM,OAAgC,EAAE;CACxC,MAAM,gBAAwC,EAAE;AAEhD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,EAAE;AAE/C,MAAI,OAAO,iBAAiB;AAE3B,OAAI,aAAa,IAAI,IAAI,CACxB,KAAI,OAAO,UAAU,UAAU;AAC9B,kBAAc,OAAO;AACrB,SAAK,gBAAgB,QAAQ,IAAI,KAAK,MAAM;SAE5C,MAAK,gBAAgB,QAAQ;OAG9B,MAAK,gBAAgB,QAAQ;AAE9B;;AAGD,MAAI,eAAe,IAAI,IAAI,CAAE;AAG7B,MAAI,OAAO,UAAU,SACpB,KAAI;AAEH,OAAI,MAAM,WAAW,IAAI,IAAI,MAAM,WAAW,IAAI,CACjD,MAAK,OAAO,yBAAyB,KAAK,MAAM,MAAM,CAAC;OAEvD,MAAK,OAAO;UAEN;AACP,QAAK,OAAO;;MAGb,MAAK,OAAO;;AAId,QAAO,eAAe,MAAM,mBAAmB;EAC9C,OAAO;EACP,YAAY;EACZ,cAAc;EACd,UAAU;EACV,CAAC;AAEF,QAAO;;;;;;AAOR,SAAS,gBAAgB,MAAwD;CAChF,MAAM,SAAkC,EAAE;AAC1C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAChD,MAAI,IAAI,WAAW,IAAI,CAAE;AACzB,SAAO,OAAO,yBAAyB,MAAM;;AAE9C,QAAO;;AAKR,IAAI;AAMJ,IAAI;AAEJ,eAAe,qBAAqB;AACnC,KAAI,kBAAkB,OAIrB,kBADqB,MAAM,OAAO,0BACL;AAE9B,KAAI,yBAAyB,OAI5B,yBADsB,MAAM,OAAO,2BACE;;;;;;;AA6BvC,SAAS,qBACR,IACA,QACA,aACyB;CACzB,MAAM,cAAc,cAAc,GAAG,YAAY,WAAW;CAC5D,MAAM,mBAAmB,cAAc,GAAG,YAAY,iBAAiB;AAEvE,KAAI,WAAW,aAAa;EAI3B,MAAM,kBAAkB,WAAW,GAAG,GACnC,GAAG,GAAG,IAAI,IAAI,iBAAiB,CAAC,iBAChC,IAAI,IAAI,iBAAiB;EAC5B,MAAM,UAAU,WAAW,GAAG,GAC3B,sBAAsB,GAAG,GACzB,GAAG;AACN,SAAO,GAAG,IAAI,IAAI,IAAI,YAAY,CAAC,qBAAqB,IAAI,IAAI,YAAY,CAAC,qBAAqB,gBAAgB,MAAM,QAAQ;;AAGjI,QAAO,GAAG,GAAG,IAAI,IAAI,YAAY,CAAC,KAAK;;;;;AAcxC,SAAS,eAAe,SAAkC,aAAmC;AAC5F,KAAI,SACH;OAAK,MAAM,CAAC,OAAO,cAAc,OAAO,QAAQ,QAAQ,CACvD,KAAI,mBAAmB,KAAK,MAAM,CAEjC,QAAO;GAAE,OADS,cAAc,GAAG,YAAY,GAAG,UAAU;GACjC;GAAW;;AAKzC,QAAO;EAAE,OADY,cAAc,GAAG,YAAY,eAAe;EACnC,WAAW;EAAQ;;;;;;;AAQlD,SAAS,mBACR,SACA,aACyB;AAEzB,KAAI,CAAC,WAAW,OAAO,KAAK,QAAQ,CAAC,WAAW,GAAG;EAClD,MAAM,QAAQ,cAAc,GAAG,YAAY,eAAe;AAC1D,SAAO,GAAG,YAAY,IAAI,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,cAAc,GAAG,YAAY,OAAO,KAAK,CAAC;;CAGjG,MAAM,YAAsC,EAAE;AAE9C,MAAK,MAAM,CAAC,OAAO,cAAc,OAAO,QAAQ,QAAQ,EAAE;AAEzD,MAAI,CAAC,mBAAmB,KAAK,MAAM,CAClC;EAGD,MAAM,YAAY,cAAc,GAAG,YAAY,GAAG,UAAU;EAC5D,MAAM,MAAM,cAAc,QAAQ,GAAG,QAAQ,GAAG;AAChD,YAAU,KAAK,GAAG,GAAG,IAAI,IAAI,UAAU,CAAC,GAAG,MAAM;;AAIlD,KAAI,UAAU,WAAW,GAAG;EAC3B,MAAM,eAAe,cAAc,GAAG,YAAY,eAAe;AACjE,SAAO,GAAG,YAAY,IAAI,IAAI,aAAa,CAAC,SAAS,IAAI,IAAI,cAAc,GAAG,YAAY,OAAO,KAAK,CAAC;;CAIxG,MAAM,UAAU,eAAe,SAAS,YAAY;CACpD,MAAM,UAAU,cAAc,GAAG,YAAY,OAAO;CACpD,MAAM,QAAQ,QAAQ,cAAc,QAAQ,GAAG,QAAQ,GAAG;AAC1D,WAAU,KAAK,GAAG,GAAG,IAAI,IAAI,QAAQ,CAAC,GAAG,QAAQ;AAEjD,QAAO,GAAG,YAAY,IAAI,KAAK,WAAW,GAAG,KAAK;;;;;;;;;;AAWnD,SAAS,qBACR,QACA,SACA,aACyB;CACzB,MAAM,EAAE,YAAY,IAAI,aAAa,aAAa,OAAO;CACzD,MAAM,UAAU,eAAe,SAAS,YAAY;CACpD,MAAM,UAAU,cAAc,GAAG,YAAY,OAAO;AAEpD,KAAI,QAAQ,cAAc,OACzB,QAAO,GAAG,IAAI,IAAI,IAAI,QAAQ,MAAM,CAAC,KAAK,WAAW,OAAO,IAAI,IAAI,QAAQ,MAAM,CAAC,KAAK,WAAW,OAAO,IAAI,IAAI,QAAQ,CAAC,KAAK,SAAS;AAE1I,QAAO,GAAG,IAAI,IAAI,IAAI,QAAQ,MAAM,CAAC,KAAK,WAAW,OAAO,IAAI,IAAI,QAAQ,MAAM,CAAC,KAAK,WAAW,OAAO,IAAI,IAAI,QAAQ,CAAC,KAAK,SAAS;;;AAI1I,SAAS,aAAa,OAAwC;AAC7D,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;;;;;;AAQ5E,SAAS,qBACR,QACA,aAC2B;CAC3B,MAAM,aAAuC,EAAE;AAE/C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;AAClD,MAAI,CAAC,mBAAmB,KAAK,IAAI,EAAE;AAClC,WAAQ,KAAK,8CAA8C,IAAI,WAAW;AAC1E;;AAED,MAAI,SAAS,KAAM;EACnB,MAAM,MAAM,cAAc,IAAI,IAAI,GAAG,YAAY,GAAG,MAAM,GAAG,IAAI,IAAI,IAAI;AAEzE,MAAI,aAAa,MAAM,EAAE;AACxB,OAAI,MAAM,OAAO,OAAW,YAAW,KAAK,GAAG,GAAG,IAAI,KAAK,MAAM,KAAK;AACtE,OAAI,MAAM,QAAQ,OAAW,YAAW,KAAK,GAAG,GAAG,IAAI,MAAM,MAAM,MAAM;AACzE,OAAI,MAAM,OAAO,OAAW,YAAW,KAAK,GAAG,GAAG,IAAI,KAAK,MAAM,KAAK;AACtE,OAAI,MAAM,QAAQ,OAAW,YAAW,KAAK,GAAG,GAAG,IAAI,MAAM,MAAM,MAAM;aAC/D,MAAM,QAAQ,MAAM,EAC9B;OAAI,MAAM,SAAS,EAClB,YAAW,KAAK,GAAG,GAAG,IAAI,OAAO,IAAI,KAAK,MAAM,KAAK,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG;QAG3E,YAAW,KAAK,GAAG,GAAG,IAAI,KAAK,QAAQ;;AAIzC,QAAO;;AA8GR,IAAI,aAAsC;;;;;;;;;;;AAY1C,eAAsB,QAAmC;CAExD,MAAM,MAAM,mBAAmB;AAC/B,KAAI,KAAK,GACR,QAAO,IAAI;AAGZ,KAAI,CAAC,YAAY;AAChB,QAAM,oBAAoB;AAC1B,MAAI,CAAC,eAAe,YAAY,OAAO,yBAAyB,WAC/D,OAAM,IAAI,MACT,sFACA;AAGF,eAAa,IAAI,OAAiB;GAAE,SADpB,qBAAqB,cAAc,SAAS,OAAO;GACtB,KAAK,iBAAiB;GAAE,CAAC;;AAEvE,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBR,SAAgB,eAAqE;AACpF,QAAO;EACN,MAAM;EAKN,MAAM,eAAe,EAAE,UAAU;AAChC,OAAI;IAEH,MAAM,KAAK,MAAM,OAAO;IAGxB,MAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KACJ,QAAO,EACN,uBAAO,IAAI,MACV,8FACA,EACD;IAIF,MAAM,YAAY,aAAa,KAAK;IAGpC,MAAM,SAAS,QAAQ,UAAU;IACjC,MAAM,QAAQ,QAAQ;IACtB,MAAM,SAAS,QAAQ;IACvB,MAAM,QAAQ,QAAQ;IACtB,MAAM,UAAU,QAAQ;IACxB,MAAM,SAAS,QAAQ;IAGvB,MAAM,aAAa,QAAQ,QAAQ,IAAI;IAOvC,MAAM,YAAY,SAAS,SAAY,QAAQ;IAC/C,MAAM,SACL,OAAO,cAAc,YAAY,OAAO,UAAU,UAAU,IAAI,YAAY,IACzE,YACA;IAGJ,MAAM,kBAAkB,SAAS,qBAAqB,QAAQ,QAAQ,GAAG;IAGzE,IAAI;IAMJ,MAAM,kBAAuD,EAAE;IAM/D,IAAI,eAA4C;IAChD,MAAM,eAA2C,EAAE;AAEnD,QAAI,SAAS,OAAO,KAAK,MAAM,CAAC,SAAS,GAAG;KAC3C,MAAM,WAAW,MAAM,iBAAiB,GAAG;AAE3C,UAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AACjD,UAAI,SAAS,KAAM;AACnB,UAAI,QAAQ,UAAU;AACrB,WAAI,aAAa,MAAM,EAAE;AACxB,gBAAQ,KACP,gFACA;AACD;;AAGD,sBAAe,EAAE,QADF,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM,EAC5B;iBACf,SAAS,IAAI,IAAI,EAAE;AAC7B,WAAI,aAAa,MAAM,EAAE;AACxB,gBAAQ,KACP,yEAAyE,IAAI,YAC7E;AACD;;OAED,MAAM,QAAQ,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;AACpD,uBAAgB,KAAK;QAAE,MAAM;QAAK;QAAO,CAAC;YAE1C,cAAa,OAAO;;;AAQvB,QACE,gBAAgB,aAAa,OAAO,WAAW,KAChD,gBAAgB,MAAM,MAAM,EAAE,MAAM,WAAW,EAAE,CAEjD,QAAO;KAAE,SAAS,EAAE;KAAE,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE;KAAE;IAGpD;KAWC,MAAM,gBAAgB,mBAAmB,QAAQ;KACjD,MAAM,kBAAkB,qBAAqB,IAAI,OAAO;KACxD,MAAM,eAAe,SAAS,GAAG,gBAAgB,WAAW,GAAG;KAC/D,MAAM,aAAa,kBAAkB,GAAG,OAAO,oBAAoB,GAAG;KACtE,MAAM,aAAa,qBAAqB,aAAa;KACrD,MAAM,gBACL,WAAW,SAAS,IAAI,GAAG,GAAG,IAAI,KAAK,YAAY,GAAG,QAAQ,KAAK;KAMpE,MAAM,eACL,gBAAgB,SAAS,IACtB,GAAG,GAAG,IAAI,KACV,gBAAgB,KACd,MAAM,GAAG;;;+BAGW,KAAK;4BACR,IAAI,IAAI,UAAU,CAAC;uBACxB,EAAE,KAAK;yBACL,IAAI,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC;SACxD,SAAS,GAAG,kBAAkB,WAAW,GAAG,GAAG;SAE9C,EACD,GAAG,IACH,KACA,GAAG;KAKP,MAAM,aAAa,eAChB,GAAG;;oCAEyB,KAAK;8BACX,IAAI,IAAI,UAAU,CAAC;+BAClB,IAAI,KAAK,aAAa,OAAO,KAAK,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC;WAE3E,GAAG;KAGN,MAAM,EAAE,OAAO,aAAa,SAAS,kBAAkB,uBACtD,IACA,MACA,UACA;KAKD,IAAI,oBAAoB,GAAG;AAC3B,SAAI,cAAc,QAAQ,UAAU,KACnC,qBAAoB,GAAG,SAAS,WAAW,UAAU;cAC3C,cAAc,KACxB,qBAAoB,GAAG,SAAS;cACtB,UAAU,KACpB,qBAAoB,WAAW,GAAG,GAC/B,GAAG,UAAU,WACb,GAAG,mBAAmB;AAE1B,cAAS,MAAM,GAA4B;kBAC9B,YAAY,IAAI,cAAc,QAAQ,IAAI,IAAI,UAAU,CAAC;;YAE/D,gBAAgB;QACpB,aAAa;QACb,WAAW;QACX,aAAa;QACb,WAAW;QACX,gBAAgB,GAAG,OAAO,kBAAkB,GAAG,GAAG;QAClD,cAAc;QACd,kBAAkB;OACnB,QAAQ,GAAG;;IAId,MAAM,UAAU,QAAQ,OAAO,KAAK,SAAS,QAAQ;IACrD,MAAM,OAAO,UAAU,OAAO,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO;IAG5D,MAAM,aAAa,eAAe;IAClC,MAAM,cAAc,cAAc,WAAW,QAAQ,SAAS;IAC9D,MAAM,UAAU,KAAK,KAAK,QAAQ;KACjC,MAAM,OAAO,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,KAAK;KACrD,MAAM,YAAY,OAAO,KAAK,SAAS;KAKvC,MAAM,KAHL,eACA,cAAc,OACb,cAAc,WAAW,iBAAiB,WAAW,uBAC7B,GAAG,UAAU,GAAG,SAAS;KACnD,MAAM,OAAO,aAAa,IAAI;AAC9B,iBAAY,MAAM,IAAI;AACtB,YAAO;MACN;MACA,MAAM,OAAO,KAAK,OAAO;MACzB,QAAQ,OAAO,KAAK,UAAU,QAAQ;MACtC;MACA,WAAW;OACV,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC;OACzB,cAAc,IAAI,aAAa,IAAI,KAAK,OAAO,KAAK,aAAa,CAAC,GAAG;OACrE;MACD;MACA;IAGF,IAAI;AACJ,QAAI,WAAW,KAAK,SAAS,GAAG;KAC/B,MAAM,UAAU,KAAK,GAAG,GAAG;KAC3B,MAAM,UAAU,eAAe,QAAQ;KAKvC,MAAM,iBAAiB,QAHL,QAAQ,MAAM,SAAS,IAAI,GAC1C,QAAQ,MAAM,MAAM,IAAI,CAAC,KAAK,GAC9B,QAAQ;AAMX,kBAAa,aAHZ,OAAO,mBAAmB,YAAY,OAAO,mBAAmB,WAC7D,OAAO,eAAe,GACtB,IACgC,OAAO,QAAQ,GAAG,CAAC;;IAIxD,IAAI;AACJ,SAAK,MAAM,OAAO,KACjB,KAAI,IAAI,YAAY;KACnB,MAAM,IAAI,IAAI,KAAK,OAAO,KAAK,aAAa,CAAC;AAC7C,SAAI,CAAC,0BAA0B,IAAI,uBAClC,0BAAyB;;AAK5B,WAAO;KACN;KACA;KACA,WAAW;MACV,MAAM,CAAC,KAAK;MACZ,cAAc;MACd;KACD;YACO,OAAO;IAIf,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,QAAI,oBAAoB,MAAM,IAAI,qBAAqB,MAAM,EAAE;AAC9D,SAAI,qBAAqB,MAAM,CAC9B,SAAQ,KAAK,0BAA0B,UAAU;AAElD,YAAO,EAAE,SAAS,EAAE,EAAE;;AAGvB,WAAO,EACN,uBAAO,IAAI,MAAM,8BAA8B,UAAU,EACzD;;;EAUH,MAAM,UAAU,EAAE,UAAU;AAC3B,OAAI;IAEH,MAAM,KAAK,MAAM,OAAO;IAGxB,MAAM,OAAO,QAAQ;IACrB,MAAM,KAAK,QAAQ;AAEnB,QAAI,CAAC,QAAQ,CAAC,GACb,QAAO,EACN,uBAAO,IAAI,MACV,6FACA,EACD;IAIF,MAAM,YAAY,aAAa,KAAK;IACpC,MAAM,SAAS,QAAQ;IAevB,MAAM,EAAE,OAAO,aAAa,SAAS,kBAAkB,uBACtD,IACA,MACA,IACA;IACD,MAAM,YAAY,gBAAgB,IAAI,MAAM,IAAI;IAiBhD,MAAM,OAhBS,SACZ,MAAM,GAA4B;qBACpB,UAAU,IAAI,YAAY,IAAI,cAAc;cACnD,IAAI,IAAI,UAAU,CAAC;;wBAET,GAAG,kBAAkB,OAAO,cAAc,GAAG;;QAE7D,QAAQ,GAAG,GACZ,MAAM,GAA4B;qBACpB,UAAU,IAAI,YAAY,IAAI,cAAc;cACnD,IAAI,IAAI,UAAU,CAAC;;uBAEV,GAAG,aAAa,GAAG;;QAElC,QAAQ,GAAG,EAEI,KAAK;AACxB,QAAI,CAAC,IACJ;AAMD,oBAAgB,IAAI;IAEpB,MAAM,aAAa,eAAe;IAClC,MAAM,cAAc,cAAc,WAAW,QAAQ,SAAS;IAC9D,MAAM,YAAY,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,KAAK;IAC1D,MAAM,cAAc,OAAO,KAAK,SAAS;IAKzC,MAAM,UAHL,eACA,gBAAgB,OACf,gBAAgB,WAAW,iBAAiB,WAAW,uBACrB,GAAG,YAAY,GAAG,cAAc;IAIpE,MAAM,aAAa,QAAQ;AAC3B,QAAI,YAAY;KAOf,MAAM,WANS,MAAM,GAAqB;;mBAE5B,WAAW;;OAEvB,QAAQ,GAAG,EAEU,KAAK;AAC5B,SAAI,SAAS;MACZ,MAAM,SAAkC,KAAK,MAAM,QAAQ,KAAK;MAEhE,MAAM,aAAsC,EAAE;AAC9C,WAAK,MAAM,CAAC,KAAK,cAAc,OAAO,QAAQ,gBAAgB,CAC7D,KAAI,OAAO,IACV,KAAI,aAAa,IAAI,IAAI,CACxB,YAAW,aAAa,OAAO,IAAI,SAAS,WAAW,IAAI,KAAK,IAAI,KAAK,GAAG;UAE5E,YAAW,aAAa,IAAI;MAK/B,MAAM,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,OAAO,KAAK,OAAO;MAClF,MAAM,UAAU,QAAQ,OAAO,KAAK,KAAK;MACzC,MAAM,YAAY,OAAO,KAAK,SAAS;MAKvC,MAAM,QAHL,eACA,cAAc,OACb,cAAc,WAAW,iBAAiB,WAAW,uBACvB,GAAG,UAAU,GAAG,YAAY;MAG5D,MAAM,eAAwC;OAC7C,GAAG;OACH;OACA,GAAG,gBAAgB,OAAO;OAC1B;MACD,MAAM,SAAS,WAAW,IAAI;AAC9B,UAAI,OAAQ,cAAa,MAAM;AAC/B,aAAO;OACN,IAAI;OACJ;OACA,QAAQ,OAAO,KAAK,UAAU,QAAQ;OACtC,MAAM;OACN,WAAW;QACV,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC;QACzB,cAAc,IAAI,aAAa,IAAI,KAAK,OAAO,KAAK,aAAa,CAAC,GAAG;QACrE;OACD;;;IAIH,MAAM,YAAY,aAAa,IAAI;IACnC,MAAM,WAAW,WAAW,IAAI;AAChC,QAAI,SAAU,WAAU,MAAM;AAC9B,gBAAY,WAAW,IAAI;AAC3B,WAAO;KACN,IAAI;KACJ,MAAM,OAAO,KAAK,OAAO;KACzB,QAAQ,OAAO,KAAK,UAAU,QAAQ;KACtC,MAAM;KACN,WAAW;MACV,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC;MACzB,cAAc,IAAI,aAAa,IAAI,KAAK,OAAO,KAAK,aAAa,CAAC,GAAG;MACrE;KACD;YACO,OAAO;AAGf,QAAI,oBAAoB,MAAM,CAC7B;IAGD,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,WAAO,EACN,uBAAO,IAAI,MAAM,yBAAyB,UAAU,EACpD;;;EAGH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"object-cache-CHbHv83-.mjs","names":[],"sources":["../src/object-cache/codec.ts","../src/object-cache/index.ts"],"sourcesContent":["/**\n * Object-cache serialization codec.\n *\n * Cached values are JSON, with one extension: `Date` instances are preserved\n * across the round-trip. EmDash content entries carry `Date` objects for the\n * system timestamp columns (`createdAt`, `updatedAt`, `publishedAt`,\n * `scheduledAt`) and on `cacheHint.lastModified`; plain `JSON.stringify` would\n * silently flatten those to ISO strings, so a value read from cache would no\n * longer be `=== instanceof Date` and downstream `value instanceof Date`\n * branches (cursor encoding, scheduled-visibility checks) would diverge from a\n * fresh database read.\n *\n * Functions and symbol-keyed properties are NOT preserved — callers that cache\n * values carrying either (e.g. content entries with their `.edit` proxy and\n * the non-enumerable `CURSOR_RAW_VALUES` symbol) must reduce to a serializable\n * snapshot before caching and rebuild the non-serializable parts on read. See\n * `query.ts` content snapshot helpers.\n */\n\n/** Tag used to mark a serialized `Date`. Deliberately unlikely to collide. */\nconst DATE_TAG = \"$$emdashDate\";\n\ninterface TaggedDate {\n\t[DATE_TAG]: string;\n}\n\nfunction isTaggedDate(value: unknown): value is TaggedDate {\n\tif (typeof value !== \"object\" || value === null) return false;\n\t// encode() always emits the tag as the object's *only* key, so requiring\n\t// exactly one key keeps a user object that merely happens to carry a\n\t// `$$emdashDate` string alongside other fields from being collapsed to a\n\t// Date (which would silently drop those other fields).\n\tconst keys = Object.keys(value);\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowing a JSON-parsed value to read the date tag\n\treturn keys.length === 1 && typeof (value as Record<string, unknown>)[DATE_TAG] === \"string\";\n}\n\n/**\n * Serialize a value to a cache string, preserving `Date` instances.\n *\n * Uses the JSON replacer's `this` binding to inspect the *original* property\n * value: `JSON.stringify` invokes `Date.prototype.toJSON` before the replacer\n * sees it, so by the time `value` arrives it is already an ISO string. Reading\n * `this[key]` recovers the live `Date` so we can tag it.\n */\nexport function encode(value: unknown): string {\n\treturn JSON.stringify(value, function (this: Record<string, unknown>, key, val) {\n\t\tconst original = this[key];\n\t\tif (original instanceof Date) {\n\t\t\treturn { [DATE_TAG]: original.toISOString() } satisfies TaggedDate;\n\t\t}\n\t\treturn val;\n\t});\n}\n\n/**\n * Parse a cache string produced by {@link encode}, rehydrating tagged `Date`s.\n *\n * Returns `undefined` if the input is not valid JSON (treated as a cache miss\n * by the read-through layer rather than throwing).\n */\nexport function decode(raw: string): unknown {\n\ttry {\n\t\treturn JSON.parse(raw, (_key, value) => {\n\t\t\tif (isTaggedDate(value)) {\n\t\t\t\treturn new Date(value[DATE_TAG]);\n\t\t\t}\n\t\t\treturn value;\n\t\t});\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","/**\n * Object cache — distributed read-through query cache.\n *\n * Layering (per query):\n *\n * requestCached → in-request dedupe (per render, WeakMap on ALS context)\n * cachedQuery → THIS layer: distributed L2 (KV / memory), epoch-keyed\n * database → source of truth\n *\n * Optional and off by default: when no `objectCache` descriptor is configured,\n * `virtual:emdash/object-cache` exports `createObjectCache = undefined`,\n * {@link getBackend} resolves to `null`, and {@link cachedQuery} is a\n * transparent passthrough to its `load` function. Configure with\n * `memoryCache()` (Node) or `kvCache()` from `@emdash-cms/cloudflare`.\n *\n * Invalidation is epoch-based: each cache key embeds a per-namespace epoch\n * (\"last changed\" marker) read from the backend. A write calls\n * {@link invalidateObjectCache}, which stamps the namespace epoch to\n * `Date.now()`; every previously-stored key for that namespace is instantly\n * orphaned and reclaimed by its TTL. This is O(1) and needs no key\n * enumeration (KV has no prefix delete).\n *\n * The singleton backend/config and the per-isolate epoch cache live on\n * `globalThis` behind `Symbol.for` keys so Vite SSR chunk duplication can't\n * fork them (same pattern as `request-context.ts`).\n */\n\nimport { after } from \"../after.js\";\nimport { getRequestContext } from \"../request-context.js\";\nimport { decode, encode } from \"./codec.js\";\nimport type {\n\tCreateObjectCacheBackendFn,\n\tObjectCacheBackend,\n\tObjectCacheRuntimeConfig,\n} from \"./types.js\";\n\nconst DEFAULT_KEY_PREFIX = \"em\";\nconst DEFAULT_TTL_SECONDS = 3600;\nconst DEFAULT_REVALIDATE_MS = 1000;\nconst DEFAULT_TIMEOUT_MS = 2000;\n\ninterface BackendHolder {\n\t/** Whether the virtual module has been loaded and the backend resolved. */\n\tinitialized: boolean;\n\t/** Resolved backend, or `null` when no object cache is configured. */\n\tbackend: ObjectCacheBackend | null;\n\t/** In-flight initialization promise (dedupes concurrent first calls). */\n\tinitPromise: Promise<ObjectCacheBackend | null> | null;\n\tconfig: Required<Pick<ObjectCacheRuntimeConfig, \"keyPrefix\">> & {\n\t\tdefaultTtl: number;\n\t\trevalidate: number;\n\t\ttimeout: number;\n\t};\n}\n\n/**\n * Race a backend operation against a timeout so a stalled call (e.g. a KV read\n * that never resolves *and* never rejects — a cold cross-region read, or one\n * queued behind the Workers simultaneous-connection limit) degrades to a\n * rejection instead of hanging the isolate. A rejection is benign: callers\n * already treat a failed read as a cache miss / last-known epoch.\n */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {\n\tif (!(ms > 0)) return promise;\n\tlet timer: ReturnType<typeof setTimeout>;\n\tconst timeout = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(() => {\n\t\t\treject(new Error(`object-cache ${label} timed out after ${ms}ms`));\n\t\t}, ms);\n\t});\n\treturn Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n\ninterface EpochEntry {\n\tvalue: number;\n\t/** `Date.now()` at which this epoch was read from the backend. */\n\tat: number;\n\t/** In-flight read, so concurrent callers share one backend round-trip. */\n\tpromise?: Promise<number>;\n}\n\nconst BACKEND_KEY = Symbol.for(\"emdash:object-cache:backend\");\nconst EPOCH_KEY = Symbol.for(\"emdash:object-cache:epochs\");\nconst PENDING_KEY = Symbol.for(\"emdash:object-cache:pending-bumps\");\nconst g = globalThis as Record<symbol, unknown>;\n\nconst holder: BackendHolder =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[BACKEND_KEY] as BackendHolder | undefined) ??\n\t(() => {\n\t\tconst h: BackendHolder = {\n\t\t\tinitialized: false,\n\t\t\tbackend: null,\n\t\t\tinitPromise: null,\n\t\t\tconfig: {\n\t\t\t\tkeyPrefix: DEFAULT_KEY_PREFIX,\n\t\t\t\tdefaultTtl: DEFAULT_TTL_SECONDS,\n\t\t\t\trevalidate: DEFAULT_REVALIDATE_MS,\n\t\t\t\ttimeout: DEFAULT_TIMEOUT_MS,\n\t\t\t},\n\t\t};\n\t\tg[BACKEND_KEY] = h;\n\t\treturn h;\n\t})();\n\nconst epochCache: Map<string, EpochEntry> =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[EPOCH_KEY] as Map<string, EpochEntry> | undefined) ??\n\t(() => {\n\t\tconst m = new Map<string, EpochEntry>();\n\t\tg[EPOCH_KEY] = m;\n\t\treturn m;\n\t})();\n\n/** Namespaces with a backend epoch write already scheduled this tick. */\nconst pendingBumps: Set<string> =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[PENDING_KEY] as Set<string> | undefined) ??\n\t(() => {\n\t\tconst s = new Set<string>();\n\t\tg[PENDING_KEY] = s;\n\t\treturn s;\n\t})();\n\n/**\n * Resolve (once per isolate) the configured object-cache backend.\n *\n * Loads `virtual:emdash/object-cache`, which exports `createObjectCache`\n * (`undefined` when no cache is configured) and the serialized\n * `objectCacheConfig`. Returns `null` when the cache is disabled.\n */\nasync function getBackend(): Promise<ObjectCacheBackend | null> {\n\tif (holder.initialized) return holder.backend;\n\tif (holder.initPromise) return holder.initPromise;\n\n\tholder.initPromise = (async () => {\n\t\ttry {\n\t\t\tconst mod: {\n\t\t\t\tcreateObjectCache?: CreateObjectCacheBackendFn;\n\t\t\t\tobjectCacheConfig?: ObjectCacheRuntimeConfig;\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t\t\t// @ts-ignore - virtual module\n\t\t\t} = await import(\"virtual:emdash/object-cache\");\n\n\t\t\tconst config = mod.objectCacheConfig ?? {};\n\t\t\tholder.config = {\n\t\t\t\tkeyPrefix:\n\t\t\t\t\ttypeof config.keyPrefix === \"string\" && config.keyPrefix.length > 0\n\t\t\t\t\t\t? config.keyPrefix\n\t\t\t\t\t\t: DEFAULT_KEY_PREFIX,\n\t\t\t\tdefaultTtl:\n\t\t\t\t\ttypeof config.defaultTtl === \"number\" && config.defaultTtl > 0\n\t\t\t\t\t\t? config.defaultTtl\n\t\t\t\t\t\t: DEFAULT_TTL_SECONDS,\n\t\t\t\trevalidate:\n\t\t\t\t\ttypeof config.revalidate === \"number\" && config.revalidate >= 0\n\t\t\t\t\t\t? config.revalidate\n\t\t\t\t\t\t: DEFAULT_REVALIDATE_MS,\n\t\t\t\ttimeout:\n\t\t\t\t\ttypeof config.timeout === \"number\" && config.timeout >= 0\n\t\t\t\t\t\t? config.timeout\n\t\t\t\t\t\t: DEFAULT_TIMEOUT_MS,\n\t\t\t};\n\n\t\t\tholder.backend =\n\t\t\t\ttypeof mod.createObjectCache === \"function\" ? mod.createObjectCache(config) : null;\n\t\t} catch (error) {\n\t\t\t// Importing the virtual module fails outside an Astro/Vite context\n\t\t\t// (e.g. unit tests, CLI). Treat as \"no cache configured\".\n\t\t\tif (import.meta.env?.DEV) {\n\t\t\t\tconsole.warn(\"[object-cache] backend unavailable:\", error);\n\t\t\t}\n\t\t\tholder.backend = null;\n\t\t}\n\t\tholder.initialized = true;\n\t\tholder.initPromise = null;\n\t\treturn holder.backend;\n\t})();\n\n\treturn holder.initPromise;\n}\n\n/**\n * Test-only override of the backend, bypassing the virtual module.\n *\n * Lets unit tests inject an in-memory backend (and optional config) without a\n * full Astro/Vite build. Pass `null` to simulate \"no cache configured\".\n *\n * @internal\n */\nexport function __setObjectCacheBackendForTests(\n\tbackend: ObjectCacheBackend | null,\n\tconfig?: Partial<BackendHolder[\"config\"]>,\n): void {\n\tholder.initialized = true;\n\tholder.initPromise = null;\n\tholder.backend = backend;\n\tholder.config = { ...holder.config, ...config };\n\tepochCache.clear();\n}\n\n/** Build the backend key for a namespace's epoch anchor. */\nfunction epochKey(namespace: string): string {\n\treturn `${holder.config.keyPrefix}:epoch:${namespace}`;\n}\n\n/**\n * Build the (epoch-independent) backend key for a cached value.\n *\n * The key is stable across invalidations — the namespace epochs are stored\n * *inside* the value envelope and validated on read, not baked into the key.\n * This lets the value and the epochs be fetched in one parallel round-trip\n * (instead of \"read epoch, then read value\"), and means an invalidated value\n * is overwritten in place rather than orphaned under a dead epoch-keyed name.\n */\nfunction valueKey(namespaces: readonly string[], key: string): string {\n\treturn `${holder.config.keyPrefix}:${namespaces.join(\",\")}:${key}`;\n}\n\n/**\n * Stored cache envelope: the namespace epochs captured at write time alongside\n * the cached value. A read is a HIT only when every stored epoch still matches\n * the current epoch for its namespace.\n */\ninterface CacheEnvelope<T> {\n\t/** Epoch per namespace, in the query's namespace order. */\n\te: number[];\n\t/** The cached value. */\n\tv: T;\n}\n\nfunction epochsMatch(stored: readonly number[], current: readonly number[]): boolean {\n\tif (stored.length !== current.length) return false;\n\tfor (let i = 0; i < stored.length; i++) {\n\t\tif (stored[i] !== current[i]) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Requests that must always read live data and never populate the cache:\n * visual edit mode, preview tokens, and isolated databases (playground / DO\n * preview, whose schema and content diverge from the configured site).\n */\nfunction shouldBypass(): boolean {\n\tconst ctx = getRequestContext();\n\tif (!ctx) return false;\n\treturn ctx.editMode === true || ctx.preview !== undefined || ctx.dbIsIsolated === true;\n}\n\n/**\n * Read the current epoch for `namespace`, reusing an isolate-cached value for\n * up to `revalidate` ms. A missing epoch (never bumped) is treated as `0`.\n *\n * Backend errors and stalls are non-fatal: the read is bounded by a timeout,\n * and on failure we fall back to the last known epoch (or `0`), so a flaky or\n * hung cache degrades to \"serve whatever's keyed\" rather than throwing or\n * hanging.\n */\nasync function getEpoch(namespace: string, backend: ObjectCacheBackend): Promise<number> {\n\tconst now = Date.now();\n\tconst cached = epochCache.get(namespace);\n\tif (cached && now - cached.at < holder.config.revalidate) {\n\t\treturn cached.value;\n\t}\n\tif (cached?.promise) return cached.promise;\n\n\tconst promise = (async () => {\n\t\tlet value: number;\n\t\ttry {\n\t\t\tconst raw = await withTimeout(\n\t\t\t\tbackend.get(epochKey(namespace)),\n\t\t\t\tholder.config.timeout,\n\t\t\t\t\"epoch read\",\n\t\t\t);\n\t\t\tconst parsed = raw === null ? 0 : Number(raw);\n\t\t\tvalue = Number.isFinite(parsed) ? parsed : 0;\n\t\t} catch {\n\t\t\tvalue = cached?.value ?? 0;\n\t\t}\n\t\t// A concurrent invalidateObjectCache may have bumped the epoch while this\n\t\t// read was in flight. Epochs are monotonic, so never let a stale backend\n\t\t// read lower a freshly-bumped local epoch — that would resurrect the very\n\t\t// values the bump just invalidated.\n\t\tconst merged = Math.max(value, epochCache.get(namespace)?.value ?? 0);\n\t\tepochCache.set(namespace, { value: merged, at: Date.now() });\n\t\treturn merged;\n\t})();\n\n\t// Concurrent callers share this in-flight read (dedup). The timeout above\n\t// guarantees `promise` settles — its success/catch handler then replaces\n\t// this entry with a fresh, promise-free one — so a stalled backend can no\n\t// longer pin the namespace to a never-settling promise (the bug that\n\t// poisoned an isolate until it was recycled).\n\tepochCache.set(namespace, { value: cached?.value ?? 0, at: cached?.at ?? 0, promise });\n\treturn promise;\n}\n\n/** Options for {@link cachedQuery}. */\nexport interface CachedQueryOptions<T> {\n\t/**\n\t * Invalidation namespace(s). A single string for self-contained data\n\t * (`settings`, `menus`), or several when the cached value depends on data\n\t * owned by other namespaces — e.g. a content entry hydrates bylines and\n\t * taxonomy terms, so it caches under\n\t * `[content:posts, \"bylines\", \"taxonomies\"]` and is invalidated when *any*\n\t * of them is bumped. Every namespace's epoch is folded into the key.\n\t */\n\tnamespace: string | readonly string[];\n\t/** Stable, fully-qualifying cache key *within* the namespace. */\n\tkey: string;\n\t/** Loader run on a miss (or when caching is disabled/bypassed). */\n\tload: () => Promise<T>;\n\t/** TTL override in seconds. Falls back to the configured `defaultTtl`. */\n\tttl?: number;\n\t/**\n\t * Predicate gating whether a freshly-loaded value is stored. Defaults to\n\t * always-cache. Use it to skip caching error/empty sentinels.\n\t */\n\tcacheable?: (value: T) => boolean;\n}\n\n/**\n * Distributed read-through cache around `load`.\n *\n * `T` must be the value as it should be *stored* — i.e. JSON-serializable with\n * the codec's `Date` support, carrying no functions or symbol-keyed props.\n * Callers caching richer objects (content entries) reduce to a serializable\n * snapshot here and rebuild on the way out; see `query.ts`.\n *\n * On a miss or when the cache is disabled/bypassed, this is equivalent to\n * `await load()`. Backend errors never propagate: a failing `get` is a miss, a\n * failing `set` is dropped.\n */\nexport async function cachedQuery<T>(options: CachedQueryOptions<T>): Promise<T> {\n\tconst backend = await getBackend();\n\tif (!backend || shouldBypass()) {\n\t\treturn options.load();\n\t}\n\n\tconst namespaces =\n\t\ttypeof options.namespace === \"string\" ? [options.namespace] : options.namespace;\n\tconst fullKey = valueKey(namespaces, options.key);\n\n\t// Kick off the value read and every namespace epoch read concurrently — one\n\t// round-trip instead of \"read epochs, then read value\". getEpoch never\n\t// rejects, so awaiting the epochs separately from the value read guarantees\n\t// we hold the pre-load epochs even when the value read errors or times out.\n\t// Storing a value under an epoch read *after* load() would mask a write that\n\t// landed during load(): the stale value would match and be served as a HIT.\n\tconst epochsPromise = Promise.all(namespaces.map((ns) => getEpoch(ns, backend)));\n\tconst rawPromise = withTimeout(backend.get(fullKey), holder.config.timeout, \"read\").catch(\n\t\t() => null,\n\t);\n\tconst currentEpochs = await epochsPromise;\n\tconst raw = await rawPromise;\n\tif (raw !== null) {\n\t\tconst decoded = decode(raw);\n\t\tif (decoded !== undefined) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- value envelope written by this function\n\t\t\tconst envelope = decoded as CacheEnvelope<T>;\n\t\t\tif (epochsMatch(envelope.e, currentEpochs)) {\n\t\t\t\treturn envelope.v;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst value = await options.load();\n\n\tconst cacheable = options.cacheable ? options.cacheable(value) : true;\n\tif (cacheable) {\n\t\tconst ttl = options.ttl ?? holder.config.defaultTtl;\n\t\t// Defer the write so it never adds to TTFB. The epochs were captured\n\t\t// before load() ran, so a write that invalidated this namespace mid-load\n\t\t// correctly orphans the value stored here.\n\t\tafter(async () => {\n\t\t\ttry {\n\t\t\t\tconst encoded = encode({ e: currentEpochs, v: value } satisfies CacheEnvelope<T>);\n\t\t\t\tawait backend.set(fullKey, encoded, ttl);\n\t\t\t} catch (error) {\n\t\t\t\tif (import.meta.env?.DEV) {\n\t\t\t\t\tconsole.warn(\"[object-cache] set failed:\", error);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\treturn value;\n}\n\n/** Whether object-cache reads are active for the current request. */\nexport async function isObjectCacheActive(): Promise<boolean> {\n\tconst backend = await getBackend();\n\treturn backend !== null && !shouldBypass();\n}\n\n/**\n * Invalidate every cached value in `namespace` by bumping its epoch.\n *\n * Sync and non-blocking: the local epoch is stamped immediately (so the\n * writing isolate is instantly consistent) and the backend write is deferred\n * via `after`. Other isolates pick up the new epoch within their `revalidate`\n * window. No-ops when the cache is disabled.\n */\nexport function invalidateObjectCache(namespace: string): void {\n\t// Monotonic so two writes in the same millisecond still produce distinct\n\t// epochs — otherwise the second write reuses the first's stamp and its\n\t// stale entries survive.\n\tconst prev = epochCache.get(namespace)?.value ?? 0;\n\tconst stamp = Math.max(prev + 1, Date.now());\n\t// Optimistic local bump: keep this isolate consistent without a round-trip.\n\tepochCache.set(namespace, { value: stamp, at: stamp });\n\n\t// Coalesce repeated bumps of the same namespace within a tick (e.g. a bulk\n\t// publish loop) into a single backend write that persists the latest epoch.\n\tif (pendingBumps.has(namespace)) return;\n\tpendingBumps.add(namespace);\n\tafter(async () => {\n\t\tpendingBumps.delete(namespace);\n\t\ttry {\n\t\t\tconst backend = await getBackend();\n\t\t\tif (!backend) return;\n\t\t\tconst latest = epochCache.get(namespace)?.value ?? stamp;\n\t\t\t// Epoch anchors are persistent (no TTL) — they must outlive the\n\t\t\t// value keys they invalidate.\n\t\t\tawait backend.set(epochKey(namespace), String(latest));\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[object-cache] epoch bump failed for\", namespace, error);\n\t\t}\n\t});\n}\n\n/**\n * Fixed namespaces for data shared across collections. Content reads fold the\n * `BYLINES` and `TAXONOMIES` epochs into their keys (via {@link cachedQuery})\n * because entries hydrate byline and taxonomy-term data — so renaming an\n * author or a category correctly invalidates every cached entry that displays\n * it, without tracking which collections reference it.\n */\nexport const CacheNamespace = {\n\tSETTINGS: \"settings\",\n\tMENUS: \"menus\",\n\tTAXONOMIES: \"taxonomies\",\n\tBYLINES: \"bylines\",\n\t/** Collection schema/metadata (label, supports, commentsEnabled, fields). */\n\tSCHEMA: \"schema\",\n\t/** Public (approved) comments. */\n\tCOMMENTS: \"comments\",\n} as const;\n\n/** Namespace for a content collection's cached queries. */\nexport function contentNamespace(collection: string): string {\n\treturn `content:${collection}`;\n}\n\n/**\n * Namespaces a content read depends on: the collection itself plus the shared\n * byline/taxonomy data folded into each entry.\n */\nexport function contentNamespaces(collection: string): readonly string[] {\n\treturn [contentNamespace(collection), CacheNamespace.BYLINES, CacheNamespace.TAXONOMIES];\n}\n\n/**\n * Invalidate all cached reads (list + entry) for a content collection.\n * Call from every write path that mutates rows in `ec_<collection>`.\n */\nexport function invalidateCollectionCache(collection: string): void {\n\tinvalidateObjectCache(contentNamespace(collection));\n}\n\n/** Invalidate cached taxonomy definitions/terms and all content that hydrates them. */\nexport function invalidateTaxonomyObjectCache(): void {\n\tinvalidateObjectCache(CacheNamespace.TAXONOMIES);\n}\n\n/** Invalidate cached bylines and all content that hydrates them. */\nexport function invalidateBylineObjectCache(): void {\n\tinvalidateObjectCache(CacheNamespace.BYLINES);\n}\n\n/** Invalidate cached navigation menus. */\nexport function invalidateMenuObjectCache(): void {\n\tinvalidateObjectCache(CacheNamespace.MENUS);\n}\n\n/** Invalidate cached collection schema/metadata reads (e.g. getCollectionInfo). */\nexport function invalidateSchemaObjectCache(): void {\n\tinvalidateObjectCache(CacheNamespace.SCHEMA);\n}\n\n/** Invalidate cached public comment reads. */\nexport function invalidateCommentObjectCache(): void {\n\tinvalidateObjectCache(CacheNamespace.COMMENTS);\n}\n\nexport type {\n\tObjectCacheBackend,\n\tObjectCacheDescriptor,\n\tObjectCacheRuntimeConfig,\n} from \"./types.js\";\n"],"mappings":"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"}
|