emdash 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-token-CbKDcPJi.mjs +26 -0
- package/dist/action-token-CbKDcPJi.mjs.map +1 -0
- package/dist/activation-PtniBLt9.mjs +382 -0
- package/dist/activation-PtniBLt9.mjs.map +1 -0
- package/dist/adapters-C_Tf5x90.d.mts +171 -0
- package/dist/adapters-C_Tf5x90.d.mts.map +1 -0
- package/dist/allowed-origins-U1z4TdEZ.mjs +116 -0
- package/dist/allowed-origins-U1z4TdEZ.mjs.map +1 -0
- package/dist/api/route-utils.d.mts +4 -4
- package/dist/api/route-utils.mjs +46 -45
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +10 -10
- package/dist/api-CXwQkV_m.mjs +5122 -0
- package/dist/api-CXwQkV_m.mjs.map +1 -0
- package/dist/api-tokens-89WlohEN.mjs +166 -0
- package/dist/api-tokens-89WlohEN.mjs.map +1 -0
- package/dist/apply-Do3KNnUk.mjs +1659 -0
- package/dist/apply-Do3KNnUk.mjs.map +1 -0
- package/dist/approvals-CAX9Yw33.mjs +460 -0
- package/dist/approvals-CAX9Yw33.mjs.map +1 -0
- package/dist/artifact-fetch-B9W4CEST.mjs +659 -0
- package/dist/artifact-fetch-B9W4CEST.mjs.map +1 -0
- package/dist/astro/index.d.mts +14 -14
- package/dist/astro/index.mjs +26 -30
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +12 -12
- package/dist/astro/middleware/auth.mjs +10 -10
- package/dist/astro/middleware/media-usage-write-fence.mjs +5 -5
- package/dist/astro/middleware/redirect.mjs +7 -7
- package/dist/astro/middleware/request-context.mjs +3 -3
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +12 -12
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +110 -106
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +14 -14
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +14 -14
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +7 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +17 -17
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +17 -17
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +17 -17
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +23 -23
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +23 -23
- package/dist/astro/routes/api/admin/bylines/index.mjs +23 -23
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +18 -18
- package/dist/astro/routes/api/admin/comments/_id_.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/bulk.mjs +17 -17
- package/dist/astro/routes/api/admin/comments/counts.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/index.mjs +17 -17
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
- package/dist/astro/routes/api/admin/media-usage/activation.mjs +20 -20
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +22 -22
- package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +22 -22
- package/dist/astro/routes/api/admin/media-usage/progress.mjs +25 -25
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +31 -31
- package/dist/astro/routes/api/admin/media-usage/work/index.mjs +22 -22
- package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +22 -22
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +6 -6
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +8 -8
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +8 -8
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +72 -68
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +7 -7
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +71 -67
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +72 -68
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +82 -78
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +72 -68
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +5 -5
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +71 -67
- 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 +81 -77
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +71 -67
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +72 -68
- 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 +83 -79
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +9 -9
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +74 -70
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +7 -7
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +72 -68
- package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +71 -67
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +71 -67
- 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 +5 -5
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +71 -67
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/transfer/_...path_.mjs +4 -4
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +40 -40
- package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +40 -40
- package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/capabilities.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +43 -43
- package/dist/astro/routes/api/admin/transfer/exports/index.mjs +44 -44
- package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +44 -44
- package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +44 -44
- package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +44 -44
- package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +42 -42
- package/dist/astro/routes/api/admin/transfer/imports/index.mjs +44 -44
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +5 -5
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +4 -4
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +15 -15
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +6 -6
- package/dist/astro/routes/api/admin/users/index.mjs +14 -14
- package/dist/astro/routes/api/auth/dev-bypass.mjs +6 -6
- package/dist/astro/routes/api/auth/invite/accept.mjs +4 -4
- package/dist/astro/routes/api/auth/invite/complete.mjs +18 -18
- package/dist/astro/routes/api/auth/invite/index.mjs +16 -16
- package/dist/astro/routes/api/auth/invite/register-options.mjs +17 -17
- package/dist/astro/routes/api/auth/logout.mjs +5 -5
- package/dist/astro/routes/api/auth/magic-link/send.mjs +18 -18
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +14 -14
- package/dist/astro/routes/api/auth/me.mjs +15 -15
- package/dist/astro/routes/api/auth/mode.mjs +5 -5
- 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 +14 -14
- package/dist/astro/routes/api/auth/passkey/index.mjs +4 -4
- package/dist/astro/routes/api/auth/passkey/options.mjs +19 -19
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +17 -17
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +18 -18
- package/dist/astro/routes/api/auth/passkey/verify.mjs +18 -18
- package/dist/astro/routes/api/auth/signup/complete.mjs +18 -18
- package/dist/astro/routes/api/auth/signup/request.mjs +17 -17
- package/dist/astro/routes/api/auth/signup/verify.mjs +4 -4
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +12 -12
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +18 -18
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +11 -11
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +31 -31
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts +1 -2
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +42 -53
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +34 -29
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +25 -24
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +17 -17
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +18 -18
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +5 -5
- package/dist/astro/routes/api/content/_collection_/index.mjs +15 -15
- package/dist/astro/routes/api/content/_collection_/trash.mjs +15 -15
- package/dist/astro/routes/api/dashboard.mjs +10 -9
- package/dist/astro/routes/api/dashboard.mjs.map +1 -1
- package/dist/astro/routes/api/dev/emails.mjs +5 -5
- package/dist/astro/routes/api/health.mjs +6 -6
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +23 -23
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +13 -13
- package/dist/astro/routes/api/import/wordpress/execute.mjs +16 -16
- package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -12
- package/dist/astro/routes/api/import/wordpress/media.mjs +19 -19
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +18 -18
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -13
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +28 -28
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +23 -23
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -13
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +34 -34
- package/dist/astro/routes/api/manifest.mjs +6 -6
- package/dist/astro/routes/api/mcp.mjs +98 -118
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +19 -19
- package/dist/astro/routes/api/media/_id_/replace.mjs +16 -16
- package/dist/astro/routes/api/media/_id_/upload.mjs +9 -9
- package/dist/astro/routes/api/media/_id_/usage.mjs +33 -33
- package/dist/astro/routes/api/media/_id_.mjs +31 -31
- package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +6 -6
- package/dist/astro/routes/api/media/file/_...key_.mjs +9 -9
- package/dist/astro/routes/api/media/folders/_id_.mjs +16 -16
- package/dist/astro/routes/api/media/folders/index.mjs +16 -16
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +5 -5
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +5 -5
- package/dist/astro/routes/api/media/providers/index.mjs +5 -5
- package/dist/astro/routes/api/media/upload-url.mjs +18 -18
- package/dist/astro/routes/api/media.mjs +35 -35
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +17 -17
- package/dist/astro/routes/api/menus/_name_/items.mjs +17 -17
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +17 -17
- package/dist/astro/routes/api/menus/_name_/translations.mjs +17 -17
- package/dist/astro/routes/api/menus/_name_.mjs +17 -17
- package/dist/astro/routes/api/menus/index.mjs +17 -17
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.d.mts +2 -1
- package/dist/astro/routes/api/oauth/device/authorize.d.mts.map +1 -1
- package/dist/astro/routes/api/oauth/device/authorize.mjs +23 -9
- package/dist/astro/routes/api/oauth/device/authorize.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/device/code.mjs +11 -11
- package/dist/astro/routes/api/oauth/device/token.mjs +10 -10
- package/dist/astro/routes/api/oauth/register.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/refresh.mjs +8 -8
- package/dist/astro/routes/api/oauth/token/revoke.mjs +8 -8
- package/dist/astro/routes/api/oauth/token.mjs +8 -8
- package/dist/astro/routes/api/openapi.json.mjs +20 -20
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +11 -11
- package/dist/astro/routes/api/redirects/404s/index.mjs +19 -19
- package/dist/astro/routes/api/redirects/404s/summary.mjs +19 -19
- package/dist/astro/routes/api/redirects/_id_.mjs +19 -19
- package/dist/astro/routes/api/redirects/index.mjs +19 -19
- package/dist/astro/routes/api/relations/_id_/index.mjs +34 -29
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +36 -31
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +5 -5
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
- package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +71 -67
- package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +71 -67
- package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -1
- package/dist/astro/routes/api/schema/block-types/index.mjs +71 -67
- package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +73 -69
- 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 +71 -67
- 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 +71 -67
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +71 -67
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +71 -67
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/reorder.mjs +71 -67
- package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +16 -16
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +71 -67
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +71 -67
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +19 -19
- package/dist/astro/routes/api/search/index.mjs +18 -18
- package/dist/astro/routes/api/search/rebuild.mjs +19 -19
- package/dist/astro/routes/api/search/stats.mjs +9 -9
- package/dist/astro/routes/api/search/suggest.mjs +18 -18
- package/dist/astro/routes/api/sections/_slug_.mjs +18 -18
- package/dist/astro/routes/api/sections/index.mjs +18 -18
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
- package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +6 -6
- package/dist/astro/routes/api/settings.mjs +21 -21
- package/dist/astro/routes/api/setup/admin-verify.mjs +19 -19
- package/dist/astro/routes/api/setup/admin.mjs +18 -18
- package/dist/astro/routes/api/setup/dev-bypass.mjs +45 -39
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
- package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
- package/dist/astro/routes/api/setup/index.mjs +80 -49
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +6 -6
- package/dist/astro/routes/api/snapshot.mjs +9 -9
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +23 -23
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +23 -23
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +23 -23
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +23 -23
- package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +22 -22
- package/dist/astro/routes/api/taxonomies/_name_.mjs +23 -23
- package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +36 -35
- package/dist/astro/routes/api/taxonomies/bulk-tag.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +23 -23
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +8 -8
- package/dist/astro/routes/api/visual-editing/action-token.mjs +6 -6
- package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +19 -19
- package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +4 -4
- 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 +16 -16
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +19 -19
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +19 -19
- package/dist/astro/routes/api/widget-areas/_name_.mjs +9 -9
- package/dist/astro/routes/api/widget-areas/index.mjs +19 -19
- package/dist/astro/routes/api/widget-components.mjs +5 -5
- 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 +28 -18
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/audit-CrgmE9LE.mjs +127 -0
- package/dist/audit-CrgmE9LE.mjs.map +1 -0
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/authoritative-records-C2YzIdbu.mjs +5172 -0
- package/dist/authoritative-records-C2YzIdbu.mjs.map +1 -0
- package/dist/authorize-WgdKF8QY.mjs +49 -0
- package/dist/authorize-WgdKF8QY.mjs.map +1 -0
- package/dist/backup-Ck8_lMO3.mjs +246 -0
- package/dist/backup-Ck8_lMO3.mjs.map +1 -0
- package/dist/block-types-BQ90asVI.mjs +132 -0
- package/dist/block-types-BQ90asVI.mjs.map +1 -0
- package/dist/block-values-ACFqgB1D.mjs +175 -0
- package/dist/block-values-ACFqgB1D.mjs.map +1 -0
- package/dist/bundle-Db1iNMv_.mjs +667 -0
- package/dist/bundle-Db1iNMv_.mjs.map +1 -0
- package/dist/byline-BayaYGoj.mjs +909 -0
- package/dist/byline-BayaYGoj.mjs.map +1 -0
- package/dist/byline-fields-B8HH-35f.mjs +254 -0
- package/dist/byline-fields-B8HH-35f.mjs.map +1 -0
- package/dist/byline-fields-hMPjf6Eu.mjs +123 -0
- package/dist/byline-fields-hMPjf6Eu.mjs.map +1 -0
- package/dist/byline-registry-B_EK_TQf.mjs +406 -0
- package/dist/byline-registry-B_EK_TQf.mjs.map +1 -0
- package/dist/bylines-DFAaFXWi.mjs +214 -0
- package/dist/bylines-DFAaFXWi.mjs.map +1 -0
- package/dist/bylines-DkIxUxmr.mjs +232 -0
- package/dist/bylines-DkIxUxmr.mjs.map +1 -0
- package/dist/cache-DkvhQ8XE.mjs +108 -0
- package/dist/cache-DkvhQ8XE.mjs.map +1 -0
- package/dist/canonical-CcSuuTlH.mjs +174 -0
- package/dist/canonical-CcSuuTlH.mjs.map +1 -0
- package/dist/capture-triggers-DJEoOTOq.mjs +362 -0
- package/dist/capture-triggers-DJEoOTOq.mjs.map +1 -0
- package/dist/challenge-store-CvBl81AQ.mjs +51 -0
- package/dist/challenge-store-CvBl81AQ.mjs.map +1 -0
- package/dist/checksum-CClweQzI.mjs +162 -0
- package/dist/checksum-CClweQzI.mjs.map +1 -0
- package/dist/chrome-tags-D5caJwwp.mjs +24 -0
- package/dist/chrome-tags-D5caJwwp.mjs.map +1 -0
- package/dist/chunks-DwBaj8aT.mjs +25 -0
- package/dist/chunks-DwBaj8aT.mjs.map +1 -0
- package/dist/cli/index.mjs +374 -761
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +18 -18
- package/dist/client/index.mjs +2 -2
- package/dist/collection-deletion-CLCdXsnV.mjs +436 -0
- package/dist/collection-deletion-CLCdXsnV.mjs.map +1 -0
- package/dist/collection-deletion-processor-hNw8x01F.mjs +217 -0
- package/dist/collection-deletion-processor-hNw8x01F.mjs.map +1 -0
- package/dist/collection-slugs-cache-CUuiduLq.mjs +85 -0
- package/dist/collection-slugs-cache-CUuiduLq.mjs.map +1 -0
- package/dist/comment-D2uvL63j.mjs +313 -0
- package/dist/comment-D2uvL63j.mjs.map +1 -0
- package/dist/comment-reaction-DevUuzg4.mjs +85 -0
- package/dist/comment-reaction-DevUuzg4.mjs.map +1 -0
- package/dist/comments-Cq0s7_hO.mjs +195 -0
- package/dist/comments-Cq0s7_hO.mjs.map +1 -0
- package/dist/comments-M4EDYAHw.mjs +99 -0
- package/dist/comments-M4EDYAHw.mjs.map +1 -0
- package/dist/components-CuIGwXMs.mjs +108 -0
- package/dist/components-CuIGwXMs.mjs.map +1 -0
- package/dist/config-52Ea5IWt.d.mts +39 -0
- package/dist/config-52Ea5IWt.d.mts.map +1 -0
- package/dist/config-CCz3xOLc.mjs +242 -0
- package/dist/config-CCz3xOLc.mjs.map +1 -0
- package/dist/config-D3d5Qudq.d.mts +6669 -0
- package/dist/config-D3d5Qudq.d.mts.map +1 -0
- package/dist/content-CbnufDsM.mjs +2018 -0
- package/dist/content-CbnufDsM.mjs.map +1 -0
- package/dist/content-list-query-D_7B1FE5.d.mts +26 -0
- package/dist/content-list-query-D_7B1FE5.d.mts.map +1 -0
- package/dist/content-policy-CYu6K1KB.mjs +46 -0
- package/dist/content-policy-CYu6K1KB.mjs.map +1 -0
- package/dist/content-repair-DlDUgFGp.mjs +442 -0
- package/dist/content-repair-DlDUgFGp.mjs.map +1 -0
- package/dist/context-CGQLA1-a.mjs +1922 -0
- package/dist/context-CGQLA1-a.mjs.map +1 -0
- package/dist/cron-c4D5C5Fx.mjs +284 -0
- package/dist/cron-c4D5C5Fx.mjs.map +1 -0
- package/dist/dashboard-CBq9kmOn.mjs +151 -0
- package/dist/dashboard-CBq9kmOn.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql-migrations.d.mts +1 -1
- package/dist/db/libsql-migrations.mjs +4 -4
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres-migrations.d.mts +1 -1
- package/dist/db/postgres-migrations.mjs +4 -4
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite-migrations.d.mts +1 -1
- package/dist/db/sqlite-migrations.mjs +4 -4
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +1 -1
- package/dist/default-DPsys51U.mjs +81 -0
- package/dist/default-DPsys51U.mjs.map +1 -0
- package/dist/deferred-tasks-CZ7rzBdo.d.mts +9 -0
- package/dist/deferred-tasks-CZ7rzBdo.d.mts.map +1 -0
- package/dist/dev-typegen-HRxwLRcT.mjs +29 -0
- package/dist/dev-typegen-HRxwLRcT.mjs.map +1 -0
- package/dist/device-flow-OcSqE9uY.mjs +521 -0
- package/dist/device-flow-OcSqE9uY.mjs.map +1 -0
- package/dist/digest-DVZ--cE9.mjs +194 -0
- package/dist/digest-DVZ--cE9.mjs.map +1 -0
- package/dist/direct-executor-DyjAqkpT.mjs +104 -0
- package/dist/direct-executor-DyjAqkpT.mjs.map +1 -0
- package/dist/dist-FPR93_xf.mjs +810 -0
- package/dist/dist-FPR93_xf.mjs.map +1 -0
- package/dist/email-console-DyQIgEnz.mjs +50 -0
- package/dist/email-console-DyQIgEnz.mjs.map +1 -0
- package/dist/email-locale-BYb16rVd.mjs +25 -0
- package/dist/email-locale-BYb16rVd.mjs.map +1 -0
- package/dist/emdash-runtime-CrzxJFmF.mjs +4214 -0
- package/dist/emdash-runtime-CrzxJFmF.mjs.map +1 -0
- package/dist/enrich-DBtCncE2.mjs +194 -0
- package/dist/enrich-DBtCncE2.mjs.map +1 -0
- package/dist/entry-lock-BN5y4YbO.d.mts +4481 -0
- package/dist/entry-lock-BN5y4YbO.d.mts.map +1 -0
- package/dist/entry-lock-BaRBHCsH.mjs +1173 -0
- package/dist/entry-lock-BaRBHCsH.mjs.map +1 -0
- package/dist/entry-lock-C6NcnZPV.mjs +163 -0
- package/dist/entry-lock-C6NcnZPV.mjs.map +1 -0
- package/dist/entry-locks-BTyQLdbC.mjs +167 -0
- package/dist/entry-locks-BTyQLdbC.mjs.map +1 -0
- package/dist/error-BfsNNrIE.mjs +90 -0
- package/dist/error-BfsNNrIE.mjs.map +1 -0
- package/dist/errors-BN1QAnEY.mjs +460 -0
- package/dist/errors-BN1QAnEY.mjs.map +1 -0
- package/dist/errors-Bt3goF8I.mjs +120 -0
- package/dist/errors-Bt3goF8I.mjs.map +1 -0
- package/dist/escape-BveidMa2.mjs +9 -0
- package/dist/escape-BveidMa2.mjs.map +1 -0
- package/dist/fence-Bb_IbCxI.mjs +162 -0
- package/dist/fence-Bb_IbCxI.mjs.map +1 -0
- package/dist/field-defs-cache-CsZctvbu.mjs +77 -0
- package/dist/field-defs-cache-CsZctvbu.mjs.map +1 -0
- package/dist/field-map-6m9rF49t.mjs +83 -0
- package/dist/field-map-6m9rF49t.mjs.map +1 -0
- package/dist/focal-point-CZ3d1wK5.mjs +23 -0
- package/dist/focal-point-CZ3d1wK5.mjs.map +1 -0
- package/dist/fts-manager-CCaZTp8y.mjs +443 -0
- package/dist/fts-manager-CCaZTp8y.mjs.map +1 -0
- package/dist/gc-xowRjczI.mjs +75 -0
- package/dist/gc-xowRjczI.mjs.map +1 -0
- package/dist/hash-COVK27Pc.mjs +37 -0
- package/dist/hash-COVK27Pc.mjs.map +1 -0
- package/dist/hook-result-CYSk0XFe.d.mts +31 -0
- package/dist/hook-result-CYSk0XFe.d.mts.map +1 -0
- package/dist/hreflang-CChPV7jx.mjs +105 -0
- package/dist/hreflang-CChPV7jx.mjs.map +1 -0
- package/dist/hreflang-DRslq3QI.d.mts +50 -0
- package/dist/hreflang-DRslq3QI.d.mts.map +1 -0
- package/dist/http-route-dispatch-CAdp2onn.mjs +339 -0
- package/dist/http-route-dispatch-CAdp2onn.mjs.map +1 -0
- package/dist/identity-NVTOOQyq.mjs +35 -0
- package/dist/identity-NVTOOQyq.mjs.map +1 -0
- package/dist/import-wpSO2N0x.mjs +2020 -0
- package/dist/import-wpSO2N0x.mjs.map +1 -0
- package/dist/index-CDs2wq4X.d.mts +623 -0
- package/dist/index-CDs2wq4X.d.mts.map +1 -0
- package/dist/index.d.mts +24 -24
- package/dist/index.mjs +103 -101
- package/dist/install-finalization-D9R9R-W8.mjs +58 -0
- package/dist/install-finalization-D9R9R-W8.mjs.map +1 -0
- package/dist/keys-Bck6LBfF.mjs +61 -0
- package/dist/keys-Bck6LBfF.mjs.map +1 -0
- package/dist/kinds-CYmRESWj.mjs +974 -0
- package/dist/kinds-CYmRESWj.mjs.map +1 -0
- package/dist/lifecycle-C815ZQfV.mjs +25 -0
- package/dist/lifecycle-C815ZQfV.mjs.map +1 -0
- package/dist/load-D99axixL.mjs +28 -0
- package/dist/load-D99axixL.mjs.map +1 -0
- package/dist/loader-CrRPfkHS.mjs +1197 -0
- package/dist/loader-CrRPfkHS.mjs.map +1 -0
- package/dist/loops-DMKlygqX.mjs +203 -0
- package/dist/loops-DMKlygqX.mjs.map +1 -0
- package/dist/manifest-builder-BQzCHNgB.mjs +189 -0
- package/dist/manifest-builder-BQzCHNgB.mjs.map +1 -0
- package/dist/manifest-schema-Ci2gmd0u.mjs +492 -0
- package/dist/manifest-schema-Ci2gmd0u.mjs.map +1 -0
- package/dist/media/image-endpoint.mjs +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +4 -4
- package/dist/media/local-runtime.d.mts +14 -14
- package/dist/media/local-runtime.mjs +6 -6
- package/dist/media-BUQePMxP.mjs +373 -0
- package/dist/media-BUQePMxP.mjs.map +1 -0
- package/dist/media-DH8ibNlY.mjs +294 -0
- package/dist/media-DH8ibNlY.mjs.map +1 -0
- package/dist/media-OEIQ3bVc.mjs +408 -0
- package/dist/media-OEIQ3bVc.mjs.map +1 -0
- package/dist/media-allowlist-pYtaBA95.mjs +44 -0
- package/dist/media-allowlist-pYtaBA95.mjs.map +1 -0
- package/dist/media-folders-Bf8_s4de.mjs +210 -0
- package/dist/media-folders-Bf8_s4de.mjs.map +1 -0
- package/dist/media-url-C6klEuNd.mjs +43 -0
- package/dist/media-url-C6klEuNd.mjs.map +1 -0
- package/dist/media-usage-D7Y64m6v.mjs +1629 -0
- package/dist/media-usage-D7Y64m6v.mjs.map +1 -0
- package/dist/media-usage-activation-DrqbmgLA.mjs +78 -0
- package/dist/media-usage-activation-DrqbmgLA.mjs.map +1 -0
- package/dist/media-usage-work-DommYhYA.mjs +152 -0
- package/dist/media-usage-work-DommYhYA.mjs.map +1 -0
- package/dist/media-usage-work-HU63hNDu.mjs +586 -0
- package/dist/media-usage-work-HU63hNDu.mjs.map +1 -0
- package/dist/menus-BQfusWEC.mjs +3608 -0
- package/dist/menus-BQfusWEC.mjs.map +1 -0
- package/dist/menus-BtwjQt5s.mjs +753 -0
- package/dist/menus-BtwjQt5s.mjs.map +1 -0
- package/dist/menus-p_M1V7J_.mjs +139 -0
- package/dist/menus-p_M1V7J_.mjs.map +1 -0
- package/dist/migrations/index.d.mts +1 -1
- package/dist/migrations/index.mjs +3 -3
- package/dist/mime-C94qBG-P.mjs +36 -0
- package/dist/mime-C94qBG-P.mjs.map +1 -0
- package/dist/mode-DpYW3w6U.mjs +23 -0
- package/dist/mode-DpYW3w6U.mjs.map +1 -0
- package/dist/node-sqlite-compat-pWFw0lcc.mjs +46 -0
- package/dist/node-sqlite-compat-pWFw0lcc.mjs.map +1 -0
- package/dist/normalize-DduyxBjf.mjs +191 -0
- package/dist/normalize-DduyxBjf.mjs.map +1 -0
- package/dist/oauth-authorization-DAXsH1H9.mjs +274 -0
- package/dist/oauth-authorization-DAXsH1H9.mjs.map +1 -0
- package/dist/oauth-clients-DENiDQri.mjs +266 -0
- package/dist/oauth-clients-DENiDQri.mjs.map +1 -0
- package/dist/oauth-state-store-y8mEYVq9.mjs +50 -0
- package/dist/oauth-state-store-y8mEYVq9.mjs.map +1 -0
- package/dist/oauth-user-lookup-CtTYAy-O.mjs +26 -0
- package/dist/oauth-user-lookup-CtTYAy-O.mjs.map +1 -0
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/options-BYa1FgIM.d.mts +81 -0
- package/dist/options-BYa1FgIM.d.mts.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/page/index.mjs +5 -5
- package/dist/parse-CORGGhHf.mjs +81 -0
- package/dist/parse-CORGGhHf.mjs.map +1 -0
- package/dist/parse-avNM1xWV.d.mts +168 -0
- package/dist/parse-avNM1xWV.d.mts.map +1 -0
- package/dist/passkey-config-Dih5YeIS.mjs +46 -0
- package/dist/passkey-config-Dih5YeIS.mjs.map +1 -0
- package/dist/paths-d51RFm8M.mjs +77 -0
- package/dist/paths-d51RFm8M.mjs.map +1 -0
- package/dist/patterns-CoqlXUvk.mjs +145 -0
- package/dist/patterns-CoqlXUvk.mjs.map +1 -0
- package/dist/placeholder-D2aksPMF.d.mts +306 -0
- package/dist/placeholder-D2aksPMF.d.mts.map +1 -0
- package/dist/plan-DqeZKuaR.mjs +135 -0
- package/dist/plan-DqeZKuaR.mjs.map +1 -0
- package/dist/plugin-test-runtime.d.mts +14 -14
- package/dist/plugin-test-runtime.mjs +110 -107
- package/dist/plugin-types.d.mts +2 -2
- package/dist/plugin-utils.d.mts +12 -12
- package/dist/plugins/adapt-sandbox-entry.d.mts +12 -12
- package/dist/plugins/adapt-sandbox-entry.mjs +3 -3
- package/dist/plugins/host.d.mts +5 -5
- package/dist/plugins/host.mjs +42 -41
- package/dist/plugins-Bb7oqjQA.mjs +277 -0
- package/dist/plugins-Bb7oqjQA.mjs.map +1 -0
- package/dist/policy-BcEqut3n.mjs +45 -0
- package/dist/policy-BcEqut3n.mjs.map +1 -0
- package/dist/portable-text-DXE8ymtD.mjs +307 -0
- package/dist/portable-text-DXE8ymtD.mjs.map +1 -0
- package/dist/preview-DEDWbwA_.mjs +107 -0
- package/dist/preview-DEDWbwA_.mjs.map +1 -0
- package/dist/protocol-BJbTzvwl.d.mts +61 -0
- package/dist/protocol-BJbTzvwl.d.mts.map +1 -0
- package/dist/public-submission-D9c9S14m.mjs +250 -0
- package/dist/public-submission-D9c9S14m.mjs.map +1 -0
- package/dist/public-url-CIRe-2T6.mjs +101 -0
- package/dist/public-url-CIRe-2T6.mjs.map +1 -0
- package/dist/query-BzifXlPS.mjs +1156 -0
- package/dist/query-BzifXlPS.mjs.map +1 -0
- package/dist/query-OzMJ6vJP.mjs +41 -0
- package/dist/query-OzMJ6vJP.mjs.map +1 -0
- package/dist/rate-limit-Csp60Vik.mjs +120 -0
- package/dist/rate-limit-Csp60Vik.mjs.map +1 -0
- package/dist/receipt-B-aU4hpJ.mjs +43 -0
- package/dist/receipt-B-aU4hpJ.mjs.map +1 -0
- package/dist/redirect-CDSw1sU3.mjs +17 -0
- package/dist/redirect-CDSw1sU3.mjs.map +1 -0
- package/dist/redirect-D1ArbO85.mjs +411 -0
- package/dist/redirect-D1ArbO85.mjs.map +1 -0
- package/dist/redirects-BjTwDTsV.mjs +148 -0
- package/dist/redirects-BjTwDTsV.mjs.map +1 -0
- package/dist/redirects-Ck50kH3n.mjs +446 -0
- package/dist/redirects-Ck50kH3n.mjs.map +1 -0
- package/dist/registry/testing.mjs +3 -3
- package/dist/registry-CFcNCayZ.mjs +6754 -0
- package/dist/registry-CFcNCayZ.mjs.map +1 -0
- package/dist/relations-Cfaa1fDl.mjs +776 -0
- package/dist/relations-Cfaa1fDl.mjs.map +1 -0
- package/dist/request-cache-iHRlHx1d.mjs +104 -0
- package/dist/request-cache-iHRlHx1d.mjs.map +1 -0
- package/dist/request-context.d.mts +1 -1
- package/dist/request-meta-D47mqyev.mjs +141 -0
- package/dist/request-meta-D47mqyev.mjs.map +1 -0
- package/dist/resolve-BPE8sMvx.mjs +99 -0
- package/dist/resolve-BPE8sMvx.mjs.map +1 -0
- package/dist/resolve-C0s1E_fH.mjs +199 -0
- package/dist/resolve-C0s1E_fH.mjs.map +1 -0
- package/dist/revision-j1p4-bOj.mjs +302 -0
- package/dist/revision-j1p4-bOj.mjs.map +1 -0
- package/dist/route-wire-C9PJV9aL.mjs +197 -0
- package/dist/route-wire-C9PJV9aL.mjs.map +1 -0
- package/dist/routes-lyYw0mMf.mjs +272 -0
- package/dist/routes-lyYw0mMf.mjs.map +1 -0
- package/dist/runner-B3eyB9Gb.mjs +6121 -0
- package/dist/runner-B3eyB9Gb.mjs.map +1 -0
- package/dist/runner-DoYH2Jcu.d.mts +68 -0
- package/dist/runner-DoYH2Jcu.d.mts.map +1 -0
- package/dist/runtime.d.mts +13 -13
- package/dist/runtime.mjs +3 -3
- package/dist/schema-BAayK46S.mjs +13 -0
- package/dist/schema-BAayK46S.mjs.map +1 -0
- package/dist/schema-Gqcl-8G4.mjs +815 -0
- package/dist/schema-Gqcl-8G4.mjs.map +1 -0
- package/dist/scopes-BqPOKoKf.mjs +34 -0
- package/dist/scopes-BqPOKoKf.mjs.map +1 -0
- package/dist/search-YJKPIEV6.mjs +448 -0
- package/dist/search-YJKPIEV6.mjs.map +1 -0
- package/dist/sections-BR_e-wK5.mjs +346 -0
- package/dist/sections-BR_e-wK5.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +37 -31
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +22 -21
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo-Dsj8hy3j.mjs +114 -0
- package/dist/seo-Dsj8hy3j.mjs.map +1 -0
- package/dist/seo-XvH4LNNp.mjs +133 -0
- package/dist/seo-XvH4LNNp.mjs.map +1 -0
- package/dist/service-BsVG05gS.mjs +150 -0
- package/dist/service-BsVG05gS.mjs.map +1 -0
- package/dist/session-user-DgKbYrlj.mjs +51 -0
- package/dist/session-user-DgKbYrlj.mjs.map +1 -0
- package/dist/settings-CNPUntkh.mjs +51 -0
- package/dist/settings-CNPUntkh.mjs.map +1 -0
- package/dist/settings-D5EHi0Tk.mjs +439 -0
- package/dist/settings-D5EHi0Tk.mjs.map +1 -0
- package/dist/settings-DSfE7wOa.mjs +61 -0
- package/dist/settings-DSfE7wOa.mjs.map +1 -0
- package/dist/setup-complete-B8W8sJ9v.mjs +26 -0
- package/dist/setup-complete-B8W8sJ9v.mjs.map +1 -0
- package/dist/setup-nonce-DnSBP260.mjs +25 -0
- package/dist/setup-nonce-DnSBP260.mjs.map +1 -0
- package/dist/site-settings-NGzdrt7O.mjs +50 -0
- package/dist/site-settings-NGzdrt7O.mjs.map +1 -0
- package/dist/site-url-D6JB7tLW.mjs +16 -0
- package/dist/site-url-D6JB7tLW.mjs.map +1 -0
- package/dist/snapshot-CdcwwWAf.mjs +414 -0
- package/dist/snapshot-CdcwwWAf.mjs.map +1 -0
- package/dist/ssrf-Dv0_tRpo.mjs +406 -0
- package/dist/ssrf-Dv0_tRpo.mjs.map +1 -0
- package/dist/stage-CUjhED3P.mjs +662 -0
- package/dist/stage-CUjhED3P.mjs.map +1 -0
- package/dist/staged-C36IWXff.mjs +659 -0
- package/dist/staged-C36IWXff.mjs.map +1 -0
- package/dist/state-CKouMCSY.mjs +153 -0
- package/dist/state-CKouMCSY.mjs.map +1 -0
- package/dist/status-lzyj4Uu7.mjs +30 -0
- package/dist/status-lzyj4Uu7.mjs.map +1 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/storage-query-B-cWcTjJ.d.mts +1594 -0
- package/dist/storage-query-B-cWcTjJ.d.mts.map +1 -0
- package/dist/tar-DsTXX6ch.mjs +449 -0
- package/dist/tar-DsTXX6ch.mjs.map +1 -0
- package/dist/target-DLQCwp_Z.mjs +27 -0
- package/dist/target-DLQCwp_Z.mjs.map +1 -0
- package/dist/taxonomies-BlAjr4BL.mjs +697 -0
- package/dist/taxonomies-BlAjr4BL.mjs.map +1 -0
- package/dist/taxonomies-Byyo7LOZ.mjs +939 -0
- package/dist/taxonomies-Byyo7LOZ.mjs.map +1 -0
- package/dist/taxonomy-CKDwM61Y.mjs +525 -0
- package/dist/taxonomy-CKDwM61Y.mjs.map +1 -0
- package/dist/tokens-BojXlk49.mjs +171 -0
- package/dist/tokens-BojXlk49.mjs.map +1 -0
- package/dist/transaction-DLPQWik1.mjs +29 -0
- package/dist/transaction-DLPQWik1.mjs.map +1 -0
- package/dist/transfer-XSTh2BCr.mjs +8706 -0
- package/dist/transfer-XSTh2BCr.mjs.map +1 -0
- package/dist/transfer-download-DkoCDA9d.mjs +33 -0
- package/dist/transfer-download-DkoCDA9d.mjs.map +1 -0
- package/dist/transfer-iQ26J-RT.mjs +166 -0
- package/dist/transfer-iQ26J-RT.mjs.map +1 -0
- package/dist/transformations-C7RQabKQ.mjs +196 -0
- package/dist/transformations-C7RQabKQ.mjs.map +1 -0
- package/dist/transport-D3RulfD6.mjs +136 -0
- package/dist/transport-D3RulfD6.mjs.map +1 -0
- package/dist/transport-DhlZlf4D.d.mts +42 -0
- package/dist/transport-DhlZlf4D.d.mts.map +1 -0
- package/dist/trusted-proxy-B6ECOZJc.mjs +51 -0
- package/dist/trusted-proxy-B6ECOZJc.mjs.map +1 -0
- package/dist/turnstile-lqUiY7lP.mjs +53 -0
- package/dist/turnstile-lqUiY7lP.mjs.map +1 -0
- package/dist/types-4Dn9bZhu.mjs +77 -0
- package/dist/types-4Dn9bZhu.mjs.map +1 -0
- package/dist/types-5LSo7PPx.d.mts +85 -0
- package/dist/types-5LSo7PPx.d.mts.map +1 -0
- package/dist/types-BQ3hwYJT.d.mts +896 -0
- package/dist/types-BQ3hwYJT.d.mts.map +1 -0
- package/dist/types-BYEk7qo3.d.mts +198 -0
- package/dist/types-BYEk7qo3.d.mts.map +1 -0
- package/dist/types-CJKsWVFR.mjs +199 -0
- package/dist/types-CJKsWVFR.mjs.map +1 -0
- package/dist/types-Ca_w8mmY.d.mts +363 -0
- package/dist/types-Ca_w8mmY.d.mts.map +1 -0
- package/dist/types-CkE41ZAG.mjs +18 -0
- package/dist/types-CkE41ZAG.mjs.map +1 -0
- package/dist/types-D0aJW2uR.d.mts +1641 -0
- package/dist/types-D0aJW2uR.d.mts.map +1 -0
- package/dist/types-DbpN4Qto.d.mts +372 -0
- package/dist/types-DbpN4Qto.d.mts.map +1 -0
- package/dist/types-ILDI1sdY.mjs +16 -0
- package/dist/types-ILDI1sdY.mjs.map +1 -0
- package/dist/types-OC5c-edM.mjs +22 -0
- package/dist/types-OC5c-edM.mjs.map +1 -0
- package/dist/types-aOOxRSx6.mjs +103 -0
- package/dist/types-aOOxRSx6.mjs.map +1 -0
- package/dist/types-m1WbUffD.d.mts +198 -0
- package/dist/types-m1WbUffD.d.mts.map +1 -0
- package/dist/types-uxLoPBio.d.mts +124 -0
- package/dist/types-uxLoPBio.d.mts.map +1 -0
- package/dist/types-we5uR-tV.d.mts +287 -0
- package/dist/types-we5uR-tV.d.mts.map +1 -0
- package/dist/upload-attempts-DduMy6B0.mjs +32 -0
- package/dist/upload-attempts-DduMy6B0.mjs.map +1 -0
- package/dist/url-pattern-B3EwxAAk.mjs +33 -0
- package/dist/url-pattern-B3EwxAAk.mjs.map +1 -0
- package/dist/user-ElwQjTQT.mjs +171 -0
- package/dist/user-ElwQjTQT.mjs.map +1 -0
- package/dist/utils-BlAXmfOG.mjs +439 -0
- package/dist/utils-BlAXmfOG.mjs.map +1 -0
- package/dist/validate-CH0X5b2c.d.mts +468 -0
- package/dist/validate-CH0X5b2c.d.mts.map +1 -0
- package/dist/validate-CfgRpcjA.mjs +492 -0
- package/dist/validate-CfgRpcjA.mjs.map +1 -0
- package/dist/validation-DvYJtv4k.mjs +218 -0
- package/dist/validation-DvYJtv4k.mjs.map +1 -0
- package/dist/version-DgYjhTud.mjs +9 -0
- package/dist/version-DgYjhTud.mjs.map +1 -0
- package/dist/version-Dq71scUb.mjs +7 -0
- package/dist/version-Dq71scUb.mjs.map +1 -0
- package/dist/widgets-ChFIj0be.mjs +126 -0
- package/dist/widgets-ChFIj0be.mjs.map +1 -0
- package/dist/wxr-taxonomies-CBZMp2YX.mjs +372 -0
- package/dist/wxr-taxonomies-CBZMp2YX.mjs.map +1 -0
- package/dist/wxr-taxonomies-DlKDpWN1.d.mts +34 -0
- package/dist/wxr-taxonomies-DlKDpWN1.d.mts.map +1 -0
- package/dist/zod-generator-DmTKI-Ig.mjs +426 -0
- package/dist/zod-generator-DmTKI-Ig.mjs.map +1 -0
- package/package.json +7 -6
- package/src/api/handlers/content.ts +593 -16
- package/src/api/handlers/device-flow.ts +74 -21
- package/src/api/handlers/index.ts +1 -0
- package/src/api/handlers/manifest.ts +79 -5
- package/src/api/handlers/media-upload.ts +17 -71
- package/src/api/handlers/media.ts +97 -3
- package/src/api/handlers/relations.ts +638 -157
- package/src/api/handlers/revision.ts +30 -0
- package/src/api/handlers/schema.ts +403 -1
- package/src/api/handlers/snapshot.ts +257 -3
- package/src/api/handlers/staged-references.ts +142 -0
- package/src/api/handlers/validate-references.ts +146 -0
- package/src/api/schemas/content.ts +13 -0
- package/src/api/schemas/relations.ts +54 -44
- package/src/api/schemas/schema.ts +14 -0
- package/src/astro/integration/routes.ts +15 -19
- package/src/astro/integration/vite-config.ts +10 -4
- package/src/astro/middleware.ts +1 -0
- package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +11 -54
- package/src/astro/routes/api/content/[collection]/[id].ts +3 -1
- package/src/astro/routes/api/oauth/device/authorize.ts +33 -4
- package/src/astro/routes/api/relations/index.ts +4 -4
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +6 -2
- package/src/astro/routes/api/setup/index.ts +66 -23
- package/src/astro/routes/api/snapshot.ts +2 -1
- package/src/astro/types.ts +10 -0
- package/src/cleanup.ts +10 -0
- package/src/cli/commands/export-seed.ts +510 -89
- package/src/components/Columns.astro +3 -1
- package/src/components/Cover.astro +3 -1
- package/src/components/WidgetArea.astro +3 -2
- package/src/database/migrations/086_relations_structural.ts +197 -0
- package/src/database/migrations/087_reference_field_relations.ts +407 -0
- package/src/database/migrations/runner.ts +4 -0
- package/src/database/reference-backfill.ts +141 -0
- package/src/database/repositories/content.ts +43 -8
- package/src/database/repositories/media.ts +40 -1
- package/src/database/repositories/redirect.ts +17 -34
- package/src/database/repositories/relation.ts +620 -208
- package/src/database/repositories/revision.ts +16 -0
- package/src/database/repositories/types.ts +38 -0
- package/src/database/transaction.ts +12 -1
- package/src/database/types.ts +14 -5
- package/src/emdash-runtime.ts +114 -11
- package/src/index.ts +11 -0
- package/src/loader.ts +169 -22
- package/src/mcp/server.ts +13 -48
- package/src/query.ts +455 -25
- package/src/references/field-map.ts +115 -0
- package/src/references/resolve.ts +160 -0
- package/src/references/staged.ts +117 -0
- package/src/references/types.ts +16 -0
- package/src/registry/artifact-fetch.ts +45 -271
- package/src/schema/registry.ts +99 -24
- package/src/schema/types.ts +58 -0
- package/src/schema/zod-generator.ts +67 -1
- package/src/seed/apply.ts +841 -103
- package/src/seed/types.ts +36 -2
- package/src/seed/validate.ts +78 -2
- package/src/transfer/analyze/unique-keys.ts +3 -12
- package/src/transfer/analyze/validate.ts +65 -39
- package/src/transfer/analyze/values.ts +12 -1
- package/src/transfer/export/readers.ts +12 -8
- package/src/transfer/format/columns.ts +26 -6
- package/src/transfer/format/kinds.ts +21 -5
- package/src/transfer/import/context.ts +8 -1
- package/src/transfer/ops/package-index.ts +1 -1
- package/dist/action-token-DYwe4Pk0.mjs +0 -26
- package/dist/action-token-DYwe4Pk0.mjs.map +0 -1
- package/dist/activation-LOkDM5WT.mjs +0 -382
- package/dist/activation-LOkDM5WT.mjs.map +0 -1
- package/dist/adapters-B64LbYcB.d.mts +0 -171
- package/dist/adapters-B64LbYcB.d.mts.map +0 -1
- package/dist/allowed-origins-CHB8oZNM.mjs +0 -116
- package/dist/allowed-origins-CHB8oZNM.mjs.map +0 -1
- package/dist/api-cFFZpyS0.mjs +0 -4718
- package/dist/api-cFFZpyS0.mjs.map +0 -1
- package/dist/api-tokens-s4i4aUp_.mjs +0 -166
- package/dist/api-tokens-s4i4aUp_.mjs.map +0 -1
- package/dist/apply-CoT603H3.mjs +0 -1163
- package/dist/apply-CoT603H3.mjs.map +0 -1
- package/dist/approvals-CqMfNxak.mjs +0 -460
- package/dist/approvals-CqMfNxak.mjs.map +0 -1
- package/dist/artifact-fetch-DZMOGZ3-.mjs +0 -818
- package/dist/artifact-fetch-DZMOGZ3-.mjs.map +0 -1
- package/dist/astro/routes/api/relations/_id_/translations.d.mts +0 -8
- package/dist/astro/routes/api/relations/_id_/translations.d.mts.map +0 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +0 -47
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +0 -1
- package/dist/audit-RfCugiSB.mjs +0 -127
- package/dist/audit-RfCugiSB.mjs.map +0 -1
- package/dist/authoritative-records-CoJvBXAD.mjs +0 -5172
- package/dist/authoritative-records-CoJvBXAD.mjs.map +0 -1
- package/dist/authorize-seCifOMJ.mjs +0 -49
- package/dist/authorize-seCifOMJ.mjs.map +0 -1
- package/dist/backup-CBu0LrXf.mjs +0 -246
- package/dist/backup-CBu0LrXf.mjs.map +0 -1
- package/dist/block-type-registry-DUI_3KxT.mjs +0 -6709
- package/dist/block-type-registry-DUI_3KxT.mjs.map +0 -1
- package/dist/block-types-BmZmLmAD.mjs +0 -132
- package/dist/block-types-BmZmLmAD.mjs.map +0 -1
- package/dist/block-values-C_VIPr5t.mjs +0 -175
- package/dist/block-values-C_VIPr5t.mjs.map +0 -1
- package/dist/byline-DYmkXqvo.mjs +0 -909
- package/dist/byline-DYmkXqvo.mjs.map +0 -1
- package/dist/byline-fields-D28AT-Fh.mjs +0 -123
- package/dist/byline-fields-D28AT-Fh.mjs.map +0 -1
- package/dist/byline-registry-gnKscbQX.mjs +0 -406
- package/dist/byline-registry-gnKscbQX.mjs.map +0 -1
- package/dist/bylines-BPwJqoYH.mjs +0 -232
- package/dist/bylines-BPwJqoYH.mjs.map +0 -1
- package/dist/bylines-ukLaYpVZ.mjs +0 -214
- package/dist/bylines-ukLaYpVZ.mjs.map +0 -1
- package/dist/cache-m7UEkRFV.mjs +0 -108
- package/dist/cache-m7UEkRFV.mjs.map +0 -1
- package/dist/canonical-YhEQezjg.mjs +0 -174
- package/dist/canonical-YhEQezjg.mjs.map +0 -1
- package/dist/capture-triggers-kJyl_yyM.mjs +0 -362
- package/dist/capture-triggers-kJyl_yyM.mjs.map +0 -1
- package/dist/challenge-store-ppU6Uv13.mjs +0 -51
- package/dist/challenge-store-ppU6Uv13.mjs.map +0 -1
- package/dist/checksum-B2IINIlN.mjs +0 -162
- package/dist/checksum-B2IINIlN.mjs.map +0 -1
- package/dist/chrome-tags-DApCQSIk.mjs +0 -24
- package/dist/chrome-tags-DApCQSIk.mjs.map +0 -1
- package/dist/chunks-Dms7Uxbt.mjs +0 -25
- package/dist/chunks-Dms7Uxbt.mjs.map +0 -1
- package/dist/collection-deletion-BIm28mo3.mjs +0 -436
- package/dist/collection-deletion-BIm28mo3.mjs.map +0 -1
- package/dist/collection-deletion-processor-Br6rxs3u.mjs +0 -217
- package/dist/collection-deletion-processor-Br6rxs3u.mjs.map +0 -1
- package/dist/collection-slugs-cache-CjGk__zp.mjs +0 -85
- package/dist/collection-slugs-cache-CjGk__zp.mjs.map +0 -1
- package/dist/comment-CrBl22OF.mjs +0 -313
- package/dist/comment-CrBl22OF.mjs.map +0 -1
- package/dist/comment-reaction-JEQ2Fpd8.mjs +0 -85
- package/dist/comment-reaction-JEQ2Fpd8.mjs.map +0 -1
- package/dist/comments-2ah9du9y.mjs +0 -195
- package/dist/comments-2ah9du9y.mjs.map +0 -1
- package/dist/comments-fHZ8ON9O.mjs +0 -99
- package/dist/comments-fHZ8ON9O.mjs.map +0 -1
- package/dist/components-CXbq7CSM.mjs +0 -108
- package/dist/components-CXbq7CSM.mjs.map +0 -1
- package/dist/config-5ii2haYu.d.mts +0 -6528
- package/dist/config-5ii2haYu.d.mts.map +0 -1
- package/dist/config-IhybvkIQ.mjs +0 -242
- package/dist/config-IhybvkIQ.mjs.map +0 -1
- package/dist/config-Py43JK9r.d.mts +0 -39
- package/dist/config-Py43JK9r.d.mts.map +0 -1
- package/dist/content-DFaIEDj9.mjs +0 -2281
- package/dist/content-DFaIEDj9.mjs.map +0 -1
- package/dist/content-list-query-Dra4Dalc.d.mts +0 -26
- package/dist/content-list-query-Dra4Dalc.d.mts.map +0 -1
- package/dist/content-policy-DFNw91-d.mjs +0 -46
- package/dist/content-policy-DFNw91-d.mjs.map +0 -1
- package/dist/content-repair-Q8IUrpLL.mjs +0 -442
- package/dist/content-repair-Q8IUrpLL.mjs.map +0 -1
- package/dist/context-BeGccdUX.mjs +0 -1921
- package/dist/context-BeGccdUX.mjs.map +0 -1
- package/dist/cron-7DCJwPXq.mjs +0 -284
- package/dist/cron-7DCJwPXq.mjs.map +0 -1
- package/dist/dashboard-DjrB-xxZ.mjs +0 -151
- package/dist/dashboard-DjrB-xxZ.mjs.map +0 -1
- package/dist/default-DQ6vsviu.mjs +0 -81
- package/dist/default-DQ6vsviu.mjs.map +0 -1
- package/dist/deferred-tasks-BPxXUB4x.d.mts +0 -9
- package/dist/deferred-tasks-BPxXUB4x.d.mts.map +0 -1
- package/dist/dev-typegen-CZaPLl3V.mjs +0 -29
- package/dist/dev-typegen-CZaPLl3V.mjs.map +0 -1
- package/dist/device-flow-CRd6DXmW.mjs +0 -466
- package/dist/device-flow-CRd6DXmW.mjs.map +0 -1
- package/dist/digest-BarSh-pN.mjs +0 -194
- package/dist/digest-BarSh-pN.mjs.map +0 -1
- package/dist/direct-executor-BmuFJJZV.mjs +0 -104
- package/dist/direct-executor-BmuFJJZV.mjs.map +0 -1
- package/dist/dist-BNwANFJR.mjs +0 -810
- package/dist/dist-BNwANFJR.mjs.map +0 -1
- package/dist/email-console-bJn3ctqA.mjs +0 -50
- package/dist/email-console-bJn3ctqA.mjs.map +0 -1
- package/dist/email-locale-CN6WAMx0.mjs +0 -25
- package/dist/email-locale-CN6WAMx0.mjs.map +0 -1
- package/dist/emdash-runtime-DAjghyEG.mjs +0 -4186
- package/dist/emdash-runtime-DAjghyEG.mjs.map +0 -1
- package/dist/enrich-Dl3kZ-lP.mjs +0 -194
- package/dist/enrich-Dl3kZ-lP.mjs.map +0 -1
- package/dist/entry-lock-BJYdBfXb.mjs +0 -1097
- package/dist/entry-lock-BJYdBfXb.mjs.map +0 -1
- package/dist/entry-lock-DHQOxN7R.mjs +0 -163
- package/dist/entry-lock-DHQOxN7R.mjs.map +0 -1
- package/dist/entry-lock-DVhCt49A.d.mts +0 -4368
- package/dist/entry-lock-DVhCt49A.d.mts.map +0 -1
- package/dist/entry-locks-D0foLdjQ.mjs +0 -167
- package/dist/entry-locks-D0foLdjQ.mjs.map +0 -1
- package/dist/error-CfvOHP0M.mjs +0 -90
- package/dist/error-CfvOHP0M.mjs.map +0 -1
- package/dist/errors-BF0_LFhW.mjs +0 -460
- package/dist/errors-BF0_LFhW.mjs.map +0 -1
- package/dist/errors-DgE1zJGG.mjs +0 -120
- package/dist/errors-DgE1zJGG.mjs.map +0 -1
- package/dist/escape-CuaZhZvL.mjs +0 -9
- package/dist/escape-CuaZhZvL.mjs.map +0 -1
- package/dist/fence-CMDXJYc_.mjs +0 -162
- package/dist/fence-CMDXJYc_.mjs.map +0 -1
- package/dist/field-defs-cache-HIgBbqZZ.mjs +0 -77
- package/dist/field-defs-cache-HIgBbqZZ.mjs.map +0 -1
- package/dist/focal-point-Gr34o-4o.mjs +0 -23
- package/dist/focal-point-Gr34o-4o.mjs.map +0 -1
- package/dist/fts-manager-S-K0laoS.mjs +0 -443
- package/dist/fts-manager-S-K0laoS.mjs.map +0 -1
- package/dist/gc-DjKY0Ay1.mjs +0 -75
- package/dist/gc-DjKY0Ay1.mjs.map +0 -1
- package/dist/hash-SDAW6zC-.mjs +0 -37
- package/dist/hash-SDAW6zC-.mjs.map +0 -1
- package/dist/hook-result-B5bXDgS2.d.mts +0 -31
- package/dist/hook-result-B5bXDgS2.d.mts.map +0 -1
- package/dist/hreflang-D5uNnsGJ.d.mts +0 -50
- package/dist/hreflang-D5uNnsGJ.d.mts.map +0 -1
- package/dist/hreflang-DMFBL2eb.mjs +0 -105
- package/dist/hreflang-DMFBL2eb.mjs.map +0 -1
- package/dist/http-route-dispatch-BitqzwMy.mjs +0 -339
- package/dist/http-route-dispatch-BitqzwMy.mjs.map +0 -1
- package/dist/identity-IyNWwXr6.mjs +0 -35
- package/dist/identity-IyNWwXr6.mjs.map +0 -1
- package/dist/import-C3JZplWq.mjs +0 -2020
- package/dist/import-C3JZplWq.mjs.map +0 -1
- package/dist/index-D-hdtLe8.d.mts +0 -623
- package/dist/index-D-hdtLe8.d.mts.map +0 -1
- package/dist/install-finalization-DfM1fj5x.mjs +0 -58
- package/dist/install-finalization-DfM1fj5x.mjs.map +0 -1
- package/dist/keys-yxLm5l35.mjs +0 -61
- package/dist/keys-yxLm5l35.mjs.map +0 -1
- package/dist/kinds-CRu_T5T5.mjs +0 -961
- package/dist/kinds-CRu_T5T5.mjs.map +0 -1
- package/dist/lifecycle-BzeGPeQ-.mjs +0 -25
- package/dist/lifecycle-BzeGPeQ-.mjs.map +0 -1
- package/dist/load-XgJiE6_3.mjs +0 -28
- package/dist/load-XgJiE6_3.mjs.map +0 -1
- package/dist/loader-BzqeR1ZN.mjs +0 -1086
- package/dist/loader-BzqeR1ZN.mjs.map +0 -1
- package/dist/loops-BQSzv5Zn.mjs +0 -203
- package/dist/loops-BQSzv5Zn.mjs.map +0 -1
- package/dist/manifest-builder-B8zR-73J.mjs +0 -189
- package/dist/manifest-builder-B8zR-73J.mjs.map +0 -1
- package/dist/manifest-schema-CY3zng_M.mjs +0 -492
- package/dist/manifest-schema-CY3zng_M.mjs.map +0 -1
- package/dist/media-Pu1L6xir.mjs +0 -408
- package/dist/media-Pu1L6xir.mjs.map +0 -1
- package/dist/media-Qp5S3eH3.mjs +0 -355
- package/dist/media-Qp5S3eH3.mjs.map +0 -1
- package/dist/media-allowlist-BSoa9Vvm.mjs +0 -44
- package/dist/media-allowlist-BSoa9Vvm.mjs.map +0 -1
- package/dist/media-folders-BE0oo2eX.mjs +0 -210
- package/dist/media-folders-BE0oo2eX.mjs.map +0 -1
- package/dist/media-h7tOiDLj.mjs +0 -221
- package/dist/media-h7tOiDLj.mjs.map +0 -1
- package/dist/media-url-jArBpzIG.mjs +0 -43
- package/dist/media-url-jArBpzIG.mjs.map +0 -1
- package/dist/media-usage-My-Vi-ex.mjs +0 -1629
- package/dist/media-usage-My-Vi-ex.mjs.map +0 -1
- package/dist/media-usage-activation-HKp7zk7W.mjs +0 -78
- package/dist/media-usage-activation-HKp7zk7W.mjs.map +0 -1
- package/dist/media-usage-work-C2OZLCAd.mjs +0 -586
- package/dist/media-usage-work-C2OZLCAd.mjs.map +0 -1
- package/dist/media-usage-work-DwUNbgvS.mjs +0 -152
- package/dist/media-usage-work-DwUNbgvS.mjs.map +0 -1
- package/dist/menus-BM7BPLOb.mjs +0 -139
- package/dist/menus-BM7BPLOb.mjs.map +0 -1
- package/dist/menus-ITeAbcIF.mjs +0 -753
- package/dist/menus-ITeAbcIF.mjs.map +0 -1
- package/dist/menus-TsSGmSR4.mjs +0 -3608
- package/dist/menus-TsSGmSR4.mjs.map +0 -1
- package/dist/mime-ChLqYwDk.mjs +0 -36
- package/dist/mime-ChLqYwDk.mjs.map +0 -1
- package/dist/mode-CCuKXUci.mjs +0 -23
- package/dist/mode-CCuKXUci.mjs.map +0 -1
- package/dist/node-sqlite-compat-CONMsl-x.mjs +0 -46
- package/dist/node-sqlite-compat-CONMsl-x.mjs.map +0 -1
- package/dist/normalize-CO2P8-iE.mjs +0 -191
- package/dist/normalize-CO2P8-iE.mjs.map +0 -1
- package/dist/oauth-authorization-DlMIL5_h.mjs +0 -274
- package/dist/oauth-authorization-DlMIL5_h.mjs.map +0 -1
- package/dist/oauth-clients-Djq9r_KP.mjs +0 -266
- package/dist/oauth-clients-Djq9r_KP.mjs.map +0 -1
- package/dist/oauth-state-store-BbjnZXNC.mjs +0 -50
- package/dist/oauth-state-store-BbjnZXNC.mjs.map +0 -1
- package/dist/oauth-user-lookup-vue9a64A.mjs +0 -26
- package/dist/oauth-user-lookup-vue9a64A.mjs.map +0 -1
- package/dist/options-SQCHQCPH.d.mts +0 -81
- package/dist/options-SQCHQCPH.d.mts.map +0 -1
- package/dist/parse-CVgO-Qai.d.mts +0 -168
- package/dist/parse-CVgO-Qai.d.mts.map +0 -1
- package/dist/parse-DY6OD1j_.mjs +0 -81
- package/dist/parse-DY6OD1j_.mjs.map +0 -1
- package/dist/passkey-config-X_H8128a.mjs +0 -46
- package/dist/passkey-config-X_H8128a.mjs.map +0 -1
- package/dist/paths-JxokKIbr.mjs +0 -77
- package/dist/paths-JxokKIbr.mjs.map +0 -1
- package/dist/patterns-BhAvqO0S.mjs +0 -145
- package/dist/patterns-BhAvqO0S.mjs.map +0 -1
- package/dist/placeholder-MSEd8gty.d.mts +0 -306
- package/dist/placeholder-MSEd8gty.d.mts.map +0 -1
- package/dist/plan-xjcWrAah.mjs +0 -135
- package/dist/plan-xjcWrAah.mjs.map +0 -1
- package/dist/plugins-DUXUt8B9.mjs +0 -277
- package/dist/plugins-DUXUt8B9.mjs.map +0 -1
- package/dist/policy-CUvwAARc.mjs +0 -45
- package/dist/policy-CUvwAARc.mjs.map +0 -1
- package/dist/portable-text-5O_ZGUjB.mjs +0 -307
- package/dist/portable-text-5O_ZGUjB.mjs.map +0 -1
- package/dist/preview-CIUcPYr6.mjs +0 -107
- package/dist/preview-CIUcPYr6.mjs.map +0 -1
- package/dist/protocol-B97DZaFr.d.mts +0 -61
- package/dist/protocol-B97DZaFr.d.mts.map +0 -1
- package/dist/public-submission-BFBUrqst.mjs +0 -250
- package/dist/public-submission-BFBUrqst.mjs.map +0 -1
- package/dist/public-url-Bn7U0D7-.mjs +0 -101
- package/dist/public-url-Bn7U0D7-.mjs.map +0 -1
- package/dist/query-B48peoRT.mjs +0 -890
- package/dist/query-B48peoRT.mjs.map +0 -1
- package/dist/query-C4IH0uhR.mjs +0 -41
- package/dist/query-C4IH0uhR.mjs.map +0 -1
- package/dist/rate-limit-BZ6nybgc.mjs +0 -120
- package/dist/rate-limit-BZ6nybgc.mjs.map +0 -1
- package/dist/receipt-B4wJj5Jq.mjs +0 -43
- package/dist/receipt-B4wJj5Jq.mjs.map +0 -1
- package/dist/redirect-Cd1E7VUt.mjs +0 -17
- package/dist/redirect-Cd1E7VUt.mjs.map +0 -1
- package/dist/redirect-iJf5fltD.mjs +0 -415
- package/dist/redirect-iJf5fltD.mjs.map +0 -1
- package/dist/redirects-CYu1u1aK.mjs +0 -446
- package/dist/redirects-CYu1u1aK.mjs.map +0 -1
- package/dist/redirects-Cv9apQKj.mjs +0 -148
- package/dist/redirects-Cv9apQKj.mjs.map +0 -1
- package/dist/relation-M4NUCb0b.mjs +0 -334
- package/dist/relation-M4NUCb0b.mjs.map +0 -1
- package/dist/relations-CCif47Oq.mjs +0 -403
- package/dist/relations-CCif47Oq.mjs.map +0 -1
- package/dist/relations-CK9CYUyr.mjs +0 -310
- package/dist/relations-CK9CYUyr.mjs.map +0 -1
- package/dist/request-cache-DzLYKm_x.mjs +0 -104
- package/dist/request-cache-DzLYKm_x.mjs.map +0 -1
- package/dist/request-meta-sZXt3sRo.mjs +0 -141
- package/dist/request-meta-sZXt3sRo.mjs.map +0 -1
- package/dist/resolve-2WbQsCvT.mjs +0 -199
- package/dist/resolve-2WbQsCvT.mjs.map +0 -1
- package/dist/route-wire--1hLuACz.mjs +0 -197
- package/dist/route-wire--1hLuACz.mjs.map +0 -1
- package/dist/routes-CUsM40Cc.mjs +0 -272
- package/dist/routes-CUsM40Cc.mjs.map +0 -1
- package/dist/runner-1y4J9fJB.mjs +0 -5742
- package/dist/runner-1y4J9fJB.mjs.map +0 -1
- package/dist/runner-D5_LzKXI.d.mts +0 -68
- package/dist/runner-D5_LzKXI.d.mts.map +0 -1
- package/dist/schema-BABiRdsH.mjs +0 -13
- package/dist/schema-BABiRdsH.mjs.map +0 -1
- package/dist/schema-Du3MzCG0.mjs +0 -446
- package/dist/schema-Du3MzCG0.mjs.map +0 -1
- package/dist/scopes-Dbk-ElhT.mjs +0 -34
- package/dist/scopes-Dbk-ElhT.mjs.map +0 -1
- package/dist/search-Bt1PL1jf.mjs +0 -448
- package/dist/search-Bt1PL1jf.mjs.map +0 -1
- package/dist/sections-Ddptx_o9.mjs +0 -346
- package/dist/sections-Ddptx_o9.mjs.map +0 -1
- package/dist/seo-CZT2oLtT.mjs +0 -114
- package/dist/seo-CZT2oLtT.mjs.map +0 -1
- package/dist/seo-D5w3-Azy.mjs +0 -133
- package/dist/seo-D5w3-Azy.mjs.map +0 -1
- package/dist/service-DuykXPyW.mjs +0 -150
- package/dist/service-DuykXPyW.mjs.map +0 -1
- package/dist/session-user-BhaVp8w0.mjs +0 -51
- package/dist/session-user-BhaVp8w0.mjs.map +0 -1
- package/dist/settings-2xNYr90o.mjs +0 -51
- package/dist/settings-2xNYr90o.mjs.map +0 -1
- package/dist/settings-CgjRWGW9.mjs +0 -439
- package/dist/settings-CgjRWGW9.mjs.map +0 -1
- package/dist/settings-DYidIB26.mjs +0 -61
- package/dist/settings-DYidIB26.mjs.map +0 -1
- package/dist/setup-complete-bOogcHWG.mjs +0 -26
- package/dist/setup-complete-bOogcHWG.mjs.map +0 -1
- package/dist/setup-nonce-0EGuKM4s.mjs +0 -25
- package/dist/setup-nonce-0EGuKM4s.mjs.map +0 -1
- package/dist/site-settings-DOgRUoFX.mjs +0 -50
- package/dist/site-settings-DOgRUoFX.mjs.map +0 -1
- package/dist/site-url-BU_bbxwj.mjs +0 -16
- package/dist/site-url-BU_bbxwj.mjs.map +0 -1
- package/dist/snapshot-CugwUqKQ.mjs +0 -237
- package/dist/snapshot-CugwUqKQ.mjs.map +0 -1
- package/dist/ssrf-CJUnJymJ.mjs +0 -406
- package/dist/ssrf-CJUnJymJ.mjs.map +0 -1
- package/dist/stage-DgrBz_du.mjs +0 -662
- package/dist/stage-DgrBz_du.mjs.map +0 -1
- package/dist/state-DDiPySSG.mjs +0 -153
- package/dist/state-DDiPySSG.mjs.map +0 -1
- package/dist/status-DZKIG4a9.mjs +0 -30
- package/dist/status-DZKIG4a9.mjs.map +0 -1
- package/dist/storage-query-Bdv7A62k.d.mts +0 -1576
- package/dist/storage-query-Bdv7A62k.d.mts.map +0 -1
- package/dist/tar-_1AgTYFu.mjs +0 -449
- package/dist/tar-_1AgTYFu.mjs.map +0 -1
- package/dist/target-_7pn2Gbg.mjs +0 -27
- package/dist/target-_7pn2Gbg.mjs.map +0 -1
- package/dist/taxonomies-C1IwNe8Q.mjs +0 -697
- package/dist/taxonomies-C1IwNe8Q.mjs.map +0 -1
- package/dist/taxonomies-C_XMoRcX.mjs +0 -939
- package/dist/taxonomies-C_XMoRcX.mjs.map +0 -1
- package/dist/taxonomy-C3Fu1YeH.mjs +0 -525
- package/dist/taxonomy-C3Fu1YeH.mjs.map +0 -1
- package/dist/tokens-u78jETkX.mjs +0 -171
- package/dist/tokens-u78jETkX.mjs.map +0 -1
- package/dist/transaction-BcJO4Tmc.mjs +0 -29
- package/dist/transaction-BcJO4Tmc.mjs.map +0 -1
- package/dist/transfer-BhUCrgDc.mjs +0 -8675
- package/dist/transfer-BhUCrgDc.mjs.map +0 -1
- package/dist/transfer-DIGvR9Af.mjs +0 -166
- package/dist/transfer-DIGvR9Af.mjs.map +0 -1
- package/dist/transfer-download-Pml7RMio.mjs +0 -33
- package/dist/transfer-download-Pml7RMio.mjs.map +0 -1
- package/dist/transformations-9gfpwY65.mjs +0 -196
- package/dist/transformations-9gfpwY65.mjs.map +0 -1
- package/dist/transport-BIfH7_hx.d.mts +0 -42
- package/dist/transport-BIfH7_hx.d.mts.map +0 -1
- package/dist/transport-DKWIq53q.mjs +0 -136
- package/dist/transport-DKWIq53q.mjs.map +0 -1
- package/dist/trusted-proxy-tKiVtyTD.mjs +0 -51
- package/dist/trusted-proxy-tKiVtyTD.mjs.map +0 -1
- package/dist/turnstile-DpsoyOfg.mjs +0 -53
- package/dist/turnstile-DpsoyOfg.mjs.map +0 -1
- package/dist/types-B4sUC2p1.d.mts +0 -198
- package/dist/types-B4sUC2p1.d.mts.map +0 -1
- package/dist/types-B7Lmsh1E.d.mts +0 -887
- package/dist/types-B7Lmsh1E.d.mts.map +0 -1
- package/dist/types-BZakh7Ym.mjs +0 -22
- package/dist/types-BZakh7Ym.mjs.map +0 -1
- package/dist/types-B_CppEmv.d.mts +0 -264
- package/dist/types-B_CppEmv.d.mts.map +0 -1
- package/dist/types-BcrBrE6-.mjs +0 -159
- package/dist/types-BcrBrE6-.mjs.map +0 -1
- package/dist/types-BkUg2yHx.mjs +0 -92
- package/dist/types-BkUg2yHx.mjs.map +0 -1
- package/dist/types-CAZgq9fO.d.mts +0 -124
- package/dist/types-CAZgq9fO.d.mts.map +0 -1
- package/dist/types-Cd-O03VG.d.mts +0 -363
- package/dist/types-Cd-O03VG.d.mts.map +0 -1
- package/dist/types-Cr-Kg-3D.d.mts +0 -1641
- package/dist/types-Cr-Kg-3D.d.mts.map +0 -1
- package/dist/types-D2fvWd1i.d.mts +0 -359
- package/dist/types-D2fvWd1i.d.mts.map +0 -1
- package/dist/types-D7HUsguf.mjs +0 -77
- package/dist/types-D7HUsguf.mjs.map +0 -1
- package/dist/types-DA1Te3Oy.d.mts +0 -198
- package/dist/types-DA1Te3Oy.d.mts.map +0 -1
- package/dist/types-DhV6Q5bP.mjs +0 -18
- package/dist/types-DhV6Q5bP.mjs.map +0 -1
- package/dist/types-DnnVSl6Y.d.mts +0 -85
- package/dist/types-DnnVSl6Y.d.mts.map +0 -1
- package/dist/types-VT4-BugW.mjs +0 -16
- package/dist/types-VT4-BugW.mjs.map +0 -1
- package/dist/upload-attempts-B68ZGNu9.mjs +0 -32
- package/dist/upload-attempts-B68ZGNu9.mjs.map +0 -1
- package/dist/url-pattern-CCeZMGOh.mjs +0 -33
- package/dist/url-pattern-CCeZMGOh.mjs.map +0 -1
- package/dist/user-DDSL-sGw.mjs +0 -171
- package/dist/user-DDSL-sGw.mjs.map +0 -1
- package/dist/utils-DdhmO9w9.mjs +0 -439
- package/dist/utils-DdhmO9w9.mjs.map +0 -1
- package/dist/validate-CHosPEhs.mjs +0 -453
- package/dist/validate-CHosPEhs.mjs.map +0 -1
- package/dist/validate-CM79QXYE.d.mts +0 -432
- package/dist/validate-CM79QXYE.d.mts.map +0 -1
- package/dist/validation-BFecQczG.mjs +0 -218
- package/dist/validation-BFecQczG.mjs.map +0 -1
- package/dist/version-BRZXVifl.mjs +0 -9
- package/dist/version-BRZXVifl.mjs.map +0 -1
- package/dist/version-DUQDHgrf.mjs +0 -7
- package/dist/version-DUQDHgrf.mjs.map +0 -1
- package/dist/widgets-WdV72brA.mjs +0 -126
- package/dist/widgets-WdV72brA.mjs.map +0 -1
- package/dist/wxr-taxonomies-BHTdqkmZ.d.mts +0 -34
- package/dist/wxr-taxonomies-BHTdqkmZ.d.mts.map +0 -1
- package/dist/wxr-taxonomies-DP6ESgMN.mjs +0 -372
- package/dist/wxr-taxonomies-DP6ESgMN.mjs.map +0 -1
- package/dist/zod-generator-D6uE4WiS.mjs +0 -389
- package/dist/zod-generator-D6uE4WiS.mjs.map +0 -1
- package/src/astro/routes/api/relations/[id]/translations.ts +0 -33
- /package/dist/{api-tokens-CE1ECIg0.mjs → api-tokens-FaG1zPsj.mjs} +0 -0
- /package/dist/{ssrf-DpBH7aOt.mjs → ssrf-DZOt8Qof.mjs} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations-Cfaa1fDl.mjs","names":["isRecord"],"sources":["../src/api/handlers/validate-references.ts","../src/api/handlers/relations.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { requestCached } from \"../../request-cache.js\";\nimport type { ApiResult } from \"../types.js\";\n\nexport interface ReferenceFieldConstraints {\n\t/** Field slug — how the entry API addresses the selection. */\n\tslug: string;\n\t/** Relation slug the field binds to. */\n\trelation: string;\n\t/** Relation id, for the reads that address the link table directly. */\n\trelationId: string | null;\n\t/** Which end of the relation the field's own collection sits on. */\n\trelationSide: \"parent\" | \"child\";\n\t/** How many entries this side of the relation may hold. `null` is unlimited. */\n\tmaxSelected: number | null;\n\t/** How many this field's selection may hand each entry it selects. */\n\tmaxOpposite: number | null;\n\trequired: boolean;\n}\n\nfunction validationError(message: string): ApiResult<never> {\n\treturn { success: false, error: { code: \"VALIDATION_ERROR\", message } };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/**\n * Every reference field on a collection that is bound to a relation, by field\n * slug — the key the entry API takes a selection under.\n */\nexport function referenceFieldConstraints(\n\tdb: Kysely<Database>,\n\tcollection: string,\n): Promise<Map<string, ReferenceFieldConstraints>> {\n\treturn requestCached(`reference-field-constraints:${collection}`, async () => {\n\t\tconst fields = await db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.innerJoin(\"_emdash_collections\", \"_emdash_collections.id\", \"_emdash_fields.collection_id\")\n\t\t\t.select([\"_emdash_fields.slug\", \"_emdash_fields.required\", \"_emdash_fields.validation\"])\n\t\t\t.where(\"_emdash_collections.slug\", \"=\", collection)\n\t\t\t.where(\"_emdash_fields.type\", \"=\", \"reference\")\n\t\t\t.execute();\n\n\t\t// Cardinality lives on the relation, not the field: with both ends of a\n\t\t// relation bindable, two fields carrying their own limits could disagree\n\t\t// about one edge set. The field contributes only which end it views.\n\t\tconst relations = await db\n\t\t\t.selectFrom(\"_emdash_relations\")\n\t\t\t.select([\"id\", \"slug\", \"max_children_per_parent\", \"max_parents_per_child\"])\n\t\t\t.execute();\n\t\tconst limits = new Map(relations.map((r) => [r.slug, r]));\n\n\t\tconst constraints = new Map<string, ReferenceFieldConstraints>();\n\t\tfor (const field of fields) {\n\t\t\tif (!field.validation) continue;\n\n\t\t\tlet parsed: unknown;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(field.validation);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isRecord(parsed) || typeof parsed.relation !== \"string\") continue;\n\n\t\t\tconst relation = limits.get(parsed.relation);\n\t\t\tconst relationSide = parsed.relationSide === \"child\" ? \"child\" : \"parent\";\n\t\t\tconst onChildSide = relationSide === \"child\";\n\n\t\t\tconstraints.set(field.slug, {\n\t\t\t\tslug: field.slug,\n\t\t\t\trelation: parsed.relation,\n\t\t\t\trelationId: relation?.id ?? null,\n\t\t\t\trelationSide,\n\t\t\t\tmaxSelected: onChildSide\n\t\t\t\t\t? (relation?.max_parents_per_child ?? null)\n\t\t\t\t\t: (relation?.max_children_per_parent ?? null),\n\t\t\t\tmaxOpposite: onChildSide\n\t\t\t\t\t? (relation?.max_children_per_parent ?? null)\n\t\t\t\t\t: (relation?.max_parents_per_child ?? null),\n\t\t\t\trequired: field.required === 1,\n\t\t\t});\n\t\t}\n\t\treturn constraints;\n\t});\n}\n\n/**\n * The field viewing one end of a relation, for the relation-scoped routes, which\n * address a relation rather than a field. At most one field binds each\n * (relation, side), so this is unambiguous.\n */\nexport function constraintsForRelationSide(\n\tconstraints: Map<string, ReferenceFieldConstraints>,\n\trelation: string,\n\tside: \"parent\" | \"child\",\n): ReferenceFieldConstraints | undefined {\n\tfor (const field of constraints.values()) {\n\t\tif (field.relation === relation && field.relationSide === side) return field;\n\t}\n\treturn undefined;\n}\n\nexport function validateReferenceSelection(\n\tconstraints: ReferenceFieldConstraints,\n\tchildIds: string[],\n): ApiResult<true> {\n\tconst max = constraints.maxSelected;\n\tif (max !== null && childIds.length > max) {\n\t\treturn validationError(\n\t\t\tmax === 1\n\t\t\t\t? `Field '${constraints.slug}' accepts a single reference, received ${childIds.length}.`\n\t\t\t\t: `Field '${constraints.slug}' accepts at most ${max} references, received ${childIds.length}.`,\n\t\t);\n\t}\n\tif (constraints.required && childIds.length === 0) {\n\t\treturn validationError(\n\t\t\t`Field '${constraints.slug}' is required and must reference at least one entry.`,\n\t\t);\n\t}\n\treturn { success: true, data: true };\n}\n\nexport async function validateRequiredReferencesPresent(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\treferences: Record<string, string[]> | undefined,\n\ttranslationOf: string | undefined,\n): Promise<ApiResult<true>> {\n\t// References belong to a translation group, so a new locale row inherits\n\t// the source group's existing edges when its payload omits them.\n\tif (translationOf) return { success: true, data: true };\n\n\tconst constraints = await referenceFieldConstraints(db, collection);\n\tfor (const field of constraints.values()) {\n\t\tif (!field.required) continue;\n\t\tif (references && Object.hasOwn(references, field.slug)) continue;\n\t\treturn validationError(\n\t\t\t`Field '${field.slug}' is required and must reference at least one entry.`,\n\t\t);\n\t}\n\treturn { success: true, data: true };\n}\n","import type { Kysely } from \"kysely\";\n\nimport { ContentRepository } from \"../../database/repositories/content.js\";\nimport {\n\tRelationRepository,\n\ttype ContentReference,\n\ttype CreateRelationInput,\n\ttype Relation,\n\ttype UpdateRelationInput,\n} from \"../../database/repositories/relation.js\";\nimport { RevisionRepository } from \"../../database/repositories/revision.js\";\nimport { InvalidCursorError } from \"../../database/repositories/types.js\";\nimport type { ContentItem } from \"../../database/repositories/types.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { invalidateCollectionCache } from \"../../object-cache/index.js\";\nimport {\n\tpageStagedGroups,\n\treadStagedReferences,\n\ttype PageOfGroups,\n} from \"../../references/staged.js\";\nimport { requestCached } from \"../../request-cache.js\";\nimport { invalidateSchemaCache } from \"../../schema/index.js\";\nimport { SchemaRegistry } from \"../../schema/registry.js\";\nimport type { ApiResult } from \"../types.js\";\nimport {\n\tconstraintsForRelationSide,\n\treferenceFieldConstraints,\n\tvalidateReferenceSelection,\n\ttype ReferenceFieldConstraints,\n} from \"./validate-references.js\";\n\n/** Map an edge-read failure: a bad pagination cursor is a 400 client error,\n * everything else is the generic 500-shaped reference-read error. */\nfunction referencesGetError(error: unknown): ApiResult<never> {\n\tif (error instanceof InvalidCursorError) {\n\t\treturn { success: false, error: { code: \"INVALID_CURSOR\", message: error.message } };\n\t}\n\treturn {\n\t\tsuccess: false,\n\t\terror: { code: \"REFERENCES_GET_ERROR\", message: \"Failed to get references\" },\n\t};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** True for SQLite UNIQUE / Postgres unique_violation messages (matches the\n * fingerprint used in the content handlers). Narrow enough not to catch NOT\n * NULL / CHECK violations whose messages also say \"constraint\". */\nfunction isUniqueViolation(error: unknown): boolean {\n\tconst message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\treturn message.includes(\"unique constraint failed\") || message.includes(\"duplicate key\");\n}\n\nexport async function handleRelationCreate(\n\tdb: Kysely<Database>,\n\tinput: CreateRelationInput,\n): Promise<ApiResult<{ relation: Relation }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\n\t\t// Invariant: a relation must point at collections that exist. There is no\n\t\t// SQL FK (the edge endpoints are content translation_groups, which\n\t\t// precludes one), so a ghost collection would yield a\n\t\t// structurally-valid-but-permanently-useless relation.\n\t\tconst registry = new SchemaRegistry(db);\n\t\tfor (const collection of [input.parentCollection, input.childCollection]) {\n\t\t\tif (!(await registry.getCollection(collection))) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"COLLECTION_NOT_FOUND\",\n\t\t\t\t\t\tmessage: `Collection '${collection}' not found`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst relation = await repo.create(input);\n\t\treturn { success: true, data: { relation } };\n\t} catch (error) {\n\t\tif (isUniqueViolation(error)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: \"A relation with this slug already exists\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"RELATION_CREATE_ERROR\", message: \"Failed to create relation\" },\n\t\t};\n\t}\n}\n\n/**\n * A relation plus what deleting it would take with it: the reference fields\n * that view it, and how many links it holds. Both delete dialogs enumerate\n * these before the user confirms.\n */\nexport interface RelationWithUsage extends Relation {\n\tboundFields: BoundField[];\n\tlinkCount: number;\n}\n\nexport async function handleRelationGet(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<{ relation: RelationWithUsage }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\t\tconst relation = await repo.findById(id);\n\t\tif (!relation) {\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\t\t}\n\t\tconst [boundFields, edgeCounts] = await Promise.all([\n\t\t\tfieldsBoundToRelation(db, relation.slug),\n\t\t\trepo.countEdgesByRelation(),\n\t\t]);\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { relation: { ...relation, boundFields, linkCount: edgeCounts.get(relation.id) ?? 0 } },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"RELATION_GET_ERROR\", message: \"Failed to get relation\" },\n\t\t};\n\t}\n}\n\n/**\n * List relations, optionally only those `collection` takes part in.\n *\n * The admin's relation picker filters this way: a reference field can only bind\n * to a relation with its own collection on one end.\n */\nexport async function handleRelationList(\n\tdb: Kysely<Database>,\n\topts: { collection?: string } = {},\n): Promise<ApiResult<{ relations: RelationWithUsage[] }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\t\tconst [relations, boundByRelation, edgeCounts] = await Promise.all([\n\t\t\topts.collection ? repo.findForCollection(opts.collection) : repo.list(),\n\t\t\tfieldsBoundByRelation(db),\n\t\t\trepo.countEdgesByRelation(),\n\t\t]);\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\trelations: relations.map((relation) => ({\n\t\t\t\t\t...relation,\n\t\t\t\t\tboundFields: boundByRelation.get(relation.slug) ?? [],\n\t\t\t\t\tlinkCount: edgeCounts.get(relation.id) ?? 0,\n\t\t\t\t})),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"RELATION_LIST_ERROR\", message: \"Failed to list relations\" },\n\t\t};\n\t}\n}\n\nexport async function handleRelationUpdate(\n\tdb: Kysely<Database>,\n\tid: string,\n\tinput: UpdateRelationInput,\n): Promise<ApiResult<{ relation: Relation }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\t\tconst relation = await repo.update(id, input);\n\t\tif (!relation) {\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\t\t}\n\t\treturn { success: true, data: { relation } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"RELATION_UPDATE_ERROR\", message: \"Failed to update relation\" },\n\t\t};\n\t}\n}\n\n/** A reference field that views a relation, and which end it views it from. */\nexport interface BoundField {\n\tcollectionSlug: string;\n\tfieldSlug: string;\n\tside: \"parent\" | \"child\";\n}\n\n/**\n * Every reference field on the site, grouped by the slug of the relation it\n * binds.\n *\n * Filtered in JS rather than through `json_extract`: `_emdash_fields` holds one\n * row per field on the whole site, and the reference-typed subset of that is\n * small enough that a scan beats a dialect-specific JSON path. Grouping the\n * whole set in one pass also keeps the relations list to a single scan instead\n * of one per row.\n */\nexport async function fieldsBoundByRelation(\n\tdb: Kysely<Database>,\n): Promise<Map<string, BoundField[]>> {\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.innerJoin(\"_emdash_collections\", \"_emdash_collections.id\", \"_emdash_fields.collection_id\")\n\t\t.select([\n\t\t\t\"_emdash_fields.slug as fieldSlug\",\n\t\t\t\"_emdash_collections.slug as collectionSlug\",\n\t\t\t\"_emdash_fields.validation as validation\",\n\t\t])\n\t\t.where(\"_emdash_fields.type\", \"=\", \"reference\")\n\t\t.execute();\n\n\tconst byRelation = new Map<string, BoundField[]>();\n\tfor (const row of rows) {\n\t\tif (!row.validation) continue;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(row.validation);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isRecord(parsed) || typeof parsed.relation !== \"string\") continue;\n\t\tconst bound: BoundField = {\n\t\t\tcollectionSlug: row.collectionSlug,\n\t\t\tfieldSlug: row.fieldSlug,\n\t\t\tside: parsed.relationSide === \"child\" ? \"child\" : \"parent\",\n\t\t};\n\t\tconst list = byRelation.get(parsed.relation);\n\t\tif (list) list.push(bound);\n\t\telse byRelation.set(parsed.relation, [bound]);\n\t}\n\treturn byRelation;\n}\n\n/** Every reference field bound to one relation, across both of its ends. */\nexport async function fieldsBoundToRelation(\n\tdb: Kysely<Database>,\n\trelationSlug: string,\n): Promise<BoundField[]> {\n\treturn (await fieldsBoundByRelation(db)).get(relationSlug) ?? [];\n}\n\n/**\n * Delete a relation, the reference fields bound to it, and its edges.\n *\n * A relation is only ever deleted deliberately — from the relations admin page,\n * or by the checkbox on a field delete — and it cannot leave a field pointing at\n * nothing, so the fields go with it either way. Callers show the count first;\n * `deletedFields` reports what actually went.\n *\n * Order matters: fields first, then the relation row and its edges. On D1\n * `withTransaction` degrades to sequential statements, so an interrupted run\n * leaves a relation with fewer bound fields — visible on the relations page and\n * finishable — rather than fields pointing at a relation that no longer exists.\n */\nexport async function handleRelationDelete(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<{ deleted: true; deletedFields: string[] }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\t\tconst relation = await repo.findById(id);\n\t\tif (!relation) {\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\t\t}\n\n\t\tconst bound = await fieldsBoundToRelation(db, relation.slug);\n\n\t\tconst deleted = await withTransaction(db, async (trx) => {\n\t\t\tconst registry = new SchemaRegistry(trx);\n\t\t\tfor (const field of bound) {\n\t\t\t\tawait registry.deleteField(field.collectionSlug, field.fieldSlug);\n\t\t\t}\n\t\t\treturn new RelationRepository(trx).delete(id);\n\t\t});\n\n\t\tif (!deleted) {\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\t\t}\n\n\t\tfor (const collection of new Set(bound.map((f) => f.collectionSlug))) {\n\t\t\tinvalidateCollectionCache(collection);\n\t\t\tinvalidateSchemaCache(collection);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tdeleted: true,\n\t\t\t\tdeletedFields: bound.map((f) => `${f.collectionSlug}.${f.fieldSlug}`),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Relation delete error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"RELATION_DELETE_ERROR\", message: \"Failed to delete relation\" },\n\t\t};\n\t}\n}\n\nexport type EntryRef = {\n\tid: string;\n\tslug: string | null;\n\tcollection: string;\n\t/**\n\t * Display label sourced from the collection's configured `titleField`, then\n\t * `title`, then `name` — `null` when none is set, leaving the client to fall\n\t * back to slug/id. Mirrors the admin's `getEntryTitle`.\n\t */\n\ttitle: string | null;\n\t/** The actual locale of the resolved variant — see `pickVariant`. */\n\tlocale: string | null;\n\t/**\n\t * The edge's target: the translation group `id` was resolved from. Stable\n\t * across locales, unlike `id`, so callers comparing a ref against a content\n\t * row (the admin's picker) match the entry rather than one of its variants.\n\t */\n\ttranslationGroup: string | null;\n\tsortOrder?: number;\n};\n\n/**\n * Display title for a resolved entry: the collection's configured `titleField`,\n * then `title`, then `name`, else null.\n */\nfunction entryTitle(data: Record<string, unknown>, titleField?: string): string | null {\n\tif (titleField) {\n\t\tconst configured = data[titleField];\n\t\tif (typeof configured === \"string\" && configured.length > 0) return configured;\n\t}\n\tif (typeof data.title === \"string\" && data.title.length > 0) return data.title;\n\tif (typeof data.name === \"string\" && data.name.length > 0) return data.name;\n\treturn null;\n}\n\n/**\n * The collection's configured `titleField`, memoized for the request: a single\n * content read hydrates every reference field, and several of them commonly\n * target the same collection.\n */\nexport async function getReferenceTitleField(\n\tdb: Kysely<Database>,\n\tcollection: string,\n): Promise<string | undefined> {\n\treturn requestCached(`reference-title-field:${collection}`, async () => {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"title_field\")\n\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t.executeTakeFirst();\n\t\treturn row?.title_field ?? undefined;\n\t});\n}\n\n/** Resolve a relation from an id OR its slug. */\nasync function resolveRelation(\n\trepo: RelationRepository,\n\tidOrSlug: string,\n): Promise<Relation | null> {\n\treturn (await repo.findById(idOrSlug)) ?? (await repo.findBySlug(idOrSlug));\n}\n\n/**\n * Pick the locale variant matching `locale`, falling back to the first entry\n * (lowest locale code). The fallback is intentional — an edge is keyed by\n * `translation_group`, so a referenced entry that exists only in another locale\n * is still a real reference — but the returned ref carries the variant's actual\n * `locale` so callers never mistake a fallback for the requested locale.\n */\nfunction pickVariant(items: ContentItem[], locale: string | null): ContentItem | undefined {\n\treturn items.find((i) => i.locale === locale) ?? items[0];\n}\n\n/**\n * Resolve edge groups to loadable entries in `collection` at `locale`.\n * Dangling groups (no surviving entry) are skipped — cleanup is a later slice.\n *\n * All groups are loaded in one batched query (chunked at `SQL_BATCH_SIZE`)\n * rather than a `findTranslations` per edge, so a parent with N children costs\n * a constant number of queries, not N+1. Edge order (the caller's `sort_order`)\n * is preserved by iterating `edges`.\n *\n * `includeDrafts` is false for callers without `content:read_drafts`: the load\n * is restricted to published entries so a draft/scheduled entry referenced by an\n * edge is skipped exactly like a dangling one, never leaking its id/slug/locale.\n */\nexport function resolveEntries(\n\tcontent: ContentRepository,\n\tcollection: string,\n\tedges: ContentReference[],\n\tpick: (e: ContentReference) => string,\n\tlocale: string | null,\n\tincludeDrafts: boolean,\n\ttitleField?: string,\n): Promise<EntryRef[]> {\n\treturn resolveGroupSelection(\n\t\tcontent,\n\t\tcollection,\n\t\tedges.map((edge) => ({ group: pick(edge), sortOrder: edge.sortOrder })),\n\t\tlocale,\n\t\tincludeDrafts,\n\t\ttitleField,\n\t);\n}\n\n/**\n * `resolveEntries` for a selection that has no links behind it yet — a pending\n * one staged in a draft revision, which is already a list of translation groups\n * in the order the editor chose. Position stands in for the `sort_order` the\n * links will carry once it is published.\n */\nexport function resolveEntryGroups(\n\tcontent: ContentRepository,\n\tcollection: string,\n\tgroups: string[],\n\tlocale: string | null,\n\tincludeDrafts: boolean,\n\ttitleField?: string,\n): Promise<EntryRef[]> {\n\treturn resolveGroupSelection(\n\t\tcontent,\n\t\tcollection,\n\t\tgroups.map((group, index) => ({ group, sortOrder: index })),\n\t\tlocale,\n\t\tincludeDrafts,\n\t\ttitleField,\n\t);\n}\n\nasync function resolveGroupSelection(\n\tcontent: ContentRepository,\n\tcollection: string,\n\tselection: Array<{ group: string; sortOrder: number }>,\n\tlocale: string | null,\n\tincludeDrafts: boolean,\n\ttitleField?: string,\n): Promise<EntryRef[]> {\n\tconst all = await content.findTranslationsForGroups(\n\t\tcollection,\n\t\tselection.map((entry) => entry.group),\n\t\t{ publishedOnly: !includeDrafts },\n\t);\n\n\t// Group the flat variant list by translation_group so each selected entry can\n\t// pick its own locale variant.\n\tconst variantsByGroup = new Map<string, ContentItem[]>();\n\tfor (const item of all) {\n\t\tif (item.translationGroup == null) continue;\n\t\tconst list = variantsByGroup.get(item.translationGroup);\n\t\tif (list) list.push(item);\n\t\telse variantsByGroup.set(item.translationGroup, [item]);\n\t}\n\n\tconst refs: EntryRef[] = [];\n\tfor (const selected of selection) {\n\t\tconst variants = variantsByGroup.get(selected.group);\n\t\tif (!variants) continue;\n\t\tconst entry = pickVariant(variants, locale);\n\t\tif (!entry) continue;\n\t\trefs.push({\n\t\t\tid: entry.id,\n\t\t\tslug: entry.slug,\n\t\t\tcollection,\n\t\t\ttitle: entryTitle(entry.data, titleField),\n\t\t\tlocale: entry.locale,\n\t\t\ttranslationGroup: entry.translationGroup,\n\t\t\tsortOrder: selected.sortOrder,\n\t\t});\n\t}\n\treturn refs;\n}\n\n/** Pagination inputs for the edge read endpoints. */\nexport type PageOptions = { limit?: number; cursor?: string };\n\n/**\n * The page of a pending selection this end stages, when there is one.\n *\n * These routes address a relation and a side; a staged selection is keyed by the\n * field slug the entry API takes it under, so the field viewing this end is what\n * connects them. Returns undefined when the caller may not see drafts, the entry\n * has no draft, or the draft stages nothing for this field — all of which mean\n * the published links are the answer.\n */\nasync function stagedPageFor(\n\tdb: Kysely<Database>,\n\toptions: {\n\t\tcollection: string;\n\t\trelationSlug: string;\n\t\tside: \"parent\" | \"child\";\n\t\tdraftRevisionId: string | null | undefined;\n\t\tincludeDrafts: boolean;\n\t\tpage: PageOptions;\n\t},\n): Promise<PageOfGroups | undefined> {\n\tif (!options.includeDrafts || !options.draftRevisionId) return undefined;\n\n\tconst field = constraintsForRelationSide(\n\t\tawait referenceFieldConstraints(db, options.collection),\n\t\toptions.relationSlug,\n\t\toptions.side,\n\t);\n\tif (!field) return undefined;\n\n\tconst revision = await new RevisionRepository(db).findById(options.draftRevisionId);\n\tconst groups = readStagedReferences(revision?.data)?.[field.slug];\n\tif (!groups) return undefined;\n\n\treturn pageStagedGroups(groups, options.page);\n}\n\nexport async function handleReferenceChildrenGet(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\trelation: string,\n\tpage: PageOptions = {},\n\tincludeDrafts = false,\n): Promise<ApiResult<{ children: EntryRef[]; nextCursor?: string }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\t\tconst content = new ContentRepository(db);\n\n\t\tconst rel = await resolveRelation(repo, relation);\n\t\tif (!rel)\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\t\tif (collection !== rel.parentCollection) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Entry is not the parent side of this relation\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst entry = await content.findByIdOrSlug(collection, entryId);\n\t\t// A caller without draft access must not anchor on a non-published entry —\n\t\t// return NOT_FOUND (not 403) so they can't probe draft ids by status code,\n\t\t// mirroring the single-item content read.\n\t\tif (!entry?.translationGroup || (!includeDrafts && entry.status !== \"published\")) {\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Content entry not found\" } };\n\t\t}\n\n\t\tconst titleField = await getReferenceTitleField(db, rel.childCollection);\n\n\t\t// A pending selection replaces the published one for a caller that may see\n\t\t// it, page for page — the entry read answers the same way, and a walk that\n\t\t// started on one of them must not continue on the other.\n\t\tconst staged = await stagedPageFor(db, {\n\t\t\tcollection,\n\t\t\trelationSlug: rel.slug,\n\t\t\tside: \"parent\",\n\t\t\tdraftRevisionId: entry.draftRevisionId,\n\t\t\tincludeDrafts,\n\t\t\tpage,\n\t\t});\n\t\tif (staged) {\n\t\t\tconst children = await resolveEntryGroups(\n\t\t\t\tcontent,\n\t\t\t\trel.childCollection,\n\t\t\t\tstaged.groups,\n\t\t\t\tentry.locale,\n\t\t\t\tincludeDrafts,\n\t\t\t\ttitleField,\n\t\t\t);\n\t\t\treturn { success: true, data: { children, nextCursor: staged.nextCursor } };\n\t\t}\n\n\t\tconst edges = await repo.getChildrenPage(rel.id, entry.translationGroup, page);\n\t\tconst children = await resolveEntries(\n\t\t\tcontent,\n\t\t\trel.childCollection,\n\t\t\tedges.items,\n\t\t\t(e) => e.childGroup,\n\t\t\tentry.locale,\n\t\t\tincludeDrafts,\n\t\t\ttitleField,\n\t\t);\n\t\treturn { success: true, data: { children, nextCursor: edges.nextCursor } };\n\t} catch (error) {\n\t\treturn referencesGetError(error);\n\t}\n}\n\n/**\n * A selection expressed in the identifiers the link table actually stores:\n * `translation_group` on both ends, because an edge names a thing rather than\n * one locale's row of it.\n */\nexport interface ReferenceSelectionWrite extends ResolvedReferenceTargets {\n\t/** The selecting entry's own translation group. */\n\tentryGroup: string;\n}\n\n/**\n * The same selection, resolved before the entry it belongs to is known — which\n * is what a create has to do, since its row does not exist until it is written\n * and, on D1, writing it cannot be taken back.\n */\nexport interface ResolvedReferenceTargets {\n\t/** The relation, by id or slug — `setChildren` / `setParents` take either. */\n\trelation: string;\n\t/** The end of the relation the selecting entry sits on. */\n\tside: \"parent\" | \"child\";\n\t/** The selected entries' translation groups, in the caller's order. */\n\tgroups: string[];\n}\n\n/**\n * Resolve a bound field's selection to what the link table stores, without\n * writing anything and without needing the selecting entry to exist.\n *\n * The field decides the relation and the end it views: a field on the `parent`\n * end selects children, one on the `child` end selects the parents pointing at\n * it. `entryGroup` is the selecting entry's own translation group when it has\n * one — the far-side count discounts links it already holds, since replacing a\n * selection with itself adds nothing. A new entry passes `null`: it holds no\n * links yet.\n */\nasync function resolveReferenceTargets(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tconstraints: ReferenceFieldConstraints,\n\tselectedIds: string[],\n\tentryGroup: string | null,\n): Promise<ApiResult<ResolvedReferenceTargets>> {\n\tconst repo = new RelationRepository(db);\n\tconst content = new ContentRepository(db);\n\tconst side = constraints.relationSide;\n\n\tconst rel = await resolveRelation(repo, constraints.relation);\n\tif (!rel) return { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\n\tconst ownCollection = side === \"parent\" ? rel.parentCollection : rel.childCollection;\n\tconst otherCollection = side === \"parent\" ? rel.childCollection : rel.parentCollection;\n\tif (collection !== ownCollection) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\tmessage: `Entry is not the ${side} side of this relation`,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst selection = validateReferenceSelection(constraints, selectedIds);\n\tif (!selection.success) return selection;\n\n\t// Resolve every selected entry within the relation's other collection in one\n\t// batch (constant queries, not an N+1 of point lookups for a set up to 1000).\n\t// An id that does not resolve there fails collection-agreement (invariant 3);\n\t// order is preserved by iterating the caller's ids.\n\tconst resolved = await content.findManyByIdOrSlug(otherCollection, selectedIds);\n\tconst groups: string[] = [];\n\tfor (const selectedId of selectedIds) {\n\t\tconst other = resolved.get(selectedId);\n\t\tif (!other?.translationGroup) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `${side === \"parent\" ? \"Child\" : \"Parent\"} entry '${selectedId}' not found in ${otherCollection}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tgroups.push(other.translationGroup);\n\t}\n\n\tconst far = await validateOppositeSideLimit(db, {\n\t\trelationId: rel.id,\n\t\tfarLimit: side === \"parent\" ? rel.maxParentsPerChild : rel.maxChildrenPerParent,\n\t\tside,\n\t\tentryGroup,\n\t\tgroups,\n\t\tlabels: selectedIds,\n\t});\n\tif (!far.success) return far;\n\n\treturn { success: true, data: { relation: rel.id, side, groups } };\n}\n\n/**\n * Check what a selection does to the *other* end's cardinality.\n *\n * Cardinality binds both ends, but only one end ever selects: a field on the\n * parent side that hands a child its second parent breaks `maxParentsPerChild`\n * even though the parent's own limit is untouched. Links the selecting entry\n * already holds are discounted — replacing a selection with itself adds nothing.\n *\n * `labels` names each group the way the caller addressed it, so the message\n * quotes the id someone typed. Publication has only the groups, which it stages;\n * it quotes those.\n */\nexport async function validateOppositeSideLimit(\n\tdb: Kysely<Database>,\n\toptions: {\n\t\trelationId: string;\n\t\t/** The opposite end's limit. `null` is unlimited — nothing to check. */\n\t\tfarLimit: number | null;\n\t\t/** The end the selecting entry sits on. */\n\t\tside: \"parent\" | \"child\";\n\t\tentryGroup: string | null;\n\t\tgroups: string[];\n\t\tlabels?: string[];\n\t},\n): Promise<ApiResult<true>> {\n\tconst { farLimit, side, groups } = options;\n\tif (farLimit === null || groups.length === 0) return { success: true, data: true };\n\n\tconst counts = await new RelationRepository(db).countEdgesByGroup(\n\t\toptions.relationId,\n\t\tside === \"parent\" ? \"child\" : \"parent\",\n\t\tgroups,\n\t\toptions.entryGroup,\n\t);\n\tfor (const [index, group] of groups.entries()) {\n\t\tif ((counts.get(group) ?? 0) + 1 <= farLimit) continue;\n\t\tconst farSide = side === \"parent\" ? \"parent\" : \"child\";\n\t\tconst label = options.labels?.[index] ?? group;\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\tmessage:\n\t\t\t\t\tfarLimit === 1\n\t\t\t\t\t\t? `Entry '${label}' already has a ${farSide} on this relation, which allows one.`\n\t\t\t\t\t\t: `Entry '${label}' already has the maximum of ${farLimit} ${farSide} entries on this relation.`,\n\t\t\t},\n\t\t};\n\t}\n\treturn { success: true, data: true };\n}\n\n/**\n * {@link resolveReferenceTargets} for an entry that already exists, anchoring the\n * selection on that entry's translation group.\n */\nasync function resolveReferenceSide(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tconstraints: ReferenceFieldConstraints,\n\tselectedIds: string[],\n): Promise<ApiResult<ReferenceSelectionWrite>> {\n\tconst entry = await new ContentRepository(db).findByIdOrSlug(collection, entryId);\n\tif (!entry?.translationGroup) {\n\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Content entry not found\" } };\n\t}\n\n\tconst resolved = await resolveReferenceTargets(\n\t\tdb,\n\t\tcollection,\n\t\tconstraints,\n\t\tselectedIds,\n\t\tentry.translationGroup,\n\t);\n\tif (!resolved.success) return resolved;\n\treturn { success: true, data: { ...resolved.data, entryGroup: entry.translationGroup } };\n}\n\n/**\n * Replace one end's links from an already-resolved selection.\n *\n * Resolution has already proved the relation, the entry and every selected entry\n * exist, so nothing here fails on the caller's input. What it can still hit is\n * the far end's limit closing between resolving and writing — a slot another\n * request took in that window. The write refuses the edge rather than exceeding\n * the limit, and throws so the save reports it; the throw carries an `apiError`\n * the content handlers map to a response.\n */\nexport async function writeReferenceSelection(\n\tdb: Kysely<Database>,\n\tselection: ReferenceSelectionWrite,\n): Promise<void> {\n\tconst repo = new RelationRepository(db);\n\tconst rel = await resolveRelation(repo, selection.relation);\n\tif (!rel) return;\n\n\tconst rejected =\n\t\tselection.side === \"parent\"\n\t\t\t? await repo.setChildren(rel.id, selection.entryGroup, selection.groups)\n\t\t\t: await repo.setParents(rel.id, selection.entryGroup, selection.groups);\n\tif (rejected.length > 0) {\n\t\tconst farSide = selection.side === \"parent\" ? \"parent\" : \"child\";\n\t\tthrow Object.assign(\n\t\t\tnew Error(\n\t\t\t\t`Entry '${rejected[0]}' already has the maximum number of ${farSide} entries on this relation.`,\n\t\t\t),\n\t\t\t{ apiError: { code: \"VALIDATION_ERROR\" } },\n\t\t);\n\t}\n\n\t// A link is part of what a cached entry renders on both ends — a parent's\n\t// selection and a child's backlinks — while no row of either collection has\n\t// changed. Nothing else here would tell those snapshots they are stale.\n\tinvalidateCollectionCache(rel.parentCollection);\n\tif (rel.childCollection !== rel.parentCollection) {\n\t\tinvalidateCollectionCache(rel.childCollection);\n\t}\n}\n\n/**\n * Replace a reference field's selection on one entry, addressed by field slug.\n *\n * The field decides which relation and which end: a field bound to the parent\n * end replaces that entry's children, one bound to the child end replaces the\n * parents pointing at it. This is what the entry create/update body writes\n * through, so a site addresses a selection the way it addresses any other field.\n */\nexport async function setReferenceSelection(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tfieldSlug: string,\n\tselectedIds: string[],\n): Promise<ApiResult<{ relationId: string; entryGroup: string }>> {\n\tconst resolved = await resolveReferenceSelection(db, collection, entryId, fieldSlug, selectedIds);\n\tif (!resolved.success) return resolved;\n\tawait writeReferenceSelection(db, resolved.data);\n\treturn {\n\t\tsuccess: true,\n\t\tdata: { relationId: resolved.data.relation, entryGroup: resolved.data.entryGroup },\n\t};\n}\n\n/**\n * `setReferenceSelection` up to but not including the write.\n *\n * A collection that keeps drafts stages the result in its draft revision instead\n * of writing links, so a save reports a bad id or an over-long selection exactly\n * as a direct write would, and publication has nothing left to resolve.\n */\nexport async function resolveReferenceSelection(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tfieldSlug: string,\n\tselectedIds: string[],\n): Promise<ApiResult<ReferenceSelectionWrite>> {\n\tconst constraints = (await referenceFieldConstraints(db, collection)).get(fieldSlug);\n\tif (!constraints) return notAReferenceField(fieldSlug, collection);\n\treturn resolveReferenceSide(db, collection, entryId, constraints, selectedIds);\n}\n\n/**\n * `resolveReferenceSelection` for an entry that does not exist yet.\n *\n * A create resolves its whole payload before it writes its row, because on D1\n * that row is committed the moment it is written: a selection that cannot be\n * resolved has to be refused while there is still nothing to take back.\n *\n * `entryGroup` is the group the new row will join — a new translation inherits\n * the source's — or null when the row starts a group of its own and so holds no\n * links yet.\n */\nexport async function resolveReferenceSelectionTargets(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tfieldSlug: string,\n\tselectedIds: string[],\n\tentryGroup: string | null,\n): Promise<ApiResult<ResolvedReferenceTargets>> {\n\tconst constraints = (await referenceFieldConstraints(db, collection)).get(fieldSlug);\n\tif (!constraints) return notAReferenceField(fieldSlug, collection);\n\treturn resolveReferenceTargets(db, collection, constraints, selectedIds, entryGroup);\n}\n\nfunction notAReferenceField(fieldSlug: string, collection: string): ApiResult<never> {\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Field '${fieldSlug}' is not a reference field on ${collection}`,\n\t\t},\n\t};\n}\n\nexport async function handleReferenceParentsGet(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\trelation: string,\n\tpage: PageOptions = {},\n\tincludeDrafts = false,\n): Promise<ApiResult<{ parents: EntryRef[]; nextCursor?: string }>> {\n\ttry {\n\t\tconst repo = new RelationRepository(db);\n\t\tconst content = new ContentRepository(db);\n\n\t\tconst rel = await resolveRelation(repo, relation);\n\t\tif (!rel)\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Relation not found\" } };\n\t\tif (collection !== rel.childCollection) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Entry is not the child side of this relation\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst entry = await content.findByIdOrSlug(collection, entryId);\n\t\t// Same draft-anchor guard as the children read: a non-draft-reader anchoring\n\t\t// on an unpublished entry gets NOT_FOUND, not its backlinks.\n\t\tif (!entry?.translationGroup || (!includeDrafts && entry.status !== \"published\")) {\n\t\t\treturn { success: false, error: { code: \"NOT_FOUND\", message: \"Content entry not found\" } };\n\t\t}\n\n\t\tconst titleField = await getReferenceTitleField(db, rel.parentCollection);\n\n\t\t// As on the children read: a field bound to this end stages its pending\n\t\t// selection in the entry's draft, and that is what a drafts-aware caller\n\t\t// pages through.\n\t\tconst staged = await stagedPageFor(db, {\n\t\t\tcollection,\n\t\t\trelationSlug: rel.slug,\n\t\t\tside: \"child\",\n\t\t\tdraftRevisionId: entry.draftRevisionId,\n\t\t\tincludeDrafts,\n\t\t\tpage,\n\t\t});\n\t\tif (staged) {\n\t\t\tconst parents = await resolveEntryGroups(\n\t\t\t\tcontent,\n\t\t\t\trel.parentCollection,\n\t\t\t\tstaged.groups,\n\t\t\t\tentry.locale,\n\t\t\t\tincludeDrafts,\n\t\t\t\ttitleField,\n\t\t\t);\n\t\t\treturn { success: true, data: { parents, nextCursor: staged.nextCursor } };\n\t\t}\n\n\t\tconst edges = await repo.getParentsPage(rel.id, entry.translationGroup, page);\n\t\tconst parents = await resolveEntries(\n\t\t\tcontent,\n\t\t\trel.parentCollection,\n\t\t\tedges.items,\n\t\t\t(e) => e.parentGroup,\n\t\t\tentry.locale,\n\t\t\tincludeDrafts,\n\t\t\ttitleField,\n\t\t);\n\t\treturn { success: true, data: { parents, nextCursor: edges.nextCursor } };\n\t} catch (error) {\n\t\treturn referencesGetError(error);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { getRequestContext } from "./request-context.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/request-cache.ts
|
|
4
|
+
const STORE_KEY = Symbol.for("emdash:request-cache");
|
|
5
|
+
const g = globalThis;
|
|
6
|
+
const store = g[STORE_KEY] ?? (() => {
|
|
7
|
+
const wm = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
g[STORE_KEY] = wm;
|
|
9
|
+
return wm;
|
|
10
|
+
})();
|
|
11
|
+
/**
|
|
12
|
+
* Return a cached result for `key` if one exists in the current
|
|
13
|
+
* request scope, otherwise call `fn`, cache its promise, and return it.
|
|
14
|
+
*
|
|
15
|
+
* Caches the *promise*, not the resolved value, so concurrent calls
|
|
16
|
+
* with the same key share a single in-flight query.
|
|
17
|
+
*/
|
|
18
|
+
function requestCached(key, fn) {
|
|
19
|
+
const ctx = getRequestContext();
|
|
20
|
+
if (!ctx) return fn();
|
|
21
|
+
let cache = store.get(ctx);
|
|
22
|
+
if (!cache) {
|
|
23
|
+
cache = /* @__PURE__ */ new Map();
|
|
24
|
+
store.set(ctx, cache);
|
|
25
|
+
}
|
|
26
|
+
const existing = cache.get(key);
|
|
27
|
+
if (existing) {
|
|
28
|
+
if (ctx.metrics) ctx.metrics.cacheHits += 1;
|
|
29
|
+
return existing;
|
|
30
|
+
}
|
|
31
|
+
if (ctx.metrics) ctx.metrics.cacheMisses += 1;
|
|
32
|
+
const promise = Promise.resolve().then(fn).catch((error) => {
|
|
33
|
+
cache.delete(key);
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
|
36
|
+
cache.set(key, promise);
|
|
37
|
+
return promise;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Look up an entry in the request-scoped cache without inserting one.
|
|
41
|
+
*
|
|
42
|
+
* Returns the in-flight or resolved promise if the key exists in the
|
|
43
|
+
* current request, otherwise `undefined`. Callers can use this to
|
|
44
|
+
* opportunistically satisfy a narrower query (e.g. `getSiteSetting("seo")`)
|
|
45
|
+
* from a broader one (`getSiteSettings()`) that's already been loaded
|
|
46
|
+
* by a parent template — avoiding a redundant round-trip.
|
|
47
|
+
*
|
|
48
|
+
* No-ops outside a request context.
|
|
49
|
+
*/
|
|
50
|
+
function peekRequestCache(key) {
|
|
51
|
+
const ctx = getRequestContext();
|
|
52
|
+
if (!ctx) return void 0;
|
|
53
|
+
return store.get(ctx)?.get(key);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pre-populate the request-scoped cache with a resolved value.
|
|
57
|
+
*
|
|
58
|
+
* Internal helper shared between hydration paths (taxonomy terms,
|
|
59
|
+
* bylines, etc.) that already have the data in hand and want downstream
|
|
60
|
+
* callers using `requestCached(key, ...)` to skip the database entirely.
|
|
61
|
+
* Not exported from the package entrypoint — keep it internal until we
|
|
62
|
+
* have a documented plugin/extension surface for hydration.
|
|
63
|
+
*
|
|
64
|
+
* No-ops outside a request context (local dev without ALS).
|
|
65
|
+
*
|
|
66
|
+
* Does not overwrite an existing entry — if a query for this key is already
|
|
67
|
+
* in flight, its promise wins.
|
|
68
|
+
*/
|
|
69
|
+
function setRequestCacheEntry(key, value) {
|
|
70
|
+
const ctx = getRequestContext();
|
|
71
|
+
if (!ctx) return;
|
|
72
|
+
let cache = store.get(ctx);
|
|
73
|
+
if (!cache) {
|
|
74
|
+
cache = /* @__PURE__ */ new Map();
|
|
75
|
+
store.set(ctx, cache);
|
|
76
|
+
}
|
|
77
|
+
if (cache.has(key)) return;
|
|
78
|
+
cache.set(key, Promise.resolve(value));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Remove a key from the request-scoped cache.
|
|
82
|
+
*
|
|
83
|
+
* Used by write paths that need to invalidate a downstream read cache —
|
|
84
|
+
* `setRequestCacheEntry` deliberately doesn't overwrite, and `requestCached`
|
|
85
|
+
* returns the cached promise even after the underlying data has changed.
|
|
86
|
+
* Without an explicit clear, a `read → write → read` sequence in a single
|
|
87
|
+
* request can return stale data on the second read.
|
|
88
|
+
*
|
|
89
|
+
* Concrete case: `BylineRepository.update` invalidates
|
|
90
|
+
* `byline-field-group-values:${translation_group}` after writing a
|
|
91
|
+
* non-translatable custom-field value, so the post-update `findById`
|
|
92
|
+
* (and any later reads in the same request) see the fresh value.
|
|
93
|
+
*
|
|
94
|
+
* No-ops outside a request context.
|
|
95
|
+
*/
|
|
96
|
+
function clearRequestCacheEntry(key) {
|
|
97
|
+
const ctx = getRequestContext();
|
|
98
|
+
if (!ctx) return;
|
|
99
|
+
store.get(ctx)?.delete(key);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { setRequestCacheEntry as i, peekRequestCache as n, requestCached as r, clearRequestCacheEntry as t };
|
|
104
|
+
//# sourceMappingURL=request-cache-iHRlHx1d.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-cache-iHRlHx1d.mjs","names":[],"sources":["../src/request-cache.ts"],"sourcesContent":["/**\n * Per-request query cache\n *\n * Deduplicates identical database queries within a single page render.\n * Uses the ALS request context as a WeakMap key so the cache is\n * automatically GC'd when the request completes.\n *\n * When no request context is available (e.g. local dev without D1\n * replicas), queries bypass the cache — local SQLite is fast enough\n * that deduplication doesn't matter.\n *\n * The WeakMap is stored on globalThis with a Symbol key to guarantee\n * a singleton even when bundlers duplicate this module across chunks\n * (same pattern as request-context.ts).\n */\n\nimport type { EmDashRequestContext } from \"./request-context.js\";\nimport { getRequestContext } from \"./request-context.js\";\n\ntype CacheStore = WeakMap<EmDashRequestContext, Map<string, Promise<unknown>>>;\n\nconst STORE_KEY = Symbol.for(\"emdash:request-cache\");\nconst g = globalThis as Record<symbol, unknown>;\nconst store: CacheStore =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[STORE_KEY] as CacheStore | undefined) ??\n\t(() => {\n\t\tconst wm: CacheStore = new WeakMap();\n\t\tg[STORE_KEY] = wm;\n\t\treturn wm;\n\t})();\n\n/**\n * Return a cached result for `key` if one exists in the current\n * request scope, otherwise call `fn`, cache its promise, and return it.\n *\n * Caches the *promise*, not the resolved value, so concurrent calls\n * with the same key share a single in-flight query.\n */\nexport function requestCached<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\tconst ctx = getRequestContext();\n\tif (!ctx) return fn();\n\n\tlet cache = store.get(ctx);\n\tif (!cache) {\n\t\tcache = new Map();\n\t\tstore.set(ctx, cache);\n\t}\n\n\tconst existing = cache.get(key);\n\tif (existing) {\n\t\tif (ctx.metrics) ctx.metrics.cacheHits += 1;\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- heterogeneous cache; key namespacing guarantees the stored promise resolves to T\n\t\treturn existing as Promise<T>;\n\t}\n\tif (ctx.metrics) ctx.metrics.cacheMisses += 1;\n\n\tconst promise = Promise.resolve()\n\t\t.then(fn)\n\t\t.catch((error) => {\n\t\t\tcache.delete(key);\n\t\t\tthrow error;\n\t\t});\n\tcache.set(key, promise);\n\treturn promise;\n}\n\n/**\n * Look up an entry in the request-scoped cache without inserting one.\n *\n * Returns the in-flight or resolved promise if the key exists in the\n * current request, otherwise `undefined`. Callers can use this to\n * opportunistically satisfy a narrower query (e.g. `getSiteSetting(\"seo\")`)\n * from a broader one (`getSiteSettings()`) that's already been loaded\n * by a parent template — avoiding a redundant round-trip.\n *\n * No-ops outside a request context.\n */\nexport function peekRequestCache<T>(key: string): Promise<T> | undefined {\n\tconst ctx = getRequestContext();\n\tif (!ctx) return undefined;\n\tconst cache = store.get(ctx);\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- heterogeneous cache; caller is responsible for using a T-compatible key\n\treturn cache?.get(key) as Promise<T> | undefined;\n}\n\n/**\n * Pre-populate the request-scoped cache with a resolved value.\n *\n * Internal helper shared between hydration paths (taxonomy terms,\n * bylines, etc.) that already have the data in hand and want downstream\n * callers using `requestCached(key, ...)` to skip the database entirely.\n * Not exported from the package entrypoint — keep it internal until we\n * have a documented plugin/extension surface for hydration.\n *\n * No-ops outside a request context (local dev without ALS).\n *\n * Does not overwrite an existing entry — if a query for this key is already\n * in flight, its promise wins.\n */\nexport function setRequestCacheEntry<T>(key: string, value: T): void {\n\tconst ctx = getRequestContext();\n\tif (!ctx) return;\n\n\tlet cache = store.get(ctx);\n\tif (!cache) {\n\t\tcache = new Map();\n\t\tstore.set(ctx, cache);\n\t}\n\n\tif (cache.has(key)) return;\n\tcache.set(key, Promise.resolve(value));\n}\n\n/**\n * Remove a key from the request-scoped cache.\n *\n * Used by write paths that need to invalidate a downstream read cache —\n * `setRequestCacheEntry` deliberately doesn't overwrite, and `requestCached`\n * returns the cached promise even after the underlying data has changed.\n * Without an explicit clear, a `read → write → read` sequence in a single\n * request can return stale data on the second read.\n *\n * Concrete case: `BylineRepository.update` invalidates\n * `byline-field-group-values:${translation_group}` after writing a\n * non-translatable custom-field value, so the post-update `findById`\n * (and any later reads in the same request) see the fresh value.\n *\n * No-ops outside a request context.\n */\nexport function clearRequestCacheEntry(key: string): void {\n\tconst ctx = getRequestContext();\n\tif (!ctx) return;\n\tconst cache = store.get(ctx);\n\tcache?.delete(key);\n}\n"],"mappings":";;;AAqBA,MAAM,YAAY,OAAO,IAAI,uBAAuB;AACpD,MAAM,IAAI;AACV,MAAM,QAEJ,EAAE,qBACI;CACN,MAAM,qBAAiB,IAAI,SAAS;AACpC,GAAE,aAAa;AACf,QAAO;IACJ;;;;;;;;AASL,SAAgB,cAAiB,KAAa,IAAkC;CAC/E,MAAM,MAAM,mBAAmB;AAC/B,KAAI,CAAC,IAAK,QAAO,IAAI;CAErB,IAAI,QAAQ,MAAM,IAAI,IAAI;AAC1B,KAAI,CAAC,OAAO;AACX,0BAAQ,IAAI,KAAK;AACjB,QAAM,IAAI,KAAK,MAAM;;CAGtB,MAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,KAAI,UAAU;AACb,MAAI,IAAI,QAAS,KAAI,QAAQ,aAAa;AAE1C,SAAO;;AAER,KAAI,IAAI,QAAS,KAAI,QAAQ,eAAe;CAE5C,MAAM,UAAU,QAAQ,SAAS,CAC/B,KAAK,GAAG,CACR,OAAO,UAAU;AACjB,QAAM,OAAO,IAAI;AACjB,QAAM;GACL;AACH,OAAM,IAAI,KAAK,QAAQ;AACvB,QAAO;;;;;;;;;;;;;AAcR,SAAgB,iBAAoB,KAAqC;CACxE,MAAM,MAAM,mBAAmB;AAC/B,KAAI,CAAC,IAAK,QAAO;AAGjB,QAFc,MAAM,IAAI,IAAI,EAEd,IAAI,IAAI;;;;;;;;;;;;;;;;AAiBvB,SAAgB,qBAAwB,KAAa,OAAgB;CACpE,MAAM,MAAM,mBAAmB;AAC/B,KAAI,CAAC,IAAK;CAEV,IAAI,QAAQ,MAAM,IAAI,IAAI;AAC1B,KAAI,CAAC,OAAO;AACX,0BAAQ,IAAI,KAAK;AACjB,QAAM,IAAI,KAAK,MAAM;;AAGtB,KAAI,MAAM,IAAI,IAAI,CAAE;AACpB,OAAM,IAAI,KAAK,QAAQ,QAAQ,MAAM,CAAC;;;;;;;;;;;;;;;;;;AAmBvC,SAAgB,uBAAuB,KAAmB;CACzD,MAAM,MAAM,mBAAmB;AAC/B,KAAI,CAAC,IAAK;AAEV,CADc,MAAM,IAAI,IAAI,EACrB,OAAO,IAAI"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { n as normalizeTrustedHeaders, t as getTrustedProxyHeaders } from "./trusted-proxy-B6ECOZJc.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/request-meta.ts
|
|
4
|
+
/**
|
|
5
|
+
* Loose validation for IPv4 and IPv6 addresses.
|
|
6
|
+
* Accepts digits, hex chars, dots, and colons — rejects anything else
|
|
7
|
+
* (e.g. HTML tags, scripts, or other non-IP garbage in spoofed headers).
|
|
8
|
+
*/
|
|
9
|
+
const IP_PATTERN = /^[\da-fA-F.:]+$/;
|
|
10
|
+
/**
|
|
11
|
+
* Extract the first IP from an X-Forwarded-For header value.
|
|
12
|
+
* The header may contain a comma-separated list of IPs; the first
|
|
13
|
+
* entry is the original client IP.
|
|
14
|
+
*
|
|
15
|
+
* Returns null if the extracted value doesn't look like an IP address.
|
|
16
|
+
*/
|
|
17
|
+
function parseFirstForwardedIp(header) {
|
|
18
|
+
const trimmed = header.split(",")[0]?.trim();
|
|
19
|
+
if (!trimmed) return null;
|
|
20
|
+
return IP_PATTERN.test(trimmed) ? trimmed : null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read an IP from an operator-declared trusted header. XFF-style headers
|
|
24
|
+
* (any name ending in `forwarded-for`) are parsed as comma-separated lists
|
|
25
|
+
* and the first entry is used; everything else is treated as a single
|
|
26
|
+
* trimmed value.
|
|
27
|
+
*/
|
|
28
|
+
function readIpFromHeader(headers, name) {
|
|
29
|
+
const value = headers.get(name);
|
|
30
|
+
if (!value) return null;
|
|
31
|
+
if (name.endsWith("forwarded-for")) return parseFirstForwardedIp(value);
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (!trimmed) return null;
|
|
34
|
+
return IP_PATTERN.test(trimmed) ? trimmed : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get the Cloudflare `cf` object from the request, if present.
|
|
38
|
+
* Returns undefined when not running on Cloudflare Workers.
|
|
39
|
+
*/
|
|
40
|
+
function getCfObject(request) {
|
|
41
|
+
return request.cf;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract geographic information from the Cloudflare `cf` object
|
|
45
|
+
* attached to the request. Returns null when not running on CF Workers.
|
|
46
|
+
*/
|
|
47
|
+
function extractGeo(cf) {
|
|
48
|
+
if (!cf) return null;
|
|
49
|
+
const country = cf.country ?? null;
|
|
50
|
+
const region = cf.region ?? null;
|
|
51
|
+
const city = cf.city ?? null;
|
|
52
|
+
if (country === null && region === null && city === null) return null;
|
|
53
|
+
return {
|
|
54
|
+
country,
|
|
55
|
+
region,
|
|
56
|
+
city
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extract normalized request metadata from a Request object.
|
|
61
|
+
*
|
|
62
|
+
* IP resolution order:
|
|
63
|
+
* 1. `CF-Connecting-IP` — trusted only when a `cf` object is present on the
|
|
64
|
+
* request. CF edge overwrites any client-supplied value, so this is the
|
|
65
|
+
* cryptographically trustworthy path on Workers. Operator-declared
|
|
66
|
+
* trusted headers cannot override it.
|
|
67
|
+
* 2. `X-Forwarded-For` first entry — trusted only with a `cf` object.
|
|
68
|
+
* 3. Operator-declared trusted proxy headers (from `config.trustedProxyHeaders`
|
|
69
|
+
* or the `EMDASH_TRUSTED_PROXY_HEADERS` env var), tried in order. Used as
|
|
70
|
+
* the primary source off-CF and as a fill-in on CF.
|
|
71
|
+
* 4. `null`
|
|
72
|
+
*
|
|
73
|
+
* The second argument accepts either the EmDash config or a pre-resolved
|
|
74
|
+
* list of trusted headers, so callers that already have the list don't have
|
|
75
|
+
* to round-trip through the config every request.
|
|
76
|
+
*/
|
|
77
|
+
function extractRequestMeta(request, configOrTrustedHeaders) {
|
|
78
|
+
const headers = request.headers;
|
|
79
|
+
const cf = getCfObject(request);
|
|
80
|
+
const trusted = resolveTrustedHeaders(configOrTrustedHeaders);
|
|
81
|
+
let ip = null;
|
|
82
|
+
if (cf) {
|
|
83
|
+
const cfIp = headers.get("cf-connecting-ip")?.trim();
|
|
84
|
+
if (cfIp && IP_PATTERN.test(cfIp)) ip = cfIp;
|
|
85
|
+
if (!ip) {
|
|
86
|
+
const xff = headers.get("x-forwarded-for");
|
|
87
|
+
ip = xff ? parseFirstForwardedIp(xff) : null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!ip) for (const name of trusted) {
|
|
91
|
+
const value = readIpFromHeader(headers, name);
|
|
92
|
+
if (value) {
|
|
93
|
+
ip = value;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const userAgent = headers.get("user-agent")?.trim() || null;
|
|
98
|
+
const referer = headers.get("referer")?.trim() || null;
|
|
99
|
+
const geo = extractGeo(cf);
|
|
100
|
+
return {
|
|
101
|
+
ip,
|
|
102
|
+
userAgent,
|
|
103
|
+
referer,
|
|
104
|
+
geo
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function resolveTrustedHeaders(value) {
|
|
108
|
+
if (Array.isArray(value)) return normalizeTrustedHeaders(value);
|
|
109
|
+
return getTrustedProxyHeaders(value);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Headers that must never cross the RPC boundary to sandboxed plugins.
|
|
113
|
+
* Session tokens, auth credentials, and infrastructure headers are stripped
|
|
114
|
+
* to prevent malicious plugins from exfiltrating sensitive data.
|
|
115
|
+
*/
|
|
116
|
+
const SANDBOX_STRIPPED_HEADERS = new Set([
|
|
117
|
+
"cookie",
|
|
118
|
+
"set-cookie",
|
|
119
|
+
"authorization",
|
|
120
|
+
"proxy-authorization",
|
|
121
|
+
"cf-access-jwt-assertion",
|
|
122
|
+
"cf-access-client-id",
|
|
123
|
+
"cf-access-client-secret",
|
|
124
|
+
"x-emdash-request"
|
|
125
|
+
]);
|
|
126
|
+
/**
|
|
127
|
+
* Copy request headers into a plain object, stripping sensitive headers
|
|
128
|
+
* that must not be exposed to sandboxed plugin code.
|
|
129
|
+
*/
|
|
130
|
+
function sanitizeHeadersForSandbox(headers, allowedHeaders) {
|
|
131
|
+
const safe = {};
|
|
132
|
+
const allowed = allowedHeaders ? new Set(allowedHeaders.map((header) => header.toLowerCase())) : null;
|
|
133
|
+
headers.forEach((value, key) => {
|
|
134
|
+
if (!SANDBOX_STRIPPED_HEADERS.has(key) && !key.startsWith("cf-access-") && (allowed === null || allowed.has(key))) safe[key] = value;
|
|
135
|
+
});
|
|
136
|
+
return safe;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
export { sanitizeHeadersForSandbox as n, extractRequestMeta as t };
|
|
141
|
+
//# sourceMappingURL=request-meta-D47mqyev.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-meta-D47mqyev.mjs","names":[],"sources":["../src/plugins/request-meta.ts"],"sourcesContent":["/**\n * Request Metadata Extraction\n *\n * Extracts normalized metadata (IP, user agent, referer, geo) from\n * incoming requests. Used by plugin route handlers to access request\n * context without touching raw headers.\n *\n */\n\nimport type { EmDashConfig } from \"../astro/integration/runtime.js\";\nimport { getTrustedProxyHeaders, normalizeTrustedHeaders } from \"../auth/trusted-proxy.js\";\nimport type { GeoInfo, RequestMeta } from \"./types.js\";\n\n/**\n * Cloudflare Workers `cf` object shape (subset we use).\n * Present on requests when running on Cloudflare Workers.\n */\ninterface CfProperties {\n\tcountry?: string;\n\tregion?: string;\n\tcity?: string;\n}\n\n/**\n * Loose validation for IPv4 and IPv6 addresses.\n * Accepts digits, hex chars, dots, and colons — rejects anything else\n * (e.g. HTML tags, scripts, or other non-IP garbage in spoofed headers).\n */\nconst IP_PATTERN = /^[\\da-fA-F.:]+$/;\n\n/**\n * Extract the first IP from an X-Forwarded-For header value.\n * The header may contain a comma-separated list of IPs; the first\n * entry is the original client IP.\n *\n * Returns null if the extracted value doesn't look like an IP address.\n */\nfunction parseFirstForwardedIp(header: string): string | null {\n\tconst first = header.split(\",\")[0];\n\tconst trimmed = first?.trim();\n\tif (!trimmed) return null;\n\treturn IP_PATTERN.test(trimmed) ? trimmed : null;\n}\n\n/**\n * Read an IP from an operator-declared trusted header. XFF-style headers\n * (any name ending in `forwarded-for`) are parsed as comma-separated lists\n * and the first entry is used; everything else is treated as a single\n * trimmed value.\n */\nfunction readIpFromHeader(headers: Headers, name: string): string | null {\n\tconst value = headers.get(name);\n\tif (!value) return null;\n\tif (name.endsWith(\"forwarded-for\")) {\n\t\treturn parseFirstForwardedIp(value);\n\t}\n\tconst trimmed = value.trim();\n\tif (!trimmed) return null;\n\treturn IP_PATTERN.test(trimmed) ? trimmed : null;\n}\n\n/**\n * Get the Cloudflare `cf` object from the request, if present.\n * Returns undefined when not running on Cloudflare Workers.\n */\nfunction getCfObject(request: Request): CfProperties | undefined {\n\treturn (request as unknown as { cf?: CfProperties }).cf;\n}\n\n/**\n * Extract geographic information from the Cloudflare `cf` object\n * attached to the request. Returns null when not running on CF Workers.\n */\nfunction extractGeo(cf: CfProperties | undefined): GeoInfo | null {\n\tif (!cf) return null;\n\n\tconst country = cf.country ?? null;\n\tconst region = cf.region ?? null;\n\tconst city = cf.city ?? null;\n\n\t// Only return geo if at least one field is populated\n\tif (country === null && region === null && city === null) return null;\n\n\treturn { country, region, city };\n}\n\n/**\n * Extract normalized request metadata from a Request object.\n *\n * IP resolution order:\n * 1. `CF-Connecting-IP` — trusted only when a `cf` object is present on the\n * request. CF edge overwrites any client-supplied value, so this is the\n * cryptographically trustworthy path on Workers. Operator-declared\n * trusted headers cannot override it.\n * 2. `X-Forwarded-For` first entry — trusted only with a `cf` object.\n * 3. Operator-declared trusted proxy headers (from `config.trustedProxyHeaders`\n * or the `EMDASH_TRUSTED_PROXY_HEADERS` env var), tried in order. Used as\n * the primary source off-CF and as a fill-in on CF.\n * 4. `null`\n *\n * The second argument accepts either the EmDash config or a pre-resolved\n * list of trusted headers, so callers that already have the list don't have\n * to round-trip through the config every request.\n */\nexport function extractRequestMeta(\n\trequest: Request,\n\tconfigOrTrustedHeaders?: EmDashConfig | null | { trustedProxyHeaders?: string[] } | string[],\n): RequestMeta {\n\tconst headers = request.headers;\n\tconst cf = getCfObject(request);\n\tconst trusted = resolveTrustedHeaders(configOrTrustedHeaders);\n\n\tlet ip: string | null = null;\n\n\t// On Cloudflare, prefer the cryptographically trustworthy headers first.\n\tif (cf) {\n\t\tconst cfIp = headers.get(\"cf-connecting-ip\")?.trim();\n\t\tif (cfIp && IP_PATTERN.test(cfIp)) {\n\t\t\tip = cfIp;\n\t\t}\n\t\tif (!ip) {\n\t\t\tconst xff = headers.get(\"x-forwarded-for\");\n\t\t\tip = xff ? parseFirstForwardedIp(xff) : null;\n\t\t}\n\t}\n\n\t// Fall through to operator-declared trusted headers. On CF this fills\n\t// in when the CF headers are absent; off-CF it's the primary source.\n\tif (!ip) {\n\t\tfor (const name of trusted) {\n\t\t\tconst value = readIpFromHeader(headers, name);\n\t\t\tif (value) {\n\t\t\t\tip = value;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst userAgent = headers.get(\"user-agent\")?.trim() || null;\n\tconst referer = headers.get(\"referer\")?.trim() || null;\n\tconst geo = extractGeo(cf);\n\n\treturn { ip, userAgent, referer, geo };\n}\n\nfunction resolveTrustedHeaders(\n\tvalue: EmDashConfig | null | { trustedProxyHeaders?: string[] } | string[] | undefined,\n): string[] {\n\tif (Array.isArray(value)) {\n\t\t// Apply the same RFC 7230 validation the config/env path does so a\n\t\t// caller passing a pre-resolved list with bad entries can't crash\n\t\t// `Headers.get()` downstream.\n\t\treturn normalizeTrustedHeaders(value);\n\t}\n\treturn getTrustedProxyHeaders(value);\n}\n\n// =============================================================================\n// Header Sanitization for Sandbox\n// =============================================================================\n\n/**\n * Headers that must never cross the RPC boundary to sandboxed plugins.\n * Session tokens, auth credentials, and infrastructure headers are stripped\n * to prevent malicious plugins from exfiltrating sensitive data.\n */\nconst SANDBOX_STRIPPED_HEADERS = new Set([\n\t\"cookie\",\n\t\"set-cookie\",\n\t\"authorization\",\n\t\"proxy-authorization\",\n\t\"cf-access-jwt-assertion\",\n\t\"cf-access-client-id\",\n\t\"cf-access-client-secret\",\n\t\"x-emdash-request\",\n]);\n\n/**\n * Copy request headers into a plain object, stripping sensitive headers\n * that must not be exposed to sandboxed plugin code.\n */\nexport function sanitizeHeadersForSandbox(\n\theaders: Headers,\n\tallowedHeaders?: readonly string[],\n): Record<string, string> {\n\tconst safe: Record<string, string> = {};\n\tconst allowed = allowedHeaders\n\t\t? new Set(allowedHeaders.map((header) => header.toLowerCase()))\n\t\t: null;\n\theaders.forEach((value, key) => {\n\t\tif (\n\t\t\t!SANDBOX_STRIPPED_HEADERS.has(key) &&\n\t\t\t!key.startsWith(\"cf-access-\") &&\n\t\t\t(allowed === null || allowed.has(key))\n\t\t) {\n\t\t\tsafe[key] = value;\n\t\t}\n\t});\n\treturn safe;\n}\n"],"mappings":";;;;;;;;AA4BA,MAAM,aAAa;;;;;;;;AASnB,SAAS,sBAAsB,QAA+B;CAE7D,MAAM,UADQ,OAAO,MAAM,IAAI,CAAC,IACT,MAAM;AAC7B,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,WAAW,KAAK,QAAQ,GAAG,UAAU;;;;;;;;AAS7C,SAAS,iBAAiB,SAAkB,MAA6B;CACxE,MAAM,QAAQ,QAAQ,IAAI,KAAK;AAC/B,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,KAAK,SAAS,gBAAgB,CACjC,QAAO,sBAAsB,MAAM;CAEpC,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,WAAW,KAAK,QAAQ,GAAG,UAAU;;;;;;AAO7C,SAAS,YAAY,SAA4C;AAChE,QAAQ,QAA6C;;;;;;AAOtD,SAAS,WAAW,IAA8C;AACjE,KAAI,CAAC,GAAI,QAAO;CAEhB,MAAM,UAAU,GAAG,WAAW;CAC9B,MAAM,SAAS,GAAG,UAAU;CAC5B,MAAM,OAAO,GAAG,QAAQ;AAGxB,KAAI,YAAY,QAAQ,WAAW,QAAQ,SAAS,KAAM,QAAO;AAEjE,QAAO;EAAE;EAAS;EAAQ;EAAM;;;;;;;;;;;;;;;;;;;;AAqBjC,SAAgB,mBACf,SACA,wBACc;CACd,MAAM,UAAU,QAAQ;CACxB,MAAM,KAAK,YAAY,QAAQ;CAC/B,MAAM,UAAU,sBAAsB,uBAAuB;CAE7D,IAAI,KAAoB;AAGxB,KAAI,IAAI;EACP,MAAM,OAAO,QAAQ,IAAI,mBAAmB,EAAE,MAAM;AACpD,MAAI,QAAQ,WAAW,KAAK,KAAK,CAChC,MAAK;AAEN,MAAI,CAAC,IAAI;GACR,MAAM,MAAM,QAAQ,IAAI,kBAAkB;AAC1C,QAAK,MAAM,sBAAsB,IAAI,GAAG;;;AAM1C,KAAI,CAAC,GACJ,MAAK,MAAM,QAAQ,SAAS;EAC3B,MAAM,QAAQ,iBAAiB,SAAS,KAAK;AAC7C,MAAI,OAAO;AACV,QAAK;AACL;;;CAKH,MAAM,YAAY,QAAQ,IAAI,aAAa,EAAE,MAAM,IAAI;CACvD,MAAM,UAAU,QAAQ,IAAI,UAAU,EAAE,MAAM,IAAI;CAClD,MAAM,MAAM,WAAW,GAAG;AAE1B,QAAO;EAAE;EAAI;EAAW;EAAS;EAAK;;AAGvC,SAAS,sBACR,OACW;AACX,KAAI,MAAM,QAAQ,MAAM,CAIvB,QAAO,wBAAwB,MAAM;AAEtC,QAAO,uBAAuB,MAAM;;;;;;;AAYrC,MAAM,2BAA2B,IAAI,IAAI;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;;;;;AAMF,SAAgB,0BACf,SACA,gBACyB;CACzB,MAAM,OAA+B,EAAE;CACvC,MAAM,UAAU,iBACb,IAAI,IAAI,eAAe,KAAK,WAAW,OAAO,aAAa,CAAC,CAAC,GAC7D;AACH,SAAQ,SAAS,OAAO,QAAQ;AAC/B,MACC,CAAC,yBAAyB,IAAI,IAAI,IAClC,CAAC,IAAI,WAAW,aAAa,KAC5B,YAAY,QAAQ,QAAQ,IAAI,IAAI,EAErC,MAAK,OAAO;GAEZ;AACF,QAAO"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import "./dialect-helpers-7_IqPdh9.mjs";
|
|
2
|
+
import { i as isI18nEnabled, n as getFallbackChain } from "./config-CvmjJcVw.mjs";
|
|
3
|
+
import "./datetime-normalization-PeiEdcRY.mjs";
|
|
4
|
+
import "./after-PcEvMY_6.mjs";
|
|
5
|
+
import "./base64-DvnNgP7e.mjs";
|
|
6
|
+
import "./object-cache-CqWNY5uP.mjs";
|
|
7
|
+
import "./types-aOOxRSx6.mjs";
|
|
8
|
+
import { t as RevisionRepository } from "./revision-j1p4-bOj.mjs";
|
|
9
|
+
import "./request-cache-iHRlHx1d.mjs";
|
|
10
|
+
import { a as pageStagedGroups, c as RelationRepository, n as REFERENCE_PAGE_MAX_LIMIT, o as readStagedReferences, t as REFERENCE_PAGE_LIMIT } from "./staged-C36IWXff.mjs";
|
|
11
|
+
import { c as loadEntriesByGroups, s as getDb } from "./loader-CrRPfkHS.mjs";
|
|
12
|
+
import { n as getReferenceFieldMap } from "./field-map-6m9rF49t.mjs";
|
|
13
|
+
|
|
14
|
+
//#region src/references/resolve.ts
|
|
15
|
+
/**
|
|
16
|
+
* Resolve an entry's reference selections to loadable entries, for the public
|
|
17
|
+
* query path.
|
|
18
|
+
*
|
|
19
|
+
* The cost is bounded and predictable: one link read per selected field, then
|
|
20
|
+
* one entry read per *distinct* target collection — never one per link. A
|
|
21
|
+
* caller that selects no fields issues nothing at all, which is what keeps
|
|
22
|
+
* references off the logged-out hot path of every render that does not ask for
|
|
23
|
+
* them.
|
|
24
|
+
*/
|
|
25
|
+
function pageOptions(query) {
|
|
26
|
+
if (query === true) return { limit: REFERENCE_PAGE_LIMIT };
|
|
27
|
+
const requested = query.limit ?? REFERENCE_PAGE_LIMIT;
|
|
28
|
+
return {
|
|
29
|
+
limit: Math.min(Math.max(requested, 1), REFERENCE_PAGE_MAX_LIMIT),
|
|
30
|
+
cursor: query.cursor
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function localeChainFor(locale) {
|
|
34
|
+
if (locale === null) return [];
|
|
35
|
+
return isI18nEnabled() ? getFallbackChain(locale) : [locale];
|
|
36
|
+
}
|
|
37
|
+
async function resolveReferencePages(options) {
|
|
38
|
+
const fieldMap = await getReferenceFieldMap(options.collection);
|
|
39
|
+
const requested = Object.entries(options.selection).filter((entry) => entry[1] !== void 0 && fieldMap.has(entry[0]));
|
|
40
|
+
if (requested.length === 0) return {};
|
|
41
|
+
const db = await getDb();
|
|
42
|
+
const relations = new RelationRepository(db);
|
|
43
|
+
const staged = options.serveDrafts && options.draftRevisionId ? readStagedReferences((await new RevisionRepository(db).findById(options.draftRevisionId))?.data) : void 0;
|
|
44
|
+
const pages = new Map(await Promise.all(requested.map(async ([slug, query]) => {
|
|
45
|
+
const binding = fieldMap.get(slug);
|
|
46
|
+
const page = pageOptions(query);
|
|
47
|
+
const stagedGroups = staged?.[slug];
|
|
48
|
+
if (stagedGroups) return [slug, pageStagedGroups(stagedGroups, page)];
|
|
49
|
+
const links = binding.side === "child" ? await relations.getParentsPageById(binding.relationId, options.entryGroup, page) : await relations.getChildrenPageById(binding.relationId, options.entryGroup, page);
|
|
50
|
+
return [slug, {
|
|
51
|
+
groups: links.items.map((link) => binding.side === "child" ? link.parentGroup : link.childGroup),
|
|
52
|
+
nextCursor: links.nextCursor
|
|
53
|
+
}];
|
|
54
|
+
})));
|
|
55
|
+
const groupsByCollection = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const [slug, page] of pages) {
|
|
57
|
+
const target = fieldMap.get(slug).targetCollection;
|
|
58
|
+
const groups = groupsByCollection.get(target) ?? /* @__PURE__ */ new Set();
|
|
59
|
+
for (const group of page.groups) groups.add(group);
|
|
60
|
+
groupsByCollection.set(target, groups);
|
|
61
|
+
}
|
|
62
|
+
const localeChain = localeChainFor(options.locale);
|
|
63
|
+
const variantsByCollection = /* @__PURE__ */ new Map();
|
|
64
|
+
await Promise.all(Array.from(groupsByCollection, async ([collection, groups]) => {
|
|
65
|
+
const loaded = await loadEntriesByGroups(collection, [...groups], {
|
|
66
|
+
publishedOnly: !options.serveDrafts,
|
|
67
|
+
localeChain
|
|
68
|
+
});
|
|
69
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const entry of loaded) {
|
|
71
|
+
const group = entry.data.translationGroup;
|
|
72
|
+
if (typeof group === "string") byGroup.set(group, entry);
|
|
73
|
+
}
|
|
74
|
+
variantsByCollection.set(collection, byGroup);
|
|
75
|
+
}));
|
|
76
|
+
const resolved = {};
|
|
77
|
+
for (const [slug, page] of pages) {
|
|
78
|
+
const collection = fieldMap.get(slug).targetCollection;
|
|
79
|
+
const byGroup = variantsByCollection.get(collection);
|
|
80
|
+
const entries = [];
|
|
81
|
+
for (const group of page.groups) {
|
|
82
|
+
const variant = byGroup?.get(group);
|
|
83
|
+
if (variant) entries.push(variant);
|
|
84
|
+
}
|
|
85
|
+
resolved[slug] = page.nextCursor ? {
|
|
86
|
+
collection,
|
|
87
|
+
entries,
|
|
88
|
+
nextCursor: page.nextCursor
|
|
89
|
+
} : {
|
|
90
|
+
collection,
|
|
91
|
+
entries
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return resolved;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { resolveReferencePages };
|
|
99
|
+
//# sourceMappingURL=resolve-BPE8sMvx.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-BPE8sMvx.mjs","names":[],"sources":["../src/references/resolve.ts"],"sourcesContent":["/**\n * Resolve an entry's reference selections to loadable entries, for the public\n * query path.\n *\n * The cost is bounded and predictable: one link read per selected field, then\n * one entry read per *distinct* target collection — never one per link. A\n * caller that selects no fields issues nothing at all, which is what keeps\n * references off the logged-out hot path of every render that does not ask for\n * them.\n */\n\nimport { RelationRepository } from \"../database/repositories/relation.js\";\nimport { RevisionRepository } from \"../database/repositories/revision.js\";\nimport { getFallbackChain, isI18nEnabled } from \"../i18n/config.js\";\nimport { getDb, loadEntriesByGroups, type LoadedEntry } from \"../loader.js\";\nimport { getReferenceFieldMap } from \"./field-map.js\";\nimport {\n\tpageStagedGroups,\n\treadStagedReferences,\n\tREFERENCE_PAGE_LIMIT,\n\tREFERENCE_PAGE_MAX_LIMIT,\n\ttype PageOfGroups,\n} from \"./staged.js\";\nimport type { ReferenceQuery, ReferenceSelection } from \"./types.js\";\n\n/** One reference field's resolved page, before the query layer wraps the entries. */\nexport interface ResolvedReferencePage {\n\t/** The collection the entries belong to — what the `edit` proxy is scoped to. */\n\tcollection: string;\n\tentries: LoadedEntry[];\n\tnextCursor?: string;\n}\n\nexport interface ResolveReferencesOptions {\n\t/** The collection the selecting entry belongs to. */\n\tcollection: string;\n\t/** The selecting entry's own translation group. */\n\tentryGroup: string;\n\t/** The locale the parent resolved to, used to pick each target's variant. */\n\tlocale: string | null;\n\t/** The selecting entry's draft revision, when it has one. */\n\tdraftRevisionId?: string;\n\t/**\n\t * Whether this render may see unpublished content. It decides both halves of\n\t * draft visibility: whether a pending selection staged in the draft revision\n\t * replaces the published one, and whether an unpublished target resolves at\n\t * all.\n\t */\n\tserveDrafts: boolean;\n\t/** The fields to resolve, by field slug. */\n\tselection: ReferenceSelection;\n}\n\nfunction pageOptions(query: ReferenceQuery): { limit: number; cursor?: string } {\n\tif (query === true) return { limit: REFERENCE_PAGE_LIMIT };\n\tconst requested = query.limit ?? REFERENCE_PAGE_LIMIT;\n\treturn {\n\t\tlimit: Math.min(Math.max(requested, 1), REFERENCE_PAGE_MAX_LIMIT),\n\t\tcursor: query.cursor,\n\t};\n}\n\nfunction localeChainFor(locale: string | null): string[] {\n\tif (locale === null) return [];\n\treturn isI18nEnabled() ? getFallbackChain(locale) : [locale];\n}\n\nexport async function resolveReferencePages(\n\toptions: ResolveReferencesOptions,\n): Promise<Record<string, ResolvedReferencePage>> {\n\tconst fieldMap = await getReferenceFieldMap(options.collection);\n\tconst requested = Object.entries(options.selection).filter(\n\t\t(entry): entry is [string, ReferenceQuery] => entry[1] !== undefined && fieldMap.has(entry[0]),\n\t);\n\tif (requested.length === 0) return {};\n\n\tconst db = await getDb();\n\tconst relations = new RelationRepository(db);\n\n\tconst staged =\n\t\toptions.serveDrafts && options.draftRevisionId\n\t\t\t? readStagedReferences(\n\t\t\t\t\t(await new RevisionRepository(db).findById(options.draftRevisionId))?.data,\n\t\t\t\t)\n\t\t\t: undefined;\n\n\t// Phase one: each field's page of translation groups, in selection order.\n\t// Concurrent, like phase two — the fields are independent, and awaiting them\n\t// in turn would make N fields N sequential round trips.\n\tconst pages = new Map(\n\t\tawait Promise.all(\n\t\t\trequested.map(async ([slug, query]): Promise<[string, PageOfGroups]> => {\n\t\t\t\tconst binding = fieldMap.get(slug)!;\n\t\t\t\tconst page = pageOptions(query);\n\n\t\t\t\tconst stagedGroups = staged?.[slug];\n\t\t\t\tif (stagedGroups) return [slug, pageStagedGroups(stagedGroups, page)];\n\n\t\t\t\tconst links =\n\t\t\t\t\tbinding.side === \"child\"\n\t\t\t\t\t\t? await relations.getParentsPageById(binding.relationId, options.entryGroup, page)\n\t\t\t\t\t\t: await relations.getChildrenPageById(binding.relationId, options.entryGroup, page);\n\t\t\t\treturn [\n\t\t\t\t\tslug,\n\t\t\t\t\t{\n\t\t\t\t\t\tgroups: links.items.map((link) =>\n\t\t\t\t\t\t\tbinding.side === \"child\" ? link.parentGroup : link.childGroup,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tnextCursor: links.nextCursor,\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t}),\n\t\t),\n\t);\n\n\t// Phase two: one entry read per distinct target collection, however many\n\t// fields point at it.\n\tconst groupsByCollection = new Map<string, Set<string>>();\n\tfor (const [slug, page] of pages) {\n\t\tconst target = fieldMap.get(slug)!.targetCollection;\n\t\tconst groups = groupsByCollection.get(target) ?? new Set<string>();\n\t\tfor (const group of page.groups) groups.add(group);\n\t\tgroupsByCollection.set(target, groups);\n\t}\n\n\tconst localeChain = localeChainFor(options.locale);\n\tconst variantsByCollection = new Map<string, Map<string, LoadedEntry>>();\n\tawait Promise.all(\n\t\tArray.from(groupsByCollection, async ([collection, groups]) => {\n\t\t\tconst loaded = await loadEntriesByGroups(collection, [...groups], {\n\t\t\t\tpublishedOnly: !options.serveDrafts,\n\t\t\t\tlocaleChain,\n\t\t\t});\n\t\t\tconst byGroup = new Map<string, LoadedEntry>();\n\t\t\tfor (const entry of loaded) {\n\t\t\t\tconst group = entry.data.translationGroup;\n\t\t\t\tif (typeof group === \"string\") byGroup.set(group, entry);\n\t\t\t}\n\t\t\tvariantsByCollection.set(collection, byGroup);\n\t\t}),\n\t);\n\n\t// Phase three: rebuild each field in link order. A group with no surviving\n\t// variant — deleted, or unpublished for a render that may not see drafts —\n\t// drops out, exactly as a dangling link does.\n\tconst resolved: Record<string, ResolvedReferencePage> = {};\n\tfor (const [slug, page] of pages) {\n\t\tconst collection = fieldMap.get(slug)!.targetCollection;\n\t\tconst byGroup = variantsByCollection.get(collection);\n\t\tconst entries: LoadedEntry[] = [];\n\t\tfor (const group of page.groups) {\n\t\t\tconst variant = byGroup?.get(group);\n\t\t\tif (variant) entries.push(variant);\n\t\t}\n\t\tresolved[slug] = page.nextCursor\n\t\t\t? { collection, entries, nextCursor: page.nextCursor }\n\t\t\t: { collection, entries };\n\t}\n\treturn resolved;\n}\n"],"mappings":"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"}
|