dsh-locator 0.7.1 → 0.7.5

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README-es.md +37 -5
  3. package/README-hi.md +35 -5
  4. package/README-pt.md +33 -5
  5. package/README.en.md +34 -4
  6. package/README.md +27 -5
  7. package/client.js +117 -17
  8. package/cordis.patch.yml +13 -0
  9. package/lib/config.d.ts +20 -0
  10. package/lib/config.d.ts.map +1 -1
  11. package/lib/config.js +38 -4
  12. package/lib/config.js.map +1 -1
  13. package/lib/content-search-worker.d.ts +8 -0
  14. package/lib/content-search-worker.d.ts.map +1 -0
  15. package/lib/content-search-worker.js +54 -0
  16. package/lib/content-search-worker.js.map +1 -0
  17. package/lib/content-search.d.ts +15 -0
  18. package/lib/content-search.d.ts.map +1 -0
  19. package/lib/content-search.js +44 -0
  20. package/lib/content-search.js.map +1 -0
  21. package/lib/content.d.ts +83 -4
  22. package/lib/content.d.ts.map +1 -1
  23. package/lib/content.js +496 -77
  24. package/lib/content.js.map +1 -1
  25. package/lib/find.d.ts +1 -1
  26. package/lib/find.d.ts.map +1 -1
  27. package/lib/find.js +8 -3
  28. package/lib/find.js.map +1 -1
  29. package/lib/http.d.ts +1 -1
  30. package/lib/http.d.ts.map +1 -1
  31. package/lib/http.js +4 -2
  32. package/lib/http.js.map +1 -1
  33. package/lib/index.d.ts +5 -1
  34. package/lib/index.d.ts.map +1 -1
  35. package/lib/index.js +106 -27
  36. package/lib/index.js.map +1 -1
  37. package/lib/reindex.d.ts +1 -1
  38. package/lib/reindex.d.ts.map +1 -1
  39. package/lib/reindex.js +2 -1
  40. package/lib/reindex.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/config.ts +58 -4
  43. package/src/content-search-worker.ts +52 -0
  44. package/src/content-search.ts +45 -0
  45. package/src/content.ts +456 -71
  46. package/src/find.ts +8 -3
  47. package/src/http.ts +4 -2
  48. package/src/index.ts +106 -25
  49. package/src/reindex.ts +2 -1
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Search worker: the FTS5 query runs here so a slow content query can never
3
+ * stall the DSH host's event loop. A read-only connection makes the build's
4
+ * writes in the main thread safe to observe concurrently (WAL).
5
+ * @module dsh-locator/content-search-worker
6
+ */
7
+
8
+ import { parentPort, workerData } from 'node:worker_threads'
9
+ import { DatabaseSync } from 'node:sqlite'
10
+ import { searchContent } from './content-search.ts'
11
+ import type { ContentHit } from './content.ts'
12
+
13
+ interface Request {
14
+ id: number
15
+ open?: string
16
+ close?: boolean
17
+ query?: string
18
+ limit?: number
19
+ }
20
+
21
+ let db: DatabaseSync | null = null
22
+
23
+ function open(path: string): void {
24
+ try { db?.close() } catch { /* ignore */ }
25
+ db = null
26
+ try { db = new DatabaseSync(path, { readOnly: true }) } catch { db = null }
27
+ }
28
+
29
+ const initial = (workerData as { dbPath?: string } | undefined)?.dbPath
30
+ if (initial !== undefined) open(initial)
31
+
32
+ parentPort?.on('message', (request: Request) => {
33
+ if (request.open !== undefined) {
34
+ open(request.open)
35
+ parentPort?.postMessage({ id: request.id, ok: true })
36
+ return
37
+ }
38
+ if (request.close === true) {
39
+ try { db?.close() } catch { /* ignore */ }
40
+ db = null
41
+ parentPort?.postMessage({ id: request.id, ok: true })
42
+ return
43
+ }
44
+ let hits: ContentHit[] = []
45
+ let error = ''
46
+ if (db !== null) {
47
+ try { hits = searchContent(db, request.query ?? '', request.limit ?? 30) } catch (cause) {
48
+ error = cause instanceof Error ? cause.message : String(cause)
49
+ }
50
+ }
51
+ parentPort?.postMessage({ id: request.id, hits, error })
52
+ })
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Content FTS5 query shared by the host thread and the search worker.
3
+ * @module dsh-locator/content-search
4
+ */
5
+
6
+ import type { DatabaseSync } from 'node:sqlite'
7
+ import type { ContentHit } from './content.ts'
8
+
9
+ /**
10
+ * Run one content query against an open SQLite handle.
11
+ * @param db - open database (read-only in the worker).
12
+ * @param query - user query; shorter than three characters falls back to LIKE.
13
+ * @param limit - maximum hits.
14
+ * @returns hits, ordered by relevance.
15
+ */
16
+ export function searchContent(db: DatabaseSync, query: string, limit: number): ContentHit[] {
17
+ const text = query.trim()
18
+ if (text === '') return []
19
+ const capped = Math.max(1, Math.min(100, limit))
20
+ let rows: Array<Record<string, unknown>>
21
+ if ([...text].length < 3) {
22
+ // A one- or two-character query cannot use the trigram index, so bound the
23
+ // payload as well as the row count: never ship a whole megabyte body.
24
+ rows = db.prepare(`SELECT kind, path, title, session_id, message_id,
25
+ substr(body, 1, 400) AS snippet, 0 AS score
26
+ FROM docs WHERE body LIKE '%' || ? || '%' LIMIT ?`).all(text, capped) as Array<Record<string, unknown>>
27
+ } else {
28
+ const match = text.split(/\s+/).filter(Boolean).map(term => '"' + term.replace(/"/g, '') + '"').join(' AND ')
29
+ // Keep MATCH in this statement: FTS5 snippet() and bm25() are only defined
30
+ // inside a matching query (a rowid subquery silently degrades both). The
31
+ // query runs in the search worker, so its cost stays off the host thread.
32
+ rows = db.prepare(`SELECT kind, path, title, session_id, message_id,
33
+ snippet(docs, 0, char(1), char(2), '…', 16) AS snippet, bm25(docs) AS score
34
+ FROM docs WHERE docs MATCH ? ORDER BY score LIMIT ?`).all(match, capped) as Array<Record<string, unknown>>
35
+ }
36
+ return rows.map(row => ({
37
+ kind: row.kind === 'session' ? 'session' : 'file',
38
+ path: String(row.path ?? ''),
39
+ title: String(row.title ?? ''),
40
+ snippet: String(row.snippet ?? row.body ?? '').replace(/[\u0001\u0002]/g, ''),
41
+ ...(row.session_id ? { sessionId: String(row.session_id) } : {}),
42
+ ...(row.message_id ? { messageId: String(row.message_id) } : {}),
43
+ score: Number(row.score ?? 0),
44
+ }))
45
+ }