Core: Support Orama Cloud crawler index

This commit is contained in:
Fuma Nama
2025-03-14 19:59:45 +08:00
parent 4844f3881a
commit 72f79cfe46
22 changed files with 658 additions and 559 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-core': patch
---
Support Orama Cloud crawler index
+1 -5
View File
@@ -24,11 +24,7 @@ if (item === 'true') {
}
`;
export function Provider({
children,
}: {
children: ReactNode;
}): React.ReactElement {
export function Provider({ children }: { children: ReactNode }) {
return (
<RootProvider
search={{
@@ -3,11 +3,13 @@ title: Orama Cloud
description: Integrate with Orama Cloud
---
## Setup
To begin, create an account on Orama Cloud.
1. Create a new index from Dashboard.
## REST API
REST API integration requires your docs to upload the indexes.
1. Create a new REST API index from Dashboard.
2. Use the following schema:
```json
@@ -23,7 +25,7 @@ To begin, create an account on Orama Cloud.
}
```
3. Then, using the API key and index ID from dashboard, create a script to sync search indexes.
3. Then, using the private API key and index ID from dashboard, create a script to sync search indexes.
```json doc-gen:file
{
@@ -68,3 +70,50 @@ const { search, setSearch, query } = useDocsSearch({
},
});
```
## Web Crawler
1. Create a Crawler index from dashboard, and configure it correctly with the "Documentation" preset.
2. Copy the public API key and index ID from dashboard
### Search Client
Same as REST API integration, but make sure to set `index` to `crawler`.
```ts
import { useDocsSearch } from 'fumadocs-core/search/client';
import { OramaClient } from '@oramacloud/client';
const client = new OramaClient({
endpoint: '<endpoint_url>',
api_key: '<api_key>',
});
const { search, setSearch, query } = useDocsSearch({
type: 'orama-cloud',
index: 'crawler',
client,
params: {
// optional search params
},
});
```
It's same for Fumadocs UI:
```tsx
'use client';
import { OramaClient } from '@oramacloud/client';
import type { SharedProps } from 'fumadocs-ui/components/dialog/search';
import SearchDialog from 'fumadocs-ui/components/dialog/search-orama';
const client = new OramaClient({
endpoint: '<endpoint_url>',
api_key: '<api_key>',
});
export default function CustomSearchDialog(props: SharedProps) {
return <SearchDialog {...props} index="crawler" client={client} />;
}
```
+9 -9
View File
@@ -19,7 +19,7 @@
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@shikijs/rehype": "^3.1.0",
"@shikijs/rehype": "^3.2.1",
"@theguild/remark-mermaid": "^0.2.0",
"class-variance-authority": "^0.7.1",
"fumadocs-core": "workspace:*",
@@ -33,9 +33,9 @@
"hast-util-to-jsx-runtime": "^2.3.6",
"katex": "^0.16.21",
"lucide-react": "^0.479.0",
"next": "15.2.1",
"next": "15.2.2",
"octokit": "^4.1.2",
"oxc-transform": "^0.56.5",
"oxc-transform": "^0.58.1",
"phenomenon": "^1.6.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -47,17 +47,17 @@
"remark-rehype": "^11.1.1",
"remark-stringify": "^11.0.0",
"scroll-into-view-if-needed": "^3.1.0",
"shiki": "^3.1.0",
"shiki": "^3.2.1",
"tailwind-merge": "^3.0.2",
"twoslash": "^0.3.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@fumadocs/cli": "workspace:*",
"@next/bundle-analyzer": "15.2.1",
"@next/env": "15.2.1",
"@next/eslint-plugin-next": "15.2.1",
"@tailwindcss/postcss": "^4.0.12",
"@next/bundle-analyzer": "15.2.2",
"@next/env": "15.2.2",
"@next/eslint-plugin-next": "15.2.2",
"@tailwindcss/postcss": "^4.0.14",
"@types/hast": "^3.0.4",
"@types/mdx": "^2.0.13",
"@types/node": "22.13.10",
@@ -68,7 +68,7 @@
"gray-matter": "^4.0.3",
"next-validate-link": "^1.5.1",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.12",
"tailwindcss": "^4.0.14",
"tailwindcss-animate": "^1.0.7",
"ts-morph": "^25.0.1",
"tsconfig": "workspace:*",
+1 -1
View File
@@ -11,7 +11,7 @@
"@fumadocs/content-collections": "workspace:*",
"fumadocs-core": "workspace:*",
"fumadocs-ui": "workspace:*",
"next": "^15.2.1",
"next": "^15.2.2",
"react": "19.0.0",
"react-dom": "19.0.0"
},
+3 -3
View File
@@ -8,12 +8,12 @@
"start": "next start"
},
"dependencies": {
"@orama/stopwords": "^3.1.2",
"@orama/tokenizers": "^3.1.2",
"@orama/stopwords": "^3.1.3",
"@orama/tokenizers": "^3.1.3",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"next": "15.2.1",
"next": "15.2.2",
"react": "19.0.0",
"react-dom": "19.0.0"
},
+1 -1
View File
@@ -11,7 +11,7 @@
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"next": "15.2.1",
"next": "15.2.2",
"react": "19.0.0",
"react-dom": "19.0.0"
},
+5 -5
View File
@@ -10,25 +10,25 @@
"types:check": "fumadocs-mdx && tsc --noEmit"
},
"dependencies": {
"@scalar/api-client-react": "^1.1.42",
"@scalar/api-client-react": "^1.2.1",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-openapi": "workspace:*",
"fumadocs-ui": "workspace:*",
"next": "15.2.1",
"next": "15.2.2",
"react": "19.0.0",
"react-dom": "19.0.0",
"shiki": "^3.1.0",
"shiki": "^3.2.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.12",
"@tailwindcss/postcss": "^4.0.14",
"@types/mdx": "^2.0.13",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"postcss": "^8.5.3",
"rimraf": "^6.0.1",
"tailwindcss": "^4.0.12",
"tailwindcss": "^4.0.14",
"typescript": "^5.8.2"
}
}
+1 -1
View File
@@ -12,7 +12,7 @@
"fast-glob": "^3.3.3",
"fumadocs-core": "workspace:*",
"fumadocs-ui": "workspace:*",
"next": "15.2.1",
"next": "15.2.2",
"react": "19.0.0",
"react-dom": "19.0.0"
},
+1 -1
View File
@@ -23,7 +23,7 @@
"concurrently": "^9.1.2",
"eslint": "^9.20.1",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-tailwindcss": "^3.18.0",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
+6 -6
View File
@@ -97,19 +97,19 @@
},
"dependencies": {
"@formatjs/intl-localematcher": "^0.6.0",
"@orama/orama": "^3.1.2",
"@shikijs/rehype": "^3.1.0",
"@shikijs/transformers": "^3.1.0",
"@orama/orama": "^3.1.3",
"@shikijs/rehype": "^3.2.1",
"@shikijs/transformers": "^3.2.1",
"github-slugger": "^2.0.0",
"hast-util-to-estree": "^3.1.3",
"hast-util-to-jsx-runtime": "^2.3.6",
"image-size": "^2.0.0",
"image-size": "^2.0.1",
"negotiator": "^1.0.0",
"react-remove-scroll": "^2.6.3",
"remark": "^15.0.0",
"remark-gfm": "^4.0.1",
"scroll-into-view-if-needed": "^3.1.0",
"shiki": "^3.1.0",
"shiki": "^3.2.1",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
@@ -127,7 +127,7 @@
"eslint-config-custom": "workspace:*",
"mdast-util-mdx-jsx": "^3.2.0",
"mdast-util-mdxjs-esm": "^2.0.1",
"next": "^15.2.1",
"next": "^15.2.2",
"remark-mdx": "^3.1.0",
"remark-rehype": "^11.1.1",
"tsconfig": "workspace:*",
+58 -4
View File
@@ -3,8 +3,22 @@ import type { OramaClient, ClientSearchParams } from '@oramacloud/client';
import { removeUndefined } from '@/utils/remove-undefined';
import type { OramaIndex } from '@/search/orama-cloud';
interface CrawlerIndex {
path: string;
title: string;
content: string;
section: string;
category: string;
}
export interface OramaCloudOptions {
client: OramaClient;
/**
* The type of your index.
*
* You can set it to `crawler` if you use crawler instead of the JSON index with schema provided by Fumadocs
*/
index?: 'default' | 'crawler';
params?: ClientSearchParams;
}
@@ -13,7 +27,48 @@ export async function searchDocs(
tag: string | undefined,
options: OramaCloudOptions,
): Promise<SortedResult[]> {
const { client, params: extraParams = {} } = options;
const list: SortedResult[] = [];
const { index = 'default', client, params: extraParams = {} } = options;
if (index === 'crawler') {
const result = await client.search({
...extraParams,
term: query,
where: {
category: tag
? {
eq: tag.slice(0, 1).toUpperCase() + tag.slice(1),
}
: undefined,
...extraParams.where,
},
limit: 10,
});
if (!result) return list;
if (index === 'crawler') {
for (const hit of result.hits) {
const doc = hit.document as unknown as CrawlerIndex;
list.push(
{
id: hit.id,
type: 'page',
content: doc.title,
url: doc.path,
},
{
id: 'page' + hit.id,
type: 'text',
content: doc.content,
url: doc.path,
},
);
}
return list;
}
}
const params: ClientSearchParams = {
...extraParams,
@@ -30,10 +85,9 @@ export async function searchDocs(
};
const result = await client.search(params);
if (!result) return [];
if (!result || !result.groups) return list;
const list: SortedResult[] = [];
for (const item of result.groups ?? []) {
for (const item of result.groups) {
let addedHead = false;
for (const hit of item.result) {
+3 -3
View File
@@ -8,16 +8,16 @@
"@content-collections/core": "^0.8.2",
"@content-collections/mdx": "^0.2.2",
"@content-collections/next": "^0.2.6",
"@tailwindcss/postcss": "^4.0.12",
"@tailwindcss/postcss": "^4.0.14",
"@types/mdx": "^2.0.13",
"@types/node": "22.13.10",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"next": "15.2.1",
"next": "15.2.2",
"postcss": "^8.5.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwindcss": "^4.0.12",
"tailwindcss": "^4.0.14",
"typescript": "^5.8.2"
}
}
+1 -1
View File
@@ -40,7 +40,7 @@
"estree-util-to-js": "^2.0.0",
"estree-util-value-to-estree": "^3.3.2",
"npm-to-yarn": "^3.0.1",
"oxc-transform": "^0.56.5",
"oxc-transform": "^0.58.1",
"unist-util-visit": "^5.0.0",
"zod": "^3.24.2"
},
+1 -1
View File
@@ -43,7 +43,7 @@
"eslint-config-custom": "workspace:*",
"fast-glob": "^3.3.3",
"fumadocs-core": "workspace:*",
"next": "15.2.1",
"next": "15.2.2",
"tsconfig": "workspace:*",
"unified": "^11.0.5",
"vfile": "^6.0.3"
+1 -1
View File
@@ -69,7 +69,7 @@
"eslint-config-custom": "workspace:*",
"fumadocs-core": "workspace:*",
"mdast-util-mdx-jsx": "^3.2.0",
"next": "^15.2.1",
"next": "^15.2.2",
"tsconfig": "workspace:*",
"unified": "^11.0.5",
"vfile": "^6.0.3",
+6 -6
View File
@@ -54,7 +54,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.2",
"@scalar/openapi-parser": "0.10.9",
"@scalar/openapi-parser": "0.10.10",
"ajv-draft-04": "^1.0.0",
"class-variance-authority": "^0.7.1",
"fast-glob": "^3.3.3",
@@ -64,24 +64,24 @@
"hast-util-to-jsx-runtime": "^2.3.6",
"js-yaml": "^4.1.0",
"lucide-react": "^0.479.0",
"next-themes": "^0.4.4",
"next-themes": "^0.4.6",
"openapi-sampler": "^1.6.1",
"react-hook-form": "^7.54.2",
"remark": "^15.0.1",
"remark-rehype": "^11.1.1",
"shiki": "^3.1.0",
"shiki": "^3.2.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@scalar/api-client-react": "^1.1.42",
"@scalar/api-client-react": "^1.2.1",
"@types/js-yaml": "^4.0.9",
"@types/node": "22.13.10",
"@types/openapi-sampler": "^1.0.3",
"@types/react": "^19.0.10",
"eslint-config-custom": "workspace:*",
"next": "15.2.1",
"next": "15.2.2",
"openapi-types": "^12.1.3",
"tailwindcss": "^4.0.12",
"tailwindcss": "^4.0.14",
"tsc-alias": "^1.8.11",
"tsconfig": "workspace:*"
},
+2 -2
View File
@@ -41,7 +41,7 @@
},
"dependencies": {
"@radix-ui/react-popover": "^1.1.6",
"@shikijs/twoslash": "^3.1.0",
"@shikijs/twoslash": "^3.2.1",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-gfm": "^3.1.0",
"mdast-util-to-hast": "^13.2.0",
@@ -55,7 +55,7 @@
"@types/react": "^19.0.10",
"eslint-config-custom": "workspace:*",
"fumadocs-ui": "workspace:*",
"shiki": "^3.1.0",
"shiki": "^3.2.1",
"tsconfig": "workspace:*"
},
"peerDependencies": {
+1 -1
View File
@@ -41,7 +41,7 @@
"hast-util-to-jsx-runtime": "^2.3.6",
"remark": "^15.0.1",
"remark-rehype": "^11.1.1",
"shiki": "^3.1.0",
"shiki": "^3.2.1",
"ts-morph": "^25.0.1",
"unist-util-visit": "^5.0.0"
},
+5 -5
View File
@@ -76,22 +76,22 @@
"class-variance-authority": "^0.7.1",
"lodash.merge": "^4.6.2",
"lucide-react": "^0.479.0",
"next-themes": "^0.4.4",
"next-themes": "^0.4.6",
"postcss-selector-parser": "^7.1.0",
"react-medium-image-zoom": "^5.2.14",
"tailwind-merge": "^3.0.2"
},
"devDependencies": {
"@fumadocs/cli": "workspace:*",
"@next/eslint-plugin-next": "^15.2.1",
"@tailwindcss/cli": "^4.0.12",
"@next/eslint-plugin-next": "^15.2.2",
"@tailwindcss/cli": "^4.0.14",
"@types/lodash.merge": "^4.6.9",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"eslint-config-custom": "workspace:*",
"fumadocs-core": "workspace:*",
"next": "15.2.1",
"tailwindcss": "^4.0.12",
"next": "15.2.2",
"tailwindcss": "^4.0.14",
"tsc-alias": "^1.8.11",
"tsconfig": "workspace:*"
},
@@ -16,6 +16,8 @@ import {
export interface OramaSearchDialogProps extends SharedProps {
client: OramaCloudOptions['client'];
searchOptions?: OramaCloudOptions['params'];
index?: OramaCloudOptions['index'];
footer?: ReactNode;
defaultTag?: string;
@@ -46,6 +48,7 @@ export default function OramaSearchDialog({
defaultTag,
showOrama = false,
allowClear = false,
index,
...props
}: OramaSearchDialogProps): ReactNode {
const [tag, setTag] = useState(defaultTag);
@@ -53,6 +56,7 @@ export default function OramaSearchDialog({
{
type: 'orama-cloud',
client,
index,
params: searchOptions,
},
undefined,
+490 -499
View File
File diff suppressed because it is too large Load Diff