emdash 0.0.0-a → 0.0.1
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 -1
- 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 +1333 -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-B-u2F2b6.mjs +1412 -0
- package/dist/runner-B-u2F2b6.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 +687 -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 +353 -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 +328 -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 +120 -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 +117 -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 +105 -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 +62 -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 +72 -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 +699 -0
- package/src/cli/commands/schema.ts +233 -0
- package/src/cli/commands/search-cmd.ts +54 -0
- package/src/cli/commands/seed.ts +288 -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 +136 -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 +42 -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 +253 -0
- package/src/storage/s3.ts +271 -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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Query Functions
|
|
3
|
+
*
|
|
4
|
+
* Programmatic API for searching content using FTS5.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Kysely } from "kysely";
|
|
8
|
+
import { sql } from "kysely";
|
|
9
|
+
|
|
10
|
+
import type { Database } from "../database/types.js";
|
|
11
|
+
import { validateIdentifier } from "../database/validate.js";
|
|
12
|
+
import { getDb } from "../loader.js";
|
|
13
|
+
import { FTSManager } from "./fts-manager.js";
|
|
14
|
+
import type {
|
|
15
|
+
SearchOptions,
|
|
16
|
+
CollectionSearchOptions,
|
|
17
|
+
SearchResult,
|
|
18
|
+
SearchResponse,
|
|
19
|
+
SuggestOptions,
|
|
20
|
+
Suggestion,
|
|
21
|
+
SearchStats,
|
|
22
|
+
} from "./types.js";
|
|
23
|
+
|
|
24
|
+
/** Pattern to split on whitespace for query term extraction */
|
|
25
|
+
const WHITESPACE_SPLIT_PATTERN = /\s+/;
|
|
26
|
+
const FTS_OPERATORS_PATTERN = /\b(AND|OR|NOT|NEAR)\b/i;
|
|
27
|
+
const DOUBLE_QUOTE_PATTERN = /"/g;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Search across multiple collections
|
|
31
|
+
*
|
|
32
|
+
* Public API that auto-injects the database.
|
|
33
|
+
*
|
|
34
|
+
* @param query - Search query (FTS5 syntax supported)
|
|
35
|
+
* @param options - Search options
|
|
36
|
+
* @returns Search results with pagination
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { search } from "emdash";
|
|
41
|
+
*
|
|
42
|
+
* const results = await search("hello world", {
|
|
43
|
+
* collections: ["posts", "pages"],
|
|
44
|
+
* limit: 20
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export async function search(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
49
|
+
const db = await getDb();
|
|
50
|
+
return searchWithDb(db, query, options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Search across multiple collections (with explicit db)
|
|
55
|
+
*
|
|
56
|
+
* @internal Use `search()` in templates. This variant is for admin routes
|
|
57
|
+
* that already have a database handle.
|
|
58
|
+
*
|
|
59
|
+
* @param db - Kysely database instance
|
|
60
|
+
* @param query - Search query (FTS5 syntax supported)
|
|
61
|
+
* @param options - Search options
|
|
62
|
+
* @returns Search results with pagination
|
|
63
|
+
*/
|
|
64
|
+
export async function searchWithDb(
|
|
65
|
+
db: Kysely<Database>,
|
|
66
|
+
query: string,
|
|
67
|
+
options: SearchOptions = {},
|
|
68
|
+
): Promise<SearchResponse> {
|
|
69
|
+
const ftsManager = new FTSManager(db);
|
|
70
|
+
const limit = options.limit ?? 20;
|
|
71
|
+
const status = options.status ?? "published";
|
|
72
|
+
|
|
73
|
+
// Get searchable collections
|
|
74
|
+
let collections = options.collections;
|
|
75
|
+
if (!collections || collections.length === 0) {
|
|
76
|
+
collections = await getSearchableCollections(db);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (collections.length === 0) {
|
|
80
|
+
return { items: [] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Search each collection and merge results
|
|
84
|
+
const allResults: SearchResult[] = [];
|
|
85
|
+
|
|
86
|
+
for (const collection of collections) {
|
|
87
|
+
const config = await ftsManager.getSearchConfig(collection);
|
|
88
|
+
if (!config?.enabled) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const collectionResults = await searchSingleCollection(
|
|
93
|
+
db,
|
|
94
|
+
collection,
|
|
95
|
+
query,
|
|
96
|
+
{
|
|
97
|
+
status,
|
|
98
|
+
locale: options.locale,
|
|
99
|
+
limit: limit * 2, // Get extra for merging
|
|
100
|
+
},
|
|
101
|
+
config.weights,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
allResults.push(...collectionResults);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Sort by score descending
|
|
108
|
+
allResults.sort((a, b) => b.score - a.score);
|
|
109
|
+
|
|
110
|
+
// Apply limit
|
|
111
|
+
const items = allResults.slice(0, limit);
|
|
112
|
+
|
|
113
|
+
return { items };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Search within a single collection
|
|
118
|
+
*
|
|
119
|
+
* @param db - Kysely database instance
|
|
120
|
+
* @param collection - Collection slug
|
|
121
|
+
* @param query - Search query (FTS5 syntax supported)
|
|
122
|
+
* @param options - Search options
|
|
123
|
+
* @returns Search results with pagination
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const results = await searchCollection(db, "posts", "hello world", {
|
|
128
|
+
* limit: 10
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export async function searchCollection(
|
|
133
|
+
db: Kysely<Database>,
|
|
134
|
+
collection: string,
|
|
135
|
+
query: string,
|
|
136
|
+
options: CollectionSearchOptions = {},
|
|
137
|
+
): Promise<SearchResponse> {
|
|
138
|
+
const ftsManager = new FTSManager(db);
|
|
139
|
+
const config = await ftsManager.getSearchConfig(collection);
|
|
140
|
+
|
|
141
|
+
if (!config?.enabled) {
|
|
142
|
+
return { items: [] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const items = await searchSingleCollection(db, collection, query, options, config.weights);
|
|
146
|
+
|
|
147
|
+
return { items };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Internal function to search a single collection
|
|
152
|
+
*/
|
|
153
|
+
async function searchSingleCollection(
|
|
154
|
+
db: Kysely<Database>,
|
|
155
|
+
collection: string,
|
|
156
|
+
query: string,
|
|
157
|
+
options: CollectionSearchOptions,
|
|
158
|
+
weights?: Record<string, number>,
|
|
159
|
+
): Promise<SearchResult[]> {
|
|
160
|
+
// Validate before any raw SQL interpolation
|
|
161
|
+
validateIdentifier(collection, "collection slug");
|
|
162
|
+
|
|
163
|
+
const ftsManager = new FTSManager(db);
|
|
164
|
+
const ftsTable = ftsManager.getFtsTableName(collection);
|
|
165
|
+
const contentTable = ftsManager.getContentTableName(collection);
|
|
166
|
+
const limit = options.limit ?? 20;
|
|
167
|
+
const status = options.status ?? "published";
|
|
168
|
+
const locale = options.locale;
|
|
169
|
+
|
|
170
|
+
// Check if FTS table exists
|
|
171
|
+
if (!(await ftsManager.ftsTableExists(collection))) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Escape the query for FTS5
|
|
176
|
+
const escapedQuery = escapeQuery(query);
|
|
177
|
+
if (!escapedQuery) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get searchable fields for snippet generation
|
|
182
|
+
const searchableFields = await ftsManager.getSearchableFields(collection);
|
|
183
|
+
|
|
184
|
+
// Build weight string for bm25 if weights provided
|
|
185
|
+
// Format: bm25(table, weight1, weight2, ...)
|
|
186
|
+
// First two weights are for 'id' and 'locale' columns (UNINDEXED, so 0)
|
|
187
|
+
let bm25Args = "";
|
|
188
|
+
if (weights && searchableFields.length > 0) {
|
|
189
|
+
const weightValues = ["0", "0"]; // id column, locale column
|
|
190
|
+
for (const field of searchableFields) {
|
|
191
|
+
weightValues.push(String(weights[field] ?? 1));
|
|
192
|
+
}
|
|
193
|
+
bm25Args = weightValues.join(", ");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Build and execute the search query
|
|
197
|
+
// Using raw SQL because Kysely doesn't have FTS5 support
|
|
198
|
+
const bm25Expr = bm25Args ? `bm25("${ftsTable}", ${bm25Args})` : `bm25("${ftsTable}")`;
|
|
199
|
+
|
|
200
|
+
// Snippet column index is 2 (after id=0, locale=1, first searchable field=2)
|
|
201
|
+
const results = await sql<{
|
|
202
|
+
id: string;
|
|
203
|
+
slug: string | null;
|
|
204
|
+
locale: string;
|
|
205
|
+
title: string | null;
|
|
206
|
+
snippet: string;
|
|
207
|
+
score: number;
|
|
208
|
+
}>`
|
|
209
|
+
SELECT
|
|
210
|
+
c.id,
|
|
211
|
+
c.slug,
|
|
212
|
+
c.locale,
|
|
213
|
+
c.title,
|
|
214
|
+
snippet("${sql.raw(ftsTable)}", 2, '<mark>', '</mark>', '...', 32) as snippet,
|
|
215
|
+
${sql.raw(bm25Expr)} as score
|
|
216
|
+
FROM "${sql.raw(ftsTable)}" f
|
|
217
|
+
JOIN "${sql.raw(contentTable)}" c ON f.id = c.id
|
|
218
|
+
WHERE "${sql.raw(ftsTable)}" MATCH ${escapedQuery}
|
|
219
|
+
AND c.status = ${status}
|
|
220
|
+
AND c.deleted_at IS NULL
|
|
221
|
+
${locale ? sql`AND c.locale = ${locale}` : sql``}
|
|
222
|
+
ORDER BY score
|
|
223
|
+
LIMIT ${limit}
|
|
224
|
+
`.execute(db);
|
|
225
|
+
|
|
226
|
+
return results.rows.map((row) => ({
|
|
227
|
+
collection,
|
|
228
|
+
id: row.id,
|
|
229
|
+
slug: row.slug,
|
|
230
|
+
locale: row.locale,
|
|
231
|
+
title: row.title ?? undefined,
|
|
232
|
+
snippet: row.snippet,
|
|
233
|
+
score: Math.abs(row.score), // bm25 returns negative scores
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get search suggestions for autocomplete
|
|
239
|
+
*
|
|
240
|
+
* @param db - Kysely database instance
|
|
241
|
+
* @param query - Partial search query
|
|
242
|
+
* @param options - Suggestion options
|
|
243
|
+
* @returns Array of suggestions
|
|
244
|
+
*/
|
|
245
|
+
export async function getSuggestions(
|
|
246
|
+
db: Kysely<Database>,
|
|
247
|
+
query: string,
|
|
248
|
+
options: SuggestOptions = {},
|
|
249
|
+
): Promise<Suggestion[]> {
|
|
250
|
+
const limit = options.limit ?? 5;
|
|
251
|
+
const locale = options.locale;
|
|
252
|
+
|
|
253
|
+
// Get searchable collections
|
|
254
|
+
let collections = options.collections;
|
|
255
|
+
if (!collections || collections.length === 0) {
|
|
256
|
+
collections = await getSearchableCollections(db);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (collections.length === 0) {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const suggestions: Suggestion[] = [];
|
|
264
|
+
|
|
265
|
+
for (const collection of collections) {
|
|
266
|
+
const ftsManager = new FTSManager(db);
|
|
267
|
+
const config = await ftsManager.getSearchConfig(collection);
|
|
268
|
+
if (!config?.enabled) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Validate before raw SQL interpolation
|
|
273
|
+
validateIdentifier(collection, "collection slug");
|
|
274
|
+
|
|
275
|
+
const ftsTable = ftsManager.getFtsTableName(collection);
|
|
276
|
+
const contentTable = ftsManager.getContentTableName(collection);
|
|
277
|
+
|
|
278
|
+
// Use prefix search for autocomplete
|
|
279
|
+
const prefixQuery = `${escapeQuery(query)}*`;
|
|
280
|
+
if (!prefixQuery || prefixQuery === "*") {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const results = await sql<{
|
|
285
|
+
id: string;
|
|
286
|
+
title: string;
|
|
287
|
+
}>`
|
|
288
|
+
SELECT
|
|
289
|
+
c.id,
|
|
290
|
+
c.title
|
|
291
|
+
FROM "${sql.raw(ftsTable)}" f
|
|
292
|
+
JOIN "${sql.raw(contentTable)}" c ON f.id = c.id
|
|
293
|
+
WHERE "${sql.raw(ftsTable)}" MATCH ${prefixQuery}
|
|
294
|
+
AND c.status = 'published'
|
|
295
|
+
AND c.deleted_at IS NULL
|
|
296
|
+
AND c.title IS NOT NULL
|
|
297
|
+
${locale ? sql`AND c.locale = ${locale}` : sql``}
|
|
298
|
+
ORDER BY bm25("${sql.raw(ftsTable)}")
|
|
299
|
+
LIMIT ${limit}
|
|
300
|
+
`.execute(db);
|
|
301
|
+
|
|
302
|
+
for (const row of results.rows) {
|
|
303
|
+
suggestions.push({
|
|
304
|
+
collection,
|
|
305
|
+
id: row.id,
|
|
306
|
+
title: row.title,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return suggestions.slice(0, limit);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get search statistics for all collections
|
|
316
|
+
*/
|
|
317
|
+
export async function getSearchStats(db: Kysely<Database>): Promise<SearchStats> {
|
|
318
|
+
const ftsManager = new FTSManager(db);
|
|
319
|
+
const collections = await getSearchableCollections(db);
|
|
320
|
+
const stats: SearchStats = { collections: {} };
|
|
321
|
+
|
|
322
|
+
for (const collection of collections) {
|
|
323
|
+
const collectionStats = await ftsManager.getIndexStats(collection);
|
|
324
|
+
if (collectionStats) {
|
|
325
|
+
stats.collections[collection] = collectionStats;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return stats;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get list of collections with search enabled
|
|
334
|
+
*/
|
|
335
|
+
async function getSearchableCollections(db: Kysely<Database>): Promise<string[]> {
|
|
336
|
+
const results = await db
|
|
337
|
+
.selectFrom("_emdash_collections")
|
|
338
|
+
.select(["slug", "search_config"])
|
|
339
|
+
.execute();
|
|
340
|
+
|
|
341
|
+
return results
|
|
342
|
+
.filter((r) => {
|
|
343
|
+
if (!r.search_config) return false;
|
|
344
|
+
try {
|
|
345
|
+
const config = JSON.parse(r.search_config);
|
|
346
|
+
return config.enabled === true;
|
|
347
|
+
} catch {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
.map((r) => r.slug);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Escape a query string for FTS5
|
|
356
|
+
*
|
|
357
|
+
* Handles special characters and prevents injection.
|
|
358
|
+
*/
|
|
359
|
+
function escapeQuery(query: string): string {
|
|
360
|
+
if (!query || typeof query !== "string") {
|
|
361
|
+
return "";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Trim whitespace
|
|
365
|
+
query = query.trim();
|
|
366
|
+
|
|
367
|
+
if (query.length === 0) {
|
|
368
|
+
return "";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// FTS5 special characters that need escaping in terms: " * ^
|
|
372
|
+
// We'll wrap terms in quotes to handle most cases
|
|
373
|
+
// But first, escape any existing quotes
|
|
374
|
+
const escaped = query.replace(DOUBLE_QUOTE_PATTERN, '""');
|
|
375
|
+
|
|
376
|
+
// If the query contains FTS5 operators (AND, OR, NOT, NEAR),
|
|
377
|
+
// pass through as-is (user knows what they're doing)
|
|
378
|
+
if (FTS_OPERATORS_PATTERN.test(query)) {
|
|
379
|
+
return escaped;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// If already quoted, pass through
|
|
383
|
+
if (query.startsWith('"') && query.endsWith('"')) {
|
|
384
|
+
return query;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// For simple queries, wrap each word to handle special chars
|
|
388
|
+
const terms = escaped.split(WHITESPACE_SPLIT_PATTERN).filter((t) => t.length > 0);
|
|
389
|
+
if (terms.length === 0) {
|
|
390
|
+
return "";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Join with implicit AND, add prefix matching (*) to all terms
|
|
394
|
+
// This allows "hel wor" to match "hello world"
|
|
395
|
+
return terms.map((t) => `"${t}"*`).join(" ");
|
|
396
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Extraction
|
|
3
|
+
*
|
|
4
|
+
* Extracts plain text from Portable Text blocks for FTS indexing.
|
|
5
|
+
* Uses @portabletext/toolkit as base with extensions for custom block types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { toPlainText } from "@portabletext/toolkit";
|
|
9
|
+
|
|
10
|
+
import type { PortableTextBlock } from "../content/converters/types.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate that a value looks like a Portable Text block array.
|
|
14
|
+
* Each element must have at least a `_type` string property.
|
|
15
|
+
*/
|
|
16
|
+
function isPortableTextArray(value: unknown[]): value is PortableTextBlock[] {
|
|
17
|
+
return value.every(
|
|
18
|
+
(item) =>
|
|
19
|
+
typeof item === "object" &&
|
|
20
|
+
item !== null &&
|
|
21
|
+
"_type" in item &&
|
|
22
|
+
typeof item._type === "string",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract additional text from custom block types that toPlainText doesn't handle
|
|
28
|
+
*/
|
|
29
|
+
function extractCustomBlockText(block: PortableTextBlock): string {
|
|
30
|
+
// Code blocks - include the code content
|
|
31
|
+
if (block._type === "code" && "code" in block && typeof block.code === "string") {
|
|
32
|
+
return block.code;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Image blocks - include alt text and caption
|
|
36
|
+
if (block._type === "image") {
|
|
37
|
+
const parts: string[] = [];
|
|
38
|
+
if ("alt" in block && typeof block.alt === "string" && block.alt) {
|
|
39
|
+
parts.push(block.alt);
|
|
40
|
+
}
|
|
41
|
+
if ("caption" in block && typeof block.caption === "string" && block.caption) {
|
|
42
|
+
parts.push(block.caption);
|
|
43
|
+
}
|
|
44
|
+
return parts.join(" ");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract plain text from Portable Text blocks
|
|
52
|
+
*
|
|
53
|
+
* Uses @portabletext/toolkit's toPlainText for standard blocks,
|
|
54
|
+
* plus extracts text from custom block types (code, images with alt/caption).
|
|
55
|
+
*
|
|
56
|
+
* @param blocks - Array of Portable Text blocks (or a JSON string)
|
|
57
|
+
* @returns Plain text content
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const text = extractPlainText([
|
|
62
|
+
* {
|
|
63
|
+
* _type: "block",
|
|
64
|
+
* _key: "abc",
|
|
65
|
+
* children: [{ _type: "span", _key: "s1", text: "Hello World" }]
|
|
66
|
+
* }
|
|
67
|
+
* ]);
|
|
68
|
+
* // Returns: "Hello World"
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function extractPlainText(blocks: PortableTextBlock[] | string | null | undefined): string {
|
|
72
|
+
if (!blocks) {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle JSON string input
|
|
77
|
+
let parsedBlocks: PortableTextBlock[];
|
|
78
|
+
if (typeof blocks === "string") {
|
|
79
|
+
try {
|
|
80
|
+
parsedBlocks = JSON.parse(blocks);
|
|
81
|
+
} catch {
|
|
82
|
+
// If it's not valid JSON, treat as plain text
|
|
83
|
+
return blocks;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
parsedBlocks = blocks;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!Array.isArray(parsedBlocks)) {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Use official toPlainText for standard blocks.
|
|
94
|
+
// toPlainText expects `{ _type: string; [key: string]: any }[]` but our blocks use
|
|
95
|
+
// `unknown` index sigs. They're structurally compatible at runtime — spread each block
|
|
96
|
+
// to satisfy the wider index signature without an unsafe cast.
|
|
97
|
+
const toolkitBlocks = parsedBlocks.map((b) => {
|
|
98
|
+
const obj: Record<string, unknown> & { _type: string } = { _type: b._type };
|
|
99
|
+
for (const [key, val] of Object.entries(b)) {
|
|
100
|
+
obj[key] = val;
|
|
101
|
+
}
|
|
102
|
+
return obj;
|
|
103
|
+
});
|
|
104
|
+
const standardText = toPlainText(toolkitBlocks);
|
|
105
|
+
|
|
106
|
+
// Extract text from custom block types that toPlainText doesn't handle
|
|
107
|
+
const customTexts = parsedBlocks.map(extractCustomBlockText).filter((text) => text.length > 0);
|
|
108
|
+
|
|
109
|
+
// Combine both
|
|
110
|
+
const allTexts = [standardText, ...customTexts].filter((t) => t.length > 0);
|
|
111
|
+
return allTexts.join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Extract searchable text from a content entry
|
|
116
|
+
*
|
|
117
|
+
* Extracts text from specified fields, handling both plain text and Portable Text.
|
|
118
|
+
*
|
|
119
|
+
* @param entry - Content entry data
|
|
120
|
+
* @param fields - Field names to extract text from
|
|
121
|
+
* @returns Object mapping field names to extracted text
|
|
122
|
+
*/
|
|
123
|
+
export function extractSearchableFields(
|
|
124
|
+
entry: Record<string, unknown>,
|
|
125
|
+
fields: string[],
|
|
126
|
+
): Record<string, string> {
|
|
127
|
+
const result: Record<string, string> = {};
|
|
128
|
+
|
|
129
|
+
for (const field of fields) {
|
|
130
|
+
const value = entry[field];
|
|
131
|
+
|
|
132
|
+
if (value === null || value === undefined) {
|
|
133
|
+
result[field] = "";
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (typeof value === "string") {
|
|
138
|
+
// Could be plain text or JSON Portable Text
|
|
139
|
+
if (value.startsWith("[")) {
|
|
140
|
+
result[field] = extractPlainText(value);
|
|
141
|
+
} else {
|
|
142
|
+
result[field] = value;
|
|
143
|
+
}
|
|
144
|
+
} else if (Array.isArray(value)) {
|
|
145
|
+
// Validate the array looks like Portable Text before treating it as such
|
|
146
|
+
if (isPortableTextArray(value)) {
|
|
147
|
+
result[field] = extractPlainText(value);
|
|
148
|
+
} else {
|
|
149
|
+
result[field] = JSON.stringify(value);
|
|
150
|
+
}
|
|
151
|
+
} else if (typeof value === "object") {
|
|
152
|
+
// Object — serialize to JSON for searchable text
|
|
153
|
+
result[field] = JSON.stringify(value);
|
|
154
|
+
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
155
|
+
result[field] = `${value}`;
|
|
156
|
+
} else {
|
|
157
|
+
result[field] = "";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the EmDash search system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Search configuration for a collection
|
|
9
|
+
*/
|
|
10
|
+
export interface SearchConfig {
|
|
11
|
+
/** Whether search is enabled for this collection */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/** Field weights for ranking (higher = more important) */
|
|
14
|
+
weights?: Record<string, number>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for search queries
|
|
19
|
+
*/
|
|
20
|
+
export interface SearchOptions {
|
|
21
|
+
/** Collections to search (defaults to all searchable collections) */
|
|
22
|
+
collections?: string[];
|
|
23
|
+
/** Filter by content status (defaults to 'published') */
|
|
24
|
+
status?: string;
|
|
25
|
+
/** Filter by locale (omit to search all locales) */
|
|
26
|
+
locale?: string;
|
|
27
|
+
/** Maximum results to return (defaults to 20) */
|
|
28
|
+
limit?: number;
|
|
29
|
+
/** Pagination cursor */
|
|
30
|
+
cursor?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for collection-specific search
|
|
35
|
+
*/
|
|
36
|
+
export interface CollectionSearchOptions {
|
|
37
|
+
/** Filter by content status (defaults to 'published') */
|
|
38
|
+
status?: string;
|
|
39
|
+
/** Filter by locale (omit to search all locales) */
|
|
40
|
+
locale?: string;
|
|
41
|
+
/** Maximum results to return (defaults to 20) */
|
|
42
|
+
limit?: number;
|
|
43
|
+
/** Pagination cursor */
|
|
44
|
+
cursor?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A single search result
|
|
49
|
+
*/
|
|
50
|
+
export interface SearchResult {
|
|
51
|
+
/** Collection the result belongs to */
|
|
52
|
+
collection: string;
|
|
53
|
+
/** Entry ID */
|
|
54
|
+
id: string;
|
|
55
|
+
/** Entry slug */
|
|
56
|
+
slug: string | null;
|
|
57
|
+
/** Content locale */
|
|
58
|
+
locale: string;
|
|
59
|
+
/** Entry title (if available) */
|
|
60
|
+
title?: string;
|
|
61
|
+
/** Highlighted snippet showing match context */
|
|
62
|
+
snippet?: string;
|
|
63
|
+
/** Relevance score (higher = more relevant) */
|
|
64
|
+
score: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Response from a search query
|
|
69
|
+
*/
|
|
70
|
+
export interface SearchResponse {
|
|
71
|
+
/** Search results */
|
|
72
|
+
items: SearchResult[];
|
|
73
|
+
/** Cursor for next page of results */
|
|
74
|
+
nextCursor?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Options for suggestion/autocomplete queries
|
|
79
|
+
*/
|
|
80
|
+
export interface SuggestOptions {
|
|
81
|
+
/** Collections to search (defaults to all searchable collections) */
|
|
82
|
+
collections?: string[];
|
|
83
|
+
/** Filter by locale (omit to search all locales) */
|
|
84
|
+
locale?: string;
|
|
85
|
+
/** Maximum suggestions to return (defaults to 5) */
|
|
86
|
+
limit?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A single suggestion result
|
|
91
|
+
*/
|
|
92
|
+
export interface Suggestion {
|
|
93
|
+
/** Collection the suggestion belongs to */
|
|
94
|
+
collection: string;
|
|
95
|
+
/** Entry ID */
|
|
96
|
+
id: string;
|
|
97
|
+
/** Entry title */
|
|
98
|
+
title: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Search index statistics
|
|
103
|
+
*/
|
|
104
|
+
export interface SearchStats {
|
|
105
|
+
collections: Record<
|
|
106
|
+
string,
|
|
107
|
+
{
|
|
108
|
+
/** Number of indexed entries */
|
|
109
|
+
indexed: number;
|
|
110
|
+
/** When the index was last rebuilt */
|
|
111
|
+
lastRebuilt?: string;
|
|
112
|
+
}
|
|
113
|
+
>;
|
|
114
|
+
}
|