sensemaking 0.27.1 → 0.28.1

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 (138) hide show
  1. package/README.md +2 -2
  2. package/bin/cli.js +16 -7
  3. package/dist/cjs/cli/constants.d.cts +2 -0
  4. package/dist/cjs/cli/constants.d.ts +2 -0
  5. package/dist/cjs/cli/constants.js +21 -0
  6. package/dist/cjs/cli/constants.js.map +1 -0
  7. package/dist/cjs/cli/named.js +2 -1
  8. package/dist/cjs/cli/named.js.map +1 -1
  9. package/dist/cjs/cli/shared.js +5 -4
  10. package/dist/cjs/cli/shared.js.map +1 -1
  11. package/dist/cjs/cli.js +10 -9
  12. package/dist/cjs/cli.js.map +1 -1
  13. package/dist/cjs/embed/query.js.map +1 -1
  14. package/dist/cjs/store/duckdb/engine.d.cts +11 -0
  15. package/dist/cjs/store/duckdb/engine.d.ts +11 -0
  16. package/dist/cjs/store/duckdb/engine.js +193 -0
  17. package/dist/cjs/store/duckdb/engine.js.map +1 -0
  18. package/dist/cjs/store/duckdb/lexical.js +34 -20
  19. package/dist/cjs/store/duckdb/lexical.js.map +1 -1
  20. package/dist/cjs/store/duckdb/native.d.cts +5 -2
  21. package/dist/cjs/store/duckdb/native.d.ts +5 -2
  22. package/dist/cjs/store/duckdb/native.js +157 -13
  23. package/dist/cjs/store/duckdb/native.js.map +1 -1
  24. package/dist/cjs/store/duckdb/open.js +151 -3
  25. package/dist/cjs/store/duckdb/open.js.map +1 -1
  26. package/dist/cjs/store/duckdb/ordered-bm25.d.cts +3 -3
  27. package/dist/cjs/store/duckdb/ordered-bm25.d.ts +3 -3
  28. package/dist/cjs/store/duckdb/ordered-bm25.js +4 -8
  29. package/dist/cjs/store/duckdb/ordered-bm25.js.map +1 -1
  30. package/dist/cjs/store/engine.d.cts +28 -0
  31. package/dist/cjs/store/engine.d.ts +28 -0
  32. package/dist/cjs/store/engine.js +362 -0
  33. package/dist/cjs/store/engine.js.map +1 -0
  34. package/dist/cjs/store/native.d.cts +8 -5
  35. package/dist/cjs/store/native.d.ts +8 -5
  36. package/dist/cjs/store/native.js +78 -73
  37. package/dist/cjs/store/native.js.map +1 -1
  38. package/dist/cjs/store/open.js +100 -40
  39. package/dist/cjs/store/open.js.map +1 -1
  40. package/dist/cjs/store/sqlite/engine.d.cts +12 -0
  41. package/dist/cjs/store/sqlite/engine.d.ts +12 -0
  42. package/dist/cjs/store/sqlite/engine.js +53 -0
  43. package/dist/cjs/store/sqlite/engine.js.map +1 -0
  44. package/dist/cjs/store/sqlite/lexical.js +3 -4
  45. package/dist/cjs/store/sqlite/lexical.js.map +1 -1
  46. package/dist/cjs/store/sqlite/open.js +119 -6
  47. package/dist/cjs/store/sqlite/open.js.map +1 -1
  48. package/dist/cjs/store/turso/engine.d.cts +9 -0
  49. package/dist/cjs/store/turso/engine.d.ts +9 -0
  50. package/dist/cjs/store/turso/engine.js +189 -0
  51. package/dist/cjs/store/turso/engine.js.map +1 -0
  52. package/dist/cjs/store/turso/lexical.js +16 -11
  53. package/dist/cjs/store/turso/lexical.js.map +1 -1
  54. package/dist/cjs/store/turso/native.d.cts +5 -1
  55. package/dist/cjs/store/turso/native.d.ts +5 -1
  56. package/dist/cjs/store/turso/native.js +157 -8
  57. package/dist/cjs/store/turso/native.js.map +1 -1
  58. package/dist/cjs/store/turso/open.js +172 -27
  59. package/dist/cjs/store/turso/open.js.map +1 -1
  60. package/dist/cjs/store/turso/reconcile.d.cts +0 -1
  61. package/dist/cjs/store/turso/reconcile.d.ts +0 -1
  62. package/dist/cjs/store/turso/reconcile.js +22 -23
  63. package/dist/cjs/store/turso/reconcile.js.map +1 -1
  64. package/dist/cjs/store/types.d.cts +2 -0
  65. package/dist/cjs/store/types.d.ts +2 -0
  66. package/dist/cjs/store/types.js.map +1 -1
  67. package/dist/esm/cli/constants.d.ts +2 -0
  68. package/dist/esm/cli/constants.js +2 -0
  69. package/dist/esm/cli/constants.js.map +1 -0
  70. package/dist/esm/cli/named.js +2 -1
  71. package/dist/esm/cli/named.js.map +1 -1
  72. package/dist/esm/cli/shared.js +6 -5
  73. package/dist/esm/cli/shared.js.map +1 -1
  74. package/dist/esm/cli.js +10 -9
  75. package/dist/esm/cli.js.map +1 -1
  76. package/dist/esm/embed/query.js +1 -1
  77. package/dist/esm/embed/query.js.map +1 -1
  78. package/dist/esm/store/duckdb/engine.d.ts +11 -0
  79. package/dist/esm/store/duckdb/engine.js +18 -0
  80. package/dist/esm/store/duckdb/engine.js.map +1 -0
  81. package/dist/esm/store/duckdb/lexical.js +11 -11
  82. package/dist/esm/store/duckdb/lexical.js.map +1 -1
  83. package/dist/esm/store/duckdb/native.d.ts +5 -2
  84. package/dist/esm/store/duckdb/native.js +16 -12
  85. package/dist/esm/store/duckdb/native.js.map +1 -1
  86. package/dist/esm/store/duckdb/open.js +40 -2
  87. package/dist/esm/store/duckdb/open.js.map +1 -1
  88. package/dist/esm/store/duckdb/ordered-bm25.d.ts +3 -3
  89. package/dist/esm/store/duckdb/ordered-bm25.js +4 -9
  90. package/dist/esm/store/duckdb/ordered-bm25.js.map +1 -1
  91. package/dist/esm/store/engine.d.ts +28 -0
  92. package/dist/esm/store/engine.js +85 -0
  93. package/dist/esm/store/engine.js.map +1 -0
  94. package/dist/esm/store/native.d.ts +8 -5
  95. package/dist/esm/store/native.js +59 -57
  96. package/dist/esm/store/native.js.map +1 -1
  97. package/dist/esm/store/open.js +24 -7
  98. package/dist/esm/store/open.js.map +1 -1
  99. package/dist/esm/store/sqlite/engine.d.ts +12 -0
  100. package/dist/esm/store/sqlite/engine.js +16 -0
  101. package/dist/esm/store/sqlite/engine.js.map +1 -0
  102. package/dist/esm/store/sqlite/lexical.js +2 -3
  103. package/dist/esm/store/sqlite/lexical.js.map +1 -1
  104. package/dist/esm/store/sqlite/open.js +38 -7
  105. package/dist/esm/store/sqlite/open.js.map +1 -1
  106. package/dist/esm/store/turso/engine.d.ts +9 -0
  107. package/dist/esm/store/turso/engine.js +14 -0
  108. package/dist/esm/store/turso/engine.js.map +1 -0
  109. package/dist/esm/store/turso/lexical.js +9 -9
  110. package/dist/esm/store/turso/lexical.js.map +1 -1
  111. package/dist/esm/store/turso/native.d.ts +5 -1
  112. package/dist/esm/store/turso/native.js +16 -10
  113. package/dist/esm/store/turso/native.js.map +1 -1
  114. package/dist/esm/store/turso/open.js +44 -3
  115. package/dist/esm/store/turso/open.js.map +1 -1
  116. package/dist/esm/store/turso/reconcile.d.ts +0 -1
  117. package/dist/esm/store/turso/reconcile.js +3 -8
  118. package/dist/esm/store/turso/reconcile.js.map +1 -1
  119. package/dist/esm/store/types.d.ts +2 -0
  120. package/dist/esm/store/types.js.map +1 -1
  121. package/package.json +21 -7
  122. package/skills/sense/EXAMPLES.md +0 -99
  123. package/skills/sense/SKILL.md +0 -145
  124. package/skills/sense/references/search.md +0 -63
  125. package/skills/sense/references/sql.md +0 -120
  126. package/skills/sense/references/stores/duckdb.md +0 -35
  127. package/skills/sense/references/stores/sqlite.md +0 -66
  128. package/skills/sense/references/stores/turso.md +0 -36
  129. package/skills/sense/references/terminology.md +0 -32
  130. package/skills/sense-bases/EXAMPLES.md +0 -103
  131. package/skills/sense-bases/SKILL.md +0 -147
  132. package/skills/sense-setup/EXAMPLES.md +0 -129
  133. package/skills/sense-setup/SKILL.md +0 -112
  134. package/skills/sense-setup/references/embeddings.md +0 -51
  135. package/skills/sense-setup/references/store-benchmarks.md +0 -16
  136. package/skills/sense-setup/references/stores/duckdb.md +0 -28
  137. package/skills/sense-setup/references/stores/sqlite.md +0 -21
  138. package/skills/sense-setup/references/stores/turso.md +0 -25
