emdash 0.0.0-b → 0.0.2
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/README.md +87 -43
- package/dist/adapters-BLMa4JGD.d.mts +106 -0
- package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
- package/dist/apply-Bjfq_b4-.mjs +1293 -0
- package/dist/apply-Bjfq_b4-.mjs.map +1 -0
- package/dist/astro/index.d.mts +51 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +1336 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/middleware/auth.d.mts +31 -0
- package/dist/astro/middleware/auth.d.mts.map +1 -0
- package/dist/astro/middleware/auth.mjs +654 -0
- package/dist/astro/middleware/auth.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts +22 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -0
- package/dist/astro/middleware/redirect.mjs +63 -0
- package/dist/astro/middleware/redirect.mjs.map +1 -0
- package/dist/astro/middleware/request-context.d.mts +18 -0
- package/dist/astro/middleware/request-context.d.mts.map +1 -0
- package/dist/astro/middleware/request-context.mjs +1310 -0
- package/dist/astro/middleware/request-context.mjs.map +1 -0
- package/dist/astro/middleware/setup.d.mts +20 -0
- package/dist/astro/middleware/setup.d.mts.map +1 -0
- package/dist/astro/middleware/setup.mjs +47 -0
- package/dist/astro/middleware/setup.mjs.map +1 -0
- package/dist/astro/middleware.d.mts +13 -0
- package/dist/astro/middleware.d.mts.map +1 -0
- package/dist/astro/middleware.mjs +1613 -0
- package/dist/astro/middleware.mjs.map +1 -0
- package/dist/astro/types.d.mts +250 -0
- package/dist/astro/types.d.mts.map +1 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-MBPo9ozB.mjs +59 -0
- package/dist/base64-MBPo9ozB.mjs.map +1 -0
- package/dist/byline-CL847F26.mjs +213 -0
- package/dist/byline-CL847F26.mjs.map +1 -0
- package/dist/bylines-C2a-2TGt.mjs +136 -0
- package/dist/bylines-C2a-2TGt.mjs.map +1 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3909 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/cf-access.d.mts +60 -0
- package/dist/client/cf-access.d.mts.map +1 -0
- package/dist/client/cf-access.mjs +179 -0
- package/dist/client/cf-access.mjs.map +1 -0
- package/dist/client/index.d.mts +398 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +346 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/config-CKE8p9xM.mjs +55 -0
- package/dist/config-CKE8p9xM.mjs.map +1 -0
- package/dist/connection-B4zVnQIa.mjs +40 -0
- package/dist/connection-B4zVnQIa.mjs.map +1 -0
- package/dist/content-D6C2WsZC.mjs +824 -0
- package/dist/content-D6C2WsZC.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/db/libsql.d.mts +11 -0
- package/dist/db/libsql.d.mts.map +1 -0
- package/dist/db/libsql.mjs +17 -0
- package/dist/db/libsql.mjs.map +1 -0
- package/dist/db/postgres.d.mts +11 -0
- package/dist/db/postgres.d.mts.map +1 -0
- package/dist/db/postgres.mjs +30 -0
- package/dist/db/postgres.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +11 -0
- package/dist/db/sqlite.d.mts.map +1 -0
- package/dist/db/sqlite.mjs +16 -0
- package/dist/db/sqlite.mjs.map +1 -0
- package/dist/default-Cyi4aAxu.mjs +81 -0
- package/dist/default-Cyi4aAxu.mjs.map +1 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
- package/dist/error-Cxz0tQeO.mjs +27 -0
- package/dist/error-Cxz0tQeO.mjs.map +1 -0
- package/dist/index-C1xF3OGh.d.mts +4527 -0
- package/dist/index-C1xF3OGh.d.mts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-yOOlckBj.mjs +28 -0
- package/dist/load-yOOlckBj.mjs.map +1 -0
- package/dist/loader-fz8Q_3EO.mjs +447 -0
- package/dist/loader-fz8Q_3EO.mjs.map +1 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
- package/dist/media/index.d.mts +26 -0
- package/dist/media/index.d.mts.map +1 -0
- package/dist/media/index.mjs +55 -0
- package/dist/media/index.mjs.map +1 -0
- package/dist/media/local-runtime.d.mts +39 -0
- package/dist/media/local-runtime.d.mts.map +1 -0
- package/dist/media/local-runtime.mjs +133 -0
- package/dist/media/local-runtime.mjs.map +1 -0
- package/dist/media-DqHVh136.mjs +200 -0
- package/dist/media-DqHVh136.mjs.map +1 -0
- package/dist/mode-C2EzN1uE.mjs +23 -0
- package/dist/mode-C2EzN1uE.mjs.map +1 -0
- package/dist/page/index.d.mts +140 -0
- package/dist/page/index.d.mts.map +1 -0
- package/dist/page/index.mjs +416 -0
- package/dist/page/index.mjs.map +1 -0
- package/dist/placeholder-CmGAmqeO.d.mts +276 -0
- package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
- package/dist/placeholder-SmpOx-_v.mjs +243 -0
- package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
- package/dist/plugin-utils.d.mts +58 -0
- package/dist/plugin-utils.d.mts.map +1 -0
- package/dist/plugin-utils.mjs +78 -0
- package/dist/plugin-utils.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
- package/dist/query-CS_iSj34.mjs +460 -0
- package/dist/query-CS_iSj34.mjs.map +1 -0
- package/dist/redirect-DIfIni3r.mjs +329 -0
- package/dist/redirect-DIfIni3r.mjs.map +1 -0
- package/dist/registry-D_w5HW4G.mjs +863 -0
- package/dist/registry-D_w5HW4G.mjs.map +1 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.d.mts.map +1 -0
- package/dist/request-context.mjs +43 -0
- package/dist/request-context.mjs.map +1 -0
- package/dist/runner-C0hCbYnD.mjs +1412 -0
- package/dist/runner-C0hCbYnD.mjs.map +1 -0
- package/dist/runner-EAtf0ZIe.d.mts +27 -0
- package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
- package/dist/runtime.d.mts +26 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +42 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/search-DG603UrT.mjs +9211 -0
- package/dist/search-DG603UrT.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +70 -0
- package/dist/seo/index.d.mts.map +1 -0
- package/dist/seo/index.mjs +70 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/storage/local.d.mts +39 -0
- package/dist/storage/local.d.mts.map +1 -0
- package/dist/storage/local.mjs +166 -0
- package/dist/storage/local.mjs.map +1 -0
- package/dist/storage/s3.d.mts +32 -0
- package/dist/storage/s3.d.mts.map +1 -0
- package/dist/storage/s3.mjs +175 -0
- package/dist/storage/s3.mjs.map +1 -0
- package/dist/tokens-DpgrkrXK.mjs +171 -0
- package/dist/tokens-DpgrkrXK.mjs.map +1 -0
- package/dist/transport-BFGblqwG.d.mts +42 -0
- package/dist/transport-BFGblqwG.d.mts.map +1 -0
- package/dist/transport-yxiQsi8I.mjs +418 -0
- package/dist/transport-yxiQsi8I.mjs.map +1 -0
- package/dist/types-BRuPJGdV.d.mts +102 -0
- package/dist/types-BRuPJGdV.d.mts.map +1 -0
- package/dist/types-C4-fAxN3.d.mts +182 -0
- package/dist/types-C4-fAxN3.d.mts.map +1 -0
- package/dist/types-CMMN0pNg.mjs +31 -0
- package/dist/types-CMMN0pNg.mjs.map +1 -0
- package/dist/types-CUBbjgmP.mjs +16 -0
- package/dist/types-CUBbjgmP.mjs.map +1 -0
- package/dist/types-DRjfYOEv.d.mts +426 -0
- package/dist/types-DRjfYOEv.d.mts.map +1 -0
- package/dist/types-DY5zk5HN.mjs +73 -0
- package/dist/types-DY5zk5HN.mjs.map +1 -0
- package/dist/types-DaNLHo_T.d.mts +184 -0
- package/dist/types-DaNLHo_T.d.mts.map +1 -0
- package/dist/types-DvhsUmSJ.d.mts +1111 -0
- package/dist/types-DvhsUmSJ.d.mts.map +1 -0
- package/dist/validate-CpBtVMsD.d.mts +378 -0
- package/dist/validate-CpBtVMsD.d.mts.map +1 -0
- package/dist/validate-CqRJb_xU.mjs +97 -0
- package/dist/validate-CqRJb_xU.mjs.map +1 -0
- package/dist/validate-O7PWmlnq.mjs +328 -0
- package/dist/validate-O7PWmlnq.mjs.map +1 -0
- package/locals.d.ts +46 -0
- package/package.json +233 -19
- package/src/api/authorize.ts +63 -0
- package/src/api/csrf.ts +48 -0
- package/src/api/error.ts +99 -0
- package/src/api/errors.ts +445 -0
- package/src/api/escape.ts +9 -0
- package/src/api/handlers/api-tokens.ts +240 -0
- package/src/api/handlers/comments.ts +314 -0
- package/src/api/handlers/content.ts +1315 -0
- package/src/api/handlers/dashboard.ts +205 -0
- package/src/api/handlers/device-flow.ts +684 -0
- package/src/api/handlers/index.ts +163 -0
- package/src/api/handlers/manifest.ts +158 -0
- package/src/api/handlers/marketplace.ts +930 -0
- package/src/api/handlers/media.ts +207 -0
- package/src/api/handlers/menus.ts +493 -0
- package/src/api/handlers/oauth-authorization.ts +429 -0
- package/src/api/handlers/oauth-clients.ts +349 -0
- package/src/api/handlers/oauth-user-lookup.ts +39 -0
- package/src/api/handlers/plugins.ts +254 -0
- package/src/api/handlers/redirects.ts +360 -0
- package/src/api/handlers/revision.ts +145 -0
- package/src/api/handlers/schema.ts +534 -0
- package/src/api/handlers/sections.ts +289 -0
- package/src/api/handlers/seo.ts +115 -0
- package/src/api/handlers/settings.ts +49 -0
- package/src/api/handlers/snapshot.ts +350 -0
- package/src/api/handlers/taxonomies.ts +523 -0
- package/src/api/index.ts +6 -0
- package/src/api/openapi/document.ts +2368 -0
- package/src/api/openapi/index.ts +1 -0
- package/src/api/parse.ts +139 -0
- package/src/api/redirect.ts +14 -0
- package/src/api/rev.ts +67 -0
- package/src/api/schemas/auth.ts +112 -0
- package/src/api/schemas/bylines.ts +85 -0
- package/src/api/schemas/comments.ts +117 -0
- package/src/api/schemas/common.ts +89 -0
- package/src/api/schemas/content.ts +191 -0
- package/src/api/schemas/import.ts +52 -0
- package/src/api/schemas/index.ts +17 -0
- package/src/api/schemas/media.ts +116 -0
- package/src/api/schemas/menus.ts +111 -0
- package/src/api/schemas/redirects.ts +155 -0
- package/src/api/schemas/schema.ts +203 -0
- package/src/api/schemas/search.ts +63 -0
- package/src/api/schemas/sections.ts +67 -0
- package/src/api/schemas/settings.ts +63 -0
- package/src/api/schemas/setup.ts +37 -0
- package/src/api/schemas/taxonomies.ts +113 -0
- package/src/api/schemas/users.ts +96 -0
- package/src/api/schemas/widgets.ts +80 -0
- package/src/api/site-url.ts +25 -0
- package/src/api/types.ts +82 -0
- package/src/astro/index.ts +27 -0
- package/src/astro/integration/index.ts +303 -0
- package/src/astro/integration/routes.ts +834 -0
- package/src/astro/integration/runtime.ts +338 -0
- package/src/astro/integration/virtual-modules.ts +469 -0
- package/src/astro/integration/vite-config.ts +335 -0
- package/src/astro/middleware/auth.ts +743 -0
- package/src/astro/middleware/redirect.ts +89 -0
- package/src/astro/middleware/request-context.ts +129 -0
- package/src/astro/middleware/setup.ts +89 -0
- package/src/astro/middleware.ts +398 -0
- package/src/astro/routes/PluginRegistry.tsx +15 -0
- package/src/astro/routes/admin.astro +81 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
- package/src/astro/routes/api/admin/bylines/index.ts +72 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +116 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +84 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +82 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +115 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
- package/src/astro/routes/api/auth/signup/complete.ts +85 -0
- package/src/astro/routes/api/auth/signup/request.ts +77 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +101 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
- package/src/astro/routes/api/content/[collection]/index.ts +59 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
- package/src/astro/routes/api/manifest.ts +62 -0
- package/src/astro/routes/api/mcp.ts +124 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +137 -0
- package/src/astro/routes/api/media.ts +190 -0
- package/src/astro/routes/api/menus/[name]/items.ts +87 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
- package/src/astro/routes/api/menus/[name].ts +65 -0
- package/src/astro/routes/api/menus/index.ts +47 -0
- package/src/astro/routes/api/oauth/authorize.ts +412 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +51 -0
- package/src/astro/routes/api/oauth/device/token.ts +69 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +184 -0
- package/src/astro/routes/api/openapi.json.ts +32 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +84 -0
- package/src/astro/routes/api/redirects/index.ts +52 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +58 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
- package/src/astro/routes/api/schema/collections/index.ts +47 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +55 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +53 -0
- package/src/astro/routes/api/sections/[slug].ts +84 -0
- package/src/astro/routes/api/sections/index.ts +52 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +67 -0
- package/src/astro/routes/api/setup/admin-verify.ts +100 -0
- package/src/astro/routes/api/setup/admin.ts +94 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +126 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +75 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
- package/src/astro/routes/api/taxonomies/index.ts +59 -0
- package/src/astro/routes/api/themes/preview.ts +77 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +68 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +68 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
- package/src/astro/routes/api/widget-areas/[name].ts +87 -0
- package/src/astro/routes/api/widget-areas/index.ts +99 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +77 -0
- package/src/astro/routes/sitemap.xml.ts +97 -0
- package/src/astro/storage/adapters.ts +74 -0
- package/src/astro/storage/index.ts +19 -0
- package/src/astro/storage/types.ts +60 -0
- package/src/astro/types.ts +346 -0
- package/src/auth/api-tokens.ts +25 -0
- package/src/auth/challenge-store.ts +80 -0
- package/src/auth/mode.ts +96 -0
- package/src/auth/oauth-state-store.ts +96 -0
- package/src/auth/passkey-config.ts +27 -0
- package/src/auth/rate-limit.ts +158 -0
- package/src/auth/scopes.ts +33 -0
- package/src/auth/types.ts +104 -0
- package/src/aws-sdk.d.ts +100 -0
- package/src/bylines/index.ts +237 -0
- package/src/cleanup.ts +153 -0
- package/src/cli/client-factory.ts +100 -0
- package/src/cli/commands/auth.ts +46 -0
- package/src/cli/commands/bundle-utils.ts +247 -0
- package/src/cli/commands/bundle.ts +609 -0
- package/src/cli/commands/content.ts +442 -0
- package/src/cli/commands/dev.ts +191 -0
- package/src/cli/commands/doctor.ts +211 -0
- package/src/cli/commands/export-seed.ts +630 -0
- package/src/cli/commands/import/wordpress.ts +1056 -0
- package/src/cli/commands/init.ts +192 -0
- package/src/cli/commands/login.ts +547 -0
- package/src/cli/commands/media.ts +165 -0
- package/src/cli/commands/menu.ts +67 -0
- package/src/cli/commands/plugin-init.ts +291 -0
- package/src/cli/commands/plugin-validate.ts +31 -0
- package/src/cli/commands/plugin.ts +33 -0
- package/src/cli/commands/publish.ts +697 -0
- package/src/cli/commands/schema.ts +233 -0
- package/src/cli/commands/search-cmd.ts +54 -0
- package/src/cli/commands/seed.ts +286 -0
- package/src/cli/commands/taxonomy.ts +128 -0
- package/src/cli/commands/types.ts +68 -0
- package/src/cli/credentials.ts +236 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/output.ts +75 -0
- package/src/cli/wxr/parser.ts +969 -0
- package/src/client/cf-access.ts +193 -0
- package/src/client/index.ts +854 -0
- package/src/client/portable-text.ts +413 -0
- package/src/client/transport.ts +200 -0
- package/src/comments/moderator.ts +46 -0
- package/src/comments/notifications.ts +144 -0
- package/src/comments/query.ts +105 -0
- package/src/comments/service.ts +213 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/EmDashBodyEnd.astro +32 -0
- package/src/components/EmDashBodyStart.astro +32 -0
- package/src/components/EmDashHead.astro +53 -0
- package/src/components/EmDashImage.astro +178 -0
- package/src/components/EmDashMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1905 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +108 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +116 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/content/converters/index.ts +9 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
- package/src/content/converters/types.ts +120 -0
- package/src/content/index.ts +5 -0
- package/src/database/connection.ts +67 -0
- package/src/database/dialect-helpers.ts +138 -0
- package/src/database/index.ts +5 -0
- package/src/database/migrations/001_initial.ts +170 -0
- package/src/database/migrations/002_media_status.ts +26 -0
- package/src/database/migrations/003_schema_registry.ts +79 -0
- package/src/database/migrations/004_plugins.ts +62 -0
- package/src/database/migrations/005_menus.ts +67 -0
- package/src/database/migrations/006_taxonomy_defs.ts +51 -0
- package/src/database/migrations/007_widgets.ts +42 -0
- package/src/database/migrations/008_auth.ts +194 -0
- package/src/database/migrations/009_user_disabled.ts +27 -0
- package/src/database/migrations/011_sections.ts +65 -0
- package/src/database/migrations/012_search.ts +25 -0
- package/src/database/migrations/013_scheduled_publishing.ts +51 -0
- package/src/database/migrations/014_draft_revisions.ts +72 -0
- package/src/database/migrations/015_indexes.ts +82 -0
- package/src/database/migrations/016_api_tokens.ts +89 -0
- package/src/database/migrations/017_authorization_codes.ts +45 -0
- package/src/database/migrations/018_seo.ts +56 -0
- package/src/database/migrations/019_i18n.ts +618 -0
- package/src/database/migrations/020_collection_url_pattern.ts +23 -0
- package/src/database/migrations/021_remove_section_categories.ts +43 -0
- package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
- package/src/database/migrations/023_plugin_metadata.ts +33 -0
- package/src/database/migrations/024_media_placeholders.ts +32 -0
- package/src/database/migrations/025_oauth_clients.ts +28 -0
- package/src/database/migrations/026_cron_tasks.ts +49 -0
- package/src/database/migrations/027_comments.ts +87 -0
- package/src/database/migrations/028_drop_author_url.ts +9 -0
- package/src/database/migrations/029_redirects.ts +67 -0
- package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
- package/src/database/migrations/031_bylines.ts +90 -0
- package/src/database/migrations/032_rate_limits.ts +39 -0
- package/src/database/migrations/runner.ts +170 -0
- package/src/database/repositories/audit.ts +294 -0
- package/src/database/repositories/byline.ts +387 -0
- package/src/database/repositories/comment.ts +458 -0
- package/src/database/repositories/content.ts +1144 -0
- package/src/database/repositories/index.ts +30 -0
- package/src/database/repositories/media.ts +347 -0
- package/src/database/repositories/options.ts +150 -0
- package/src/database/repositories/plugin-storage.ts +373 -0
- package/src/database/repositories/redirect.ts +480 -0
- package/src/database/repositories/revision.ts +200 -0
- package/src/database/repositories/seo.ts +176 -0
- package/src/database/repositories/taxonomy.ts +294 -0
- package/src/database/repositories/types.ts +132 -0
- package/src/database/repositories/user.ts +258 -0
- package/src/database/transaction.ts +54 -0
- package/src/database/types.ts +501 -0
- package/src/database/validate.ts +138 -0
- package/src/db/adapters.ts +125 -0
- package/src/db/index.ts +37 -0
- package/src/db/libsql.ts +23 -0
- package/src/db/postgres.ts +30 -0
- package/src/db/sqlite.ts +27 -0
- package/src/emdash-runtime.ts +2096 -0
- package/src/fields/boolean.ts +34 -0
- package/src/fields/datetime.ts +44 -0
- package/src/fields/file.ts +41 -0
- package/src/fields/image.ts +34 -0
- package/src/fields/index.ts +42 -0
- package/src/fields/integer.ts +50 -0
- package/src/fields/json.ts +37 -0
- package/src/fields/multiselect.ts +48 -0
- package/src/fields/number.ts +52 -0
- package/src/fields/portable-text.ts +33 -0
- package/src/fields/reference.ts +29 -0
- package/src/fields/richtext.ts +31 -0
- package/src/fields/select.ts +46 -0
- package/src/fields/slug.ts +38 -0
- package/src/fields/text.ts +55 -0
- package/src/fields/textarea.ts +52 -0
- package/src/fields/types.ts +64 -0
- package/src/i18n/config.ts +68 -0
- package/src/import/index.ts +90 -0
- package/src/import/menus.ts +436 -0
- package/src/import/registry.ts +111 -0
- package/src/import/sections.ts +103 -0
- package/src/import/settings.ts +281 -0
- package/src/import/sources/wordpress-plugin.ts +641 -0
- package/src/import/sources/wordpress-rest.ts +191 -0
- package/src/import/sources/wxr.ts +330 -0
- package/src/import/ssrf.ts +260 -0
- package/src/import/types.ts +418 -0
- package/src/import/utils.ts +412 -0
- package/src/index.ts +481 -0
- package/src/loader.ts +770 -0
- package/src/mcp/server.ts +1463 -0
- package/src/media/index.ts +32 -0
- package/src/media/local-runtime.ts +213 -0
- package/src/media/local.ts +46 -0
- package/src/media/normalize.ts +190 -0
- package/src/media/placeholder.ts +150 -0
- package/src/media/provider-loader.ts +78 -0
- package/src/media/types.ts +279 -0
- package/src/menus/index.ts +324 -0
- package/src/menus/types.ts +112 -0
- package/src/page/context.ts +93 -0
- package/src/page/fragments.ts +89 -0
- package/src/page/index.ts +58 -0
- package/src/page/jsonld.ts +94 -0
- package/src/page/metadata.ts +185 -0
- package/src/page/seo-contributions.ts +136 -0
- package/src/plugin-utils.ts +80 -0
- package/src/plugins/adapt-sandbox-entry.ts +207 -0
- package/src/plugins/context.ts +833 -0
- package/src/plugins/cron.ts +361 -0
- package/src/plugins/define-plugin.ts +259 -0
- package/src/plugins/email-console.ts +73 -0
- package/src/plugins/email.ts +209 -0
- package/src/plugins/hooks.ts +1273 -0
- package/src/plugins/index.ts +193 -0
- package/src/plugins/manager.ts +595 -0
- package/src/plugins/manifest-schema.ts +230 -0
- package/src/plugins/marketplace.ts +460 -0
- package/src/plugins/request-meta.ts +139 -0
- package/src/plugins/routes.ts +302 -0
- package/src/plugins/sandbox/index.ts +18 -0
- package/src/plugins/sandbox/noop.ts +76 -0
- package/src/plugins/sandbox/types.ts +173 -0
- package/src/plugins/scheduler/node.ts +122 -0
- package/src/plugins/scheduler/piggyback.ts +71 -0
- package/src/plugins/scheduler/types.ts +27 -0
- package/src/plugins/state.ts +208 -0
- package/src/plugins/storage-indexes.ts +326 -0
- package/src/plugins/storage-query.ts +240 -0
- package/src/plugins/types.ts +1284 -0
- package/src/preview/helpers.ts +27 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/tokens.ts +279 -0
- package/src/preview/urls.ts +118 -0
- package/src/query.ts +674 -0
- package/src/redirects/patterns.ts +224 -0
- package/src/request-context.ts +67 -0
- package/src/runtime.ts +21 -0
- package/src/schema/index.ts +29 -0
- package/src/schema/query.ts +44 -0
- package/src/schema/registry.ts +965 -0
- package/src/schema/types.ts +276 -0
- package/src/schema/zod-generator.ts +413 -0
- package/src/search/fts-manager.ts +452 -0
- package/src/search/index.ts +26 -0
- package/src/search/query.ts +396 -0
- package/src/search/text-extraction.ts +162 -0
- package/src/search/types.ts +114 -0
- package/src/sections/index.ts +226 -0
- package/src/sections/types.ts +86 -0
- package/src/seed/apply.ts +1141 -0
- package/src/seed/default.ts +86 -0
- package/src/seed/index.ts +28 -0
- package/src/seed/load.ts +35 -0
- package/src/seed/types.ts +341 -0
- package/src/seed/validate.ts +642 -0
- package/src/seo/index.ts +179 -0
- package/src/settings/index.ts +203 -0
- package/src/settings/types.ts +58 -0
- package/src/storage/index.ts +28 -0
- package/src/storage/local.ts +249 -0
- package/src/storage/s3.ts +263 -0
- package/src/storage/types.ts +204 -0
- package/src/taxonomies/index.ts +309 -0
- package/src/taxonomies/types.ts +61 -0
- package/src/ui.ts +75 -0
- package/src/utils/base64.ts +73 -0
- package/src/utils/hash.ts +36 -0
- package/src/utils/sanitize.ts +20 -0
- package/src/utils/slugify.ts +29 -0
- package/src/utils/url.ts +48 -0
- package/src/virtual-modules.d.ts +111 -0
- package/src/visual-editing/editable.ts +108 -0
- package/src/visual-editing/toolbar.ts +1229 -0
- package/src/widgets/components.ts +105 -0
- package/src/widgets/index.ts +131 -0
- package/src/widgets/types.ts +81 -0
package/src/loader.ts
ADDED
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Astro Live Collections loader for EmDash
|
|
3
|
+
*
|
|
4
|
+
* This loader implements the Astro LiveLoader interface to fetch content
|
|
5
|
+
* at runtime from the database, enabling live editing without rebuilds.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Single `_emdash` Astro collection handles all content types
|
|
9
|
+
* - Dialect comes from virtual module (configured in astro.config.mjs)
|
|
10
|
+
* - Each content type maps to its own database table: ec_posts, ec_products, etc.
|
|
11
|
+
* - `getEmDashCollection()` / `getEmDashEntry()` wrap Astro's live collection API
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { LiveLoader } from "astro/loaders";
|
|
15
|
+
import { Kysely, sql, type Dialect } from "kysely";
|
|
16
|
+
|
|
17
|
+
import { currentTimestampValue, isPostgres } from "./database/dialect-helpers.js";
|
|
18
|
+
import { decodeCursor, encodeCursor } from "./database/repositories/types.js";
|
|
19
|
+
import type { Database } from "./index.js";
|
|
20
|
+
import { getRequestContext } from "./request-context.js";
|
|
21
|
+
|
|
22
|
+
const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* System columns that are not part of the content data
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* System columns excluded from entry.data
|
|
29
|
+
* Note: slug is intentionally NOT excluded - it's useful as data.slug in templates
|
|
30
|
+
*/
|
|
31
|
+
const SYSTEM_COLUMNS = new Set([
|
|
32
|
+
"id",
|
|
33
|
+
// "slug" - kept in data for template access
|
|
34
|
+
"status",
|
|
35
|
+
"author_id",
|
|
36
|
+
"primary_byline_id",
|
|
37
|
+
"created_at",
|
|
38
|
+
"updated_at",
|
|
39
|
+
"published_at",
|
|
40
|
+
"scheduled_at",
|
|
41
|
+
"deleted_at",
|
|
42
|
+
"version",
|
|
43
|
+
"live_revision_id",
|
|
44
|
+
"draft_revision_id",
|
|
45
|
+
"locale",
|
|
46
|
+
"translation_group",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the table name for a collection type
|
|
51
|
+
*/
|
|
52
|
+
function getTableName(type: string): string {
|
|
53
|
+
return `ec_${type}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Cache for taxonomy names (only used for the primary database).
|
|
58
|
+
* Skipped when a per-request DB override is active (e.g. preview mode)
|
|
59
|
+
* because the override DB may have different taxonomies.
|
|
60
|
+
*/
|
|
61
|
+
let taxonomyNames: Set<string> | null = null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all taxonomy names (cached for primary DB, fresh for overrides)
|
|
65
|
+
*/
|
|
66
|
+
async function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {
|
|
67
|
+
const hasDbOverride = !!getRequestContext()?.db;
|
|
68
|
+
|
|
69
|
+
if (!hasDbOverride && taxonomyNames) {
|
|
70
|
+
return taxonomyNames;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const defs = await db.selectFrom("_emdash_taxonomy_defs").select("name").execute();
|
|
75
|
+
const names = new Set(defs.map((d) => d.name));
|
|
76
|
+
if (!hasDbOverride) {
|
|
77
|
+
taxonomyNames = names;
|
|
78
|
+
}
|
|
79
|
+
return names;
|
|
80
|
+
} catch {
|
|
81
|
+
// Table doesn't exist yet, return empty set
|
|
82
|
+
const empty = new Set<string>();
|
|
83
|
+
if (!hasDbOverride) {
|
|
84
|
+
taxonomyNames = empty;
|
|
85
|
+
}
|
|
86
|
+
return empty;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* System columns to include in data (mapped to camelCase where needed)
|
|
92
|
+
*/
|
|
93
|
+
const INCLUDE_IN_DATA: Record<string, string> = {
|
|
94
|
+
id: "id",
|
|
95
|
+
status: "status",
|
|
96
|
+
author_id: "authorId",
|
|
97
|
+
primary_byline_id: "primaryBylineId",
|
|
98
|
+
created_at: "createdAt",
|
|
99
|
+
updated_at: "updatedAt",
|
|
100
|
+
published_at: "publishedAt",
|
|
101
|
+
scheduled_at: "scheduledAt",
|
|
102
|
+
draft_revision_id: "draftRevisionId",
|
|
103
|
+
live_revision_id: "liveRevisionId",
|
|
104
|
+
locale: "locale",
|
|
105
|
+
translation_group: "translationGroup",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** System date columns that should be converted to Date objects */
|
|
109
|
+
const DATE_COLUMNS = new Set(["created_at", "updated_at", "published_at", "scheduled_at"]);
|
|
110
|
+
|
|
111
|
+
/** Safely extract a string value from a record, returning fallback if not a string */
|
|
112
|
+
function rowStr(row: Record<string, unknown>, key: string, fallback = ""): string {
|
|
113
|
+
const val = row[key];
|
|
114
|
+
return typeof val === "string" ? val : fallback;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Map a database row to entry data
|
|
119
|
+
* Extracts content fields (non-system columns) and parses JSON where needed.
|
|
120
|
+
* System columns needed for templates (id, status, dates) are included with camelCase names.
|
|
121
|
+
*/
|
|
122
|
+
function mapRowToData(row: Record<string, unknown>): Record<string, unknown> {
|
|
123
|
+
const data: Record<string, unknown> = {};
|
|
124
|
+
|
|
125
|
+
for (const [key, value] of Object.entries(row)) {
|
|
126
|
+
// Include certain system columns (mapped to camelCase where needed)
|
|
127
|
+
if (key in INCLUDE_IN_DATA) {
|
|
128
|
+
// Convert date columns from ISO strings to Date objects
|
|
129
|
+
if (DATE_COLUMNS.has(key)) {
|
|
130
|
+
data[INCLUDE_IN_DATA[key]] = typeof value === "string" ? new Date(value) : null;
|
|
131
|
+
} else {
|
|
132
|
+
data[INCLUDE_IN_DATA[key]] = value;
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (SYSTEM_COLUMNS.has(key)) continue;
|
|
138
|
+
|
|
139
|
+
// Try to parse JSON strings (for portableText, json fields, etc.)
|
|
140
|
+
if (typeof value === "string") {
|
|
141
|
+
try {
|
|
142
|
+
// Only parse if it looks like JSON (starts with { or [)
|
|
143
|
+
if (value.startsWith("{") || value.startsWith("[")) {
|
|
144
|
+
data[key] = JSON.parse(value);
|
|
145
|
+
} else {
|
|
146
|
+
data[key] = value;
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
data[key] = value;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
data[key] = value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Map revision data (already-parsed JSON object) to entry data.
|
|
161
|
+
* Strips _-prefixed metadata keys (e.g. _slug) used internally by revisions.
|
|
162
|
+
*/
|
|
163
|
+
function mapRevisionData(data: Record<string, unknown>): Record<string, unknown> {
|
|
164
|
+
const result: Record<string, unknown> = {};
|
|
165
|
+
for (const [key, value] of Object.entries(data)) {
|
|
166
|
+
if (key.startsWith("_")) continue; // revision metadata
|
|
167
|
+
result[key] = value;
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Virtual module imports are lazy-loaded to avoid errors when importing
|
|
173
|
+
// emdash outside of Astro/Vite context (e.g., in astro.config.mjs)
|
|
174
|
+
let virtualConfig:
|
|
175
|
+
| {
|
|
176
|
+
database?: { config: unknown };
|
|
177
|
+
i18n?: { defaultLocale: string; locales: string[]; prefixDefaultLocale?: boolean } | null;
|
|
178
|
+
}
|
|
179
|
+
| undefined;
|
|
180
|
+
let virtualCreateDialect: ((config: unknown) => Dialect) | undefined;
|
|
181
|
+
|
|
182
|
+
async function loadVirtualModules() {
|
|
183
|
+
if (virtualConfig === undefined) {
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
185
|
+
// @ts-ignore - virtual module
|
|
186
|
+
const configModule = await import("virtual:emdash/config");
|
|
187
|
+
virtualConfig = configModule.default;
|
|
188
|
+
}
|
|
189
|
+
if (virtualCreateDialect === undefined) {
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
191
|
+
// @ts-ignore - virtual module
|
|
192
|
+
const dialectModule = await import("virtual:emdash/dialect");
|
|
193
|
+
virtualCreateDialect = dialectModule.createDialect;
|
|
194
|
+
// dialectType is no longer needed here — dialect detection is
|
|
195
|
+
// done via the db adapter instance in dialect-helpers.ts
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Entry data type - generic object
|
|
201
|
+
*/
|
|
202
|
+
export type EntryData = Record<string, unknown>;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Sort direction
|
|
206
|
+
*/
|
|
207
|
+
export type SortDirection = "asc" | "desc";
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Order by specification - field name to direction
|
|
211
|
+
* @example { created_at: "desc" } - Sort by created_at descending
|
|
212
|
+
* @example { title: "asc" } - Sort by title ascending
|
|
213
|
+
*/
|
|
214
|
+
export type OrderBySpec = Record<string, SortDirection>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Build WHERE clause for status filtering.
|
|
218
|
+
* When filtering for 'published' status, also include scheduled content
|
|
219
|
+
* whose scheduled_at time has passed (treating it as effectively published).
|
|
220
|
+
*/
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance
|
|
222
|
+
function buildStatusCondition(
|
|
223
|
+
db: Kysely<any>,
|
|
224
|
+
status: string,
|
|
225
|
+
tablePrefix?: string,
|
|
226
|
+
): ReturnType<typeof sql> {
|
|
227
|
+
const statusField = tablePrefix ? `${tablePrefix}.status` : "status";
|
|
228
|
+
const scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : "scheduled_at";
|
|
229
|
+
|
|
230
|
+
if (status === "published") {
|
|
231
|
+
// Include both published content AND scheduled content past its publish time.
|
|
232
|
+
// scheduled_at is stored as text (ISO 8601). On Postgres, we must cast it
|
|
233
|
+
// to timestamptz for the comparison with CURRENT_TIMESTAMP to work.
|
|
234
|
+
const scheduledAtExpr = isPostgres(db)
|
|
235
|
+
? sql`${sql.ref(scheduledAtField)}::timestamptz`
|
|
236
|
+
: sql.ref(scheduledAtField);
|
|
237
|
+
return sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${currentTimestampValue(db)}))`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// For other statuses (draft, archived), just match exactly
|
|
241
|
+
return sql`${sql.ref(statusField)} = ${status}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Resolved primary sort field and direction (used for cursor pagination).
|
|
246
|
+
*/
|
|
247
|
+
interface PrimarySort {
|
|
248
|
+
field: string;
|
|
249
|
+
direction: SortDirection;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get the primary sort field from an orderBy spec (first valid field, or default).
|
|
254
|
+
*/
|
|
255
|
+
function getPrimarySort(orderBy: OrderBySpec | undefined, tablePrefix?: string): PrimarySort {
|
|
256
|
+
if (orderBy) {
|
|
257
|
+
for (const [field, direction] of Object.entries(orderBy)) {
|
|
258
|
+
if (FIELD_NAME_PATTERN.test(field)) {
|
|
259
|
+
const fullField = tablePrefix ? `${tablePrefix}.${field}` : field;
|
|
260
|
+
return { field: fullField, direction };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const defaultField = tablePrefix ? `${tablePrefix}.created_at` : "created_at";
|
|
265
|
+
return { field: defaultField, direction: "desc" };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Build ORDER BY clause from orderBy spec
|
|
270
|
+
* Validates field names to prevent SQL injection (alphanumeric + underscore only)
|
|
271
|
+
* Supports multiple sort fields in object key order
|
|
272
|
+
*/
|
|
273
|
+
function buildOrderByClause(
|
|
274
|
+
orderBy: OrderBySpec | undefined,
|
|
275
|
+
tablePrefix?: string,
|
|
276
|
+
): ReturnType<typeof sql> {
|
|
277
|
+
// Default to created_at DESC
|
|
278
|
+
if (!orderBy || Object.keys(orderBy).length === 0) {
|
|
279
|
+
const field = tablePrefix ? `${tablePrefix}.created_at` : "created_at";
|
|
280
|
+
return sql`ORDER BY ${sql.ref(field)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : "id")} DESC`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const sortParts: ReturnType<typeof sql>[] = [];
|
|
284
|
+
|
|
285
|
+
for (const [field, direction] of Object.entries(orderBy)) {
|
|
286
|
+
// Validate field name (alphanumeric + underscore only)
|
|
287
|
+
if (!FIELD_NAME_PATTERN.test(field)) {
|
|
288
|
+
continue; // Skip invalid field names
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const fullField = tablePrefix ? `${tablePrefix}.${field}` : field;
|
|
292
|
+
const dir = direction === "asc" ? sql`ASC` : sql`DESC`;
|
|
293
|
+
sortParts.push(sql`${sql.ref(fullField)} ${dir}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// If no valid sort fields, fall back to default
|
|
297
|
+
if (sortParts.length === 0) {
|
|
298
|
+
const defaultField = tablePrefix ? `${tablePrefix}.created_at` : "created_at";
|
|
299
|
+
return sql`ORDER BY ${sql.ref(defaultField)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : "id")} DESC`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Add id as tiebreaker to ensure stable cursor ordering
|
|
303
|
+
const primary = getPrimarySort(orderBy, tablePrefix);
|
|
304
|
+
const idField = tablePrefix ? `${tablePrefix}.id` : "id";
|
|
305
|
+
const idDir = primary.direction === "asc" ? sql`ASC` : sql`DESC`;
|
|
306
|
+
sortParts.push(sql`${sql.ref(idField)} ${idDir}`);
|
|
307
|
+
|
|
308
|
+
return sql`ORDER BY ${sql.join(sortParts, sql`, `)}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Build a cursor WHERE condition for keyset pagination.
|
|
313
|
+
* Uses the primary sort field + id as tiebreaker for stable ordering.
|
|
314
|
+
*/
|
|
315
|
+
function buildCursorCondition(
|
|
316
|
+
cursor: string,
|
|
317
|
+
orderBy: OrderBySpec | undefined,
|
|
318
|
+
tablePrefix?: string,
|
|
319
|
+
): ReturnType<typeof sql> | null {
|
|
320
|
+
const decoded = decodeCursor(cursor);
|
|
321
|
+
if (!decoded) return null;
|
|
322
|
+
|
|
323
|
+
const { orderValue, id: cursorId } = decoded;
|
|
324
|
+
const primary = getPrimarySort(orderBy, tablePrefix);
|
|
325
|
+
const idField = tablePrefix ? `${tablePrefix}.id` : "id";
|
|
326
|
+
|
|
327
|
+
if (primary.direction === "desc") {
|
|
328
|
+
return sql`(${sql.ref(primary.field)} < ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} < ${cursorId}))`;
|
|
329
|
+
}
|
|
330
|
+
return sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Filter for loadCollection - type is required
|
|
335
|
+
*/
|
|
336
|
+
export interface CollectionFilter {
|
|
337
|
+
type: string;
|
|
338
|
+
status?: "draft" | "published" | "archived";
|
|
339
|
+
limit?: number;
|
|
340
|
+
/**
|
|
341
|
+
* Opaque cursor for keyset pagination.
|
|
342
|
+
* Pass the `nextCursor` value from a previous result to fetch the next page.
|
|
343
|
+
*/
|
|
344
|
+
cursor?: string;
|
|
345
|
+
/**
|
|
346
|
+
* Filter by field values or taxonomy terms
|
|
347
|
+
*/
|
|
348
|
+
where?: Record<string, string | string[]>;
|
|
349
|
+
/**
|
|
350
|
+
* Order results by field(s)
|
|
351
|
+
* @default { created_at: "desc" }
|
|
352
|
+
*/
|
|
353
|
+
orderBy?: OrderBySpec;
|
|
354
|
+
/**
|
|
355
|
+
* Filter by locale (e.g. 'en', 'fr').
|
|
356
|
+
* When set, only returns content in this locale.
|
|
357
|
+
*/
|
|
358
|
+
locale?: string;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Filter for loadEntry - type and id are required
|
|
363
|
+
*/
|
|
364
|
+
export interface EntryFilter {
|
|
365
|
+
type: string;
|
|
366
|
+
id: string;
|
|
367
|
+
/**
|
|
368
|
+
* When set, fetch content data from this revision instead of the content table.
|
|
369
|
+
* Used by preview mode to serve draft revision data.
|
|
370
|
+
*/
|
|
371
|
+
revisionId?: string;
|
|
372
|
+
/**
|
|
373
|
+
* Locale to scope slug lookup. Only affects slug resolution;
|
|
374
|
+
* IDs are globally unique and always resolve regardless of locale.
|
|
375
|
+
*/
|
|
376
|
+
locale?: string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Cached database instance (shared across calls)
|
|
380
|
+
let dbInstance: Kysely<Database> | null = null;
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get the database instance. Used by query wrapper functions and middleware.
|
|
384
|
+
*
|
|
385
|
+
* Checks the ALS request context first — if a per-request DB override is set
|
|
386
|
+
* (e.g. by DO preview middleware), it takes precedence over the module-level
|
|
387
|
+
* cached instance. This allows preview mode to route queries to an isolated
|
|
388
|
+
* Durable Object database without modifying any calling code.
|
|
389
|
+
*
|
|
390
|
+
* Initializes the default database on first call using config from virtual module.
|
|
391
|
+
*/
|
|
392
|
+
export async function getDb(): Promise<Kysely<Database>> {
|
|
393
|
+
// Per-request DB override via ALS (normal mode)
|
|
394
|
+
const ctx = getRequestContext();
|
|
395
|
+
if (ctx?.db) {
|
|
396
|
+
return ctx.db as Kysely<Database>; // eslint-disable-line typescript-eslint(no-unsafe-type-assertion) -- db is typed as unknown in RequestContext to avoid circular deps
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!dbInstance) {
|
|
400
|
+
await loadVirtualModules();
|
|
401
|
+
if (!virtualConfig?.database || typeof virtualCreateDialect !== "function") {
|
|
402
|
+
throw new Error(
|
|
403
|
+
"EmDash database not configured. Add database config to emdash() in astro.config.mjs",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const dialect = virtualCreateDialect(virtualConfig.database.config);
|
|
407
|
+
dbInstance = new Kysely<Database>({ dialect });
|
|
408
|
+
}
|
|
409
|
+
return dbInstance;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Create an EmDash Live Collections loader
|
|
414
|
+
*
|
|
415
|
+
* This loader handles ALL content types in a single Astro collection.
|
|
416
|
+
* Use `getEmDashCollection()` and `getEmDashEntry()` to query
|
|
417
|
+
* specific content types.
|
|
418
|
+
*
|
|
419
|
+
* Database is configured in astro.config.mjs via the emdash() integration.
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```ts
|
|
423
|
+
* // src/live.config.ts
|
|
424
|
+
* import { defineLiveCollection } from "astro:content";
|
|
425
|
+
* import { emdashLoader } from "emdash";
|
|
426
|
+
*
|
|
427
|
+
* export const collections = {
|
|
428
|
+
* emdash: defineLiveCollection({
|
|
429
|
+
* loader: emdashLoader(),
|
|
430
|
+
* }),
|
|
431
|
+
* };
|
|
432
|
+
* ```
|
|
433
|
+
*/
|
|
434
|
+
export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter> {
|
|
435
|
+
return {
|
|
436
|
+
name: "emdash",
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Load all entries for a content type
|
|
440
|
+
*/
|
|
441
|
+
async loadCollection({ filter }) {
|
|
442
|
+
try {
|
|
443
|
+
// Get DB instance (initializes on first use)
|
|
444
|
+
const db = await getDb();
|
|
445
|
+
|
|
446
|
+
// Type filter is required
|
|
447
|
+
const type = filter?.type;
|
|
448
|
+
if (!type) {
|
|
449
|
+
return {
|
|
450
|
+
error: new Error(
|
|
451
|
+
"type filter is required. Use getEmDashCollection() instead of getLiveCollection() directly.",
|
|
452
|
+
),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Query the per-collection table (ec_posts, ec_products, etc.)
|
|
457
|
+
const tableName = getTableName(type);
|
|
458
|
+
|
|
459
|
+
// Build query with dynamic table name
|
|
460
|
+
const status = filter?.status || "published";
|
|
461
|
+
const limit = filter?.limit;
|
|
462
|
+
const cursor = filter?.cursor;
|
|
463
|
+
const where = filter?.where;
|
|
464
|
+
const orderBy = filter?.orderBy;
|
|
465
|
+
const locale = filter?.locale;
|
|
466
|
+
|
|
467
|
+
// Cursor pagination: over-fetch by 1 to detect next page
|
|
468
|
+
const fetchLimit = limit ? limit + 1 : undefined;
|
|
469
|
+
|
|
470
|
+
// Build cursor condition if cursor is provided
|
|
471
|
+
const cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;
|
|
472
|
+
const cursorConditionPrefixed = cursor
|
|
473
|
+
? buildCursorCondition(cursor, orderBy, tableName)
|
|
474
|
+
: null;
|
|
475
|
+
|
|
476
|
+
// Check if we need taxonomy filtering
|
|
477
|
+
let result: { rows: Record<string, unknown>[] };
|
|
478
|
+
|
|
479
|
+
if (where && Object.keys(where).length > 0) {
|
|
480
|
+
// Get taxonomy names to detect taxonomy filters
|
|
481
|
+
const taxNames = await getTaxonomyNames(db);
|
|
482
|
+
const taxonomyFilters: Record<string, string | string[]> = {};
|
|
483
|
+
|
|
484
|
+
for (const [key, value] of Object.entries(where)) {
|
|
485
|
+
if (taxNames.has(key)) {
|
|
486
|
+
taxonomyFilters[key] = value;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// If we have taxonomy filters, use JOIN
|
|
491
|
+
if (Object.keys(taxonomyFilters).length > 0) {
|
|
492
|
+
// Build query with taxonomy JOIN
|
|
493
|
+
// For now, support single taxonomy filter (can extend later for multiple)
|
|
494
|
+
const [taxName, termSlugs] = Object.entries(taxonomyFilters)[0];
|
|
495
|
+
const slugs = Array.isArray(termSlugs) ? termSlugs : [termSlugs];
|
|
496
|
+
const orderByClause = buildOrderByClause(orderBy, tableName);
|
|
497
|
+
|
|
498
|
+
const statusCondition = buildStatusCondition(db, status, tableName);
|
|
499
|
+
const localeCondition = locale
|
|
500
|
+
? sql`AND ${sql.ref(tableName)}.locale = ${locale}`
|
|
501
|
+
: sql``;
|
|
502
|
+
const cursorCond = cursorConditionPrefixed
|
|
503
|
+
? sql`AND ${cursorConditionPrefixed}`
|
|
504
|
+
: sql``;
|
|
505
|
+
result = await sql<Record<string, unknown>>`
|
|
506
|
+
SELECT DISTINCT ${sql.ref(tableName)}.* FROM ${sql.ref(tableName)}
|
|
507
|
+
INNER JOIN content_taxonomies ct
|
|
508
|
+
ON ct.collection = ${type}
|
|
509
|
+
AND ct.entry_id = ${sql.ref(tableName)}.id
|
|
510
|
+
INNER JOIN taxonomies t
|
|
511
|
+
ON t.id = ct.taxonomy_id
|
|
512
|
+
WHERE ${sql.ref(tableName)}.deleted_at IS NULL
|
|
513
|
+
AND ${statusCondition}
|
|
514
|
+
${localeCondition}
|
|
515
|
+
${cursorCond}
|
|
516
|
+
AND t.name = ${taxName}
|
|
517
|
+
AND t.slug IN (${sql.join(slugs.map((s) => sql`${s}`))})
|
|
518
|
+
${orderByClause}
|
|
519
|
+
${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
|
|
520
|
+
`.execute(db);
|
|
521
|
+
} else {
|
|
522
|
+
// No taxonomy filters, use simple query
|
|
523
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
524
|
+
const statusCondition = buildStatusCondition(db, status);
|
|
525
|
+
const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
|
|
526
|
+
const cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;
|
|
527
|
+
result = await sql<Record<string, unknown>>`
|
|
528
|
+
SELECT * FROM ${sql.ref(tableName)}
|
|
529
|
+
WHERE deleted_at IS NULL
|
|
530
|
+
AND ${statusCondition}
|
|
531
|
+
${localeFilter}
|
|
532
|
+
${cursorCond}
|
|
533
|
+
${orderByClause}
|
|
534
|
+
${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
|
|
535
|
+
`.execute(db);
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
// No where clause, use simple query
|
|
539
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
540
|
+
const statusCondition = buildStatusCondition(db, status);
|
|
541
|
+
const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
|
|
542
|
+
const cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;
|
|
543
|
+
result = await sql<Record<string, unknown>>`
|
|
544
|
+
SELECT * FROM ${sql.ref(tableName)}
|
|
545
|
+
WHERE deleted_at IS NULL
|
|
546
|
+
AND ${statusCondition}
|
|
547
|
+
${localeFilter}
|
|
548
|
+
${cursorCond}
|
|
549
|
+
${orderByClause}
|
|
550
|
+
${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
|
|
551
|
+
`.execute(db);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Detect whether there are more results (over-fetched by 1)
|
|
555
|
+
const hasMore = limit ? result.rows.length > limit : false;
|
|
556
|
+
const rows = hasMore ? result.rows.slice(0, limit) : result.rows;
|
|
557
|
+
|
|
558
|
+
// Map rows to entries
|
|
559
|
+
const i18nConfig = virtualConfig?.i18n;
|
|
560
|
+
const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
|
|
561
|
+
const entries = rows.map((row) => {
|
|
562
|
+
const slug = rowStr(row, "slug") || rowStr(row, "id");
|
|
563
|
+
const rowLocale = rowStr(row, "locale");
|
|
564
|
+
const shouldPrefix =
|
|
565
|
+
i18nEnabled &&
|
|
566
|
+
rowLocale !== "" &&
|
|
567
|
+
(rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
|
|
568
|
+
const id = shouldPrefix ? `${rowLocale}/${slug}` : slug;
|
|
569
|
+
return {
|
|
570
|
+
id,
|
|
571
|
+
slug: rowStr(row, "slug"),
|
|
572
|
+
status: rowStr(row, "status", "draft"),
|
|
573
|
+
data: mapRowToData(row),
|
|
574
|
+
cacheHint: {
|
|
575
|
+
tags: [rowStr(row, "id")],
|
|
576
|
+
lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Encode nextCursor from the last row if there are more results
|
|
582
|
+
let nextCursor: string | undefined;
|
|
583
|
+
if (hasMore && rows.length > 0) {
|
|
584
|
+
const lastRow = rows.at(-1)!;
|
|
585
|
+
const primary = getPrimarySort(orderBy);
|
|
586
|
+
// Strip table prefix from field name for row lookup
|
|
587
|
+
const fieldName = primary.field.includes(".")
|
|
588
|
+
? primary.field.split(".").pop()!
|
|
589
|
+
: primary.field;
|
|
590
|
+
const lastOrderValue = lastRow[fieldName];
|
|
591
|
+
const orderStr =
|
|
592
|
+
typeof lastOrderValue === "string" || typeof lastOrderValue === "number"
|
|
593
|
+
? String(lastOrderValue)
|
|
594
|
+
: "";
|
|
595
|
+
nextCursor = encodeCursor(orderStr, String(lastRow.id));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Collection-level cache hint uses the most recent updated_at
|
|
599
|
+
let collectionLastModified: Date | undefined;
|
|
600
|
+
for (const row of rows) {
|
|
601
|
+
if (row.updated_at) {
|
|
602
|
+
const d = new Date(rowStr(row, "updated_at"));
|
|
603
|
+
if (!collectionLastModified || d > collectionLastModified) {
|
|
604
|
+
collectionLastModified = d;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
entries,
|
|
611
|
+
nextCursor,
|
|
612
|
+
cacheHint: {
|
|
613
|
+
tags: [type],
|
|
614
|
+
lastModified: collectionLastModified,
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
} catch (error) {
|
|
618
|
+
// Handle missing table gracefully - return empty collection
|
|
619
|
+
// This happens before migrations have run
|
|
620
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
621
|
+
const lowerMessage = message.toLowerCase();
|
|
622
|
+
if (
|
|
623
|
+
lowerMessage.includes("no such table") ||
|
|
624
|
+
(lowerMessage.includes("table") && lowerMessage.includes("does not exist")) ||
|
|
625
|
+
(lowerMessage.includes("relation") && lowerMessage.includes("does not exist"))
|
|
626
|
+
) {
|
|
627
|
+
return { entries: [] };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
error: new Error(`Failed to load collection: ${message}`),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Load a single entry by type and ID/slug
|
|
638
|
+
*
|
|
639
|
+
* When filter.revisionId is set (preview mode), the entry's data
|
|
640
|
+
* comes from the revisions table instead of the content table columns.
|
|
641
|
+
*/
|
|
642
|
+
async loadEntry({ filter }) {
|
|
643
|
+
try {
|
|
644
|
+
// Get DB instance
|
|
645
|
+
const db = await getDb();
|
|
646
|
+
|
|
647
|
+
// Both type and id are required
|
|
648
|
+
const type = filter?.type;
|
|
649
|
+
const id = filter?.id;
|
|
650
|
+
|
|
651
|
+
if (!type || !id) {
|
|
652
|
+
return {
|
|
653
|
+
error: new Error(
|
|
654
|
+
"type and id filters are required. Use getEmDashEntry() instead of getLiveEntry() directly.",
|
|
655
|
+
),
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Query the per-collection table
|
|
660
|
+
const tableName = getTableName(type);
|
|
661
|
+
const locale = filter?.locale;
|
|
662
|
+
|
|
663
|
+
// Use raw SQL for dynamic table name, match by slug or id
|
|
664
|
+
// When locale is specified, prefer locale-scoped slug match,
|
|
665
|
+
// but IDs are globally unique so always check id without locale scope
|
|
666
|
+
const result = locale
|
|
667
|
+
? await sql<Record<string, unknown>>`
|
|
668
|
+
SELECT * FROM ${sql.ref(tableName)}
|
|
669
|
+
WHERE deleted_at IS NULL
|
|
670
|
+
AND ((slug = ${id} AND locale = ${locale}) OR id = ${id})
|
|
671
|
+
LIMIT 1
|
|
672
|
+
`.execute(db)
|
|
673
|
+
: await sql<Record<string, unknown>>`
|
|
674
|
+
SELECT * FROM ${sql.ref(tableName)}
|
|
675
|
+
WHERE deleted_at IS NULL
|
|
676
|
+
AND (slug = ${id} OR id = ${id})
|
|
677
|
+
LIMIT 1
|
|
678
|
+
`.execute(db);
|
|
679
|
+
|
|
680
|
+
const row = result.rows[0];
|
|
681
|
+
if (!row) {
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const i18nConfig = virtualConfig?.i18n;
|
|
686
|
+
const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
|
|
687
|
+
const entrySlug = rowStr(row, "slug") || rowStr(row, "id");
|
|
688
|
+
const entryLocale = rowStr(row, "locale");
|
|
689
|
+
const shouldPrefixEntry =
|
|
690
|
+
i18nEnabled &&
|
|
691
|
+
entryLocale !== "" &&
|
|
692
|
+
(entryLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
|
|
693
|
+
const entryId = shouldPrefixEntry ? `${entryLocale}/${entrySlug}` : entrySlug;
|
|
694
|
+
|
|
695
|
+
// Preview mode: override content fields with revision data,
|
|
696
|
+
// keeping system metadata from the content table row.
|
|
697
|
+
const revisionId = filter?.revisionId;
|
|
698
|
+
if (revisionId) {
|
|
699
|
+
const revRow = await sql<{ data: string }>`
|
|
700
|
+
SELECT data FROM revisions
|
|
701
|
+
WHERE id = ${revisionId}
|
|
702
|
+
LIMIT 1
|
|
703
|
+
`.execute(db);
|
|
704
|
+
|
|
705
|
+
const revData = revRow.rows[0];
|
|
706
|
+
if (revData) {
|
|
707
|
+
const parsed: Record<string, unknown> = JSON.parse(revData.data);
|
|
708
|
+
// System metadata from content table + content fields from revision
|
|
709
|
+
const systemData: Record<string, unknown> = {};
|
|
710
|
+
for (const [key, mappedKey] of Object.entries(INCLUDE_IN_DATA)) {
|
|
711
|
+
if (key in row) {
|
|
712
|
+
if (DATE_COLUMNS.has(key)) {
|
|
713
|
+
systemData[mappedKey] = typeof row[key] === "string" ? new Date(row[key]) : null;
|
|
714
|
+
} else {
|
|
715
|
+
systemData[mappedKey] = row[key];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Use slug from revision metadata if present, else from content table
|
|
720
|
+
const slug = typeof parsed._slug === "string" ? parsed._slug : rowStr(row, "slug");
|
|
721
|
+
const revSlug = slug || rowStr(row, "id");
|
|
722
|
+
const revLocale = rowStr(row, "locale");
|
|
723
|
+
const shouldPrefixRev =
|
|
724
|
+
i18nEnabled &&
|
|
725
|
+
revLocale !== "" &&
|
|
726
|
+
(revLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
|
|
727
|
+
const revId = shouldPrefixRev ? `${revLocale}/${revSlug}` : revSlug;
|
|
728
|
+
return {
|
|
729
|
+
id: revId,
|
|
730
|
+
slug,
|
|
731
|
+
status: rowStr(row, "status", "draft"),
|
|
732
|
+
data: { ...systemData, slug, ...mapRevisionData(parsed) },
|
|
733
|
+
cacheHint: {
|
|
734
|
+
tags: [rowStr(row, "id")],
|
|
735
|
+
lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return {
|
|
742
|
+
id: entryId,
|
|
743
|
+
slug: rowStr(row, "slug"),
|
|
744
|
+
status: rowStr(row, "status", "draft"),
|
|
745
|
+
data: mapRowToData(row),
|
|
746
|
+
cacheHint: {
|
|
747
|
+
tags: [rowStr(row, "id")],
|
|
748
|
+
lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
// Handle missing table gracefully - return undefined (not found)
|
|
753
|
+
// This happens before migrations have run
|
|
754
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
755
|
+
const lowerMessage = message.toLowerCase();
|
|
756
|
+
if (
|
|
757
|
+
lowerMessage.includes("no such table") ||
|
|
758
|
+
(lowerMessage.includes("table") && lowerMessage.includes("does not exist")) ||
|
|
759
|
+
(lowerMessage.includes("relation") && lowerMessage.includes("does not exist"))
|
|
760
|
+
) {
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return {
|
|
765
|
+
error: new Error(`Failed to load entry: ${message}`),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
};
|
|
770
|
+
}
|