From 97c3afcb8df2fe0a1aaadf007216b1e01a611f19 Mon Sep 17 00:00:00 2001 From: Dbisina Date: Mon, 26 Oct 2026 20:34:14 +0100 Subject: [PATCH] fix(orama-db): make orama-db.json identical between builds Documents were inserted without an id, so Orama generated a time-based one that is saved in the index, and the saved index followed the generator's input order, which can change between builds. Use each document's href as its id and sort the documents by href (without a locale) before inserting them. Add generator tests. --- .changeset/orama-db-deterministic.md | 5 ++ .../src/orama-db/__tests__/generate.test.mjs | 65 +++++++++++++++++++ packages/react/src/orama-db/generate.mjs | 12 +++- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 .changeset/orama-db-deterministic.md create mode 100644 packages/react/src/orama-db/__tests__/generate.test.mjs diff --git a/.changeset/orama-db-deterministic.md b/.changeset/orama-db-deterministic.md new file mode 100644 index 000000000..9d197b900 --- /dev/null +++ b/.changeset/orama-db-deterministic.md @@ -0,0 +1,5 @@ +--- +'@doc-kit/generator-react': patch +--- + +`orama-db.json` is now identical between builds of the same docs: documents get a stable `href` id instead of a time-based one, and are sorted by `href`. diff --git a/packages/react/src/orama-db/__tests__/generate.test.mjs b/packages/react/src/orama-db/__tests__/generate.test.mjs new file mode 100644 index 000000000..dec6abf80 --- /dev/null +++ b/packages/react/src/orama-db/__tests__/generate.test.mjs @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { setConfig } from '@doc-kit/core/utils/configuration/index.mjs'; +import { create, load, search } from '@orama/orama'; +import { u } from 'unist-builder'; + +import { generate } from '../generate.mjs'; + +const config = await setConfig({ target: ['orama-db'] }); + +// Return the saved index instead of writing it to disk +config['orama-db'].output = undefined; + +const entry = (api, path, depth, name, slug, text) => ({ + api, + path, + heading: { depth, data: { name, slug } }, + content: u('root', [u('paragraph', [u('text', text)])]), +}); + +const fs = [ + entry('fs', '/fs', 1, 'File system', 'file-system', 'Files.'), + entry('fs', '/fs', 2, 'Class: FileHandle', 'class-filehandle', 'A handle.'), + entry('fs', '/fs', 3, 'readFile', 'readfile', 'Reads a file.'), +]; + +const http = [ + entry('http', '/http', 1, 'HTTP', 'http', 'Requests.'), + entry('http', '/http', 2, 'request', 'request', 'Sends a request.'), +]; + +describe('orama-db', () => { + it('produces the same output on every run', async () => { + const first = await generate([...fs, ...http]); + const second = await generate([...fs, ...http]); + + assert.equal(JSON.stringify(first), JSON.stringify(second)); + }); + + it('produces the same output whatever order the modules arrive in', async () => { + const forward = await generate([...fs, ...http]); + const reversed = await generate([...http, ...fs]); + + assert.equal(JSON.stringify(forward), JSON.stringify(reversed)); + }); + + it('keeps each module hierarchical title', async () => { + const db = create({ schema: {} }); + load(db, await generate([...http, ...fs])); + + const { hits } = await search(db, { term: 'readFile' }); + + assert.equal(hits[0].document.title, 'Class: FileHandle > readFile'); + }); + + it('can still be searched', async () => { + const db = create({ schema: {} }); + load(db, await generate([...fs, ...http])); + + const { hits } = await search(db, { term: 'request' }); + + assert.equal(hits[0].document.href, '/http.html#request'); + }); +}); diff --git a/packages/react/src/orama-db/generate.mjs b/packages/react/src/orama-db/generate.mjs index a67dfa077..9432907b2 100644 --- a/packages/react/src/orama-db/generate.mjs +++ b/packages/react/src/orama-db/generate.mjs @@ -32,17 +32,27 @@ export async function generate(input) { child => child.type === 'paragraph' ); + const href = `${entry.path}.html#${entry.heading.data.slug}`; + return { + // Orama makes up a time-based id when none is given, which would + // change the saved index on every build + id: href, title: hierarchicalTitle, description: paragraph ? transformNodeToString(paragraph, true) : undefined, - href: `${entry.path}.html#${entry.heading.data.slug}`, + href, siteSection: headings[0].heading.data.name, }; }) ); + // The saved index follows insertion order, which depends on the order the + // generator's input arrives in. Sort without a locale so it is stable on + // every machine. + documents.sort((a, b) => (a.href < b.href ? -1 : a.href > b.href ? 1 : 0)); + // Insert all documents await insertMultiple(db, documents);