@@ -0,0 +1,85 @@
1
+ import semver from 'semver';
2
+ import { SenseError } from '../errors.js';
3
+ // Which native engine built a derived index. A different engine rebuilds the cache, as a schema change does:
4
+ // native index formats and scoring belong to the engine, not to sensemaking's schema.
5
+ export const ENGINE_IDENTITY_META_KEY = 'engine_identity';
6
+ export function engineIdentity(pkg, version) {
7
+ return `${pkg}@${version !== null && version !== void 0 ? version : 'unknown'}`;
8
+ }
9
+ // Later patches win where ranges overlap, so a narrower fix is listed after the broader one it refines.
10
+ export function selectBuilders(defaults, patches, version) {
11
+ let selected = defaults;
12
+ for (const patch of patches){
13
+ if (version !== undefined && semver.satisfies(version, patch.range, {
14
+ includePrerelease: true
15
+ })) selected = {
16
+ ...selected,
17
+ ...patch.builders
18
+ };
19
+ }
20
+ return selected;
21
+ }
22
+ // Expected results every store must reproduce. The title match sorts last by path, so an engine whose scores
23
+ // collapse to zero (Turso's fts_score silent mode) or lose field weights reorders 'run' and fails the check.
24
+ export const ENGINE_CHECK_DOCS = [
25
+ {
26
+ path: 'b-text.md',
27
+ title: 'Garden',
28
+ summary: '',
29
+ text: 'The dog runs past two cats.'
30
+ },
31
+ {
32
+ path: 'c-cjk.md',
33
+ title: 'Kitchen',
34
+ summary: '',
35
+ text: '日本語のメモ'
36
+ },
37
+ {
38
+ path: 'z-title.md',
39
+ title: 'Running dogs',
40
+ summary: '',
41
+ text: 'A note about pets.'
42
+ }
43
+ ];
44
+ export const ENGINE_CHECK_QUERIES = [
45
+ {
46
+ terms: 'run',
47
+ paths: [
48
+ 'z-title.md',
49
+ 'b-text.md'
50
+ ]
51
+ },
52
+ {
53
+ terms: 'dog cat',
54
+ paths: [
55
+ 'b-text.md'
56
+ ]
57
+ },
58
+ {
59
+ terms: '日本',
60
+ paths: [
61
+ 'c-cjk.md'
62
+ ]
63
+ }
64
+ ];
65
+ // What to do when an npm-installed engine fails: get a version from the tested range.
66
+ export function npmRemedy(pkg, range) {
67
+ return `run \`npm install ${pkg}@${range}\` for a tested version (tested ${range})`;
68
+ }
69
+ // Runs the store's real lexical query over ENGINE_CHECK_DOCS; any thrown error or wrong result fails the engine.
70
+ export async function checkEngine(store, identity, remedy, query) {
71
+ for (const expected of ENGINE_CHECK_QUERIES){
72
+ let paths;
73
+ try {
74
+ paths = await query(expected.terms);
75
+ } catch (err) {
76
+ throw engineCheckFailed(store, identity, remedy, `searching "${expected.terms}" failed (${err.message})`);
77
+ }
78
+ if (paths.join('\n') !== expected.paths.join('\n')) {
79
+ throw engineCheckFailed(store, identity, remedy, `searching "${expected.terms}" returned [${paths.join(', ')}], expected [${expected.paths.join(', ')}]`);
80
+ }
81
+ }
82
+ }
83
+ export function engineCheckFailed(store, identity, remedy, failure) {
84
+ return new SenseError('STORE_CAPABILITY_MISSING', `store "${store}" cannot use ${identity}: ${failure}; ${remedy}`);
85
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/engine.ts"],"sourcesContent":["import semver from 'semver';\nimport { SenseError } from '../errors.ts';\n\n// Which native engine built a derived index. A different engine rebuilds the cache, as a schema change does:\n// native index formats and scoring belong to the engine, not to sensemaking's schema.\nexport const ENGINE_IDENTITY_META_KEY = 'engine_identity';\n\nexport function engineIdentity(pkg: string, version: string | undefined): string {\n return `${pkg}@${version ?? 'unknown'}`;\n}\n\n/**\n * One version-specific replacement of a store's SQL builders. `range` matches the engine version (the npm package's,\n * or Node's bundled SQLite for sqlite), prereleases included since every `@duckdb/node-api` release is one;\n * `reason` names the observed difference.\n */\nexport interface EnginePatch<Builders> {\n range: string;\n reason: string;\n builders: Partial<Builders>;\n}\n\n// Later patches win where ranges overlap, so a narrower fix is listed after the broader one it refines.\nexport function selectBuilders<Builders extends object>(defaults: Builders, patches: readonly EnginePatch<Builders>[], version: string | undefined): Builders {\n let selected = defaults;\n for (const patch of patches) {\n if (version !== undefined && semver.satisfies(version, patch.range, { includePrerelease: true })) selected = { ...selected, ...patch.builders };\n }\n return selected;\n}\n\nexport interface EngineCheckDoc {\n path: string;\n title: string;\n summary: string;\n text: string;\n}\n\n// Expected results every store must reproduce. The title match sorts last by path, so an engine whose scores\n// collapse to zero (Turso's fts_score silent mode) or lose field weights reorders 'run' and fails the check.\nexport const ENGINE_CHECK_DOCS: readonly EngineCheckDoc[] = [\n { path: 'b-text.md', title: 'Garden', summary: '', text: 'The dog runs past two cats.' },\n { path: 'c-cjk.md', title: 'Kitchen', summary: '', text: '日本語のメモ' },\n { path: 'z-title.md', title: 'Running dogs', summary: '', text: 'A note about pets.' },\n];\n\nexport const ENGINE_CHECK_QUERIES: ReadonlyArray<{ terms: string; paths: string[] }> = [\n { terms: 'run', paths: ['z-title.md', 'b-text.md'] },\n { terms: 'dog cat', paths: ['b-text.md'] },\n { terms: '日本', paths: ['c-cjk.md'] },\n];\n\n// What to do when an npm-installed engine fails: get a version from the tested range.\nexport function npmRemedy(pkg: string, range: string): string {\n return `run \\`npm install ${pkg}@${range}\\` for a tested version (tested ${range})`;\n}\n\n// Runs the store's real lexical query over ENGINE_CHECK_DOCS; any thrown error or wrong result fails the engine.\nexport async function checkEngine(store: string, identity: string, remedy: string, query: (terms: string) => Promise<string[]>): Promise<void> {\n for (const expected of ENGINE_CHECK_QUERIES) {\n let paths: string[];\n try {\n paths = await query(expected.terms);\n } catch (err) {\n throw engineCheckFailed(store, identity, remedy, `searching \"${expected.terms}\" failed (${(err as Error).message})`);\n }\n if (paths.join('\\n') !== expected.paths.join('\\n')) {\n throw engineCheckFailed(store, identity, remedy, `searching \"${expected.terms}\" returned [${paths.join(', ')}], expected [${expected.paths.join(', ')}]`);\n }\n }\n}\n\nexport function engineCheckFailed(store: string, identity: string, remedy: string, failure: string): SenseError {\n return new SenseError('STORE_CAPABILITY_MISSING', `store \"${store}\" cannot use ${identity}: ${failure}; ${remedy}`);\n}\n"],"names":["semver","SenseError","ENGINE_IDENTITY_META_KEY","engineIdentity","pkg","version","selectBuilders","defaults","patches","selected","patch","undefined","satisfies","range","includePrerelease","builders","ENGINE_CHECK_DOCS","path","title","summary","text","ENGINE_CHECK_QUERIES","terms","paths","npmRemedy","checkEngine","store","identity","remedy","query","expected","err","engineCheckFailed","message","join","failure"],"mappings":"AAAA,OAAOA,YAAY,SAAS;AAC5B,SAASC,UAAU,QAAQ,eAAe;AAE1C,6GAA6G;AAC7G,sFAAsF;AACtF,OAAO,MAAMC,2BAA2B,kBAAkB;AAE1D,OAAO,SAASC,eAAeC,GAAW,EAAEC,OAA2B;IACrE,OAAO,GAAGD,IAAI,CAAC,EAAEC,oBAAAA,qBAAAA,UAAW,WAAW;AACzC;AAaA,wGAAwG;AACxG,OAAO,SAASC,eAAwCC,QAAkB,EAAEC,OAAyC,EAAEH,OAA2B;IAChJ,IAAII,WAAWF;IACf,KAAK,MAAMG,SAASF,QAAS;QAC3B,IAAIH,YAAYM,aAAaX,OAAOY,SAAS,CAACP,SAASK,MAAMG,KAAK,EAAE;YAAEC,mBAAmB;QAAK,IAAIL,WAAW;YAAE,GAAGA,QAAQ;YAAE,GAAGC,MAAMK,QAAQ;QAAC;IAChJ;IACA,OAAON;AACT;AASA,6GAA6G;AAC7G,6GAA6G;AAC7G,OAAO,MAAMO,oBAA+C;IAC1D;QAAEC,MAAM;QAAaC,OAAO;QAAUC,SAAS;QAAIC,MAAM;IAA8B;IACvF;QAAEH,MAAM;QAAYC,OAAO;QAAWC,SAAS;QAAIC,MAAM;IAAS;IAClE;QAAEH,MAAM;QAAcC,OAAO;QAAgBC,SAAS;QAAIC,MAAM;IAAqB;CACtF,CAAC;AAEF,OAAO,MAAMC,uBAA0E;IACrF;QAAEC,OAAO;QAAOC,OAAO;YAAC;YAAc;SAAY;IAAC;IACnD;QAAED,OAAO;QAAWC,OAAO;YAAC;SAAY;IAAC;IACzC;QAAED,OAAO;QAAMC,OAAO;YAAC;SAAW;IAAC;CACpC,CAAC;AAEF,sFAAsF;AACtF,OAAO,SAASC,UAAUpB,GAAW,EAAES,KAAa;IAClD,OAAO,CAAC,kBAAkB,EAAET,IAAI,CAAC,EAAES,MAAM,gCAAgC,EAAEA,MAAM,CAAC,CAAC;AACrF;AAEA,iHAAiH;AACjH,OAAO,eAAeY,YAAYC,KAAa,EAAEC,QAAgB,EAAEC,MAAc,EAAEC,KAA2C;IAC5H,KAAK,MAAMC,YAAYT,qBAAsB;QAC3C,IAAIE;QACJ,IAAI;YACFA,QAAQ,MAAMM,MAAMC,SAASR,KAAK;QACpC,EAAE,OAAOS,KAAK;YACZ,MAAMC,kBAAkBN,OAAOC,UAAUC,QAAQ,CAAC,WAAW,EAAEE,SAASR,KAAK,CAAC,UAAU,EAAE,AAACS,IAAcE,OAAO,CAAC,CAAC,CAAC;QACrH;QACA,IAAIV,MAAMW,IAAI,CAAC,UAAUJ,SAASP,KAAK,CAACW,IAAI,CAAC,OAAO;YAClD,MAAMF,kBAAkBN,OAAOC,UAAUC,QAAQ,CAAC,WAAW,EAAEE,SAASR,KAAK,CAAC,YAAY,EAAEC,MAAMW,IAAI,CAAC,MAAM,aAAa,EAAEJ,SAASP,KAAK,CAACW,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1J;IACF;AACF;AAEA,OAAO,SAASF,kBAAkBN,KAAa,EAAEC,QAAgB,EAAEC,MAAc,EAAEO,OAAe;IAChG,OAAO,IAAIlC,WAAW,4BAA4B,CAAC,OAAO,EAAEyB,MAAM,aAAa,EAAEC,SAAS,EAAE,EAAEQ,QAAQ,EAAE,EAAEP,QAAQ;AACpH"}
@@ -2,10 +2,13 @@ export interface NativeDescriptor {
2
2
  store: string;
3
3
  pkg: string;
4
4
  sizeHint: string;
5
- /** Optional exact package version required by this native binding. */
6
- version?: string;
7
- /** npm specifier used only when installation is required (imports stay bare). */
8
- installSpec?: string;
5
+ /** The tested semver range: sensemaking's own `peerDependencies` entry for `pkg` (see `peerRange`). */
6
+ range: string;
7
+ }
8
+ export interface NativeModule<T> {
9
+ api: T;
10
+ version: string | undefined;
9
11
  }
10
12
  export declare function packageNodeModules(): string;
11
- export declare function loadOrInstall<T>(descriptor: NativeDescriptor, nodeModulesPath: string, importName?: string): Promise<T>;
13
+ export declare function peerRange(pkg: string): string;
14
+ export declare function loadOrInstall<T>(descriptor: NativeDescriptor, nodeModulesPath: string, importName?: string): Promise<NativeModule<T>>;
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import Module from 'node:module';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import semver from 'semver';
5
6
  import { SenseError } from '../errors.js';
6
7
  // install-module-linked is small, but a target native package can be large (@duckdb/node-api's
7
8
  // ~110MB), so it is required lazily inside the gated branch below, not for every store/index.ts load.
@@ -9,19 +10,32 @@ const _require = typeof require === 'undefined' ? Module.createRequire(import.me
9
10
  const _dirname = dirname(typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url));
10
11
  // Walks up from this file's own directory rather than a fixed dot-count: it sits at a
11
12
  // different depth under src/ than under built dist/{cjs,esm}/store output.
12
- export function packageNodeModules() {
13
+ function packageRoot() {
13
14
  let dir = _dirname;
14
15
  for(;;){
15
16
  const candidate = join(dir, 'package.json');
16
17
  if (existsSync(candidate)) {
17
- const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
18
- if (pkg.name === 'sensemaking') return join(dir, 'node_modules');
18
+ const manifest = JSON.parse(readFileSync(candidate, 'utf8'));
19
+ if (manifest.name === 'sensemaking') return {
20
+ dir,
21
+ manifest
22
+ };
19
23
  }
20
24
  const parent = dirname(dir);
21
25
  if (parent === dir) throw new Error('could not locate the sensemaking package root to install a native dependency into');
22
26
  dir = parent;
23
27
  }
24
28
  }
29
+ export function packageNodeModules() {
30
+ return join(packageRoot().dir, 'node_modules');
31
+ }
32
+ // The published contract for an optional native package: the range it is tested against.
33
+ export function peerRange(pkg) {
34
+ var _packageRoot_manifest_peerDependencies;
35
+ const range = (_packageRoot_manifest_peerDependencies = packageRoot().manifest.peerDependencies) === null || _packageRoot_manifest_peerDependencies === void 0 ? void 0 : _packageRoot_manifest_peerDependencies[pkg];
36
+ if (!range) throw new Error(`sensemaking's package.json declares no peerDependencies entry for ${pkg}`);
37
+ return range;
38
+ }
25
39
  const installing = new Map();
26
40
  // De-dupes concurrent installs: since the install is async, concurrent callers await the same
27
41
  // in-flight promise, keyed by specifier and target. A failed install is not cached, so a later call retries.
@@ -38,6 +52,13 @@ function installOnce(specifier, nodeModulesPath) {
38
52
  }
39
53
  return promise;
40
54
  }
55
+ function readPackageMetadata(pkgDir) {
56
+ try {
57
+ return JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'));
58
+ } catch {
59
+ return undefined;
60
+ }
61
+ }
41
62
  // Node caches a bare specifier's resolution, including a "not found" miss, for the process
42
63
  // lifetime; use the installed entry directly, as a URL for ESM and a path for CJS.
43
64
  function resolveEntrySpecifier(importName, nodeModulesPath) {
@@ -48,30 +69,12 @@ function resolveEntrySpecifier(importName, nodeModulesPath) {
48
69
  const entryPath = join(pkgDir, entry);
49
70
  return typeof require === 'undefined' ? pathToFileURL(entryPath).href : entryPath;
50
71
  }
51
- function readPackageMetadata(pkgDir) {
52
- try {
53
- return JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'));
54
- } catch {
55
- return undefined;
56
- }
57
- }
58
- function targetPackageMetadata(importName, nodeModulesPath) {
59
- const pkgDir = join(nodeModulesPath, ...importName.split('/'));
60
- return {
61
- exists: existsSync(pkgDir),
62
- metadata: readPackageMetadata(pkgDir)
63
- };
64
- }
65
- function resolvedPackageMetadata(importName) {
72
+ function resolvedVersion(importName) {
66
73
  try {
67
74
  let dir = dirname(_require.resolve(importName));
68
75
  for(;;){
69
- const manifest = join(dir, 'package.json');
70
- if (existsSync(manifest)) {
71
- const pkg = readPackageMetadata(dir);
72
- if (!pkg) return undefined;
73
- if (pkg.name === importName) return pkg;
74
- }
76
+ const pkg = readPackageMetadata(dir);
77
+ if ((pkg === null || pkg === void 0 ? void 0 : pkg.name) === importName) return pkg.version;
75
78
  const parent = dirname(dir);
76
79
  if (parent === dir) return undefined;
77
80
  dir = parent;
@@ -80,44 +83,43 @@ function resolvedPackageMetadata(importName) {
80
83
  return undefined;
81
84
  }
82
85
  }
83
- function versionContractError(descriptor, installed) {
84
- var _descriptor_installSpec;
85
- const installSpec = (_descriptor_installSpec = descriptor.installSpec) !== null && _descriptor_installSpec !== void 0 ? _descriptor_installSpec : descriptor.pkg;
86
- const detail = installed ? `${descriptor.pkg}@${installed} is installed` : `${descriptor.pkg} has missing or unreadable version metadata`;
87
- return new SenseError('STORE_DEPENDENCY_MISSING', `store "${descriptor.store}" needs ${installSpec}, but ${detail}; refusing to overwrite it -- run \`npm install ${installSpec}\` yourself and try again`);
88
- }
89
- function assertExpectedVersion(descriptor, metadata) {
90
- if (!descriptor.version) return;
91
- if (!(metadata === null || metadata === void 0 ? void 0 : metadata.version) || metadata.version !== descriptor.version) throw versionContractError(descriptor, metadata === null || metadata === void 0 ? void 0 : metadata.version);
86
+ // Any installed version is used; one outside the tested range is reported, not refused (the store's engine check decides).
87
+ function reportVersion(descriptor, version) {
88
+ if (version && semver.satisfies(version, descriptor.range)) return;
89
+ console.error(`sense: store "${descriptor.store}" using ${descriptor.pkg}@${version !== null && version !== void 0 ? version : 'unknown version'} (tested ${descriptor.range})`);
92
90
  }
93
91
  // Import-then-install-then-retry, over an injectable importName so the failure path can be
94
92
  // exercised in tests without a real native binding. Thrown errors always name descriptor.pkg.
95
93
  export async function loadOrInstall(descriptor, nodeModulesPath, importName = descriptor.pkg) {
96
- if (descriptor.version) {
97
- const target = targetPackageMetadata(importName, nodeModulesPath);
98
- if (target.exists) assertExpectedVersion(descriptor, target.metadata);
99
- }
100
- let loaded;
101
94
  try {
102
- loaded = await import(importName);
95
+ const api = await import(importName);
96
+ const version = resolvedVersion(importName);
97
+ reportVersion(descriptor, version);
98
+ return {
99
+ api,
100
+ version
101
+ };
103
102
  } catch {
104
- var _descriptor_installSpec, _descriptor_installSpec1;
105
- const installSpec = (_descriptor_installSpec = descriptor.installSpec) !== null && _descriptor_installSpec !== void 0 ? _descriptor_installSpec : importName;
106
- const displaySpec = (_descriptor_installSpec1 = descriptor.installSpec) !== null && _descriptor_installSpec1 !== void 0 ? _descriptor_installSpec1 : descriptor.pkg;
107
- console.error(`sense: store "${descriptor.store}" needs ${displaySpec}; installing it now (one-time download, ${descriptor.sizeHint})...`);
108
- try {
109
- await installOnce(installSpec, nodeModulesPath);
110
- } catch (installErr) {
111
- throw new SenseError('STORE_DEPENDENCY_MISSING', `store "${descriptor.store}" needs ${displaySpec}, and installing it automatically failed (${installErr.message}); this can happen offline, in a sandboxed environment, or when node_modules is read-only or owned by another user (e.g. under a global install) -- run \`npm install ${displaySpec}\` yourself and try again`);
112
- }
113
- try {
114
- const loaded = await import(resolveEntrySpecifier(importName, nodeModulesPath));
115
- assertExpectedVersion(descriptor, targetPackageMetadata(importName, nodeModulesPath).metadata);
116
- return loaded;
117
- } catch (loadErr) {
118
- throw new SenseError('STORE_DEPENDENCY_MISSING', `store "${descriptor.store}" installed ${displaySpec} but it could not be loaded (${loadErr.message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \`npm install ${displaySpec}\` to see the underlying error`);
119
- }
103
+ // Not resolvable here: install the newest version npm would pick for the tested range.
104
+ }
105
+ const spec = `${descriptor.pkg}@${descriptor.range}`;
106
+ const installSpec = `${importName}@${descriptor.range}`;
107
+ console.error(`sense: store "${descriptor.store}" needs ${spec}; installing it now (one-time download, ${descriptor.sizeHint})...`);
108
+ try {
109
+ await installOnce(installSpec, nodeModulesPath);
110
+ } catch (installErr) {
111
+ throw new SenseError('STORE_DEPENDENCY_MISSING', `store "${descriptor.store}" needs ${spec}, and installing it automatically failed (${installErr.message}); this can happen offline, in a sandboxed environment, or when node_modules is read-only or owned by another user (e.g. under a global install) -- run \`npm install ${spec}\` yourself and try again`);
112
+ }
113
+ try {
114
+ var _readPackageMetadata;
115
+ const api = await import(resolveEntrySpecifier(importName, nodeModulesPath));
116
+ const version = (_readPackageMetadata = readPackageMetadata(join(nodeModulesPath, ...importName.split('/')))) === null || _readPackageMetadata === void 0 ? void 0 : _readPackageMetadata.version;
117
+ reportVersion(descriptor, version);
118
+ return {
119
+ api,
120
+ version
121
+ };
122
+ } catch (loadErr) {
123
+ throw new SenseError('STORE_DEPENDENCY_MISSING', `store "${descriptor.store}" installed ${spec} but it could not be loaded (${loadErr.message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \`npm install ${spec}\` to see the underlying error`);
120
124
  }
121
- if (descriptor.version) assertExpectedVersion(descriptor, resolvedPackageMetadata(importName));
122
- return loaded;
123
125
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/native.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { SenseError } from '../errors.ts';\n\n// A store's optional native package, and the strings its error messages and install notice name.\nexport interface NativeDescriptor {\n store: string;\n pkg: string;\n sizeHint: string;\n /** Optional exact package version required by this native binding. */\n version?: string;\n /** npm specifier used only when installation is required (imports stay bare). */\n installSpec?: string;\n}\n\n// install-module-linked is small, but a target native package can be large (@duckdb/node-api's\n// ~110MB), so it is required lazily inside the gated branch below, not for every store/index.ts load.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\nconst _dirname = dirname(typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url));\n\n// Walks up from this file's own directory rather than a fixed dot-count: it sits at a\n// different depth under src/ than under built dist/{cjs,esm}/store output.\nexport function packageNodeModules(): string {\n let dir = _dirname;\n for (;;) {\n const candidate = join(dir, 'package.json');\n if (existsSync(candidate)) {\n const pkg = JSON.parse(readFileSync(candidate, 'utf8')) as { name?: string };\n if (pkg.name === 'sensemaking') return join(dir, 'node_modules');\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error('could not locate the sensemaking package root to install a native dependency into');\n dir = parent;\n }\n}\n\nconst installing = new Map<string, Promise<void>>();\n\n// De-dupes concurrent installs: since the install is async, concurrent callers await the same\n// in-flight promise, keyed by specifier and target. A failed install is not cached, so a later call retries.\nfunction installOnce(specifier: string, nodeModulesPath: string): Promise<void> {\n const key = `${specifier}\\n${nodeModulesPath}`;\n let promise = installing.get(key);\n if (!promise) {\n const installModuleLinked = (_require('install-module-linked') as { default: (specifier: string, nodeModulesPath: string) => Promise<string> }).default;\n promise = installModuleLinked(specifier, nodeModulesPath).then(\n () => {},\n (err) => {\n installing.delete(key);\n throw err;\n }\n );\n installing.set(key, promise);\n }\n return promise;\n}\n\n// Node caches a bare specifier's resolution, including a \"not found\" miss, for the process\n// lifetime; use the installed entry directly, as a URL for ESM and a path for CJS.\nfunction resolveEntrySpecifier(importName: string, nodeModulesPath: string): string {\n const pkgDir = join(nodeModulesPath, ...importName.split('/'));\n const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as { main?: string; exports?: string };\n const entry = typeof pkg.exports === 'string' ? pkg.exports : (pkg.main ?? 'index.js');\n const entryPath = join(pkgDir, entry);\n return typeof require === 'undefined' ? pathToFileURL(entryPath).href : entryPath;\n}\n\ntype PackageMetadata = { name?: string; version?: string };\n\nfunction readPackageMetadata(pkgDir: string): PackageMetadata | undefined {\n try {\n return JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as PackageMetadata;\n } catch {\n return undefined;\n }\n}\n\nfunction targetPackageMetadata(importName: string, nodeModulesPath: string): { exists: boolean; metadata?: PackageMetadata } {\n const pkgDir = join(nodeModulesPath, ...importName.split('/'));\n return { exists: existsSync(pkgDir), metadata: readPackageMetadata(pkgDir) };\n}\n\nfunction resolvedPackageMetadata(importName: string): PackageMetadata | undefined {\n try {\n let dir = dirname(_require.resolve(importName));\n for (;;) {\n const manifest = join(dir, 'package.json');\n if (existsSync(manifest)) {\n const pkg = readPackageMetadata(dir);\n if (!pkg) return undefined;\n if (pkg.name === importName) return pkg;\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n } catch {\n return undefined;\n }\n}\n\nfunction versionContractError(descriptor: NativeDescriptor, installed?: string): SenseError {\n const installSpec = descriptor.installSpec ?? descriptor.pkg;\n const detail = installed ? `${descriptor.pkg}@${installed} is installed` : `${descriptor.pkg} has missing or unreadable version metadata`;\n return new SenseError('STORE_DEPENDENCY_MISSING', `store \"${descriptor.store}\" needs ${installSpec}, but ${detail}; refusing to overwrite it -- run \\`npm install ${installSpec}\\` yourself and try again`);\n}\n\nfunction assertExpectedVersion(descriptor: NativeDescriptor, metadata: PackageMetadata | undefined): void {\n if (!descriptor.version) return;\n if (!metadata?.version || metadata.version !== descriptor.version) throw versionContractError(descriptor, metadata?.version);\n}\n\n// Import-then-install-then-retry, over an injectable importName so the failure path can be\n// exercised in tests without a real native binding. Thrown errors always name descriptor.pkg.\nexport async function loadOrInstall<T>(descriptor: NativeDescriptor, nodeModulesPath: string, importName: string = descriptor.pkg): Promise<T> {\n if (descriptor.version) {\n const target = targetPackageMetadata(importName, nodeModulesPath);\n if (target.exists) assertExpectedVersion(descriptor, target.metadata);\n }\n let loaded: T;\n try {\n loaded = (await import(importName)) as T;\n } catch {\n const installSpec = descriptor.installSpec ?? importName;\n const displaySpec = descriptor.installSpec ?? descriptor.pkg;\n console.error(`sense: store \"${descriptor.store}\" needs ${displaySpec}; installing it now (one-time download, ${descriptor.sizeHint})...`);\n try {\n await installOnce(installSpec, nodeModulesPath);\n } catch (installErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"${descriptor.store}\" needs ${displaySpec}, and installing it automatically failed (${(installErr as Error).message}); this can happen offline, in a sandboxed environment, or when node_modules is read-only or owned by another user (e.g. under a global install) -- run \\`npm install ${displaySpec}\\` yourself and try again`\n );\n }\n try {\n const loaded = (await import(resolveEntrySpecifier(importName, nodeModulesPath))) as T;\n assertExpectedVersion(descriptor, targetPackageMetadata(importName, nodeModulesPath).metadata);\n return loaded;\n } catch (loadErr) {\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"${descriptor.store}\" installed ${displaySpec} but it could not be loaded (${(loadErr as Error).message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \\`npm install ${displaySpec}\\` to see the underlying error`);\n }\n }\n if (descriptor.version) assertExpectedVersion(descriptor, resolvedPackageMetadata(importName));\n return loaded;\n}\n"],"names":["existsSync","readFileSync","Module","dirname","join","fileURLToPath","pathToFileURL","SenseError","_require","require","createRequire","url","_dirname","__filename","packageNodeModules","dir","candidate","pkg","JSON","parse","name","parent","Error","installing","Map","installOnce","specifier","nodeModulesPath","key","promise","get","installModuleLinked","default","then","err","delete","set","resolveEntrySpecifier","importName","pkgDir","split","entry","exports","main","entryPath","href","readPackageMetadata","undefined","targetPackageMetadata","exists","metadata","resolvedPackageMetadata","resolve","manifest","versionContractError","descriptor","installed","installSpec","detail","store","assertExpectedVersion","version","loadOrInstall","target","loaded","displaySpec","console","error","sizeHint","installErr","message","loadErr","process","platform","arch"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/native.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport semver from 'semver';\nimport { SenseError } from '../errors.ts';\n\n// A store's optional native package, and the strings its error messages and install notice name.\nexport interface NativeDescriptor {\n store: string;\n pkg: string;\n sizeHint: string;\n /** The tested semver range: sensemaking's own `peerDependencies` entry for `pkg` (see `peerRange`). */\n range: string;\n}\n\n// A loaded native package and the version that actually resolved, which the store's engine identity records.\nexport interface NativeModule<T> {\n api: T;\n version: string | undefined;\n}\n\n// install-module-linked is small, but a target native package can be large (@duckdb/node-api's\n// ~110MB), so it is required lazily inside the gated branch below, not for every store/index.ts load.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\nconst _dirname = dirname(typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url));\n\ntype PackageMetadata = { name?: string; version?: string; peerDependencies?: Record<string, string> };\n\n// Walks up from this file's own directory rather than a fixed dot-count: it sits at a\n// different depth under src/ than under built dist/{cjs,esm}/store output.\nfunction packageRoot(): { dir: string; manifest: PackageMetadata } {\n let dir = _dirname;\n for (;;) {\n const candidate = join(dir, 'package.json');\n if (existsSync(candidate)) {\n const manifest = JSON.parse(readFileSync(candidate, 'utf8')) as PackageMetadata;\n if (manifest.name === 'sensemaking') return { dir, manifest };\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error('could not locate the sensemaking package root to install a native dependency into');\n dir = parent;\n }\n}\n\nexport function packageNodeModules(): string {\n return join(packageRoot().dir, 'node_modules');\n}\n\n// The published contract for an optional native package: the range it is tested against.\nexport function peerRange(pkg: string): string {\n const range = packageRoot().manifest.peerDependencies?.[pkg];\n if (!range) throw new Error(`sensemaking's package.json declares no peerDependencies entry for ${pkg}`);\n return range;\n}\n\nconst installing = new Map<string, Promise<void>>();\n\n// De-dupes concurrent installs: since the install is async, concurrent callers await the same\n// in-flight promise, keyed by specifier and target. A failed install is not cached, so a later call retries.\nfunction installOnce(specifier: string, nodeModulesPath: string): Promise<void> {\n const key = `${specifier}\\n${nodeModulesPath}`;\n let promise = installing.get(key);\n if (!promise) {\n const installModuleLinked = (_require('install-module-linked') as { default: (specifier: string, nodeModulesPath: string) => Promise<string> }).default;\n promise = installModuleLinked(specifier, nodeModulesPath).then(\n () => {},\n (err) => {\n installing.delete(key);\n throw err;\n }\n );\n installing.set(key, promise);\n }\n return promise;\n}\n\nfunction readPackageMetadata(pkgDir: string): PackageMetadata | undefined {\n try {\n return JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as PackageMetadata;\n } catch {\n return undefined;\n }\n}\n\n// Node caches a bare specifier's resolution, including a \"not found\" miss, for the process\n// lifetime; use the installed entry directly, as a URL for ESM and a path for CJS.\nfunction resolveEntrySpecifier(importName: string, nodeModulesPath: string): string {\n const pkgDir = join(nodeModulesPath, ...importName.split('/'));\n const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as { main?: string; exports?: string };\n const entry = typeof pkg.exports === 'string' ? pkg.exports : (pkg.main ?? 'index.js');\n const entryPath = join(pkgDir, entry);\n return typeof require === 'undefined' ? pathToFileURL(entryPath).href : entryPath;\n}\n\nfunction resolvedVersion(importName: string): string | undefined {\n try {\n let dir = dirname(_require.resolve(importName));\n for (;;) {\n const pkg = readPackageMetadata(dir);\n if (pkg?.name === importName) return pkg.version;\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n } catch {\n return undefined;\n }\n}\n\n// Any installed version is used; one outside the tested range is reported, not refused (the store's engine check decides).\nfunction reportVersion(descriptor: NativeDescriptor, version: string | undefined): void {\n if (version && semver.satisfies(version, descriptor.range)) return;\n console.error(`sense: store \"${descriptor.store}\" using ${descriptor.pkg}@${version ?? 'unknown version'} (tested ${descriptor.range})`);\n}\n\n// Import-then-install-then-retry, over an injectable importName so the failure path can be\n// exercised in tests without a real native binding. Thrown errors always name descriptor.pkg.\nexport async function loadOrInstall<T>(descriptor: NativeDescriptor, nodeModulesPath: string, importName: string = descriptor.pkg): Promise<NativeModule<T>> {\n try {\n const api = (await import(importName)) as T;\n const version = resolvedVersion(importName);\n reportVersion(descriptor, version);\n return { api, version };\n } catch {\n // Not resolvable here: install the newest version npm would pick for the tested range.\n }\n const spec = `${descriptor.pkg}@${descriptor.range}`;\n const installSpec = `${importName}@${descriptor.range}`;\n console.error(`sense: store \"${descriptor.store}\" needs ${spec}; installing it now (one-time download, ${descriptor.sizeHint})...`);\n try {\n await installOnce(installSpec, nodeModulesPath);\n } catch (installErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"${descriptor.store}\" needs ${spec}, and installing it automatically failed (${(installErr as Error).message}); this can happen offline, in a sandboxed environment, or when node_modules is read-only or owned by another user (e.g. under a global install) -- run \\`npm install ${spec}\\` yourself and try again`\n );\n }\n try {\n const api = (await import(resolveEntrySpecifier(importName, nodeModulesPath))) as T;\n const version = readPackageMetadata(join(nodeModulesPath, ...importName.split('/')))?.version;\n reportVersion(descriptor, version);\n return { api, version };\n } catch (loadErr) {\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"${descriptor.store}\" installed ${spec} but it could not be loaded (${(loadErr as Error).message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \\`npm install ${spec}\\` to see the underlying error`);\n }\n}\n"],"names":["existsSync","readFileSync","Module","dirname","join","fileURLToPath","pathToFileURL","semver","SenseError","_require","require","createRequire","url","_dirname","__filename","packageRoot","dir","candidate","manifest","JSON","parse","name","parent","Error","packageNodeModules","peerRange","pkg","range","peerDependencies","installing","Map","installOnce","specifier","nodeModulesPath","key","promise","get","installModuleLinked","default","then","err","delete","set","readPackageMetadata","pkgDir","undefined","resolveEntrySpecifier","importName","split","entry","exports","main","entryPath","href","resolvedVersion","resolve","version","reportVersion","descriptor","satisfies","console","error","store","loadOrInstall","api","spec","installSpec","sizeHint","installErr","message","loadErr","process","platform","arch"],"mappings":"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"}
@@ -8,9 +8,10 @@ import { SenseError } from '../errors.js';
8
8
  import { FEATURES } from '../features/index.js';
9
9
  import { createBuilder } from './builder.js';
10
10
  import { clearCache } from './cache.js';
11
+ import { ENGINE_IDENTITY_META_KEY } from './engine.js';
11
12
  import { lockWaitBudgetMs } from './lock-wait.js';
12
13
  import { CORE_READY_META_KEY, FEATURE_SIGNATURE_META_KEY, isCoreReady, isPublishedFeatureSignature } from './readiness.js';
13
- import { getMeta } from './shared.js';
14
+ import { getMeta, setMeta } from './shared.js';
14
15
  import { changedSignatureKeys, embedIdentityAdopted } from './signature.js';
15
16
  import { stageRecorder } from './stages.js';
16
17
  import { withTransaction } from './transaction.js';
@@ -76,17 +77,20 @@ async function connectExisting(cfg, dialect, requirements) {
76
77
  observational: true
77
78
  });
78
79
  try {
80
+ var _dialect_engineIdentity;
79
81
  let state;
80
82
  try {
81
83
  state = await withTransaction(conn, async ()=>{
82
84
  const version = await getMeta(conn, 'schema_version');
83
85
  const features = await getMeta(conn, FEATURE_SIGNATURE_META_KEY);
84
86
  const ready = await getMeta(conn, CORE_READY_META_KEY);
87
+ const engine = await getMeta(conn, ENGINE_IDENTITY_META_KEY);
85
88
  await assertCoreSchema(conn);
86
89
  return {
87
90
  version,
88
91
  features,
89
- ready
92
+ ready,
93
+ engine
90
94
  };
91
95
  });
92
96
  } catch {
@@ -97,6 +101,11 @@ async function connectExisting(cfg, dialect, requirements) {
97
101
  var _state_version;
98
102
  throw notReady(`the derived index at ${dbPath} uses schema ${(_state_version = state.version) !== null && _state_version !== void 0 ? _state_version : 'unknown'}, but this sense version requires ${dialect.schemaVersion}`);
99
103
  }
104
+ const engine = await ((_dialect_engineIdentity = dialect.engineIdentity) === null || _dialect_engineIdentity === void 0 ? void 0 : _dialect_engineIdentity.call(dialect));
105
+ if (engine !== undefined && state.engine !== engine) {
106
+ var _state_engine;
107
+ throw notReady(`the derived index at ${dbPath} was built with ${(_state_engine = state.engine) !== null && _state_engine !== void 0 ? _state_engine : 'an unrecorded engine'}, but this process loads ${engine}`);
108
+ }
100
109
  const wantFeatures = featureSignature(cfg, FEATURES);
101
110
  if (!isPublishedFeatureSignature(state.features, wantFeatures)) {
102
111
  throw notReady(`the derived index at ${dbPath} was built for different configuration features`);
@@ -141,18 +150,26 @@ async function connectForBuild(cfg, dialect, requirements, cacheReplaced = false
141
150
  let closed = false;
142
151
  let builder;
143
152
  try {
144
- var _dialect_prepareConnection;
153
+ var _dialect_engineIdentity, _dialect_prepareConnection;
145
154
  await conn.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');
146
155
  const version = await getMeta(conn, 'schema_version');
147
- if (version !== null && version !== dialect.schemaVersion) {
148
- console.error('sense: cache format changed (new sensemaking version); rebuilding the index');
156
+ const engine = await ((_dialect_engineIdentity = dialect.engineIdentity) === null || _dialect_engineIdentity === void 0 ? void 0 : _dialect_engineIdentity.call(dialect));
157
+ const builtWith = version === null ? null : await getMeta(conn, ENGINE_IDENTITY_META_KEY);
158
+ const engineChanged = version !== null && engine !== undefined && builtWith !== engine;
159
+ if (version !== null && (version !== dialect.schemaVersion || engineChanged)) {
160
+ console.error(version !== dialect.schemaVersion ? 'sense: cache format changed (new sensemaking version); rebuilding the index' : `sense: index was built with ${builtWith !== null && builtWith !== void 0 ? builtWith : 'an unrecorded engine'}; rebuilding it with ${engine}`);
149
161
  await dialect.close(handle);
150
162
  closed = true;
151
- if (cacheReplaced) throw notReady(`the derived index at ${dbPath} still reports incompatible schema ${version} after cache replacement`);
163
+ if (cacheReplaced) throw notReady(`the derived index at ${dbPath} still reports incompatible schema ${version} or engine ${builtWith !== null && builtWith !== void 0 ? builtWith : 'unrecorded'} after cache replacement`);
152
164
  clearCache(cfg);
153
165
  return connectForBuild(cfg, dialect, requirements, true);
154
166
  }
155
- if (version === null) await dialect.ensureCoreSchema(handle, conn);
167
+ if (version === null) {
168
+ var _dialect_checkEngine;
169
+ await ((_dialect_checkEngine = dialect.checkEngine) === null || _dialect_checkEngine === void 0 ? void 0 : _dialect_checkEngine.call(dialect));
170
+ await dialect.ensureCoreSchema(handle, conn);
171
+ if (engine !== undefined) await setMeta(conn, ENGINE_IDENTITY_META_KEY, engine);
172
+ }
156
173
  await ((_dialect_prepareConnection = dialect.prepareConnection) === null || _dialect_prepareConnection === void 0 ? void 0 : _dialect_prepareConnection.call(dialect, handle, conn, cfg));
157
174
  if (dialect.setDerivedBusyTimeout) {
158
175
  var _ref;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/open.ts"],"sourcesContent":["import { channel } from 'node:diagnostics_channel';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from '../config/index.ts';\nimport { anyPresetEmbeds, featureSignature, STATE_DIR } from '../config/index.ts';\nimport { rekeyChunkText } from '../embed/handoff.ts';\nimport { embedPending } from '../embed/query.ts';\nimport type { EmbedCallOptions } from '../embed/types.ts';\nimport { SenseError } from '../errors.ts';\nimport { FEATURES } from '../features/index.ts';\nimport { createBuilder } from './builder.ts';\nimport { clearCache } from './cache.ts';\nimport { lockWaitBudgetMs } from './lock-wait.ts';\nimport { CORE_READY_META_KEY, FEATURE_SIGNATURE_META_KEY, isCoreReady, isPublishedFeatureSignature } from './readiness.ts';\nimport { getMeta } from './shared.ts';\nimport { changedSignatureKeys, embedIdentityAdopted } from './signature.ts';\nimport type { Stages } from './stages.ts';\nimport { stageRecorder } from './stages.ts';\nimport { withTransaction } from './transaction.ts';\nimport type { Connection, OpenDialect, Store } from './types.ts';\n\nexport type BuildRequirement = 'core' | 'lexical' | 'vectors';\n\n/** Options for the root `open(config, options)` API. */\nexport interface OpenOptions {\n /** Prepares configured capabilities by default; `false` opens compatible published state without repair. */\n build?: boolean;\n /** Cancels supported provider I/O and stops between phases after active native work finishes. */\n signal?: AbortSignal;\n}\n\nexport interface InternalOpenOptions extends OpenOptions {\n requirements?: ReadonlySet<BuildRequirement>;\n}\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n stages: Stages;\n}\n\ninterface ConnectResult<Handle> {\n handle: Handle;\n conn: Connection;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n stages: Stages;\n observational: boolean;\n}\n\nconst LOCK_POLL_MS = 50;\nconst lockWait = channel('sensemaking.store.lock-wait');\n\nfunction notReady(reason: string): SenseError {\n return new SenseError('INDEX_NOT_READY', `${reason}; run \"sense build\" or call build(config) before opening with { build: false }`);\n}\n\nfunction normaliseConfig(cfg: ResolvedConfig): ResolvedConfig {\n // `baseDir` was the original public open() input. Keep direct API callers working while\n // making the two ownership paths explicit for every internal operation.\n if (!cfg.rootDir) {\n if (!cfg.baseDir) throw new SenseError('CONFIG_INVALID', 'open requires rootDir (or legacy baseDir)');\n cfg.rootDir = cfg.baseDir;\n }\n if (!cfg.configDir) cfg.configDir = cfg.rootDir;\n return cfg;\n}\n\nfunction cachePath<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>): string {\n return join(cfg.configDir ?? cfg.baseDir, STATE_DIR, dialect.filename);\n}\n\nasync function connectUnlocked<Handle>(dbPath: string, cfg: ResolvedConfig, configDir: string, dialect: OpenDialect<Handle>, options: { existingOnly: boolean; observational: boolean }): Promise<{ handle: Handle; conn: Connection }> {\n const budgetMs = lockWaitBudgetMs(configDir);\n const deadline = Date.now() + budgetMs;\n for (;;) {\n try {\n return await dialect.connect(dbPath, cfg, options);\n } catch (err) {\n if (options.existingOnly && !existsSync(dbPath)) {\n throw notReady(`the derived index does not exist at ${dbPath}`);\n }\n if (!dialect.isLocked?.(err as Error)) throw err;\n if (lockWait.hasSubscribers) lockWait.publish({ store: cfg.store, dbPath });\n if (Date.now() >= deadline) {\n throw new SenseError('STORE_BUSY', `another sense process is using this tree's ${cfg.store} cache (${dbPath}) and did not release it within ${Math.round(budgetMs / 1000)}s; wait for that command to finish, or set \"store\" to \"sqlite\" in sense.config.json, which serves concurrent commands`);\n }\n await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));\n }\n }\n}\n\nasync function assertCoreSchema(conn: Connection): Promise<void> {\n for (const table of ['frontmatter', 'content', 'preset_files', 'indexed_sources']) {\n await (await conn.prepare(`SELECT 1 FROM ${table} LIMIT 0`)).all();\n }\n}\n\nasync function connectExisting<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>, requirements: ReadonlySet<BuildRequirement>): Promise<ConnectResult<Handle>> {\n const configDir = cfg.configDir ?? cfg.baseDir;\n const dbPath = cachePath(cfg, dialect);\n if (!existsSync(dbPath)) throw notReady(`the derived index does not exist at ${dbPath}`);\n\n const { handle, conn } = await connectUnlocked(dbPath, cfg, configDir, dialect, { existingOnly: true, observational: true });\n try {\n let state: { version: string | null; features: string | null; ready: string | null };\n try {\n state = await withTransaction(conn, async () => {\n const version = await getMeta(conn, 'schema_version');\n const features = await getMeta(conn, FEATURE_SIGNATURE_META_KEY);\n const ready = await getMeta(conn, CORE_READY_META_KEY);\n await assertCoreSchema(conn);\n return { version, features, ready };\n });\n } catch {\n if (!existsSync(dbPath)) throw notReady(`the derived index was deleted while opening ${dbPath}`);\n throw notReady(`the derived index at ${dbPath} has no compatible schema`);\n }\n\n if (state.version !== dialect.schemaVersion) {\n throw notReady(`the derived index at ${dbPath} uses schema ${state.version ?? 'unknown'}, but this sense version requires ${dialect.schemaVersion}`);\n }\n const wantFeatures = featureSignature(cfg, FEATURES);\n if (!isPublishedFeatureSignature(state.features, wantFeatures)) {\n throw notReady(`the derived index at ${dbPath} was built for different configuration features`);\n }\n if (!isCoreReady(state.ready)) {\n throw notReady(`the derived index at ${dbPath} has an incomplete core build`);\n }\n if (requirements.has('lexical')) {\n await dialect.assertLexicalReady?.(conn);\n }\n if (!existsSync(dbPath)) throw notReady(`the derived index was deleted while opening ${dbPath}`);\n\n return {\n handle,\n conn,\n cfg,\n dbPath,\n parsed: 0,\n warnings: [],\n stages: stageRecorder().take(0, 0),\n observational: true,\n };\n } catch (err) {\n await dialect.close(handle, { observational: true });\n throw err;\n }\n}\n\nasync function connectForBuild<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>, requirements: ReadonlySet<BuildRequirement>, cacheReplaced = false): Promise<ConnectResult<Handle>> {\n const configDir = cfg.configDir ?? cfg.baseDir;\n const stateDir = join(configDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, dialect.filename);\n\n const { handle, conn } = await connectUnlocked(dbPath, cfg, configDir, dialect, { existingOnly: false, observational: false });\n let closed = false;\n let builder: ReturnType<typeof createBuilder> | undefined;\n try {\n await conn.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n const version = await getMeta(conn, 'schema_version');\n if (version !== null && version !== dialect.schemaVersion) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n await dialect.close(handle);\n closed = true;\n if (cacheReplaced) throw notReady(`the derived index at ${dbPath} still reports incompatible schema ${version} after cache replacement`);\n clearCache(cfg);\n return connectForBuild(cfg, dialect, requirements, true);\n }\n\n if (version === null) await dialect.ensureCoreSchema(handle, conn);\n await dialect.prepareConnection?.(handle, conn, cfg);\n\n if (dialect.setDerivedBusyTimeout) {\n const recordedMaxMs = Number((await getMeta(conn, 'reconcile_max_ms')) ?? '0');\n await dialect.setDerivedBusyTimeout(handle, conn, Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000));\n }\n\n builder = createBuilder(conn, cfg, dialect.reconcileDialect, (snapshotCfg) => dialect.ensureFeatureSchema(handle, conn, snapshotCfg));\n const { parsed, warnings, stages, messages } = await builder.build();\n for (const message of messages) console.error(message);\n\n // Lexical preparation is a separate capability stage. A failure leaves core usable and the\n // dialect's own lexical marker stale, so unrelated map/peek/path/sql commands stay available.\n if (requirements.has('lexical')) {\n await dialect.prepareLexical?.(conn);\n }\n\n await builder.close();\n return { handle, conn, cfg, dbPath, parsed, warnings, stages, observational: false };\n } catch (err) {\n const cleanupTasks: Array<Promise<void>> = [];\n if (builder) {\n const builderToClose = builder;\n cleanupTasks.push(Promise.resolve().then(() => builderToClose.close()));\n }\n if (!closed) cleanupTasks.push(Promise.resolve().then(() => dialect.close(handle)));\n const cleanup = await Promise.allSettled(cleanupTasks);\n const cleanupErrors = cleanup.filter((result): result is PromiseRejectedResult => result.status === 'rejected').map((result) => result.reason);\n if (cleanupErrors.length > 0) throw new AggregateError([err, ...cleanupErrors], 'opening the derived index failed and cleanup also failed');\n throw err;\n }\n}\n\n// A static provider can resolve its identity while embedding. Publish it only after vectors\n// succeed and only when the durable signature still matches the prepared generation.\nexport async function prepareDocumentEmbeddings(store: Store, cfg: ResolvedConfig, allowed?: ReadonlySet<string>, options: EmbedCallOptions = {}): Promise<void> {\n options.signal?.throwIfAborted();\n const preparedSignature = await getMeta(store, FEATURE_SIGNATURE_META_KEY);\n options.signal?.throwIfAborted();\n await embedPending(store, cfg, allowed, preparedSignature, options);\n options.signal?.throwIfAborted();\n const actualSignature = featureSignature(cfg, FEATURES);\n if (preparedSignature === actualSignature) return;\n\n const changedKeys = changedSignatureKeys(preparedSignature ?? '', actualSignature);\n if (preparedSignature === null || changedKeys.size !== 1 || !changedKeys.has('embed') || !embedIdentityAdopted(preparedSignature, actualSignature)) {\n throw notReady('the index configuration changed while vectors were being prepared');\n }\n\n await store.transaction(async () => {\n options.signal?.throwIfAborted();\n const update = await store.prepare('UPDATE meta SET value = ? WHERE key = ? AND value = ?');\n options.signal?.throwIfAborted();\n const result = await update.run(actualSignature, FEATURE_SIGNATURE_META_KEY, preparedSignature);\n options.signal?.throwIfAborted();\n if (Number(result.changes) !== 1) throw notReady('another build changed the index configuration while vectors were being prepared');\n });\n}\n\nexport async function openWithDialect<Handle>(input: ResolvedConfig, dialect: OpenDialect<Handle>, options: InternalOpenOptions = {}): Promise<OpenResult> {\n options.signal?.throwIfAborted();\n const cfg = normaliseConfig(input);\n const buildEnabled = options.build !== false;\n const defaultRequirements = new Set<BuildRequirement>(buildEnabled ? ['core', 'lexical'] : ['core']);\n if (buildEnabled && anyPresetEmbeds(cfg)) defaultRequirements.add('vectors');\n const requirements = options.requirements ?? defaultRequirements;\n const connected = buildEnabled ? await connectForBuild(cfg, dialect, requirements) : await connectExisting(cfg, dialect, requirements);\n let store: Store | undefined;\n try {\n store = dialect.createStore(connected.handle, connected.conn, connected.cfg, { observational: connected.observational });\n rekeyChunkText(connected.conn, store);\n if (requirements.has('vectors') && buildEnabled) await prepareDocumentEmbeddings(store, connected.cfg, undefined, { signal: options.signal });\n options.signal?.throwIfAborted();\n return {\n store,\n cfg: connected.cfg,\n dbPath: connected.dbPath,\n parsed: connected.parsed,\n warnings: connected.warnings,\n stages: connected.stages,\n };\n } catch (err) {\n const cleanup = await Promise.allSettled([Promise.resolve().then(() => (store ? store.close() : dialect.close(connected.handle, { observational: connected.observational })))]);\n if (cleanup[0].status === 'rejected') throw new AggregateError([err, cleanup[0].reason], 'opening the derived index failed and cleanup also failed');\n throw err;\n }\n}\n\nexport async function docCount(store: Store): Promise<number> {\n const stmt = await store.prepare('SELECT COUNT(*) AS n FROM frontmatter');\n return ((await stmt.get()) as { n: number }).n;\n}\n"],"names":["channel","existsSync","mkdirSync","join","anyPresetEmbeds","featureSignature","STATE_DIR","rekeyChunkText","embedPending","SenseError","FEATURES","createBuilder","clearCache","lockWaitBudgetMs","CORE_READY_META_KEY","FEATURE_SIGNATURE_META_KEY","isCoreReady","isPublishedFeatureSignature","getMeta","changedSignatureKeys","embedIdentityAdopted","stageRecorder","withTransaction","LOCK_POLL_MS","lockWait","notReady","reason","normaliseConfig","cfg","rootDir","baseDir","configDir","cachePath","dialect","filename","connectUnlocked","dbPath","options","budgetMs","deadline","Date","now","connect","err","existingOnly","isLocked","hasSubscribers","publish","store","Math","round","Promise","resolve","setTimeout","assertCoreSchema","conn","table","prepare","all","connectExisting","requirements","handle","observational","state","version","features","ready","schemaVersion","wantFeatures","has","assertLexicalReady","parsed","warnings","stages","take","close","connectForBuild","cacheReplaced","stateDir","recursive","closed","builder","exec","console","error","ensureCoreSchema","prepareConnection","setDerivedBusyTimeout","recordedMaxMs","Number","min","max","reconcileDialect","snapshotCfg","ensureFeatureSchema","messages","build","message","prepareLexical","cleanupTasks","builderToClose","push","then","cleanup","allSettled","cleanupErrors","filter","result","status","map","length","AggregateError","prepareDocumentEmbeddings","allowed","signal","throwIfAborted","preparedSignature","actualSignature","changedKeys","size","transaction","update","run","changes","openWithDialect","input","buildEnabled","defaultRequirements","Set","add","connected","createStore","undefined","docCount","stmt","get","n"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/open.ts"],"sourcesContent":["import { channel } from 'node:diagnostics_channel';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from '../config/index.ts';\nimport { anyPresetEmbeds, featureSignature, STATE_DIR } from '../config/index.ts';\nimport { rekeyChunkText } from '../embed/handoff.ts';\nimport { embedPending } from '../embed/query.ts';\nimport type { EmbedCallOptions } from '../embed/types.ts';\nimport { SenseError } from '../errors.ts';\nimport { FEATURES } from '../features/index.ts';\nimport { createBuilder } from './builder.ts';\nimport { clearCache } from './cache.ts';\nimport { ENGINE_IDENTITY_META_KEY } from './engine.ts';\nimport { lockWaitBudgetMs } from './lock-wait.ts';\nimport { CORE_READY_META_KEY, FEATURE_SIGNATURE_META_KEY, isCoreReady, isPublishedFeatureSignature } from './readiness.ts';\nimport { getMeta, setMeta } from './shared.ts';\nimport { changedSignatureKeys, embedIdentityAdopted } from './signature.ts';\nimport type { Stages } from './stages.ts';\nimport { stageRecorder } from './stages.ts';\nimport { withTransaction } from './transaction.ts';\nimport type { Connection, OpenDialect, Store } from './types.ts';\n\nexport type BuildRequirement = 'core' | 'lexical' | 'vectors';\n\n/** Options for the root `open(config, options)` API. */\nexport interface OpenOptions {\n /** Prepares configured capabilities by default; `false` opens compatible published state without repair. */\n build?: boolean;\n /** Cancels supported provider I/O and stops between phases after active native work finishes. */\n signal?: AbortSignal;\n}\n\nexport interface InternalOpenOptions extends OpenOptions {\n requirements?: ReadonlySet<BuildRequirement>;\n}\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n stages: Stages;\n}\n\ninterface ConnectResult<Handle> {\n handle: Handle;\n conn: Connection;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n stages: Stages;\n observational: boolean;\n}\n\nconst LOCK_POLL_MS = 50;\nconst lockWait = channel('sensemaking.store.lock-wait');\n\nfunction notReady(reason: string): SenseError {\n return new SenseError('INDEX_NOT_READY', `${reason}; run \"sense build\" or call build(config) before opening with { build: false }`);\n}\n\nfunction normaliseConfig(cfg: ResolvedConfig): ResolvedConfig {\n // `baseDir` was the original public open() input. Keep direct API callers working while\n // making the two ownership paths explicit for every internal operation.\n if (!cfg.rootDir) {\n if (!cfg.baseDir) throw new SenseError('CONFIG_INVALID', 'open requires rootDir (or legacy baseDir)');\n cfg.rootDir = cfg.baseDir;\n }\n if (!cfg.configDir) cfg.configDir = cfg.rootDir;\n return cfg;\n}\n\nfunction cachePath<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>): string {\n return join(cfg.configDir ?? cfg.baseDir, STATE_DIR, dialect.filename);\n}\n\nasync function connectUnlocked<Handle>(dbPath: string, cfg: ResolvedConfig, configDir: string, dialect: OpenDialect<Handle>, options: { existingOnly: boolean; observational: boolean }): Promise<{ handle: Handle; conn: Connection }> {\n const budgetMs = lockWaitBudgetMs(configDir);\n const deadline = Date.now() + budgetMs;\n for (;;) {\n try {\n return await dialect.connect(dbPath, cfg, options);\n } catch (err) {\n if (options.existingOnly && !existsSync(dbPath)) {\n throw notReady(`the derived index does not exist at ${dbPath}`);\n }\n if (!dialect.isLocked?.(err as Error)) throw err;\n if (lockWait.hasSubscribers) lockWait.publish({ store: cfg.store, dbPath });\n if (Date.now() >= deadline) {\n throw new SenseError('STORE_BUSY', `another sense process is using this tree's ${cfg.store} cache (${dbPath}) and did not release it within ${Math.round(budgetMs / 1000)}s; wait for that command to finish, or set \"store\" to \"sqlite\" in sense.config.json, which serves concurrent commands`);\n }\n await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));\n }\n }\n}\n\nasync function assertCoreSchema(conn: Connection): Promise<void> {\n for (const table of ['frontmatter', 'content', 'preset_files', 'indexed_sources']) {\n await (await conn.prepare(`SELECT 1 FROM ${table} LIMIT 0`)).all();\n }\n}\n\nasync function connectExisting<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>, requirements: ReadonlySet<BuildRequirement>): Promise<ConnectResult<Handle>> {\n const configDir = cfg.configDir ?? cfg.baseDir;\n const dbPath = cachePath(cfg, dialect);\n if (!existsSync(dbPath)) throw notReady(`the derived index does not exist at ${dbPath}`);\n\n const { handle, conn } = await connectUnlocked(dbPath, cfg, configDir, dialect, { existingOnly: true, observational: true });\n try {\n let state: { version: string | null; features: string | null; ready: string | null; engine: string | null };\n try {\n state = await withTransaction(conn, async () => {\n const version = await getMeta(conn, 'schema_version');\n const features = await getMeta(conn, FEATURE_SIGNATURE_META_KEY);\n const ready = await getMeta(conn, CORE_READY_META_KEY);\n const engine = await getMeta(conn, ENGINE_IDENTITY_META_KEY);\n await assertCoreSchema(conn);\n return { version, features, ready, engine };\n });\n } catch {\n if (!existsSync(dbPath)) throw notReady(`the derived index was deleted while opening ${dbPath}`);\n throw notReady(`the derived index at ${dbPath} has no compatible schema`);\n }\n\n if (state.version !== dialect.schemaVersion) {\n throw notReady(`the derived index at ${dbPath} uses schema ${state.version ?? 'unknown'}, but this sense version requires ${dialect.schemaVersion}`);\n }\n const engine = await dialect.engineIdentity?.();\n if (engine !== undefined && state.engine !== engine) {\n throw notReady(`the derived index at ${dbPath} was built with ${state.engine ?? 'an unrecorded engine'}, but this process loads ${engine}`);\n }\n const wantFeatures = featureSignature(cfg, FEATURES);\n if (!isPublishedFeatureSignature(state.features, wantFeatures)) {\n throw notReady(`the derived index at ${dbPath} was built for different configuration features`);\n }\n if (!isCoreReady(state.ready)) {\n throw notReady(`the derived index at ${dbPath} has an incomplete core build`);\n }\n if (requirements.has('lexical')) {\n await dialect.assertLexicalReady?.(conn);\n }\n if (!existsSync(dbPath)) throw notReady(`the derived index was deleted while opening ${dbPath}`);\n\n return {\n handle,\n conn,\n cfg,\n dbPath,\n parsed: 0,\n warnings: [],\n stages: stageRecorder().take(0, 0),\n observational: true,\n };\n } catch (err) {\n await dialect.close(handle, { observational: true });\n throw err;\n }\n}\n\nasync function connectForBuild<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>, requirements: ReadonlySet<BuildRequirement>, cacheReplaced = false): Promise<ConnectResult<Handle>> {\n const configDir = cfg.configDir ?? cfg.baseDir;\n const stateDir = join(configDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, dialect.filename);\n\n const { handle, conn } = await connectUnlocked(dbPath, cfg, configDir, dialect, { existingOnly: false, observational: false });\n let closed = false;\n let builder: ReturnType<typeof createBuilder> | undefined;\n try {\n await conn.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n const version = await getMeta(conn, 'schema_version');\n const engine = await dialect.engineIdentity?.();\n const builtWith = version === null ? null : await getMeta(conn, ENGINE_IDENTITY_META_KEY);\n const engineChanged = version !== null && engine !== undefined && builtWith !== engine;\n if (version !== null && (version !== dialect.schemaVersion || engineChanged)) {\n console.error(version !== dialect.schemaVersion ? 'sense: cache format changed (new sensemaking version); rebuilding the index' : `sense: index was built with ${builtWith ?? 'an unrecorded engine'}; rebuilding it with ${engine}`);\n await dialect.close(handle);\n closed = true;\n if (cacheReplaced) throw notReady(`the derived index at ${dbPath} still reports incompatible schema ${version} or engine ${builtWith ?? 'unrecorded'} after cache replacement`);\n clearCache(cfg);\n return connectForBuild(cfg, dialect, requirements, true);\n }\n\n if (version === null) {\n await dialect.checkEngine?.();\n await dialect.ensureCoreSchema(handle, conn);\n if (engine !== undefined) await setMeta(conn, ENGINE_IDENTITY_META_KEY, engine);\n }\n await dialect.prepareConnection?.(handle, conn, cfg);\n\n if (dialect.setDerivedBusyTimeout) {\n const recordedMaxMs = Number((await getMeta(conn, 'reconcile_max_ms')) ?? '0');\n await dialect.setDerivedBusyTimeout(handle, conn, Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000));\n }\n\n builder = createBuilder(conn, cfg, dialect.reconcileDialect, (snapshotCfg) => dialect.ensureFeatureSchema(handle, conn, snapshotCfg));\n const { parsed, warnings, stages, messages } = await builder.build();\n for (const message of messages) console.error(message);\n\n // Lexical preparation is a separate capability stage. A failure leaves core usable and the\n // dialect's own lexical marker stale, so unrelated map/peek/path/sql commands stay available.\n if (requirements.has('lexical')) {\n await dialect.prepareLexical?.(conn);\n }\n\n await builder.close();\n return { handle, conn, cfg, dbPath, parsed, warnings, stages, observational: false };\n } catch (err) {\n const cleanupTasks: Array<Promise<void>> = [];\n if (builder) {\n const builderToClose = builder;\n cleanupTasks.push(Promise.resolve().then(() => builderToClose.close()));\n }\n if (!closed) cleanupTasks.push(Promise.resolve().then(() => dialect.close(handle)));\n const cleanup = await Promise.allSettled(cleanupTasks);\n const cleanupErrors = cleanup.filter((result): result is PromiseRejectedResult => result.status === 'rejected').map((result) => result.reason);\n if (cleanupErrors.length > 0) throw new AggregateError([err, ...cleanupErrors], 'opening the derived index failed and cleanup also failed');\n throw err;\n }\n}\n\n// A static provider can resolve its identity while embedding. Publish it only after vectors\n// succeed and only when the durable signature still matches the prepared generation.\nexport async function prepareDocumentEmbeddings(store: Store, cfg: ResolvedConfig, allowed?: ReadonlySet<string>, options: EmbedCallOptions = {}): Promise<void> {\n options.signal?.throwIfAborted();\n const preparedSignature = await getMeta(store, FEATURE_SIGNATURE_META_KEY);\n options.signal?.throwIfAborted();\n await embedPending(store, cfg, allowed, preparedSignature, options);\n options.signal?.throwIfAborted();\n const actualSignature = featureSignature(cfg, FEATURES);\n if (preparedSignature === actualSignature) return;\n\n const changedKeys = changedSignatureKeys(preparedSignature ?? '', actualSignature);\n if (preparedSignature === null || changedKeys.size !== 1 || !changedKeys.has('embed') || !embedIdentityAdopted(preparedSignature, actualSignature)) {\n throw notReady('the index configuration changed while vectors were being prepared');\n }\n\n await store.transaction(async () => {\n options.signal?.throwIfAborted();\n const update = await store.prepare('UPDATE meta SET value = ? WHERE key = ? AND value = ?');\n options.signal?.throwIfAborted();\n const result = await update.run(actualSignature, FEATURE_SIGNATURE_META_KEY, preparedSignature);\n options.signal?.throwIfAborted();\n if (Number(result.changes) !== 1) throw notReady('another build changed the index configuration while vectors were being prepared');\n });\n}\n\nexport async function openWithDialect<Handle>(input: ResolvedConfig, dialect: OpenDialect<Handle>, options: InternalOpenOptions = {}): Promise<OpenResult> {\n options.signal?.throwIfAborted();\n const cfg = normaliseConfig(input);\n const buildEnabled = options.build !== false;\n const defaultRequirements = new Set<BuildRequirement>(buildEnabled ? ['core', 'lexical'] : ['core']);\n if (buildEnabled && anyPresetEmbeds(cfg)) defaultRequirements.add('vectors');\n const requirements = options.requirements ?? defaultRequirements;\n const connected = buildEnabled ? await connectForBuild(cfg, dialect, requirements) : await connectExisting(cfg, dialect, requirements);\n let store: Store | undefined;\n try {\n store = dialect.createStore(connected.handle, connected.conn, connected.cfg, { observational: connected.observational });\n rekeyChunkText(connected.conn, store);\n if (requirements.has('vectors') && buildEnabled) await prepareDocumentEmbeddings(store, connected.cfg, undefined, { signal: options.signal });\n options.signal?.throwIfAborted();\n return {\n store,\n cfg: connected.cfg,\n dbPath: connected.dbPath,\n parsed: connected.parsed,\n warnings: connected.warnings,\n stages: connected.stages,\n };\n } catch (err) {\n const cleanup = await Promise.allSettled([Promise.resolve().then(() => (store ? store.close() : dialect.close(connected.handle, { observational: connected.observational })))]);\n if (cleanup[0].status === 'rejected') throw new AggregateError([err, cleanup[0].reason], 'opening the derived index failed and cleanup also failed');\n throw err;\n }\n}\n\nexport async function docCount(store: Store): Promise<number> {\n const stmt = await store.prepare('SELECT COUNT(*) AS n FROM frontmatter');\n return ((await stmt.get()) as { n: number }).n;\n}\n"],"names":["channel","existsSync","mkdirSync","join","anyPresetEmbeds","featureSignature","STATE_DIR","rekeyChunkText","embedPending","SenseError","FEATURES","createBuilder","clearCache","ENGINE_IDENTITY_META_KEY","lockWaitBudgetMs","CORE_READY_META_KEY","FEATURE_SIGNATURE_META_KEY","isCoreReady","isPublishedFeatureSignature","getMeta","setMeta","changedSignatureKeys","embedIdentityAdopted","stageRecorder","withTransaction","LOCK_POLL_MS","lockWait","notReady","reason","normaliseConfig","cfg","rootDir","baseDir","configDir","cachePath","dialect","filename","connectUnlocked","dbPath","options","budgetMs","deadline","Date","now","connect","err","existingOnly","isLocked","hasSubscribers","publish","store","Math","round","Promise","resolve","setTimeout","assertCoreSchema","conn","table","prepare","all","connectExisting","requirements","handle","observational","state","version","features","ready","engine","schemaVersion","engineIdentity","undefined","wantFeatures","has","assertLexicalReady","parsed","warnings","stages","take","close","connectForBuild","cacheReplaced","stateDir","recursive","closed","builder","exec","builtWith","engineChanged","console","error","checkEngine","ensureCoreSchema","prepareConnection","setDerivedBusyTimeout","recordedMaxMs","Number","min","max","reconcileDialect","snapshotCfg","ensureFeatureSchema","messages","build","message","prepareLexical","cleanupTasks","builderToClose","push","then","cleanup","allSettled","cleanupErrors","filter","result","status","map","length","AggregateError","prepareDocumentEmbeddings","allowed","signal","throwIfAborted","preparedSignature","actualSignature","changedKeys","size","transaction","update","run","changes","openWithDialect","input","buildEnabled","defaultRequirements","Set","add","connected","createStore","docCount","stmt","get","n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type EnginePatch } from '../engine.js';
2
+ export declare const SQLITE_ENGINE = "node:sqlite";
3
+ export declare const SQLITE_VERSION: string | undefined;
4
+ export declare const TOKENIZE = "porter unicode61";
5
+ export interface SqliteFtsBuilders {
6
+ createContent(): string;
7
+ rankedMatch(whereJoin: string, whereCond: string, scopeCond: string, limit: number): string;
8
+ }
9
+ export declare const SQLITE_FTS_BUILDERS: SqliteFtsBuilders;
10
+ export declare const SQLITE_PATCHES: readonly EnginePatch<SqliteFtsBuilders>[];
11
+ export declare const sqliteIdentity: () => string;
12
+ export declare function sqliteFts(): SqliteFtsBuilders;
@@ -0,0 +1,16 @@
1
+ import { engineIdentity, selectBuilders } from '../engine.js';
2
+ // node:sqlite is Node's bundled SQLite, so its version changes with Node rather than with an npm install.
3
+ export const SQLITE_ENGINE = 'node:sqlite';
4
+ export const SQLITE_VERSION = process.versions.sqlite;
5
+ // unicode61 splits on spaces, so a language written without them indexes a whole run as one
6
+ // token and word search finds nothing; the `_seg` sidecars (always populated) cover that case.
7
+ export const TOKENIZE = 'porter unicode61';
8
+ export const SQLITE_FTS_BUILDERS = {
9
+ createContent: ()=>`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${TOKENIZE}')`,
10
+ rankedMatch: (whereJoin, whereCond, scopeCond, limit)=>`SELECT content.path AS path FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0) LIMIT ${limit}`
11
+ };
12
+ export const SQLITE_PATCHES = [];
13
+ export const sqliteIdentity = ()=>engineIdentity(SQLITE_ENGINE, SQLITE_VERSION);
14
+ export function sqliteFts() {
15
+ return selectBuilders(SQLITE_FTS_BUILDERS, SQLITE_PATCHES, SQLITE_VERSION);
16
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/sqlite/engine.ts"],"sourcesContent":["import { type EnginePatch, engineIdentity, selectBuilders } from '../engine.ts';\n\n// node:sqlite is Node's bundled SQLite, so its version changes with Node rather than with an npm install.\nexport const SQLITE_ENGINE = 'node:sqlite';\nexport const SQLITE_VERSION: string | undefined = process.versions.sqlite;\n\n// unicode61 splits on spaces, so a language written without them indexes a whole run as one\n// token and word search finds nothing; the `_seg` sidecars (always populated) cover that case.\nexport const TOKENIZE = 'porter unicode61';\n\n// Every FTS5 statement this store depends on; a SQLite version that behaves differently gets a SQLITE_PATCHES entry.\nexport interface SqliteFtsBuilders {\n // The `_seg` sidecars are appended after path, never inserted: bm25() and snippet(content, 2)\n // are documented against the first three columns. Each holds its field's exploded unspaced runs.\n createContent(): string;\n // One bound parameter, the FTS5 MATCH expression; ranked by field-weighted bm25.\n rankedMatch(whereJoin: string, whereCond: string, scopeCond: string, limit: number): string;\n}\n\nexport const SQLITE_FTS_BUILDERS: SqliteFtsBuilders = {\n createContent: () => `CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${TOKENIZE}')`,\n rankedMatch: (whereJoin, whereCond, scopeCond, limit) => `SELECT content.path AS path FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0) LIMIT ${limit}`,\n};\n\nexport const SQLITE_PATCHES: readonly EnginePatch<SqliteFtsBuilders>[] = [];\n\nexport const sqliteIdentity = (): string => engineIdentity(SQLITE_ENGINE, SQLITE_VERSION);\n\nexport function sqliteFts(): SqliteFtsBuilders {\n return selectBuilders(SQLITE_FTS_BUILDERS, SQLITE_PATCHES, SQLITE_VERSION);\n}\n"],"names":["engineIdentity","selectBuilders","SQLITE_ENGINE","SQLITE_VERSION","process","versions","sqlite","TOKENIZE","SQLITE_FTS_BUILDERS","createContent","rankedMatch","whereJoin","whereCond","scopeCond","limit","SQLITE_PATCHES","sqliteIdentity","sqliteFts"],"mappings":"AAAA,SAA2BA,cAAc,EAAEC,cAAc,QAAQ,eAAe;AAEhF,0GAA0G;AAC1G,OAAO,MAAMC,gBAAgB,cAAc;AAC3C,OAAO,MAAMC,iBAAqCC,QAAQC,QAAQ,CAACC,MAAM,CAAC;AAE1E,4FAA4F;AAC5F,+FAA+F;AAC/F,OAAO,MAAMC,WAAW,mBAAmB;AAW3C,OAAO,MAAMC,sBAAyC;IACpDC,eAAe,IAAM,CAAC,0IAA0I,EAAEF,SAAS,EAAE,CAAC;IAC9KG,aAAa,CAACC,WAAWC,WAAWC,WAAWC,QAAU,CAAC,yCAAyC,EAAEH,UAAU,uBAAuB,EAAEC,UAAU,CAAC,EAAEC,UAAU,iEAAiE,EAAEC,OAAO;AAC3O,EAAE;AAEF,OAAO,MAAMC,iBAA4D,EAAE,CAAC;AAE5E,OAAO,MAAMC,iBAAiB,IAAchB,eAAeE,eAAeC,gBAAgB;AAE1F,OAAO,SAASc;IACd,OAAOhB,eAAeO,qBAAqBO,gBAAgBZ;AAC7D"}
@@ -1,5 +1,5 @@
1
1
  import { segmentMatch } from '../../text/segment.js';
2
- const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0)';
2
+ import { sqliteFts } from './engine.js';
3
3
  // Ranked BM25 word-match query, scoped by the caller-built SQL fragments. An
4
4
  // unspaced-script run in `terms` becomes a quoted grapheme phrase against the `_seg` sidecars.
5
5
  export async function queryLexical(conn, terms, opts) {
@@ -8,7 +8,6 @@ export async function queryLexical(conn, terms, opts) {
8
8
  // A run of pure unspaced-script punctuation segments to nothing searchable: an empty MATCH
9
9
  // string is an FTS5 syntax error, not zero rows, so return zero rows directly rather than issuing it.
10
10
  if (query.trim() === '') return [];
11
- const sql = `SELECT content.path AS path FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${limit}`;
12
- const stmt = await conn.prepare(sql);
11
+ const stmt = await conn.prepare(sqliteFts().rankedMatch(whereJoin, whereCond, scopeCond, limit));
13
12
  return await stmt.all(query);
14
13
  }