fumadocs-core
Version:
The library for building a documentation website in any React.js framework
406 lines (398 loc) • 10.2 kB
JavaScript
import {
searchAdvanced,
searchSimple
} from "../chunk-XOFXGHS4.js";
import "../chunk-ZMWYLUDP.js";
import {
basename,
extname
} from "../chunk-Z5V4JSQY.js";
import {
createContentHighlighter
} from "../chunk-OTD7MV33.js";
import {
findPath
} from "../chunk-2IYQ7QMS.js";
import "../chunk-JSBRDJBE.js";
// src/search/server.ts
import {
save
} from "@orama/orama";
// src/search/orama/create-endpoint.ts
function createEndpoint(server) {
const { search } = server;
return {
...server,
async staticGET() {
return Response.json(await server.export());
},
async GET(request) {
const url = new URL(request.url);
const query = url.searchParams.get("query");
if (!query) return Response.json([]);
return Response.json(
await search(query, {
tag: url.searchParams.get("tag")?.split(",") ?? void 0,
locale: url.searchParams.get("locale") ?? void 0,
mode: url.searchParams.get("mode") === "vector" ? "vector" : "full"
})
);
}
};
}
// src/search/orama/create-db.ts
import {
create,
insertMultiple
} from "@orama/orama";
var simpleSchema = {
url: "string",
title: "string",
breadcrumbs: "string[]",
description: "string",
content: "string",
keywords: "string"
};
var advancedSchema = {
content: "string",
page_id: "string",
type: "string",
breadcrumbs: "string[]",
tags: "enum[]",
url: "string",
embeddings: "vector[512]"
};
async function createDB({
indexes,
tokenizer,
search: _,
...rest
}) {
const items = typeof indexes === "function" ? await indexes() : indexes;
const db = create({
schema: advancedSchema,
...rest,
components: {
...rest.components,
tokenizer: tokenizer ?? rest.components?.tokenizer
}
});
const mapTo = [];
items.forEach((page) => {
const pageTag = page.tag ?? [];
const tags = Array.isArray(pageTag) ? pageTag : [pageTag];
const data = page.structuredData;
let id = 0;
mapTo.push({
id: page.id,
page_id: page.id,
type: "page",
content: page.title,
breadcrumbs: page.breadcrumbs,
tags,
url: page.url
});
const nextId = () => `${page.id}-${id++}`;
if (page.description) {
mapTo.push({
id: nextId(),
page_id: page.id,
tags,
type: "text",
url: page.url,
content: page.description
});
}
for (const heading of data.headings) {
mapTo.push({
id: nextId(),
page_id: page.id,
type: "heading",
tags,
url: `${page.url}#${heading.id}`,
content: heading.content
});
}
for (const content of data.contents) {
mapTo.push({
id: nextId(),
page_id: page.id,
tags,
type: "text",
url: content.heading ? `${page.url}#${content.heading}` : page.url,
content: content.content
});
}
});
await insertMultiple(db, mapTo);
return db;
}
async function createDBSimple({
indexes,
tokenizer,
...rest
}) {
const items = typeof indexes === "function" ? await indexes() : indexes;
const db = create({
schema: simpleSchema,
...rest,
components: {
...rest.components,
tokenizer: tokenizer ?? rest.components?.tokenizer
}
});
await insertMultiple(
db,
items.map((page) => ({
title: page.title,
description: page.description,
breadcrumbs: page.breadcrumbs,
url: page.url,
content: page.content,
keywords: page.keywords
}))
);
return db;
}
// src/search/orama/create-from-source.ts
function defaultBuildIndex(source) {
function isBreadcrumbItem(item) {
return typeof item === "string" && item.length > 0;
}
return async (page) => {
let breadcrumbs;
let structuredData;
if ("structuredData" in page.data) {
structuredData = page.data.structuredData;
} else if ("load" in page.data && typeof page.data.load === "function") {
structuredData = (await page.data.load()).structuredData;
}
if (!structuredData)
throw new Error(
"Cannot find structured data from page, please define the page to index function."
);
const pageTree = source.getPageTree(page.locale);
const path = findPath(
pageTree.children,
(node) => node.type === "page" && node.url === page.url
);
if (path) {
breadcrumbs = [];
path.pop();
if (isBreadcrumbItem(pageTree.name)) {
breadcrumbs.push(pageTree.name);
}
for (const segment of path) {
if (!isBreadcrumbItem(segment.name)) continue;
breadcrumbs.push(segment.name);
}
}
return {
title: page.data.title ?? basename(page.path, extname(page.path)),
breadcrumbs,
description: page.data.description,
url: page.url,
id: page.url,
structuredData
};
};
}
function createFromSource(source, _buildIndexOrOptions, _options) {
const { buildIndex = defaultBuildIndex(source), ...options } = {
...typeof _buildIndexOrOptions === "function" ? {
buildIndex: _buildIndexOrOptions
} : _buildIndexOrOptions,
..._options
};
if (source._i18n) {
return createI18nSearchAPI("advanced", {
...options,
i18n: source._i18n,
indexes: async () => {
const indexes = source.getLanguages().flatMap((entry) => {
return entry.pages.map(async (page) => ({
...await buildIndex(page),
locale: entry.language
}));
});
return Promise.all(indexes);
}
});
}
return createSearchAPI("advanced", {
...options,
indexes: async () => {
const indexes = source.getPages().map((page) => buildIndex(page));
return Promise.all(indexes);
}
});
}
// src/search/orama/_stemmers.ts
var STEMMERS = {
arabic: "ar",
armenian: "am",
bulgarian: "bg",
czech: "cz",
danish: "dk",
dutch: "nl",
english: "en",
finnish: "fi",
french: "fr",
german: "de",
greek: "gr",
hungarian: "hu",
indian: "in",
indonesian: "id",
irish: "ie",
italian: "it",
lithuanian: "lt",
nepali: "np",
norwegian: "no",
portuguese: "pt",
romanian: "ro",
russian: "ru",
serbian: "rs",
slovenian: "ru",
spanish: "es",
swedish: "se",
tamil: "ta",
turkish: "tr",
ukrainian: "uk",
sanskrit: "sk"
};
// src/search/orama/create-i18n.ts
async function getTokenizer(locale) {
return {
language: Object.keys(STEMMERS).find((lang) => STEMMERS[lang] === locale) ?? locale
};
}
async function initSimple(options) {
const map = /* @__PURE__ */ new Map();
if (options.i18n.languages.length === 0) {
return map;
}
const indexes = typeof options.indexes === "function" ? await options.indexes() : options.indexes;
for (const locale of options.i18n.languages) {
const localeIndexes = indexes.filter((index) => index.locale === locale);
const mapped = options.localeMap?.[locale] ?? await getTokenizer(locale);
map.set(
locale,
typeof mapped === "object" ? initSimpleSearch({
...options,
...mapped,
indexes: localeIndexes
}) : initSimpleSearch({
...options,
language: mapped,
indexes: localeIndexes
})
);
}
return map;
}
async function initAdvanced(options) {
const map = /* @__PURE__ */ new Map();
if (options.i18n.languages.length === 0) {
return map;
}
const indexes = typeof options.indexes === "function" ? await options.indexes() : options.indexes;
for (const locale of options.i18n.languages) {
const localeIndexes = indexes.filter((index) => index.locale === locale);
const mapped = options.localeMap?.[locale] ?? await getTokenizer(locale);
map.set(
locale,
typeof mapped === "object" ? initAdvancedSearch({
...options,
indexes: localeIndexes,
...mapped
}) : initAdvancedSearch({
...options,
language: mapped,
indexes: localeIndexes
})
);
}
return map;
}
function createI18nSearchAPI(type, options) {
const get = type === "simple" ? initSimple(options) : initAdvanced(options);
return createEndpoint({
async export() {
const map = await get;
const entries = Array.from(map.entries()).map(async ([k, v]) => [
k,
await v.export()
]);
return {
type: "i18n",
data: Object.fromEntries(await Promise.all(entries))
};
},
async search(query, searchOptions) {
const map = await get;
const locale = searchOptions?.locale ?? options.i18n.defaultLanguage;
const handler = map.get(locale);
if (handler) return handler.search(query, searchOptions);
return [];
}
});
}
// src/search/server.ts
function createSearchAPI(type, options) {
if (type === "simple") {
return createEndpoint(initSimpleSearch(options));
}
return createEndpoint(initAdvancedSearch(options));
}
function initSimpleSearch(options) {
const doc = createDBSimple(options);
return {
async export() {
return {
type: "simple",
...save(await doc)
};
},
async search(query) {
const db = await doc;
return searchSimple(db, query, options.search);
}
};
}
function initAdvancedSearch(options) {
const get = createDB(options);
return {
async export() {
return {
type: "advanced",
...save(await get)
};
},
async search(query, searchOptions) {
const db = await get;
const mode = searchOptions?.mode;
return searchAdvanced(db, query, searchOptions?.tag, {
...options.search,
mode: mode === "vector" ? "vector" : "fulltext"
}).catch((err) => {
if (mode === "vector") {
throw new Error(
"failed to search, make sure you have installed `@orama/plugin-embeddings` according to their docs.",
{
cause: err
}
);
}
throw err;
});
}
};
}
export {
createContentHighlighter,
createFromSource,
createI18nSearchAPI,
createSearchAPI,
initAdvancedSearch,
initSimpleSearch
};