Core: Migrate to new Orama Cloud

This commit is contained in:
Fuma Nama
2025-10-16 20:31:07 +08:00
parent cdbea97699
commit 55afd8a624
14 changed files with 150 additions and 93 deletions
+26
View File
@@ -0,0 +1,26 @@
---
'fumadocs-core': major
---
*Migrate to New Orama Cloud*
`@orama/core` is the new version of Orama Cloud client. See [their docs](https://docs.orama.com/docs/cloud/data-sources/rest-APIs/official-SDK/introduction) for details.
When using Fumadocs' Orama Cloud integration, you need to use the new client instead:
```ts
import { sync } from 'fumadocs-core/search/orama-cloud';
import { OramaCloud } from '@orama/core';
// update this
const orama = new OramaCloud({
projectId: '<project id>',
apiKey: '<private api key>',
});
await sync(orama, {
index: '<data source id>',
documents: records,
});
```
+3 -4
View File
@@ -1,6 +1,5 @@
NEXT_PUBLIC_ORAMA_ENDPOINT=https://cloud.orama.run/v1/indexes/docs-dev-av3ifc
NEXT_PUBLIC_ORAMA_API_KEY=jlm5Ab3P6RSkMIBNDFJw1Zt7bJbVgRcv
ORAMA_INDEX_ID=hd6arq5e816mxigo5m6nakj1
NEXT_PUBLIC_ORAMA_DATASOURCE_ID="dac01b2c-c9e3-4cc7-b8d0-acf164a26210"
NEXT_PUBLIC_ORAMA_PROJECT_ID="11e6cb04-316a-425d-b3cc-19aa02f42a3b"
NEXT_PUBLIC_ORAMA_API_KEY="c1_0706HQdxs_uCRwgAyCUVwwiHoQmxY1Oa4Q_ZQoTl3AvHh54wZTJSjM79smh"
# To deploy search indexes to Orama, put private keys in .env.local:
# ORAMA_PRIVATE_API_KEY=
+6 -10
View File
@@ -1,18 +1,14 @@
import { OramaClient } from '@oramacloud/client';
import type { Suggestion } from '@/components/not-found';
const client = new OramaClient({
endpoint: 'https://cloud.orama.run/v1/indexes/docs-fk97oe',
api_key: 'oPZjdlFbq5BpR54bV5Vj57RYt83Xosk7',
});
import { DataSourceId, orama } from '@/lib/orama/client';
export async function getSuggestions(pathname: string): Promise<Suggestion[]> {
const results = await client.search({
const results = await orama.search({
term: pathname,
mode: 'vector',
datasources: [DataSourceId],
groupBy: {
properties: ['url'],
maxResult: 1,
max_results: 1,
},
});
@@ -23,8 +19,8 @@ export async function getSuggestions(pathname: string): Promise<Suggestion[]> {
return {
id: doc.id,
href: doc.document.url,
title: doc.document.title,
href: doc.document.url as string,
title: doc.document.title as string,
};
});
}
+2 -7
View File
@@ -1,6 +1,5 @@
'use client';
import { OramaClient } from '@oramacloud/client';
import {
SearchDialog,
SearchDialogClose,
@@ -27,11 +26,7 @@ import { cn } from '@/lib/cn';
import { useTreeContext } from 'fumadocs-ui/contexts/tree';
import type { Item, Node } from 'fumadocs-core/page-tree';
import { useRouter } from 'next/navigation';
const client = new OramaClient({
endpoint: 'https://cloud.orama.run/v1/indexes/docs-fk97oe',
api_key: 'oPZjdlFbq5BpR54bV5Vj57RYt83Xosk7',
});
import { orama } from '@/lib/orama/client';
const items = [
{
@@ -65,7 +60,7 @@ export default function CustomSearchDialog(props: SharedProps) {
const [tag, setTag] = useState<string | undefined>();
const { search, setSearch, query } = useDocsSearch({
type: 'orama-cloud',
client,
client: orama,
tag,
});
const { full } = useTreeContext();
@@ -50,7 +50,7 @@ Then, using the private API key and index ID from dashboard, create a script to
```ts title="scripts/sync-content.ts"
import { sync, type OramaDocument } from 'fumadocs-core/search/orama-cloud';
import * as fs from 'node:fs/promises';
import { CloudManager } from '@oramacloud/client';
import { OramaCloud } from '@orama/core';
// the path of pre-rendered `static.json`, choose one according to your React framework
const filePath = {
@@ -61,20 +61,16 @@ const filePath = {
}['next'];
async function main() {
// private API key [!code highlight]
const apiKey = process.env.ORAMA_PRIVATE_API_KEY;
if (!apiKey) {
console.log('no api key for Orama found, skipping');
return;
}
const orama = new OramaCloud({
projectId: '<project id>',
apiKey: '<private api key>',
});
const content = await fs.readFile(filePath);
const records = JSON.parse(content.toString()) as OramaDocument[];
const manager = new CloudManager({ api_key: apiKey });
await sync(manager, {
index: '<index>',
await sync(orama, {
index: '<data source id>',
documents: records,
});
@@ -103,15 +99,18 @@ To search documents on the client side, consider:
```ts
import { useDocsSearch } from 'fumadocs-core/search/client';
import { OramaClient } from '@oramacloud/client';
import { OramaCloud } from '@orama/core';
const client = new OramaClient();
const client = new OramaCloud({
projectId: '<project id>',
apiKey: '<read only api key>',
});
const { search, setSearch, query } = useDocsSearch({
type: 'orama-cloud',
client,
params: {
// search params
// optional search params
},
});
```
@@ -129,11 +128,11 @@ 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';
import { OramaCloud } from '@orama/core';
const client = new OramaClient({
endpoint: '<endpoint_url>',
api_key: '<api_key>',
const client = new OramaCloud({
projectId: '<project id>',
apiKey: '<read only api key>',
});
const { search, setSearch, query } = useDocsSearch({
@@ -13,12 +13,12 @@ import {
type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
import { useDocsSearch } from 'fumadocs-core/search/client';
import { OramaClient } from '@oramacloud/client';
import { OramaCloud } from '@orama/core';
import { useI18n } from 'fumadocs-ui/contexts/i18n';
const client = new OramaClient({
endpoint: 'Endpoint URL',
api_key: 'API Key',
const client = new OramaCloud({
projectId: '',
apiKey: '',
});
export default function CustomSearchDialog(props: SharedProps) {
+13
View File
@@ -0,0 +1,13 @@
import { OramaCloud } from '@orama/core';
export const DataSourceId = process.env
.NEXT_PUBLIC_ORAMA_DATASOURCE_ID as string;
export const isAdmin = process.env.ORAMA_PRIVATE_API_KEY !== undefined;
export const orama = new OramaCloud({
projectId: process.env.NEXT_PUBLIC_ORAMA_PROJECT_ID as string,
apiKey:
process.env.ORAMA_PRIVATE_API_KEY ??
(process.env.NEXT_PUBLIC_ORAMA_API_KEY as string),
});
+1 -1
View File
@@ -17,7 +17,7 @@
"@fumadocs/mdx-remote": "workspace:*",
"@mixedbread/sdk": "^0.34.0",
"@orama/orama": "^3.1.15",
"@oramacloud/client": "^2.1.4",
"@orama/core": "^1.2.13",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-presence": "^1.1.5",
+6 -10
View File
@@ -1,22 +1,18 @@
import { sync, type OramaDocument } from 'fumadocs-core/search/orama-cloud';
import { type OramaDocument, sync } from 'fumadocs-core/search/orama-cloud';
import * as fs from 'node:fs/promises';
import { CloudManager } from '@oramacloud/client';
import { DataSourceId, isAdmin, orama } from '@/lib/orama/client';
export async function updateSearchIndexes(): Promise<void> {
const apiKey = process.env.ORAMA_PRIVATE_API_KEY;
if (!apiKey) {
console.log('no api key for Orama found, skipping');
if (!isAdmin) {
console.log('no private API key for Orama found, skipping');
return;
}
const content = await fs.readFile('.next/server/app/static.json.body');
const records = JSON.parse(content.toString()) as OramaDocument[];
const manager = new CloudManager({ api_key: apiKey });
await sync(manager, {
index: 'twr98yz9itca86121ukrqber',
await sync(orama, {
index: DataSourceId,
documents: records,
});
+3 -3
View File
@@ -129,7 +129,7 @@
"devDependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mixedbread/sdk": "^0.34.0",
"@oramacloud/client": "^2.1.4",
"@orama/core": "^1.2.13",
"@tanstack/react-router": "^1.132.47",
"@types/estree-jsx": "^1.0.5",
"@types/hast": "^3.0.4",
@@ -155,7 +155,7 @@
},
"peerDependencies": {
"@mixedbread/sdk": "^0.19.0",
"@oramacloud/client": "1.x.x || 2.x.x",
"@orama/core": "1.x.x",
"@tanstack/react-router": "1.x.x",
"@types/react": "*",
"algoliasearch": "5.x.x",
@@ -173,7 +173,7 @@
"@types/react": {
"optional": true
},
"@oramacloud/client": {
"@orama/core": {
"optional": true
},
"algoliasearch": {
+12 -9
View File
@@ -1,4 +1,4 @@
import type { ClientSearchParams, OramaClient } from '@oramacloud/client';
import type { OramaCloud, OramaCloudSearchParams } from '@orama/core';
import { removeUndefined } from '@/utils/remove-undefined';
import type { OramaIndex } from '@/search/orama-cloud';
import { createContentHighlighter, type SortedResult } from '@/search';
@@ -12,14 +12,14 @@ interface CrawlerIndex {
}
export interface OramaCloudOptions {
client: OramaClient;
client: OramaCloud;
/**
* 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;
params?: Partial<OramaCloudSearchParams>;
/**
* Filter results with specific tag.
@@ -38,10 +38,11 @@ export async function searchDocs(
): Promise<SortedResult[]> {
const highlighter = createContentHighlighter(query);
const list: SortedResult[] = [];
const { index = 'default', client, params: extraParams = {}, tag } = options;
const { index = 'default', client, params: extraParams, tag } = options;
if (index === 'crawler') {
const result = await client.search({
datasources: [],
...extraParams,
term: query,
where: {
@@ -50,7 +51,7 @@ export async function searchDocs(
eq: tag.slice(0, 1).toUpperCase() + tag.slice(1),
}
: undefined,
...extraParams.where,
...extraParams?.where,
},
limit: 10,
});
@@ -80,17 +81,19 @@ export async function searchDocs(
return list;
}
const params: ClientSearchParams = {
const params: OramaCloudSearchParams = {
datasources: [],
...extraParams,
term: query,
where: removeUndefined({
tag,
...extraParams.where,
...extraParams?.where,
}),
groupBy: {
// TODO: this was causing error on number of group variants
properties: ['page_id'],
maxResult: 7,
...extraParams.groupBy,
max_results: 7,
...extraParams?.groupBy,
},
};
+19 -10
View File
@@ -1,5 +1,5 @@
import type { CloudManager } from '@oramacloud/client';
import type { StructuredData } from '@/mdx-plugins';
import { type AnyObject, type OramaCloud } from '@orama/core';
export interface SyncOptions {
/**
@@ -87,27 +87,35 @@ export interface OramaIndex {
}
export async function sync(
cloudManager: CloudManager,
orama: OramaCloud,
options: SyncOptions,
): Promise<void> {
const { autoDeploy = true } = options;
const index = cloudManager.index(options.index);
const index = orama.index.set(options.index);
await index.transaction.open();
await index.snapshot(options.documents.flatMap(toIndex));
if (autoDeploy) await index.deploy();
await index.transaction.insertDocuments(
options.documents.flatMap(toIndex) as unknown as AnyObject[],
);
if (autoDeploy) await index.transaction.commit();
}
export async function syncI18n(
cloudManager: CloudManager,
orama: OramaCloud,
options: I18nSyncOptions,
): Promise<void> {
const { autoDeploy = true } = options;
const { autoDeploy = true, indexes } = options;
const tasks = options.documents.map(async (document) => {
const index = cloudManager.index(options.indexes[document.locale]);
const index = orama.index.set(indexes[document.locale]);
await index.transaction.open();
await index.snapshot(document.items.flatMap(toIndex));
if (autoDeploy) await index.deploy();
await index.transaction.insertDocuments(
document.items.flatMap(toIndex) as unknown as AnyObject[],
);
if (autoDeploy) await index.transaction.commit();
});
await Promise.all(tasks);
@@ -127,6 +135,7 @@ function toIndex(page: OramaDocument): OramaIndex[] {
id: `${page.id}-${(id++).toString()}`,
title: page.title,
url: page.url,
// TODO: explicit declare enums
page_id: page.id,
tag: page.tag,
section,
+35 -15
View File
@@ -71,12 +71,12 @@ importers:
'@mixedbread/sdk':
specifier: ^0.34.0
version: 0.34.0
'@orama/core':
specifier: ^1.2.13
version: 1.2.13
'@orama/orama':
specifier: ^3.1.15
version: 3.1.15
'@oramacloud/client':
specifier: ^2.1.4
version: 2.1.4
'@radix-ui/react-dialog':
specifier: ^1.1.15
version: 1.1.15(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
@@ -1263,9 +1263,9 @@ importers:
'@mixedbread/sdk':
specifier: ^0.34.0
version: 0.34.0
'@oramacloud/client':
specifier: ^2.1.4
version: 2.1.4
'@orama/core':
specifier: ^1.2.13
version: 1.2.13
'@tanstack/react-router':
specifier: ^1.132.47
version: 1.132.47([email protected]([email protected]))([email protected])
@@ -3560,6 +3560,9 @@ packages:
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@orama/[email protected]':
resolution: {integrity: sha512-FNjEFDxPtdLU/ycrpt4z3GMYn3mljTXmyyN7901nMtXWOilZK+lOa4IRUBQBXMcPtxsxfEZVuJuB0CvIo1ugAA==}
'@orama/[email protected]':
resolution: {integrity: sha512-Lcak3chblMejdlSHgYU2lS2cdOhDpU6vkfIJH4m+YKvqQyLqs1bB8+w6NT1MG5bO12NUK2GFc34Mn2xshMIQ1g==}
@@ -3567,13 +3570,13 @@ packages:
resolution: {integrity: sha512-ltjr1WHlY+uqEKE0JG2G6Xn36mSQGmPdPGQedQyipekBdf0iAtp8oL9dckQRX8cP+nUfOZwwPWSu7km8gGciUg==}
engines: {node: '>= 20.0.0'}
'@orama/[email protected]':
resolution: {integrity: sha512-yAuSwog+HQBAXgZ60TNKEwu04y81/09mpbYBCmz1RCxnr4ObNY2JnPZI7HmALbjAhLJ8t5p+wc2JHRK93ubO4w==}
'@orama/[email protected]':
resolution: {integrity: sha512-VFn0eR5bjwtOUc4tB7L+7vlnxBQr4xYdE0ix36/GCfsq0B99HiwOCROAx5EKsi7uqRYG5WBq+r731s1aXsW4Sg==}
engines: {node: '>= 20.0.0'}
'@oramacloud/[email protected]':
resolution: {integrity: sha512-uNPFs4wq/iOPbggCwTkVNbIr64Vfd7ZS/h+cricXVnzXWocjDTfJ3wLL4lr0qiSu41g8z+eCAGBqJ30RO2O4AA==}
'@oven/[email protected]':
resolution: {integrity: sha512-8ARgq0GDtrYwb8ge9F5xpdZ6q9IxIugeBtdbrVVmWAEuyOlOhzYt/+7gm7xcVZlPmjYmx3D/31bKXJ49mxWI4w==}
cpu: [arm64]
@@ -11150,6 +11153,11 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
[email protected]:
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
peerDependencies:
zod: ^3.24.1
[email protected]:
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
peerDependencies:
@@ -11164,6 +11172,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
[email protected]:
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
[email protected]:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
@@ -12882,22 +12893,25 @@ snapshots:
'@opentelemetry/[email protected]': {}
'@orama/[email protected]':
dependencies:
'@orama/cuid2': 2.2.3
'@orama/oramacore-events-parser': 0.0.5
zod: 3.24.3
zod-to-json-schema: 3.24.5([email protected])
'@orama/[email protected]':
dependencies:
'@noble/hashes': 1.8.0
'@orama/[email protected]': {}
'@orama/[email protected]': {}
'@orama/[email protected]':
dependencies:
'@orama/orama': 3.1.15
'@oramacloud/[email protected]':
dependencies:
'@orama/cuid2': 2.2.3
'@orama/orama': 3.1.15
lodash: 4.17.21
'@oven/[email protected]':
optional: true
@@ -21896,6 +21910,10 @@ snapshots:
compress-commons: 6.0.2
readable-stream: 4.7.0
[email protected]([email protected]):
dependencies:
zod: 3.24.3
[email protected]([email protected]):
dependencies:
zod: 3.25.76
@@ -21906,6 +21924,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
+3 -2
View File
@@ -1,8 +1,10 @@
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": [
"NEXT_PUBLIC_ORAMA_ENDPOINT",
"NEXT_PUBLIC_ORAMA_DATASOURCE_ID",
"NEXT_PUBLIC_ORAMA_PROJECT_ID",
"NEXT_PUBLIC_ORAMA_API_KEY",
"ORAMA_PRIVATE_API_KEY",
"GITHUB_APP_PRIVATE_KEY",
"GITHUB_APP_ID",
"GITHUB_TOKEN",
@@ -11,7 +13,6 @@
"ui": "stream",
"tasks": {
"build": {
"env": ["ALGOLIA_API_KEY", "ORAMA_INDEX_ID", "ORAMA_PRIVATE_API_KEY"],
"dependsOn": ["^build"],
"outputs": [
".output",