blume 2.0.3 → 2.1.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/AGENTS.md +1 -0
- package/CHANGELOG.md +142 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
- package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
- package/dist/cli/chunk-e6atmn4h.js.map +36 -0
- package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
- package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
- package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
- package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
- package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
- package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
- package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
- package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
- package/dist/cli/chunk-nrns10mj.js.map +10 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
- package/dist/cli/chunk-t2m5j4pr.js +104 -0
- package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
- package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
- package/dist/cli/chunk-tphac74f.js +126 -0
- package/dist/cli/chunk-tphac74f.js.map +11 -0
- package/dist/cli/chunk-w1r4vase.js +18 -0
- package/dist/cli/chunk-w1r4vase.js.map +10 -0
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/index.js +31 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +9 -0
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +62 -0
- package/dist/types/components/content/api-page.d.ts +100 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +158 -0
- package/dist/types/components/openapi/security.d.ts +95 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +217 -23
- package/dist/types/core/data.d.ts +53 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +88 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +152 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +670 -56
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +18 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +26 -1
- package/dist/types/deploy/redirects.d.ts +55 -11
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +27 -10
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +16 -0
- package/dist/types/openapi/references.d.ts +30 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +119 -0
- package/dist/types/ratelimit/upstash.d.ts +46 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +89 -11
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +82 -6
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +144 -36
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +13 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/metatags.d.ts +20 -0
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +71 -17
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +13 -2
- package/docs/advanced/skills.mdx +15 -1
- package/docs/cli/audit.mdx +51 -8
- package/docs/cli/doctor.mdx +5 -1
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +194 -38
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +8 -6
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +64 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +90 -0
- package/docs/configuration/search.mdx +63 -12
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +108 -2
- package/docs/content/frontmatter.mdx +80 -2
- package/docs/content/i18n.mdx +39 -4
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +32 -10
- package/docs/content/sources/contentful.mdx +35 -0
- package/docs/content/sources/custom.mdx +43 -0
- package/docs/content/sources/github-releases.mdx +39 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +31 -0
- package/docs/content/sources/remote-mdx.mdx +44 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +32 -0
- package/docs/content/syntax.mdx +69 -4
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +2 -2
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +4 -2
- package/docs/references/graphql.mdx +14 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +148 -7
- package/docs/references/scalar.mdx +3 -1
- package/package.json +43 -8
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +7 -7
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +10 -7
- package/skills/blume-migrate/references/starlight.md +11 -8
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +103 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +76 -1
- package/src/ai/cors.ts +14 -3
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +36 -6
- package/src/ai/relative-links.ts +4 -38
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +210 -66
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +42 -4
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +9 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/templates.ts +406 -108
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +38 -3
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +19 -13
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +5 -0
- package/src/audit/types.ts +4 -1
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +12 -1
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +67 -28
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/init/scaffold.ts +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +72 -30
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +66 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +103 -0
- package/src/components/content/api-page.ts +248 -0
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +136 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +318 -47
- package/src/components/layout/Search.astro +47 -19
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +126 -0
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +32 -0
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +184 -13
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +24 -5
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +7 -4
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +65 -9
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +24 -5
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +16 -3
- package/src/components/openapi/snippets.ts +873 -10
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/client-address.ts +19 -0
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +225 -23
- package/src/core/data.ts +46 -4
- package/src/core/diagnostics.ts +8 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +84 -2
- package/src/core/includes.ts +21 -4
- package/src/core/links.ts +63 -19
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +163 -16
- package/src/core/redirect-patterns.ts +520 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +339 -66
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +228 -41
- package/src/core/sources/notion.ts +7 -1
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +19 -1
- package/src/core/ui-packs/ar.ts +44 -0
- package/src/core/ui-packs/bg.ts +46 -0
- package/src/core/ui-packs/bn.ts +44 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +45 -0
- package/src/core/ui-packs/da.ts +46 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +46 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +46 -0
- package/src/core/ui-packs/fi.ts +46 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +44 -0
- package/src/core/ui-packs/hi.ts +45 -0
- package/src/core/ui-packs/hr.ts +46 -0
- package/src/core/ui-packs/hu.ts +45 -0
- package/src/core/ui-packs/id.ts +46 -0
- package/src/core/ui-packs/it.ts +46 -0
- package/src/core/ui-packs/ja.ts +46 -0
- package/src/core/ui-packs/ko.ts +45 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +45 -0
- package/src/core/ui-packs/pl.ts +46 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +46 -0
- package/src/core/ui-packs/ro.ts +46 -0
- package/src/core/ui-packs/ru.ts +46 -0
- package/src/core/ui-packs/sk.ts +45 -0
- package/src/core/ui-packs/sr.ts +46 -0
- package/src/core/ui-packs/sv.ts +45 -0
- package/src/core/ui-packs/th.ts +44 -0
- package/src/core/ui-packs/tr.ts +46 -0
- package/src/core/ui-packs/uk.ts +46 -0
- package/src/core/ui-packs/vi.ts +46 -0
- package/src/core/ui-packs/zh-tw.ts +44 -0
- package/src/core/ui-packs/zh.ts +44 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +79 -9
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +45 -9
- package/src/deploy/platforms/netlify.ts +35 -2
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +149 -17
- package/src/deploy/redirects.ts +151 -24
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +326 -102
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/relative-links.ts +3 -2
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +72 -27
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +182 -4
- package/src/openapi/references.ts +64 -8
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +40 -15
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +247 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +60 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +20 -3
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +80 -8
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +23 -5
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +191 -34
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +111 -10
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +76 -26
- package/src/seo/metatags.ts +67 -0
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +85 -6
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +31 -3
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
- package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
- package/dist/cli/chunk-35d4wj9f.js.map +0 -15
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-8ktnccpt.js.map +0 -10
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-b07cmahc.js +0 -145
- package/dist/cli/chunk-b07cmahc.js.map +0 -11
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-e04dxsz1.js +0 -39
- package/dist/cli/chunk-e04dxsz1.js.map +0 -10
- package/dist/cli/chunk-j85vccga.js.map +0 -36
- package/dist/cli/chunk-p3v96n38.js.map +0 -10
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
package/src/search/documents.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface SearchDocument {
|
|
|
54
54
|
* Filterable through the MCP tools' `filters` input.
|
|
55
55
|
*/
|
|
56
56
|
facets?: Record<string, string>;
|
|
57
|
+
/** Frontmatter `search.boost`: the page's relevance multiplier, when set. */
|
|
58
|
+
boost?: number;
|
|
59
|
+
/** Frontmatter `search.keywords`: extra terms the page is found by. */
|
|
60
|
+
keywords?: string[];
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
/**
|
|
@@ -77,6 +81,13 @@ export interface SearchRecord {
|
|
|
77
81
|
version: string;
|
|
78
82
|
/** Single faceting tag (the first frontmatter tag, when present). */
|
|
79
83
|
tag?: string;
|
|
84
|
+
/**
|
|
85
|
+
* The page's relevance multiplier, 1 unless `search.boost` sets it: a
|
|
86
|
+
* ranking attribute every record carries, so the backend can sort by it.
|
|
87
|
+
*/
|
|
88
|
+
boost: number;
|
|
89
|
+
/** Extra terms the page is found by (`search.keywords`). */
|
|
90
|
+
keywords?: string[];
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
// Tag-shaped only: a name (or closing slash/fragment) right after `<`, and no
|
|
@@ -414,6 +425,17 @@ const pageBody = async (
|
|
|
414
425
|
});
|
|
415
426
|
};
|
|
416
427
|
|
|
428
|
+
/** The ranking a page asks for: its `search.boost` and `search.keywords`. */
|
|
429
|
+
const rankingFields = (
|
|
430
|
+
search: PageRecord["meta"]["search"] | undefined
|
|
431
|
+
): Pick<SearchDocument, "boost" | "keywords"> => {
|
|
432
|
+
const { boost, keywords } = search ?? {};
|
|
433
|
+
return {
|
|
434
|
+
...(boost !== undefined && boost !== 1 && { boost }),
|
|
435
|
+
...(keywords && keywords.length > 0 && { keywords }),
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
|
|
417
439
|
/**
|
|
418
440
|
* Build search documents from the content graph. Only indexable pages are
|
|
419
441
|
* included (per the route manifest), and content comes from the source files,
|
|
@@ -531,7 +553,7 @@ export const buildSearchDocuments = async (
|
|
|
531
553
|
if (facets) {
|
|
532
554
|
document.facets = facets;
|
|
533
555
|
}
|
|
534
|
-
return document;
|
|
556
|
+
return { ...document, ...rankingFields(page?.meta?.search) };
|
|
535
557
|
})
|
|
536
558
|
);
|
|
537
559
|
};
|
|
@@ -539,13 +561,15 @@ export const buildSearchDocuments = async (
|
|
|
539
561
|
/**
|
|
540
562
|
* Map per-page search documents to the flat record shape hosted backends
|
|
541
563
|
* ingest. One record per page, keyed by route; the first tag becomes the
|
|
542
|
-
* faceting `tag
|
|
564
|
+
* faceting `tag`, and every record carries a `boost` (1 by default).
|
|
543
565
|
*/
|
|
544
566
|
export const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>
|
|
545
567
|
documents.map((doc) => ({
|
|
546
568
|
_id: doc.route,
|
|
569
|
+
boost: doc.boost ?? 1,
|
|
547
570
|
content: doc.content,
|
|
548
571
|
description: doc.description,
|
|
572
|
+
...(doc.keywords && { keywords: doc.keywords }),
|
|
549
573
|
locale: doc.locale,
|
|
550
574
|
tag: doc.tags?.[0],
|
|
551
575
|
title: doc.title,
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
components,
|
|
3
|
+
create,
|
|
4
|
+
insert,
|
|
5
|
+
insertMultiple,
|
|
6
|
+
search,
|
|
7
|
+
} from "@orama/orama";
|
|
2
8
|
import type {
|
|
3
9
|
AnyOrama,
|
|
4
10
|
EnumArrComparisonOperator,
|
|
5
11
|
EnumComparisonOperator,
|
|
12
|
+
Result,
|
|
13
|
+
SearchParamsFullText,
|
|
6
14
|
Tokenizer,
|
|
7
15
|
} from "@orama/orama";
|
|
8
16
|
|
|
@@ -31,6 +39,10 @@ export interface OramaDoc {
|
|
|
31
39
|
* but not indexed, so they ride along on the returned document untouched. */
|
|
32
40
|
breadcrumb?: string[];
|
|
33
41
|
section?: string;
|
|
42
|
+
/** The page's relevance multiplier (`search.boost`); stored, not indexed. */
|
|
43
|
+
boost?: number;
|
|
44
|
+
/** Extra terms the page is found by (`search.keywords`). */
|
|
45
|
+
keywords?: string[];
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
const SCHEMA = {
|
|
@@ -42,6 +54,7 @@ const SCHEMA = {
|
|
|
42
54
|
// serves every project's facet keys, with `containsAll` matching a filter
|
|
43
55
|
// set. Derived from `facets` at insert time.
|
|
44
56
|
facetTerms: "enum[]",
|
|
57
|
+
keywords: "string[]",
|
|
45
58
|
locale: "enum",
|
|
46
59
|
route: "string",
|
|
47
60
|
title: "string",
|
|
@@ -52,8 +65,21 @@ const SCHEMA = {
|
|
|
52
65
|
const toFacetTerms = (facets: Record<string, string>): string[] =>
|
|
53
66
|
Object.entries(facets).map(([key, value]) => `${key}:${value}`);
|
|
54
67
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Title and description outrank body text, matching the search dialog; a
|
|
70
|
+
* page's keywords count as much as its title, since its author chose them.
|
|
71
|
+
*/
|
|
72
|
+
const BOOST = { description: 2, keywords: 3, title: 3 };
|
|
73
|
+
|
|
74
|
+
/** Databases holding a boosted page, which rank by boosted score. */
|
|
75
|
+
const boostedDatabases = new WeakSet<AnyOrama>();
|
|
76
|
+
|
|
77
|
+
/** A search match as Orama's sort function sees it: id, score, document. */
|
|
78
|
+
type RankedMatch = [unknown, number, OramaDoc];
|
|
79
|
+
|
|
80
|
+
/** Rank matches by relevance times the page's `search.boost`. */
|
|
81
|
+
const byBoostedScore = (a: RankedMatch, b: RankedMatch): number =>
|
|
82
|
+
b[1] * (b[2].boost ?? 1) - a[1] * (a[2].boost ?? 1);
|
|
57
83
|
|
|
58
84
|
/**
|
|
59
85
|
* The script whose text Orama's default tokenizer keeps mostly intact. Its
|
|
@@ -296,32 +322,92 @@ const segmentingTokenizer = (locale?: string): Tokenizer | undefined => {
|
|
|
296
322
|
};
|
|
297
323
|
};
|
|
298
324
|
|
|
325
|
+
/**
|
|
326
|
+
* On a Latin-default index holding pages in non-Latin locales, the tokenizer
|
|
327
|
+
* each of those locales' pages was indexed with, keyed by locale code. A query
|
|
328
|
+
* reads it to tokenize its term the way the pages it searches were.
|
|
329
|
+
*/
|
|
330
|
+
const translationTokenizers = new WeakMap<AnyOrama, Map<string, Tokenizer>>();
|
|
331
|
+
|
|
332
|
+
/** A segmenting tokenizer for each document locale Orama's can't serve. */
|
|
333
|
+
const localeTokenizers = (documents: OramaDoc[]): Map<string, Tokenizer> => {
|
|
334
|
+
const tokenizers = new Map<string, Tokenizer>();
|
|
335
|
+
for (const code of new Set(documents.map((doc) => doc.locale ?? ""))) {
|
|
336
|
+
const tokenizer = segmentingTokenizer(code);
|
|
337
|
+
if (tokenizer) {
|
|
338
|
+
tokenizers.set(code, tokenizer);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return tokenizers;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* One tokenizer that hands each text to its locale's: Orama passes the
|
|
346
|
+
* language given to `insert` and `search` through to `tokenize`, so a page
|
|
347
|
+
* inserted under `ja` and a query searched under `ja` both reach the Japanese
|
|
348
|
+
* tokenizer, and everything else reaches Orama's own, exactly as on an index
|
|
349
|
+
* with no translations.
|
|
350
|
+
*/
|
|
351
|
+
const dispatchingTokenizer = (
|
|
352
|
+
tokenizers: Map<string, Tokenizer>
|
|
353
|
+
): Tokenizer => {
|
|
354
|
+
const standard = components.tokenizer.createTokenizer();
|
|
355
|
+
return {
|
|
356
|
+
language: standard.language,
|
|
357
|
+
normalizationCache: standard.normalizationCache,
|
|
358
|
+
tokenize: (raw, language, prop, withCache) =>
|
|
359
|
+
// Orama's tokenizer rejects a language other than its own, so the
|
|
360
|
+
// locale that picked it is not passed on.
|
|
361
|
+
(tokenizers.get(language ?? "") ?? standard).tokenize(
|
|
362
|
+
raw,
|
|
363
|
+
undefined,
|
|
364
|
+
prop,
|
|
365
|
+
withCache
|
|
366
|
+
),
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
|
|
299
370
|
/**
|
|
300
371
|
* Build an in-memory Orama full-text index from search documents. Shared by the
|
|
301
372
|
* Orama client loader (browser), the MCP server, and assistant grounding (Node),
|
|
302
373
|
* so ranking is identical wherever docs are queried. `locale` — the site's
|
|
303
374
|
* `i18n.defaultLocale` — swaps in a word-segmenting tokenizer for every
|
|
304
375
|
* non-Latin script, all of which Orama's default tokenizer reduces to zero
|
|
305
|
-
* tokens
|
|
306
|
-
* applies to every document. That is safe in one direction only: Latin words
|
|
376
|
+
* tokens. A non-Latin default's tokenizer serves every page: Latin words
|
|
307
377
|
* survive segmentation intact, so English pages on a segmented index stay
|
|
308
|
-
* searchable
|
|
309
|
-
*
|
|
378
|
+
* searchable. A Latin default keeps Orama's tokenizer for its Latin-script
|
|
379
|
+
* pages, and each page in a non-Latin locale (the Japanese and Hindi
|
|
380
|
+
* translations of an English site) is indexed with its own locale's tokenizer.
|
|
310
381
|
*/
|
|
311
382
|
export const buildOramaIndex = async (
|
|
312
383
|
documents: OramaDoc[],
|
|
313
384
|
locale?: string
|
|
314
385
|
): Promise<AnyOrama> => {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
? create({ components: { tokenizer }, schema: SCHEMA })
|
|
318
|
-
: create({ schema: SCHEMA });
|
|
319
|
-
await insertMultiple(
|
|
320
|
-
db,
|
|
321
|
-
documents.map((doc) =>
|
|
322
|
-
doc.facets ? { ...doc, facetTerms: toFacetTerms(doc.facets) } : doc
|
|
323
|
-
)
|
|
386
|
+
const rows = documents.map((doc) =>
|
|
387
|
+
doc.facets ? { ...doc, facetTerms: toFacetTerms(doc.facets) } : doc
|
|
324
388
|
);
|
|
389
|
+
const tokenizer = segmentingTokenizer(locale);
|
|
390
|
+
const translations = tokenizer ? undefined : localeTokenizers(documents);
|
|
391
|
+
let db: AnyOrama;
|
|
392
|
+
if (translations && translations.size > 0) {
|
|
393
|
+
db = create({
|
|
394
|
+
components: { tokenizer: dispatchingTokenizer(translations) },
|
|
395
|
+
schema: SCHEMA,
|
|
396
|
+
});
|
|
397
|
+
for (const row of rows) {
|
|
398
|
+
// oxlint-disable-next-line no-await-in-loop -- one at a time, in order, so each page is tokenized under its own locale and ties still rank in document order.
|
|
399
|
+
await insert(db, row, row.locale);
|
|
400
|
+
}
|
|
401
|
+
translationTokenizers.set(db, translations);
|
|
402
|
+
} else {
|
|
403
|
+
db = tokenizer
|
|
404
|
+
? create({ components: { tokenizer }, schema: SCHEMA })
|
|
405
|
+
: create({ schema: SCHEMA });
|
|
406
|
+
await insertMultiple(db, rows);
|
|
407
|
+
}
|
|
408
|
+
if (documents.some((doc) => (doc.boost ?? 1) !== 1)) {
|
|
409
|
+
boostedDatabases.add(db);
|
|
410
|
+
}
|
|
325
411
|
return db;
|
|
326
412
|
};
|
|
327
413
|
|
|
@@ -358,17 +444,56 @@ interface OramaWhereClause {
|
|
|
358
444
|
| undefined;
|
|
359
445
|
}
|
|
360
446
|
|
|
447
|
+
/** A ranked match, scored and carrying its document. */
|
|
448
|
+
type Hit = Result<OramaDoc>;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* One search, with the term tokenized under `language`. On a bigrammed
|
|
452
|
+
* tokenizer the strict pass runs first: a term is only meant to match where
|
|
453
|
+
* its bigrams sit together, and scoring them independently lets a page
|
|
454
|
+
* sharing a couple of windows outrank the page the term is about. Terms
|
|
455
|
+
* spanning several words rarely appear in full on one page, so an empty
|
|
456
|
+
* strict result falls back to the default pass rather than reporting no
|
|
457
|
+
* matches.
|
|
458
|
+
*/
|
|
459
|
+
const rankedHits = async (
|
|
460
|
+
db: AnyOrama,
|
|
461
|
+
params: SearchParamsFullText<AnyOrama, OramaDoc>,
|
|
462
|
+
language: string | undefined,
|
|
463
|
+
bigrammed: boolean
|
|
464
|
+
): Promise<Hit[]> => {
|
|
465
|
+
// The result-document generic is OramaDoc because `buildOramaIndex` is the
|
|
466
|
+
// only writer to this database and inserts OramaDoc records (plus the
|
|
467
|
+
// derived `facetTerms`).
|
|
468
|
+
const strict = bigrammed
|
|
469
|
+
? await search<AnyOrama, OramaDoc>(
|
|
470
|
+
db,
|
|
471
|
+
{ ...params, threshold: ALL_TOKENS },
|
|
472
|
+
language
|
|
473
|
+
)
|
|
474
|
+
: undefined;
|
|
475
|
+
const found =
|
|
476
|
+
strict && strict.hits.length > 0
|
|
477
|
+
? strict
|
|
478
|
+
: await search<AnyOrama, OramaDoc>(db, params, language);
|
|
479
|
+
return found.hits;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/** A hit's rank across searches: its relevance times the page's boost. */
|
|
483
|
+
const boostedScore = (hit: Hit): number =>
|
|
484
|
+
hit.score * (hit.document.boost ?? 1);
|
|
485
|
+
|
|
361
486
|
/**
|
|
362
487
|
* Query the index, returning the matching documents (highest-ranked first).
|
|
363
488
|
* `filters` narrows results by exact `where` matches on the enum fields:
|
|
364
489
|
* `locale` to one language, `contentTypes` to a set of page types, `facets`
|
|
365
490
|
* to documents carrying every requested `key:value` term.
|
|
366
491
|
*
|
|
367
|
-
* On
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
492
|
+
* On an index whose translations have tokenizers of their own, a query scoped
|
|
493
|
+
* to a locale is tokenized with that locale's tokenizer. An unscoped query
|
|
494
|
+
* searches each locale's pages with its own tokenizer, and the Latin-script
|
|
495
|
+
* pages with Orama's, then merges the matches by score: every search runs over
|
|
496
|
+
* the one index, so their scores share its term statistics.
|
|
372
497
|
*/
|
|
373
498
|
export const queryOramaIndex = async (
|
|
374
499
|
db: AnyOrama,
|
|
@@ -391,24 +516,56 @@ export const queryOramaIndex = async (
|
|
|
391
516
|
if (facetTerms.length > 0) {
|
|
392
517
|
where.facetTerms = { containsAll: facetTerms };
|
|
393
518
|
}
|
|
519
|
+
// Boosts multiply every match's score before the limit applies (Orama
|
|
520
|
+
// sorts the whole match set with a sort function), so a boosted page can
|
|
521
|
+
// rise past matches outside the first page.
|
|
394
522
|
const unfiltered = {
|
|
395
523
|
boost: BOOST,
|
|
396
524
|
limit,
|
|
397
|
-
properties: ["title", "description", "content"],
|
|
525
|
+
properties: ["title", "keywords", "description", "content"],
|
|
526
|
+
...(boostedDatabases.has(db) && { sortBy: byBoostedScore }),
|
|
398
527
|
term,
|
|
399
528
|
};
|
|
400
529
|
const params =
|
|
401
530
|
Object.keys(where).length > 0 ? { ...unfiltered, where } : unfiltered;
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
531
|
+
const translations = translationTokenizers.get(db);
|
|
532
|
+
if (!translations) {
|
|
533
|
+
const bigrammed = isBigramLanguage(db.tokenizer?.language ?? "");
|
|
534
|
+
const hits = await rankedHits(db, params, undefined, bigrammed);
|
|
535
|
+
return hits.map((hit) => hit.document);
|
|
536
|
+
}
|
|
537
|
+
if (filters?.locale) {
|
|
538
|
+
const { language = "" } = translations.get(filters.locale) ?? {};
|
|
539
|
+
const bigrammed = isBigramLanguage(language);
|
|
540
|
+
const hits = await rankedHits(db, params, filters.locale, bigrammed);
|
|
541
|
+
return hits.map((hit) => hit.document);
|
|
542
|
+
}
|
|
543
|
+
// `nin` matches only pages with a locale, which every Blume-built index
|
|
544
|
+
// gives each page. (Orama's `not` would admit locale-less pages, but it
|
|
545
|
+
// drops the sibling filters beside it.)
|
|
546
|
+
const searches = [
|
|
547
|
+
rankedHits(
|
|
548
|
+
db,
|
|
549
|
+
{
|
|
550
|
+
...params,
|
|
551
|
+
where: { ...where, locale: { nin: [...translations.keys()] } },
|
|
552
|
+
},
|
|
553
|
+
undefined,
|
|
554
|
+
false
|
|
555
|
+
),
|
|
556
|
+
...[...translations].map(([code, tokenizer]) =>
|
|
557
|
+
rankedHits(
|
|
558
|
+
db,
|
|
559
|
+
{ ...params, where: { ...where, locale: { eq: code } } },
|
|
560
|
+
code,
|
|
561
|
+
isBigramLanguage(tokenizer.language)
|
|
562
|
+
)
|
|
563
|
+
),
|
|
564
|
+
];
|
|
565
|
+
const found = await Promise.all(searches);
|
|
566
|
+
return found
|
|
567
|
+
.flat()
|
|
568
|
+
.toSorted((a, b) => boostedScore(b) - boostedScore(a))
|
|
569
|
+
.slice(0, limit)
|
|
570
|
+
.map((hit) => hit.document);
|
|
414
571
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page's `search.boost` and `search.keywords`, as Pagefind reads them from
|
|
3
|
+
* the built page. Pagefind has no page-level boost, only element weights, so
|
|
4
|
+
* the page's body carries its boost as its weight: a boosted page's matches
|
|
5
|
+
* count for more and it ranks higher. Pagefind damps repeated matches, so the
|
|
6
|
+
* lift is smaller than the full multiplier: a boost of 5 scored its page about
|
|
7
|
+
* 1.5 to 2 times higher in testing. A boost past Pagefind's largest weight,
|
|
8
|
+
* 10, counts as 10.
|
|
9
|
+
* Keywords go on an empty element as an indexed attribute, found like text
|
|
10
|
+
* and never shown.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The largest weight Pagefind takes. */
|
|
14
|
+
const MAX_WEIGHT = 10;
|
|
15
|
+
|
|
16
|
+
/** The attribute a page's keywords ride on. */
|
|
17
|
+
export const KEYWORDS_ATTRIBUTE = "data-blume-keywords";
|
|
18
|
+
|
|
19
|
+
/** What the page body carries for Pagefind. */
|
|
20
|
+
export interface PagefindRanking {
|
|
21
|
+
/** `data-pagefind-weight` for the body, when the page is boosted. */
|
|
22
|
+
weight?: string;
|
|
23
|
+
/** The keywords, space-separated, when the page sets any. */
|
|
24
|
+
keywords?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const pagefindRanking = (search: {
|
|
28
|
+
boost?: number;
|
|
29
|
+
keywords?: string[];
|
|
30
|
+
}): PagefindRanking => {
|
|
31
|
+
const { boost, keywords } = search;
|
|
32
|
+
return {
|
|
33
|
+
...(boost !== undefined &&
|
|
34
|
+
boost !== 1 && {
|
|
35
|
+
weight: String(Math.min(boost, MAX_WEIGHT)),
|
|
36
|
+
}),
|
|
37
|
+
...(keywords && keywords.length > 0 && { keywords: keywords.join(" ") }),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { relative } from "pathe";
|
|
2
|
+
|
|
3
|
+
import type { BlumeProject } from "../core/project-graph.ts";
|
|
4
|
+
|
|
5
|
+
/** Where a page is served, and its title: what a search hit links to. */
|
|
6
|
+
export interface SourcePage {
|
|
7
|
+
title: string;
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Each search-indexable page's source file, relative to the project root,
|
|
13
|
+
* paired with the page it renders. The Mixedbread endpoint links a hit to its
|
|
14
|
+
* page through the path `mxbai store sync` recorded for the chunk's file,
|
|
15
|
+
* which is relative to wherever the CLI ran: the project root, or a directory
|
|
16
|
+
* above it in a monorepo. Pages from a source without local files have no
|
|
17
|
+
* path to match and are left out.
|
|
18
|
+
*/
|
|
19
|
+
export const sourcePages = (project: BlumeProject): [string, SourcePage][] =>
|
|
20
|
+
project.manifest.routes.flatMap((route): [string, SourcePage][] =>
|
|
21
|
+
route.indexable && route.sourcePath
|
|
22
|
+
? [
|
|
23
|
+
[
|
|
24
|
+
relative(project.context.root, route.sourcePath),
|
|
25
|
+
{ title: route.title, url: route.path },
|
|
26
|
+
],
|
|
27
|
+
]
|
|
28
|
+
: []
|
|
29
|
+
);
|
|
@@ -16,10 +16,94 @@ export type AlgoliaSyncConfig = Pick<AlgoliaOptions, "appId" | "indexName">;
|
|
|
16
16
|
*/
|
|
17
17
|
const FILTER_ATTRIBUTES = ["locale", "version"] as const;
|
|
18
18
|
|
|
19
|
+
/** The custom ranking rule that orders close matches by `search.boost`. */
|
|
20
|
+
const BOOST_RANKING = "desc(boost)";
|
|
21
|
+
|
|
19
22
|
/** A faceting declaration's attribute: `filterOnly(locale)` → `locale`. */
|
|
20
23
|
const facetAttribute = (declaration: string): string =>
|
|
21
24
|
declaration.replace(/^\w+\((?<name>.*)\)$/u, "$<name>");
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The largest record Algolia accepts on its Build and Grow plans, in bytes of
|
|
28
|
+
* the record's JSON. One larger record fails the whole upload.
|
|
29
|
+
*/
|
|
30
|
+
const MAX_RECORD_BYTES = 10_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A record as uploaded: the search record's fields plus Algolia's `objectID`.
|
|
34
|
+
* (Mapped rather than the interface itself, which the SDK's
|
|
35
|
+
* `Record<string, unknown>` objects don't accept.)
|
|
36
|
+
*/
|
|
37
|
+
type AlgoliaObject = Pick<SearchRecord, keyof SearchRecord> & {
|
|
38
|
+
objectID: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** A string's or record's size as Algolia measures it: UTF-8 bytes of its JSON. */
|
|
42
|
+
const jsonBytes = (value: string | AlgoliaObject): number =>
|
|
43
|
+
Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
44
|
+
|
|
45
|
+
/** The JSON of the widest single character: a control character's `\u` escape. */
|
|
46
|
+
const WIDEST_CHARACTER = jsonBytes("\u0000");
|
|
47
|
+
|
|
48
|
+
/** The length of `text`'s longest prefix whose JSON string fits `budget`. */
|
|
49
|
+
const fittingLength = (text: string, budget: number): number => {
|
|
50
|
+
let size = jsonBytes("");
|
|
51
|
+
let end = 0;
|
|
52
|
+
[...text].some((character) => {
|
|
53
|
+
size += jsonBytes(character) - jsonBytes("");
|
|
54
|
+
if (size > budget) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
end += character.length;
|
|
58
|
+
return false;
|
|
59
|
+
});
|
|
60
|
+
return end;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Cut `text` into pieces whose JSON strings fit `budget` bytes each, at the
|
|
65
|
+
* last space that fits so no word is split; a run with no space in reach (an
|
|
66
|
+
* unspaced script, a long URL) is cut between characters.
|
|
67
|
+
*/
|
|
68
|
+
const splitText = (text: string, budget: number): string[] => {
|
|
69
|
+
const pieces: string[] = [];
|
|
70
|
+
let rest = text;
|
|
71
|
+
while (jsonBytes(rest) > budget) {
|
|
72
|
+
const end = fittingLength(rest, budget);
|
|
73
|
+
const space = rest.lastIndexOf(" ", end);
|
|
74
|
+
const cut = space > 0 ? space : end;
|
|
75
|
+
pieces.push(rest.slice(0, cut));
|
|
76
|
+
rest = rest.slice(cut).trimStart();
|
|
77
|
+
}
|
|
78
|
+
pieces.push(rest);
|
|
79
|
+
return pieces;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A page's record, split to fit Algolia's cap the way Algolia recommends for
|
|
84
|
+
* long documents: every piece carries the page's title, description, URL and
|
|
85
|
+
* facets with a stretch of its body, and the index's `distinct` on `url`
|
|
86
|
+
* shows the page once, at its best-matching piece. The first piece keeps the
|
|
87
|
+
* page's own `objectID`; the rest add `#1`, `#2`… A record whose other fields
|
|
88
|
+
* leave no room for a single character of body can't be split to fit, and
|
|
89
|
+
* uploads whole for Algolia to reject by name.
|
|
90
|
+
*/
|
|
91
|
+
const toObjects = (record: SearchRecord): AlgoliaObject[] => {
|
|
92
|
+
const whole = { ...record, objectID: record._id };
|
|
93
|
+
const budget =
|
|
94
|
+
MAX_RECORD_BYTES -
|
|
95
|
+
jsonBytes({ ...whole, content: "", objectID: `${record._id}#000` }) +
|
|
96
|
+
jsonBytes("");
|
|
97
|
+
if (jsonBytes(whole) <= MAX_RECORD_BYTES || budget < WIDEST_CHARACTER) {
|
|
98
|
+
return [whole];
|
|
99
|
+
}
|
|
100
|
+
return splitText(record.content, budget).map((content, index) => ({
|
|
101
|
+
...record,
|
|
102
|
+
content,
|
|
103
|
+
objectID: index === 0 ? record._id : `${record._id}#${index}`,
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
106
|
+
|
|
23
107
|
/**
|
|
24
108
|
* Upload the search records to Algolia. Uses the admin key from
|
|
25
109
|
* `ALGOLIA_ADMIN_API_KEY` (never the adapter options, which hold only the
|
|
@@ -27,9 +111,12 @@ const facetAttribute = (declaration: string): string =>
|
|
|
27
111
|
*
|
|
28
112
|
* Uses `replaceAllObjects`, which atomically replaces the index contents, so
|
|
29
113
|
* pages deleted or renamed since the last sync don't linger as stale search
|
|
30
|
-
* hits that 404 when clicked.
|
|
114
|
+
* hits that 404 when clicked. A page too long for one record is split across
|
|
115
|
+
* several (see {@link toObjects}). Then adds `filterOnly(locale)` and
|
|
31
116
|
* `filterOnly(version)` to the index's `attributesForFaceting`, keeping any
|
|
32
|
-
* the site declared itself, so the dialog's locale and version filters match
|
|
117
|
+
* the site declared itself, so the dialog's locale and version filters match,
|
|
118
|
+
* and, once a page is split, `url` as the index's `attributeForDistinct`
|
|
119
|
+
* unless the site set its own.
|
|
33
120
|
*/
|
|
34
121
|
export const syncAlgolia = async (
|
|
35
122
|
records: SearchRecord[],
|
|
@@ -44,16 +131,26 @@ export const syncAlgolia = async (
|
|
|
44
131
|
const { algoliasearch }: typeof AlgoliaSdk = nodeRequire("algoliasearch");
|
|
45
132
|
const client = algoliasearch(config.appId, adminKey);
|
|
46
133
|
const { indexName } = config;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
134
|
+
const objects = records.flatMap(toObjects);
|
|
135
|
+
await client.replaceAllObjects({ indexName, objects });
|
|
136
|
+
const {
|
|
137
|
+
attributeForDistinct,
|
|
138
|
+
attributesForFaceting = [],
|
|
139
|
+
customRanking = [],
|
|
140
|
+
} = await client.getSettings({ indexName });
|
|
54
141
|
const declared = new Set(attributesForFaceting.map(facetAttribute));
|
|
55
142
|
const missing = FILTER_ATTRIBUTES.filter((name) => !declared.has(name));
|
|
56
|
-
|
|
143
|
+
// Every record carries `boost` (search.boost, 1 by default): ranking by it
|
|
144
|
+
// after textual relevance puts boosted pages first among close matches.
|
|
145
|
+
// A ranking the site set in the dashboard keeps its place ahead of it.
|
|
146
|
+
const ranksByBoost = customRanking.some((rule) =>
|
|
147
|
+
/^(?:asc|desc)\(boost\)$/u.test(rule)
|
|
148
|
+
);
|
|
149
|
+
// A split page's records share its `url`: distinct on it lists the page
|
|
150
|
+
// once, at its best-matching record.
|
|
151
|
+
const setsDistinct =
|
|
152
|
+
objects.length > records.length && attributeForDistinct === undefined;
|
|
153
|
+
if (missing.length === 0 && ranksByBoost && !setsDistinct) {
|
|
57
154
|
return;
|
|
58
155
|
}
|
|
59
156
|
const { taskID } = await client.setSettings({
|
|
@@ -63,6 +160,10 @@ export const syncAlgolia = async (
|
|
|
63
160
|
...attributesForFaceting,
|
|
64
161
|
...missing.map((name) => `filterOnly(${name})`),
|
|
65
162
|
],
|
|
163
|
+
customRanking: ranksByBoost
|
|
164
|
+
? customRanking
|
|
165
|
+
: [...customRanking, BOOST_RANKING],
|
|
166
|
+
...(setsDistinct && { attributeForDistinct: "url", distinct: true }),
|
|
66
167
|
},
|
|
67
168
|
});
|
|
68
169
|
await client.waitForTask({ indexName, taskID });
|