@silicajs/search 0.3.1 → 0.3.2
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/README.md +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Server-side SQLite full-text search helpers for Silica.
|
|
4
4
|
|
|
5
|
-
The build step adds an FTS5 `search_index` table to `.silica/vault.db`. The generated Next.js `/api/search` route queries that shared vault database and returns ranked results with excerpts.
|
|
5
|
+
The build step adds an FTS5 `search_index` table to `.silica/next/data/vault.db`. The generated Next.js `/api/search` route queries that shared vault database and returns ranked results with excerpts.
|
|
6
6
|
|
|
7
7
|
Includes a benchmark helper for cold/warm search latency checks.
|
package/dist/index.js
CHANGED
|
@@ -263,9 +263,6 @@ function buildSearchTables(db, records) {
|
|
|
263
263
|
1
|
|
264
264
|
)
|
|
265
265
|
ON CONFLICT(slug) DO UPDATE SET
|
|
266
|
-
title = excluded.title,
|
|
267
|
-
menu_label = excluded.menu_label,
|
|
268
|
-
description = excluded.description,
|
|
269
266
|
tags_json = excluded.tags_json,
|
|
270
267
|
search_excerpt = excluded.search_excerpt
|
|
271
268
|
`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/benchmark.ts","../src/load.ts","../src/excerpt.ts","../src/query.ts","../src/build.ts"],"sourcesContent":["import { performance } from \"node:perf_hooks\";\nimport { loadSearchIndex } from \"./load.js\";\nimport { querySearchIndex } from \"./query.js\";\nimport type { SearchQueryOptions } from \"./types.js\";\n\nexport type SearchBenchmarkOptions = SearchQueryOptions & {\n query: string;\n warmRuns?: number;\n};\n\nexport type SearchBenchmarkResult = {\n coldMs: number;\n warmMs: number;\n warmRuns: number;\n resultCount: number;\n};\n\nexport async function benchmarkSearchIndex(\n databasePath: string,\n { query, warmRuns = 10, ...queryOptions }: SearchBenchmarkOptions,\n): Promise<SearchBenchmarkResult> {\n const coldStart = performance.now();\n const loaded = await loadSearchIndex(databasePath);\n const coldResults = querySearchIndex(loaded, query, queryOptions);\n const coldMs = performance.now() - coldStart;\n\n const runs = Math.max(1, warmRuns);\n const warmStart = performance.now();\n for (let index = 0; index < runs; index += 1) {\n querySearchIndex(loaded, query, queryOptions);\n }\n const warmMs = (performance.now() - warmStart) / runs;\n\n try {\n return {\n coldMs: round(coldMs),\n warmMs: round(warmMs),\n warmRuns: runs,\n resultCount: coldResults.length,\n };\n } finally {\n loaded.close();\n }\n}\n\nfunction round(value: number): number {\n return Math.round(value * 100) / 100;\n}\n","import Database from \"better-sqlite3\";\n\nexport type LoadedSearchIndex = {\n databasePath: string;\n db: Database.Database;\n close: () => void;\n};\n\nconst globalCache = globalThis as typeof globalThis & {\n __silicaSearchIndexes?: Map<string, LoadedSearchIndex>;\n};\n\nexport async function loadSearchIndex(\n databasePath: string,\n): Promise<LoadedSearchIndex> {\n const cache = (globalCache.__silicaSearchIndexes ??= new Map());\n const cached = cache.get(databasePath);\n if (cached) return cached;\n\n const db = new Database(databasePath, {\n fileMustExist: true,\n readonly: true,\n });\n db.pragma(\"query_only = ON\");\n\n const loaded: LoadedSearchIndex = {\n databasePath,\n db,\n close: () => {\n db.close();\n cache.delete(databasePath);\n },\n };\n cache.set(databasePath, loaded);\n return loaded;\n}\n","const WORD_BOUNDARY = /\\s+/g;\n\nexport function normalizeSearchText(value: string): string {\n return value.replace(/\\s+/g, \" \").trim();\n}\n\nexport function makeExcerpt(\n content: string,\n query: string,\n maxLength = 180,\n): string {\n const normalized = normalizeSearchText(content);\n if (normalized.length <= maxLength) return normalized;\n\n const firstTerm = query\n .toLowerCase()\n .split(WORD_BOUNDARY)\n .find((term) => term.length > 1);\n\n const matchIndex = firstTerm\n ? normalized.toLowerCase().indexOf(firstTerm)\n : -1;\n const center = matchIndex >= 0 ? matchIndex : 0;\n const half = Math.floor(maxLength / 2);\n const start = Math.max(0, center - half);\n const end = Math.min(normalized.length, start + maxLength);\n const excerpt = normalized.slice(start, end).trim();\n\n return `${start > 0 ? \"…\" : \"\"}${excerpt}${end < normalized.length ? \"…\" : \"\"}`;\n}\n","import { normalizeSearchText } from \"./excerpt.js\";\nimport type { LoadedSearchIndex } from \"./load.js\";\nimport type {\n SearchHighlightPart,\n SearchQueryOptions,\n SearchResult,\n} from \"./types.js\";\n\nconst HIGHLIGHT_START = \"\\uE000\";\nconst HIGHLIGHT_END = \"\\uE001\";\n\ntype SearchRow = {\n slug: string;\n title: string;\n highlighted_title: string | null;\n description: string | null;\n tags_json: string;\n highlighted_excerpt: string | null;\n score: number;\n};\n\ntype TagOnlyRow = Omit<\n SearchRow,\n \"highlighted_title\" | \"highlighted_excerpt\" | \"score\"\n> & {\n highlighted_title: null;\n excerpt: string;\n score: 0;\n};\n\nexport function querySearchIndex(\n loaded: LoadedSearchIndex,\n query: string,\n options: SearchQueryOptions = {},\n): SearchResult[] {\n const normalized = normalizeSearchText(query);\n const limit = options.limit ?? 10;\n const tagFilter = [\n ...new Set((options.tags ?? []).map(normalizeTag).filter(Boolean)),\n ];\n if (!normalized && tagFilter.length === 0) return [];\n\n if (!normalized) return queryByTags(loaded, tagFilter, limit);\n\n const ftsQuery = toFtsQuery(normalized);\n if (!ftsQuery) {\n return tagFilter.length > 0 ? queryByTags(loaded, tagFilter, limit) : [];\n }\n\n const tagClause = makeTagClause(tagFilter);\n const rows = loaded.db\n .prepare(\n `\n SELECT\n n.slug,\n n.title,\n highlight(search_index, 0, char(57344), char(57345)) AS highlighted_title,\n n.description,\n n.tags_json,\n snippet(search_index, -1, char(57344), char(57345), '…', 24) AS highlighted_excerpt,\n bm25(search_index, 8.0, 1.0, 3.0) AS score\n FROM search_index\n JOIN notes n ON n.rowid = search_index.rowid\n WHERE search_index MATCH ?\n ${tagClause.sql}\n ORDER BY score ASC, n.title COLLATE NOCASE ASC\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...tagClause.params, limit) as SearchRow[];\n\n return rows.map(toResult);\n}\n\nfunction normalizeTag(tag: string): string {\n return tag.trim().replace(/^#/, \"\").toLowerCase();\n}\n\nfunction queryByTags(\n loaded: LoadedSearchIndex,\n tags: string[],\n limit: number,\n): SearchResult[] {\n const tagClause = makeTagClause(tags);\n if (!tagClause.sql) return [];\n\n const rows = loaded.db\n .prepare(\n `\n SELECT\n n.slug,\n n.title,\n NULL AS highlighted_title,\n n.description,\n n.tags_json,\n n.search_excerpt AS excerpt,\n 0 AS score\n FROM notes n\n WHERE 1 = 1\n ${tagClause.sql}\n ORDER BY n.title COLLATE NOCASE ASC\n LIMIT ?\n `,\n )\n .all(...tagClause.params, limit) as TagOnlyRow[];\n\n return rows.map(toResult);\n}\n\nfunction makeTagClause(tags: string[]): { sql: string; params: string[] } {\n if (tags.length === 0) return { sql: \"\", params: [] };\n return {\n sql: `\n AND EXISTS (\n SELECT 1\n FROM note_tags nt\n WHERE nt.slug = n.slug\n AND nt.tag IN (${tags.map(() => \"?\").join(\", \")})\n )\n `,\n params: tags,\n };\n}\n\nfunction toResult(row: SearchRow | TagOnlyRow): SearchResult {\n const excerpt =\n \"highlighted_excerpt\" in row\n ? (row.highlighted_excerpt ?? \"\")\n : row.excerpt;\n return {\n slug: row.slug,\n title: row.title,\n titleParts: toHighlightParts(row.highlighted_title ?? row.title),\n description: row.description ?? undefined,\n tags: parseTags(row.tags_json),\n excerptParts: toHighlightParts(excerpt),\n score: -row.score,\n };\n}\n\nfunction parseTags(value: string): string[] {\n return JSON.parse(value) as string[];\n}\n\nfunction toFtsQuery(query: string): string | undefined {\n const terms = query.match(/[\\p{L}\\p{N}_]+/gu) ?? [];\n const normalizedTerms = terms\n .map((term) => term.toLocaleLowerCase())\n .filter(Boolean);\n if (normalizedTerms.length === 0) return;\n\n return normalizedTerms\n .map((term) => (term.length >= 3 ? `${term}*` : term))\n .join(\" \");\n}\n\nfunction toHighlightParts(value: string): SearchHighlightPart[] {\n const normalized = normalizeSearchText(value);\n if (!normalized) return [];\n\n const parts: SearchHighlightPart[] = [];\n let highlighted = false;\n for (const part of normalized.split(\n new RegExp(`(${HIGHLIGHT_START}|${HIGHLIGHT_END})`),\n )) {\n if (!part) continue;\n if (part === HIGHLIGHT_START) {\n highlighted = true;\n continue;\n }\n if (part === HIGHLIGHT_END) {\n highlighted = false;\n continue;\n }\n parts.push({ text: part, highlighted });\n }\n return parts;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport { makeExcerpt } from \"./excerpt.js\";\nimport type { SearchDatabaseMetadata, SearchRecord } from \"./types.js\";\n\nexport const SEARCH_DATABASE_FILENAME = \"search.db\";\n\nexport async function buildSearchDatabase(\n records: SearchRecord[],\n databasePath: string,\n): Promise<SearchDatabaseMetadata> {\n await fs.mkdir(path.dirname(databasePath), { recursive: true });\n await removeDatabaseFiles(databasePath);\n\n const db = new Database(databasePath);\n try {\n db.pragma(\"journal_mode = DELETE\");\n db.pragma(\"synchronous = OFF\");\n createStandaloneNotesSchema(db);\n const builtAt = buildSearchTables(db, records);\n db.exec(\"VACUUM\");\n\n return {\n version: 1,\n databasePath,\n recordCount: records.length,\n builtAt,\n };\n } finally {\n db.close();\n }\n}\n\nexport function buildSearchTables(\n db: Database.Database,\n records: SearchRecord[],\n): string {\n db.exec(`\n DROP TABLE IF EXISTS search_index;\n CREATE VIRTUAL TABLE search_index USING fts5(\n title,\n content,\n tags,\n tokenize='porter unicode61',\n prefix='3'\n );\n `);\n\n const builtAt = new Date().toISOString();\n const upsertMetadata = db.prepare(`\n INSERT INTO vault_metadata (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n `);\n const insertNote = db.prepare(`\n INSERT INTO notes (\n slug,\n file,\n source_path,\n title,\n menu_label,\n description,\n generated_description,\n frontmatter_json,\n tags_json,\n search_excerpt,\n listed,\n content_hash,\n render_hash,\n prerender\n )\n VALUES (\n @slug,\n @file,\n @sourcePath,\n @title,\n @menuLabel,\n @description,\n NULL,\n '{}',\n @tagsJson,\n @excerpt,\n 1,\n @contentHash,\n @renderHash,\n 1\n )\n ON CONFLICT(slug) DO UPDATE SET\n title = excluded.title,\n menu_label = excluded.menu_label,\n description = excluded.description,\n tags_json = excluded.tags_json,\n search_excerpt = excluded.search_excerpt\n `);\n const selectNoteRowid = db.prepare(\n \"SELECT rowid AS rowid FROM notes WHERE slug = ?\",\n );\n const insertSearch = db.prepare(`\n INSERT INTO search_index (rowid, title, content, tags)\n VALUES (?, ?, ?, ?)\n `);\n const insertTag = db.prepare(`\n INSERT OR IGNORE INTO note_tags (slug, tag)\n VALUES (?, ?)\n `);\n\n const insertRecords = db.transaction((items: SearchRecord[]) => {\n for (const record of items) {\n const tags = record.tags.map(normalizeTag).filter(Boolean);\n const excerpt = makeExcerpt(\n record.content,\n record.description ?? record.title,\n );\n insertNote.run({\n slug: record.slug,\n file: \"\",\n sourcePath: \"\",\n title: record.title,\n menuLabel: record.title,\n description: record.description,\n tagsJson: JSON.stringify(record.tags),\n excerpt,\n contentHash: record.id,\n renderHash: record.id,\n });\n const row = selectNoteRowid.get(record.slug) as { rowid: number };\n insertSearch.run(row.rowid, record.title, record.content, tags.join(\" \"));\n for (const tag of tags) {\n for (const hierarchyTag of tagHierarchy(tag)) {\n insertTag.run(record.slug, hierarchyTag);\n }\n }\n }\n\n upsertMetadata.run(\"searchVersion\", \"1\");\n upsertMetadata.run(\"searchBuiltAt\", builtAt);\n upsertMetadata.run(\"searchRecordCount\", String(items.length));\n });\n insertRecords(records);\n\n db.prepare(\"INSERT INTO search_index(search_index) VALUES(?)\").run(\n \"optimize\",\n );\n return builtAt;\n}\n\nasync function removeDatabaseFiles(databasePath: string): Promise<void> {\n await Promise.all(\n [\"\", \"-journal\", \"-shm\", \"-wal\"].map((suffix) =>\n fs.rm(`${databasePath}${suffix}`, { force: true }),\n ),\n );\n}\n\nfunction normalizeTag(tag: string): string {\n return tag.trim().replace(/^#/, \"\").toLowerCase();\n}\n\nfunction tagHierarchy(tag: string): string[] {\n const segments = tag.split(\"/\").filter(Boolean);\n return segments.map((_, index) => segments.slice(0, index + 1).join(\"/\"));\n}\n\nfunction createStandaloneNotesSchema(db: Database.Database): void {\n db.exec(`\n CREATE TABLE vault_metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE notes (\n slug TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n source_path TEXT NOT NULL,\n title TEXT NOT NULL,\n menu_label TEXT NOT NULL,\n description TEXT,\n generated_description TEXT,\n frontmatter_json TEXT NOT NULL,\n tags_json TEXT NOT NULL,\n search_excerpt TEXT NOT NULL DEFAULT '',\n created TEXT,\n modified TEXT,\n sort_key TEXT,\n listed INTEGER NOT NULL,\n content_hash TEXT NOT NULL,\n render_hash TEXT NOT NULL,\n prerender INTEGER NOT NULL\n );\n\n CREATE TABLE note_tags (\n slug TEXT NOT NULL,\n tag TEXT NOT NULL,\n PRIMARY KEY (slug, tag),\n FOREIGN KEY (slug) REFERENCES notes(slug) ON DELETE CASCADE\n );\n\n CREATE INDEX note_tags_tag_idx ON note_tags(tag, slug);\n `);\n}\n"],"mappings":"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","names":["Database","Database","normalizeTag"]}
|
|
1
|
+
{"version":3,"sources":["../src/benchmark.ts","../src/load.ts","../src/excerpt.ts","../src/query.ts","../src/build.ts"],"sourcesContent":["import { performance } from \"node:perf_hooks\";\nimport { loadSearchIndex } from \"./load.js\";\nimport { querySearchIndex } from \"./query.js\";\nimport type { SearchQueryOptions } from \"./types.js\";\n\nexport type SearchBenchmarkOptions = SearchQueryOptions & {\n query: string;\n warmRuns?: number;\n};\n\nexport type SearchBenchmarkResult = {\n coldMs: number;\n warmMs: number;\n warmRuns: number;\n resultCount: number;\n};\n\nexport async function benchmarkSearchIndex(\n databasePath: string,\n { query, warmRuns = 10, ...queryOptions }: SearchBenchmarkOptions,\n): Promise<SearchBenchmarkResult> {\n const coldStart = performance.now();\n const loaded = await loadSearchIndex(databasePath);\n const coldResults = querySearchIndex(loaded, query, queryOptions);\n const coldMs = performance.now() - coldStart;\n\n const runs = Math.max(1, warmRuns);\n const warmStart = performance.now();\n for (let index = 0; index < runs; index += 1) {\n querySearchIndex(loaded, query, queryOptions);\n }\n const warmMs = (performance.now() - warmStart) / runs;\n\n try {\n return {\n coldMs: round(coldMs),\n warmMs: round(warmMs),\n warmRuns: runs,\n resultCount: coldResults.length,\n };\n } finally {\n loaded.close();\n }\n}\n\nfunction round(value: number): number {\n return Math.round(value * 100) / 100;\n}\n","import Database from \"better-sqlite3\";\n\nexport type LoadedSearchIndex = {\n databasePath: string;\n db: Database.Database;\n close: () => void;\n};\n\nconst globalCache = globalThis as typeof globalThis & {\n __silicaSearchIndexes?: Map<string, LoadedSearchIndex>;\n};\n\nexport async function loadSearchIndex(\n databasePath: string,\n): Promise<LoadedSearchIndex> {\n const cache = (globalCache.__silicaSearchIndexes ??= new Map());\n const cached = cache.get(databasePath);\n if (cached) return cached;\n\n const db = new Database(databasePath, {\n fileMustExist: true,\n readonly: true,\n });\n db.pragma(\"query_only = ON\");\n\n const loaded: LoadedSearchIndex = {\n databasePath,\n db,\n close: () => {\n db.close();\n cache.delete(databasePath);\n },\n };\n cache.set(databasePath, loaded);\n return loaded;\n}\n","const WORD_BOUNDARY = /\\s+/g;\n\nexport function normalizeSearchText(value: string): string {\n return value.replace(/\\s+/g, \" \").trim();\n}\n\nexport function makeExcerpt(\n content: string,\n query: string,\n maxLength = 180,\n): string {\n const normalized = normalizeSearchText(content);\n if (normalized.length <= maxLength) return normalized;\n\n const firstTerm = query\n .toLowerCase()\n .split(WORD_BOUNDARY)\n .find((term) => term.length > 1);\n\n const matchIndex = firstTerm\n ? normalized.toLowerCase().indexOf(firstTerm)\n : -1;\n const center = matchIndex >= 0 ? matchIndex : 0;\n const half = Math.floor(maxLength / 2);\n const start = Math.max(0, center - half);\n const end = Math.min(normalized.length, start + maxLength);\n const excerpt = normalized.slice(start, end).trim();\n\n return `${start > 0 ? \"…\" : \"\"}${excerpt}${end < normalized.length ? \"…\" : \"\"}`;\n}\n","import { normalizeSearchText } from \"./excerpt.js\";\nimport type { LoadedSearchIndex } from \"./load.js\";\nimport type {\n SearchHighlightPart,\n SearchQueryOptions,\n SearchResult,\n} from \"./types.js\";\n\nconst HIGHLIGHT_START = \"\\uE000\";\nconst HIGHLIGHT_END = \"\\uE001\";\n\ntype SearchRow = {\n slug: string;\n title: string;\n highlighted_title: string | null;\n description: string | null;\n tags_json: string;\n highlighted_excerpt: string | null;\n score: number;\n};\n\ntype TagOnlyRow = Omit<\n SearchRow,\n \"highlighted_title\" | \"highlighted_excerpt\" | \"score\"\n> & {\n highlighted_title: null;\n excerpt: string;\n score: 0;\n};\n\nexport function querySearchIndex(\n loaded: LoadedSearchIndex,\n query: string,\n options: SearchQueryOptions = {},\n): SearchResult[] {\n const normalized = normalizeSearchText(query);\n const limit = options.limit ?? 10;\n const tagFilter = [\n ...new Set((options.tags ?? []).map(normalizeTag).filter(Boolean)),\n ];\n if (!normalized && tagFilter.length === 0) return [];\n\n if (!normalized) return queryByTags(loaded, tagFilter, limit);\n\n const ftsQuery = toFtsQuery(normalized);\n if (!ftsQuery) {\n return tagFilter.length > 0 ? queryByTags(loaded, tagFilter, limit) : [];\n }\n\n const tagClause = makeTagClause(tagFilter);\n const rows = loaded.db\n .prepare(\n `\n SELECT\n n.slug,\n n.title,\n highlight(search_index, 0, char(57344), char(57345)) AS highlighted_title,\n n.description,\n n.tags_json,\n snippet(search_index, -1, char(57344), char(57345), '…', 24) AS highlighted_excerpt,\n bm25(search_index, 8.0, 1.0, 3.0) AS score\n FROM search_index\n JOIN notes n ON n.rowid = search_index.rowid\n WHERE search_index MATCH ?\n ${tagClause.sql}\n ORDER BY score ASC, n.title COLLATE NOCASE ASC\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...tagClause.params, limit) as SearchRow[];\n\n return rows.map(toResult);\n}\n\nfunction normalizeTag(tag: string): string {\n return tag.trim().replace(/^#/, \"\").toLowerCase();\n}\n\nfunction queryByTags(\n loaded: LoadedSearchIndex,\n tags: string[],\n limit: number,\n): SearchResult[] {\n const tagClause = makeTagClause(tags);\n if (!tagClause.sql) return [];\n\n const rows = loaded.db\n .prepare(\n `\n SELECT\n n.slug,\n n.title,\n NULL AS highlighted_title,\n n.description,\n n.tags_json,\n n.search_excerpt AS excerpt,\n 0 AS score\n FROM notes n\n WHERE 1 = 1\n ${tagClause.sql}\n ORDER BY n.title COLLATE NOCASE ASC\n LIMIT ?\n `,\n )\n .all(...tagClause.params, limit) as TagOnlyRow[];\n\n return rows.map(toResult);\n}\n\nfunction makeTagClause(tags: string[]): { sql: string; params: string[] } {\n if (tags.length === 0) return { sql: \"\", params: [] };\n return {\n sql: `\n AND EXISTS (\n SELECT 1\n FROM note_tags nt\n WHERE nt.slug = n.slug\n AND nt.tag IN (${tags.map(() => \"?\").join(\", \")})\n )\n `,\n params: tags,\n };\n}\n\nfunction toResult(row: SearchRow | TagOnlyRow): SearchResult {\n const excerpt =\n \"highlighted_excerpt\" in row\n ? (row.highlighted_excerpt ?? \"\")\n : row.excerpt;\n return {\n slug: row.slug,\n title: row.title,\n titleParts: toHighlightParts(row.highlighted_title ?? row.title),\n description: row.description ?? undefined,\n tags: parseTags(row.tags_json),\n excerptParts: toHighlightParts(excerpt),\n score: -row.score,\n };\n}\n\nfunction parseTags(value: string): string[] {\n return JSON.parse(value) as string[];\n}\n\nfunction toFtsQuery(query: string): string | undefined {\n const terms = query.match(/[\\p{L}\\p{N}_]+/gu) ?? [];\n const normalizedTerms = terms\n .map((term) => term.toLocaleLowerCase())\n .filter(Boolean);\n if (normalizedTerms.length === 0) return;\n\n return normalizedTerms\n .map((term) => (term.length >= 3 ? `${term}*` : term))\n .join(\" \");\n}\n\nfunction toHighlightParts(value: string): SearchHighlightPart[] {\n const normalized = normalizeSearchText(value);\n if (!normalized) return [];\n\n const parts: SearchHighlightPart[] = [];\n let highlighted = false;\n for (const part of normalized.split(\n new RegExp(`(${HIGHLIGHT_START}|${HIGHLIGHT_END})`),\n )) {\n if (!part) continue;\n if (part === HIGHLIGHT_START) {\n highlighted = true;\n continue;\n }\n if (part === HIGHLIGHT_END) {\n highlighted = false;\n continue;\n }\n parts.push({ text: part, highlighted });\n }\n return parts;\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport { makeExcerpt } from \"./excerpt.js\";\nimport type { SearchDatabaseMetadata, SearchRecord } from \"./types.js\";\n\nexport const SEARCH_DATABASE_FILENAME = \"search.db\";\n\nexport async function buildSearchDatabase(\n records: SearchRecord[],\n databasePath: string,\n): Promise<SearchDatabaseMetadata> {\n await fs.mkdir(path.dirname(databasePath), { recursive: true });\n await removeDatabaseFiles(databasePath);\n\n const db = new Database(databasePath);\n try {\n db.pragma(\"journal_mode = DELETE\");\n db.pragma(\"synchronous = OFF\");\n createStandaloneNotesSchema(db);\n const builtAt = buildSearchTables(db, records);\n db.exec(\"VACUUM\");\n\n return {\n version: 1,\n databasePath,\n recordCount: records.length,\n builtAt,\n };\n } finally {\n db.close();\n }\n}\n\nexport function buildSearchTables(\n db: Database.Database,\n records: SearchRecord[],\n): string {\n db.exec(`\n DROP TABLE IF EXISTS search_index;\n CREATE VIRTUAL TABLE search_index USING fts5(\n title,\n content,\n tags,\n tokenize='porter unicode61',\n prefix='3'\n );\n `);\n\n const builtAt = new Date().toISOString();\n const upsertMetadata = db.prepare(`\n INSERT INTO vault_metadata (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n `);\n const insertNote = db.prepare(`\n INSERT INTO notes (\n slug,\n file,\n source_path,\n title,\n menu_label,\n description,\n generated_description,\n frontmatter_json,\n tags_json,\n search_excerpt,\n listed,\n content_hash,\n render_hash,\n prerender\n )\n VALUES (\n @slug,\n @file,\n @sourcePath,\n @title,\n @menuLabel,\n @description,\n NULL,\n '{}',\n @tagsJson,\n @excerpt,\n 1,\n @contentHash,\n @renderHash,\n 1\n )\n ON CONFLICT(slug) DO UPDATE SET\n tags_json = excluded.tags_json,\n search_excerpt = excluded.search_excerpt\n `);\n const selectNoteRowid = db.prepare(\n \"SELECT rowid AS rowid FROM notes WHERE slug = ?\",\n );\n const insertSearch = db.prepare(`\n INSERT INTO search_index (rowid, title, content, tags)\n VALUES (?, ?, ?, ?)\n `);\n const insertTag = db.prepare(`\n INSERT OR IGNORE INTO note_tags (slug, tag)\n VALUES (?, ?)\n `);\n\n const insertRecords = db.transaction((items: SearchRecord[]) => {\n for (const record of items) {\n const tags = record.tags.map(normalizeTag).filter(Boolean);\n const excerpt = makeExcerpt(\n record.content,\n record.description ?? record.title,\n );\n insertNote.run({\n slug: record.slug,\n file: \"\",\n sourcePath: \"\",\n title: record.title,\n menuLabel: record.title,\n description: record.description,\n tagsJson: JSON.stringify(record.tags),\n excerpt,\n contentHash: record.id,\n renderHash: record.id,\n });\n const row = selectNoteRowid.get(record.slug) as { rowid: number };\n insertSearch.run(row.rowid, record.title, record.content, tags.join(\" \"));\n for (const tag of tags) {\n for (const hierarchyTag of tagHierarchy(tag)) {\n insertTag.run(record.slug, hierarchyTag);\n }\n }\n }\n\n upsertMetadata.run(\"searchVersion\", \"1\");\n upsertMetadata.run(\"searchBuiltAt\", builtAt);\n upsertMetadata.run(\"searchRecordCount\", String(items.length));\n });\n insertRecords(records);\n\n db.prepare(\"INSERT INTO search_index(search_index) VALUES(?)\").run(\n \"optimize\",\n );\n return builtAt;\n}\n\nasync function removeDatabaseFiles(databasePath: string): Promise<void> {\n await Promise.all(\n [\"\", \"-journal\", \"-shm\", \"-wal\"].map((suffix) =>\n fs.rm(`${databasePath}${suffix}`, { force: true }),\n ),\n );\n}\n\nfunction normalizeTag(tag: string): string {\n return tag.trim().replace(/^#/, \"\").toLowerCase();\n}\n\nfunction tagHierarchy(tag: string): string[] {\n const segments = tag.split(\"/\").filter(Boolean);\n return segments.map((_, index) => segments.slice(0, index + 1).join(\"/\"));\n}\n\nfunction createStandaloneNotesSchema(db: Database.Database): void {\n db.exec(`\n CREATE TABLE vault_metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE notes (\n slug TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n source_path TEXT NOT NULL,\n title TEXT NOT NULL,\n menu_label TEXT NOT NULL,\n description TEXT,\n generated_description TEXT,\n frontmatter_json TEXT NOT NULL,\n tags_json TEXT NOT NULL,\n search_excerpt TEXT NOT NULL DEFAULT '',\n created TEXT,\n modified TEXT,\n sort_key TEXT,\n listed INTEGER NOT NULL,\n content_hash TEXT NOT NULL,\n render_hash TEXT NOT NULL,\n prerender INTEGER NOT NULL\n );\n\n CREATE TABLE note_tags (\n slug TEXT NOT NULL,\n tag TEXT NOT NULL,\n PRIMARY KEY (slug, tag),\n FOREIGN KEY (slug) REFERENCES notes(slug) ON DELETE CASCADE\n );\n\n CREATE INDEX note_tags_tag_idx ON note_tags(tag, slug);\n `);\n}\n"],"mappings":"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","names":["Database","Database","normalizeTag"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silicajs/search",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Server-side SQLite full-text search builder and query helpers for Silica.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"lint": "tsc --noEmit"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"better-sqlite3": "^12.
|
|
26
|
+
"better-sqlite3": "^12.11.1"
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/agdevhq/silica/tree/main/packages/search#readme",
|
|
29
29
|
"repository": {
|