sensemaking 0.18.3 → 0.19.0
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 +22 -4
- package/dist/cjs/chunk/group.js +10 -23
- package/dist/cjs/chunk/group.js.map +1 -1
- package/dist/cjs/chunk/parse.js +2 -5
- package/dist/cjs/chunk/parse.js.map +1 -1
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/shared.js +6 -10
- package/dist/cjs/cli/shared.js.map +1 -1
- package/dist/cjs/cli/status.js +44 -10
- package/dist/cjs/cli/status.js.map +1 -1
- package/dist/cjs/cli.js +2 -8
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/map.js +1 -3
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.js +3 -5
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.js +6 -8
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/config/access.js.map +1 -1
- package/dist/cjs/config/index.d.cts +1 -1
- package/dist/cjs/config/index.d.ts +1 -1
- package/dist/cjs/config/index.js +3 -0
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/config/load.js +10 -24
- package/dist/cjs/config/load.js.map +1 -1
- package/dist/cjs/config/resolve.js.map +1 -1
- package/dist/cjs/config/signals.js +2 -3
- package/dist/cjs/config/signals.js.map +1 -1
- package/dist/cjs/config/types.d.cts +2 -1
- package/dist/cjs/config/types.d.ts +2 -1
- package/dist/cjs/config/types.js +8 -0
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/config/validate.d.cts +1 -1
- package/dist/cjs/config/validate.d.ts +1 -1
- package/dist/cjs/config/validate.js +9 -10
- package/dist/cjs/config/validate.js.map +1 -1
- package/dist/cjs/embed/cohere.js +2 -2
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/distribution.js +3 -3
- package/dist/cjs/embed/distribution.js.map +1 -1
- package/dist/cjs/embed/handoff.d.cts +3 -0
- package/dist/cjs/embed/handoff.d.ts +3 -0
- package/dist/cjs/embed/handoff.js +39 -0
- package/dist/cjs/embed/handoff.js.map +1 -0
- package/dist/cjs/embed/identity.d.cts +11 -10
- package/dist/cjs/embed/identity.d.ts +11 -10
- package/dist/cjs/embed/identity.js +30 -15
- package/dist/cjs/embed/identity.js.map +1 -1
- package/dist/cjs/embed/langfit.js +2 -3
- package/dist/cjs/embed/langfit.js.map +1 -1
- package/dist/cjs/embed/languages.js +2 -4
- package/dist/cjs/embed/languages.js.map +1 -1
- package/dist/cjs/embed/openai.js +2 -2
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.js +58 -20
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.js +1 -2
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.d.cts +1 -1
- package/dist/cjs/embed/static.d.ts +1 -1
- package/dist/cjs/embed/static.js +8 -10
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.d.cts +2 -2
- package/dist/cjs/embed/store.d.ts +2 -2
- package/dist/cjs/embed/store.js +48 -34
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.js +1 -3
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/errors.d.cts +1 -1
- package/dist/cjs/errors.d.ts +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/features/embed.js +19 -17
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/features/fences.js +5 -10
- package/dist/cjs/features/fences.js.map +1 -1
- package/dist/cjs/features/links.js +20 -37
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/features/sections.js +21 -19
- package/dist/cjs/features/sections.js.map +1 -1
- package/dist/cjs/features/tags.js +5 -8
- package/dist/cjs/features/tags.js.map +1 -1
- package/dist/cjs/graph/traverse.js +1 -2
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output/column-hint.js +3 -6
- package/dist/cjs/output/column-hint.js.map +1 -1
- package/dist/cjs/output/output.js +7 -14
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/output/search-error.js +2 -5
- package/dist/cjs/output/search-error.js.map +1 -1
- package/dist/cjs/scan/frontmatter.d.cts +1 -0
- package/dist/cjs/scan/frontmatter.d.ts +1 -0
- package/dist/cjs/scan/frontmatter.js +14 -18
- package/dist/cjs/scan/frontmatter.js.map +1 -1
- package/dist/cjs/scan/list.js +2 -3
- package/dist/cjs/scan/list.js.map +1 -1
- package/dist/cjs/scan/reparse.js +12 -26
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/scan/worker-error.js.map +1 -1
- package/dist/cjs/store/cache.d.cts +2 -0
- package/dist/cjs/store/cache.d.ts +2 -0
- package/dist/cjs/store/cache.js +20 -0
- package/dist/cjs/store/cache.js.map +1 -0
- package/dist/cjs/store/duckdb/batch.js +4 -16
- package/dist/cjs/store/duckdb/batch.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +8 -20
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/fieldStats.js +2 -8
- package/dist/cjs/store/duckdb/fieldStats.js.map +1 -1
- package/dist/cjs/store/duckdb/lexical.js +21 -29
- package/dist/cjs/store/duckdb/lexical.js.map +1 -1
- package/dist/cjs/store/duckdb/native.js.map +1 -1
- package/dist/cjs/store/duckdb/open.js +12 -13
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +4 -11
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/duckdb/sql-functions.js +2 -4
- package/dist/cjs/store/duckdb/sql-functions.js.map +1 -1
- package/dist/cjs/store/duckdb/store.js +2 -3
- package/dist/cjs/store/duckdb/store.js.map +1 -1
- package/dist/cjs/store/duckdb/vectors.js +8 -18
- package/dist/cjs/store/duckdb/vectors.js.map +1 -1
- package/dist/cjs/store/index.d.cts +3 -2
- package/dist/cjs/store/index.d.ts +3 -2
- package/dist/cjs/store/index.js +7 -8
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/native.js +6 -16
- package/dist/cjs/store/native.js.map +1 -1
- package/dist/cjs/store/sql-functions.js.map +1 -1
- package/dist/cjs/store/sqlite/connection.js +4 -8
- package/dist/cjs/store/sqlite/connection.js.map +1 -1
- package/dist/cjs/store/sqlite/fieldStats.js.map +1 -1
- package/dist/cjs/store/sqlite/lexical.js +3 -5
- package/dist/cjs/store/sqlite/lexical.js.map +1 -1
- package/dist/cjs/store/sqlite/open.d.cts +0 -1
- package/dist/cjs/store/sqlite/open.d.ts +0 -1
- package/dist/cjs/store/sqlite/open.js +22 -40
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/sqlite/reconcile.js +2 -3
- package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/sql-functions.js +2 -4
- package/dist/cjs/store/sqlite/sql-functions.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/transaction.js +1 -3
- package/dist/cjs/store/transaction.js.map +1 -1
- package/dist/cjs/store/turso/connection.js +2 -6
- package/dist/cjs/store/turso/connection.js.map +1 -1
- package/dist/cjs/store/turso/fieldStats.js.map +1 -1
- package/dist/cjs/store/turso/lexical.d.cts +2 -0
- package/dist/cjs/store/turso/lexical.d.ts +2 -0
- package/dist/cjs/store/turso/lexical.js +347 -0
- package/dist/cjs/store/turso/lexical.js.map +1 -0
- package/dist/cjs/store/turso/native.js.map +1 -1
- package/dist/cjs/store/turso/open.d.cts +1 -1
- package/dist/cjs/store/turso/open.d.ts +1 -1
- package/dist/cjs/store/turso/open.js +81 -54
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/reconcile.js +22 -22
- package/dist/cjs/store/turso/reconcile.js.map +1 -1
- package/dist/cjs/store/turso/store.js +18 -41
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.d.cts +8 -0
- package/dist/cjs/store/turso/vectors.d.ts +8 -0
- package/dist/cjs/store/turso/vectors.js +435 -0
- package/dist/cjs/store/turso/vectors.js.map +1 -0
- package/dist/cjs/store/types.js +2 -3
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/text/segment.d.cts +1 -0
- package/dist/cjs/text/segment.d.ts +1 -0
- package/dist/cjs/text/segment.js +10 -14
- package/dist/cjs/text/segment.js.map +1 -1
- package/dist/cjs/watch.js +2 -5
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/chunk/group.js +10 -23
- package/dist/esm/chunk/group.js.map +1 -1
- package/dist/esm/chunk/parse.js +4 -8
- package/dist/esm/chunk/parse.js.map +1 -1
- package/dist/esm/cli/index.js +3 -5
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/shared.js +12 -22
- package/dist/esm/cli/shared.js.map +1 -1
- package/dist/esm/cli/status.js +43 -9
- package/dist/esm/cli/status.js.map +1 -1
- package/dist/esm/cli.js +2 -8
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/commands/map.js +1 -3
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.js +3 -5
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.js +3 -5
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.js +10 -16
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.js +3 -7
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/config/access.js +8 -13
- package/dist/esm/config/access.js.map +1 -1
- package/dist/esm/config/index.d.ts +1 -1
- package/dist/esm/config/index.js +1 -1
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/config/load.js +10 -24
- package/dist/esm/config/load.js.map +1 -1
- package/dist/esm/config/resolve.js +2 -3
- package/dist/esm/config/resolve.js.map +1 -1
- package/dist/esm/config/signals.js +3 -5
- package/dist/esm/config/signals.js.map +1 -1
- package/dist/esm/config/types.d.ts +2 -1
- package/dist/esm/config/types.js +8 -2
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/config/validate.d.ts +1 -1
- package/dist/esm/config/validate.js +7 -10
- package/dist/esm/config/validate.js.map +1 -1
- package/dist/esm/embed/cohere.js +2 -2
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/distribution.js +1 -1
- package/dist/esm/embed/distribution.js.map +1 -1
- package/dist/esm/embed/handoff.d.ts +3 -0
- package/dist/esm/embed/handoff.js +21 -0
- package/dist/esm/embed/handoff.js.map +1 -0
- package/dist/esm/embed/identity.d.ts +11 -10
- package/dist/esm/embed/identity.js +29 -26
- package/dist/esm/embed/identity.js.map +1 -1
- package/dist/esm/embed/langfit.js +2 -3
- package/dist/esm/embed/langfit.js.map +1 -1
- package/dist/esm/embed/languages.js +2 -4
- package/dist/esm/embed/languages.js.map +1 -1
- package/dist/esm/embed/openai.js +2 -2
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.js +21 -6
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.js +1 -2
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.d.ts +1 -1
- package/dist/esm/embed/static.js +8 -10
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.d.ts +2 -2
- package/dist/esm/embed/store.js +28 -23
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.js +1 -3
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/features/embed.js +16 -16
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/features/fences.js +9 -19
- package/dist/esm/features/fences.js.map +1 -1
- package/dist/esm/features/links.js +24 -49
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/sections.js +20 -20
- package/dist/esm/features/sections.js.map +1 -1
- package/dist/esm/features/tags.js +5 -8
- package/dist/esm/features/tags.js.map +1 -1
- package/dist/esm/features/types.js.map +1 -1
- package/dist/esm/graph/traverse.js +1 -2
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output/column-hint.js +3 -6
- package/dist/esm/output/column-hint.js.map +1 -1
- package/dist/esm/output/output.js +9 -20
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/output/search-error.js +2 -5
- package/dist/esm/output/search-error.js.map +1 -1
- package/dist/esm/scan/frontmatter.d.ts +1 -0
- package/dist/esm/scan/frontmatter.js +13 -18
- package/dist/esm/scan/frontmatter.js.map +1 -1
- package/dist/esm/scan/list.js +2 -3
- package/dist/esm/scan/list.js.map +1 -1
- package/dist/esm/scan/reparse.js +16 -34
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/scan/worker-error.js.map +1 -1
- package/dist/esm/store/cache.d.ts +2 -0
- package/dist/esm/store/cache.js +11 -0
- package/dist/esm/store/cache.js.map +1 -0
- package/dist/esm/store/duckdb/batch.js +4 -16
- package/dist/esm/store/duckdb/batch.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +8 -20
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/fieldStats.js +4 -12
- package/dist/esm/store/duckdb/fieldStats.js.map +1 -1
- package/dist/esm/store/duckdb/lexical.js +29 -36
- package/dist/esm/store/duckdb/lexical.js.map +1 -1
- package/dist/esm/store/duckdb/native.js +2 -7
- package/dist/esm/store/duckdb/native.js.map +1 -1
- package/dist/esm/store/duckdb/open.js +16 -23
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +4 -11
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/duckdb/sql-functions.js +4 -9
- package/dist/esm/store/duckdb/sql-functions.js.map +1 -1
- package/dist/esm/store/duckdb/store.js +6 -14
- package/dist/esm/store/duckdb/store.js.map +1 -1
- package/dist/esm/store/duckdb/vectors.js +14 -31
- package/dist/esm/store/duckdb/vectors.js.map +1 -1
- package/dist/esm/store/index.d.ts +3 -2
- package/dist/esm/store/index.js +5 -6
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/native.js +10 -25
- package/dist/esm/store/native.js.map +1 -1
- package/dist/esm/store/sql-functions.js +3 -5
- package/dist/esm/store/sql-functions.js.map +1 -1
- package/dist/esm/store/sqlite/connection.js +4 -8
- package/dist/esm/store/sqlite/connection.js.map +1 -1
- package/dist/esm/store/sqlite/fieldStats.js +2 -6
- package/dist/esm/store/sqlite/fieldStats.js.map +1 -1
- package/dist/esm/store/sqlite/lexical.js +5 -9
- package/dist/esm/store/sqlite/lexical.js.map +1 -1
- package/dist/esm/store/sqlite/open.d.ts +0 -1
- package/dist/esm/store/sqlite/open.js +21 -41
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js +6 -13
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/sql-functions.js +2 -4
- package/dist/esm/store/sqlite/sql-functions.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +1 -2
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/transaction.js +2 -5
- package/dist/esm/store/transaction.js.map +1 -1
- package/dist/esm/store/turso/connection.js +2 -6
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/fieldStats.js +2 -5
- package/dist/esm/store/turso/fieldStats.js.map +1 -1
- package/dist/esm/store/turso/lexical.d.ts +2 -0
- package/dist/esm/store/turso/lexical.js +128 -0
- package/dist/esm/store/turso/lexical.js.map +1 -0
- package/dist/esm/store/turso/native.js +2 -7
- package/dist/esm/store/turso/native.js.map +1 -1
- package/dist/esm/store/turso/open.d.ts +1 -1
- package/dist/esm/store/turso/open.js +36 -29
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/reconcile.js +22 -22
- package/dist/esm/store/turso/reconcile.js.map +1 -1
- package/dist/esm/store/turso/store.js +17 -28
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.d.ts +8 -0
- package/dist/esm/store/turso/vectors.js +98 -0
- package/dist/esm/store/turso/vectors.js.map +1 -0
- package/dist/esm/store/types.js +2 -3
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.js +2 -3
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/text/segment.d.ts +1 -0
- package/dist/esm/text/segment.js +13 -21
- package/dist/esm/text/segment.js.map +1 -1
- package/dist/esm/watch.js +2 -5
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +3 -1
- package/schema.json +2 -2
- package/skills/sense/SKILL.md +2 -2
- package/skills/sense-setup/SKILL.md +3 -3
- package/dist/cjs/store/meta.d.cts +0 -3
- package/dist/cjs/store/meta.d.ts +0 -3
- package/dist/cjs/store/meta.js +0 -215
- package/dist/cjs/store/meta.js.map +0 -1
- package/dist/esm/store/meta.d.ts +0 -3
- package/dist/esm/store/meta.js +0 -17
- package/dist/esm/store/meta.js.map +0 -1
|
@@ -82,19 +82,8 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
82
82
|
if (n === "Map" || n === "Set") return Array.from(n);
|
|
83
83
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
84
84
|
}
|
|
85
|
-
// Rewrites
|
|
86
|
-
//
|
|
87
|
-
// all N rows in a single round trip. A columnar engine's cost is row-at-a-time writes themselves,
|
|
88
|
-
// not just the async crossing to reach them, so this recognizes the three statement shapes the
|
|
89
|
-
// store issues (INSERT ... VALUES (...), UPDATE ... SET ... WHERE ..., DELETE ... WHERE ...) and
|
|
90
|
-
// rewrites each into a multi-row form, verified against a live DuckDB connection at
|
|
91
|
-
// implementation time (PRINCIPLES: documented-means-tested). Unrecognized SQL returns null;
|
|
92
|
-
// connection.ts's runBatch falls back to a prepare-once, bind-and-run loop -- still one call
|
|
93
|
-
// from outside the store.
|
|
94
|
-
//
|
|
95
|
-
// This only ever runs against sense's own SQL literals (reconcile, the feature hooks), never
|
|
96
|
-
// arbitrary user SQL (`sense sql` is a separate, unrewritten passthrough), so the shapes below
|
|
97
|
-
// are exactly what those call sites emit, not a general parser.
|
|
85
|
+
// Rewrites the store's single-row parameterized INSERT/UPDATE/DELETE into one multi-row statement --
|
|
86
|
+
// DuckDB's columnar engine pays for row-at-a-time writes, not just the round trip (PRINCIPLES: documented-means-tested).
|
|
98
87
|
var INSERT_RE = RegExp("^(INSERT\\s+(?:OR\\s+\\w+\\s+)?INTO\\s+\\S+\\s*\\([^)]*\\)\\s*VALUES\\s*)\\(([^()]*)\\)(.*)$", "is");
|
|
99
88
|
var UPDATE_RE = RegExp("^UPDATE\\s+(\\S+)\\s+SET\\s+(.+?)\\s+WHERE\\s+(.+)$", "is");
|
|
100
89
|
var DELETE_RE = RegExp("^DELETE\\s+FROM\\s+(\\S+)\\s+WHERE\\s+(.+)$", "is");
|
|
@@ -102,9 +91,8 @@ function placeholderCount(tuple) {
|
|
|
102
91
|
var _tuple_match;
|
|
103
92
|
return ((_tuple_match = tuple.match(/\?/g)) !== null && _tuple_match !== void 0 ? _tuple_match : []).length;
|
|
104
93
|
}
|
|
105
|
-
// "a = ?, b = ?" or "a = ? AND b = ?" -> ["a", "b"].
|
|
106
|
-
//
|
|
107
|
-
// length won't match the caller's row width, which rewriteUpdate/rewriteDelete then reject.
|
|
94
|
+
// "a = ?, b = ?" or "a = ? AND b = ?" -> ["a", "b"]. A clause that doesn't fit yields a shorter
|
|
95
|
+
// list, which rewriteUpdate/rewriteDelete reject as a width mismatch.
|
|
108
96
|
function equalityColumns(clause, sep) {
|
|
109
97
|
var parts = clause.split(sep).map(function(p) {
|
|
110
98
|
return p.trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/batch.ts"],"sourcesContent":["// Rewrites
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/batch.ts"],"sourcesContent":["// Rewrites the store's single-row parameterized INSERT/UPDATE/DELETE into one multi-row statement --\n// DuckDB's columnar engine pays for row-at-a-time writes, not just the round trip (PRINCIPLES: documented-means-tested).\n\nconst INSERT_RE = /^(INSERT\\s+(?:OR\\s+\\w+\\s+)?INTO\\s+\\S+\\s*\\([^)]*\\)\\s*VALUES\\s*)\\(([^()]*)\\)(.*)$/is;\nconst UPDATE_RE = /^UPDATE\\s+(\\S+)\\s+SET\\s+(.+?)\\s+WHERE\\s+(.+)$/is;\nconst DELETE_RE = /^DELETE\\s+FROM\\s+(\\S+)\\s+WHERE\\s+(.+)$/is;\n\nfunction placeholderCount(tuple: string): number {\n return (tuple.match(/\\?/g) ?? []).length;\n}\n\n// \"a = ?, b = ?\" or \"a = ? AND b = ?\" -> [\"a\", \"b\"]. A clause that doesn't fit yields a shorter\n// list, which rewriteUpdate/rewriteDelete reject as a width mismatch.\nfunction equalityColumns(clause: string, sep: RegExp): string[] | null {\n const parts = clause.split(sep).map((p) => p.trim());\n const cols: string[] = [];\n for (const part of parts) {\n const m = /^(.+?)=\\s*\\?$/.exec(part);\n if (!m) return null;\n cols.push(m[1].trim());\n }\n return cols;\n}\n\nfunction tuples(rowCount: number, width: number): string {\n const row = `(${Array.from({ length: width }, () => '?').join(', ')})`;\n return Array.from({ length: rowCount }, () => row).join(', ');\n}\n\nexport function rewriteInsert(sql: string, rowCount: number): { sql: string; width: number } | null {\n const m = INSERT_RE.exec(sql.trim());\n if (!m) return null;\n const [, head, tuple, tail] = m;\n const width = placeholderCount(tuple);\n if (width === 0) return null;\n const rows = Array.from({ length: rowCount }, () => `(${tuple})`).join(', ');\n return { sql: `${head}${rows}${tail}`, width };\n}\n\nexport function rewriteUpdate(sql: string, rowCount: number): { sql: string; width: number } | null {\n const m = UPDATE_RE.exec(sql.trim());\n if (!m) return null;\n const [, table, setClause, whereClause] = m;\n const setCols = equalityColumns(setClause, /,/);\n const whereCols = equalityColumns(whereClause, /\\bAND\\b/i);\n if (!setCols || !whereCols) return null;\n const dataCols = [...setCols, ...whereCols];\n const setAssign = setCols.map((c) => `${c} = data.${c}`).join(', ');\n const joinCond = whereCols.map((c) => `${table}.${c} = data.${c}`).join(' AND ');\n return { sql: `UPDATE ${table} SET ${setAssign} FROM (VALUES ${tuples(rowCount, dataCols.length)}) AS data(${dataCols.join(', ')}) WHERE ${joinCond}`, width: dataCols.length };\n}\n\nexport function rewriteDelete(sql: string, rowCount: number): { sql: string; width: number } | null {\n const m = DELETE_RE.exec(sql.trim());\n if (!m) return null;\n const [, table, whereClause] = m;\n const cols = equalityColumns(whereClause, /\\bAND\\b/i);\n if (!cols) return null;\n if (cols.length === 1) {\n return { sql: `DELETE FROM ${table} WHERE ${cols[0]} IN (${Array.from({ length: rowCount }, () => '?').join(', ')})`, width: 1 };\n }\n return { sql: `DELETE FROM ${table} WHERE (${cols.join(', ')}) IN (VALUES ${tuples(rowCount, cols.length)})`, width: cols.length };\n}\n\n// Tries each shape in turn; null means the caller should fall back to a per-row loop.\nexport function rewriteBatch(sql: string, rowCount: number): { sql: string; width: number } | null {\n return rewriteInsert(sql, rowCount) ?? rewriteUpdate(sql, rowCount) ?? rewriteDelete(sql, rowCount);\n}\n"],"names":["rewriteBatch","rewriteDelete","rewriteInsert","rewriteUpdate","INSERT_RE","UPDATE_RE","DELETE_RE","placeholderCount","tuple","match","length","equalityColumns","clause","sep","parts","split","map","p","trim","cols","part","m","exec","push","tuples","rowCount","width","row","Array","from","join","sql","head","tail","rows","table","setClause","whereClause","setCols","whereCols","dataCols","setAssign","c","joinCond"],"mappings":"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"}
|
|
@@ -402,9 +402,8 @@ function _wrap_async_generator(fn) {
|
|
|
402
402
|
return new _async_generator(fn.apply(this, arguments));
|
|
403
403
|
};
|
|
404
404
|
}
|
|
405
|
-
// getRowObjectsJS returns INT64 columns as BigInt regardless of magnitude, while sqlite's
|
|
406
|
-
//
|
|
407
|
-
// setReadBigInts contract); stringifyJson keeps JSON output safe.
|
|
405
|
+
// getRowObjectsJS returns INT64 columns as BigInt regardless of magnitude, while sqlite's small
|
|
406
|
+
// ints are numbers and consumers assume number; in-range values convert here, out-of-range stays BigInt.
|
|
408
407
|
var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
409
408
|
function storeValueToJs(value) {
|
|
410
409
|
return (typeof value === "undefined" ? "undefined" : _type_of(value)) === 'bigint' && value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value;
|
|
@@ -433,21 +432,13 @@ function storeRowToJs(row) {
|
|
|
433
432
|
}
|
|
434
433
|
return out;
|
|
435
434
|
}
|
|
436
|
-
// @duckdb/node-api
|
|
437
|
-
//
|
|
438
|
-
// destroys one before then. The portable Statement interface has no dispose (sqlite's node:sqlite
|
|
439
|
-
// statements need none, and callers like graph/traverse.ts's ring loop reuse one Statement across
|
|
440
|
-
// several run/get/all calls, so a per-call destroy would be wrong), so cleanup happens here,
|
|
441
|
-
// locally, once nothing references the wrapper any more. destroySync() is safe to call more than
|
|
442
|
-
// once -- runBatch below and vectors.ts already call it explicitly while the same connection-close
|
|
443
|
-
// sweep can also reach a statement that outlives its wrapper, with no reported issue.
|
|
435
|
+
// @duckdb/node-api only destroys a prepared statement when its connection closes, but a connection can stay open for a whole `sense watch` session.
|
|
436
|
+
// Statement has no dispose (callers reuse one instance across several calls), so this FinalizationRegistry reclaims it once nothing references the wrapper.
|
|
444
437
|
var preparedFinalizer = new FinalizationRegistry(function(prepared) {
|
|
445
438
|
prepared.destroySync();
|
|
446
439
|
});
|
|
447
|
-
// Wraps one already-prepared DuckDB statement: prepare() is the only async step
|
|
448
|
-
//
|
|
449
|
-
// run/get/all rebind and re-execute the same native statement, matching how a caller (e.g.
|
|
450
|
-
// graph/traverse.ts's ring loop) reuses one Statement across several calls.
|
|
440
|
+
// Wraps one already-prepared DuckDB statement: prepare() is the only async step, so run/get/all rebind and
|
|
441
|
+
// re-execute the same native statement, matching how callers like graph/traverse.ts's ring loop reuse one Statement across several calls.
|
|
451
442
|
var DuckdbStatement = /*#__PURE__*/ function() {
|
|
452
443
|
"use strict";
|
|
453
444
|
function DuckdbStatement(prepared) {
|
|
@@ -618,11 +609,8 @@ function createConnection(duckdb) {
|
|
|
618
609
|
});
|
|
619
610
|
})();
|
|
620
611
|
},
|
|
621
|
-
runBatch: // One crossing regardless of row count: rewriteBatch
|
|
622
|
-
//
|
|
623
|
-
// back to a bind-and-run loop over one prepared statement, still a single call to the caller.
|
|
624
|
-
// Joins the caller's transaction when there is one (reconcile); otherwise opens its own, so
|
|
625
|
-
// a standalone batch stays atomic.
|
|
612
|
+
runBatch: // One crossing regardless of row count: rewriteBatch folds recognized shapes into a multi-row statement (batch.ts),
|
|
613
|
+
// else falls back to a bind-and-run loop, one call either way. Joins the caller's transaction when there is one, else opens its own.
|
|
626
614
|
function runBatch(sql, paramRows) {
|
|
627
615
|
return _async_to_generator(function() {
|
|
628
616
|
return _ts_generator(this, function(_state) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/connection.ts"],"sourcesContent":["import type { DuckDBConnection, DuckDBPreparedStatement, DuckDBValue } from '@duckdb/node-api';\nimport { withTransaction } from '../transaction.ts';\nimport type { Connection, RunResult, Statement } from '../types.ts';\nimport { rewriteBatch } from './batch.ts';\n\n// getRowObjectsJS returns INT64 columns as BigInt regardless of magnitude, while sqlite's\n// small ints are numbers and consumers assume number. Beyond the safe range stays BigInt (the\n// setReadBigInts contract); stringifyJson keeps JSON output safe.\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nexport function storeValueToJs(value: unknown): unknown {\n return typeof value === 'bigint' && value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value;\n}\nexport function storeRowToJs(row: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) out[key] = storeValueToJs(value);\n return out;\n}\n\n// @duckdb/node-api tracks every prepared statement itself and destroys any survivor when the\n// connection closes, but sense's stores stay open for a whole `sense watch` session -- nothing\n// destroys one before then. The portable Statement interface has no dispose (sqlite's node:sqlite\n// statements need none, and callers like graph/traverse.ts's ring loop reuse one Statement across\n// several run/get/all calls, so a per-call destroy would be wrong), so cleanup happens here,\n// locally, once nothing references the wrapper any more. destroySync() is safe to call more than\n// once -- runBatch below and vectors.ts already call it explicitly while the same connection-close\n// sweep can also reach a statement that outlives its wrapper, with no reported issue.\nconst preparedFinalizer = new FinalizationRegistry<DuckDBPreparedStatement>((prepared) => {\n prepared.destroySync();\n});\n\n// Wraps one already-prepared DuckDB statement: prepare() is the only async step (Store.prepare\n// already crosses the async boundary once), so columns() reads back synchronously from it and\n// run/get/all rebind and re-execute the same native statement, matching how a caller (e.g.\n// graph/traverse.ts's ring loop) reuses one Statement across several calls.\nclass DuckdbStatement implements Statement {\n private prepared: DuckDBPreparedStatement;\n\n constructor(prepared: DuckDBPreparedStatement) {\n this.prepared = prepared;\n preparedFinalizer.register(this, prepared);\n }\n\n async run(...params: unknown[]): Promise<RunResult> {\n if (params.length > 0) this.prepared.bind(params as DuckDBValue[]);\n const result = await this.prepared.run();\n return { changes: result.rowsChanged, lastInsertRowid: 0 };\n }\n\n async get(...params: unknown[]): Promise<unknown> {\n const rows = await this.all(...params);\n return rows[0];\n }\n\n async all(...params: unknown[]): Promise<unknown[]> {\n if (params.length > 0) this.prepared.bind(params as DuckDBValue[]);\n const reader = await this.prepared.runAndReadAll();\n return (reader.getRowObjectsJS() as Array<Record<string, unknown>>).map(storeRowToJs);\n }\n\n async *iterate(...params: unknown[]): AsyncIterable<unknown> {\n // Never called on the portable Connection/Statement surface today (only Store.raw streams);\n // materializing keeps this a correct, if eager, AsyncIterable.\n yield* await this.all(...params);\n }\n\n columns(): Array<{ name: string }> {\n const out: Array<{ name: string }> = [];\n for (let i = 0; i < this.prepared.columnCount; i++) out.push({ name: this.prepared.columnName(i) });\n return out;\n }\n\n setReadBigInts(_enabled: boolean): void {\n // No-op: in-range ints are already numbers here (storeRowToJs converts what getRowObjectsJS\n // hands back as BigInt), so the node:sqlite throw-at-step-time problem does not exist.\n }\n}\n\nexport function createConnection(duckdb: DuckDBConnection): Connection {\n const conn: Connection = {\n async exec(sql: string): Promise<void> {\n await duckdb.run(sql);\n },\n async prepare(sql: string): Promise<Statement> {\n return new DuckdbStatement(await duckdb.prepare(sql));\n },\n // One crossing regardless of row count: rewriteBatch turns the caller's single-row SQL into\n // one multi-row statement when it recognizes the shape (see batch.ts); anything else falls\n // back to a bind-and-run loop over one prepared statement, still a single call to the caller.\n // Joins the caller's transaction when there is one (reconcile); otherwise opens its own, so\n // a standalone batch stays atomic.\n async runBatch(sql: string, paramRows: unknown[][]): Promise<void> {\n if (paramRows.length === 0) return;\n await withTransaction(conn, async () => {\n const rewritten = rewriteBatch(sql, paramRows.length);\n if (rewritten) {\n const stmt = await duckdb.prepare(rewritten.sql);\n try {\n stmt.bind(paramRows.flat() as DuckDBValue[]);\n await stmt.run();\n } finally {\n stmt.destroySync();\n }\n return;\n }\n const stmt = await duckdb.prepare(sql);\n try {\n for (const row of paramRows) {\n stmt.bind(row as DuckDBValue[]);\n await stmt.run();\n }\n } finally {\n stmt.destroySync();\n }\n });\n },\n };\n return conn;\n}\n"],"names":["createConnection","storeRowToJs","storeValueToJs","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","value","row","out","Object","entries","key","preparedFinalizer","FinalizationRegistry","prepared","destroySync","DuckdbStatement","register","run","params","result","length","bind","changes","rowsChanged","lastInsertRowid","get","rows","all","reader","runAndReadAll","getRowObjectsJS","map","iterate","columns","i","columnCount","push","name","columnName","setReadBigInts","_enabled","duckdb","conn","exec","sql","prepare","runBatch","paramRows","withTransaction","rewritten","stmt","rewriteBatch","flat"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/connection.ts"],"sourcesContent":["import type { DuckDBConnection, DuckDBPreparedStatement, DuckDBValue } from '@duckdb/node-api';\nimport { withTransaction } from '../transaction.ts';\nimport type { Connection, RunResult, Statement } from '../types.ts';\nimport { rewriteBatch } from './batch.ts';\n\n// getRowObjectsJS returns INT64 columns as BigInt regardless of magnitude, while sqlite's small\n// ints are numbers and consumers assume number; in-range values convert here, out-of-range stays BigInt.\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nexport function storeValueToJs(value: unknown): unknown {\n return typeof value === 'bigint' && value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value;\n}\nexport function storeRowToJs(row: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) out[key] = storeValueToJs(value);\n return out;\n}\n\n// @duckdb/node-api only destroys a prepared statement when its connection closes, but a connection can stay open for a whole `sense watch` session.\n// Statement has no dispose (callers reuse one instance across several calls), so this FinalizationRegistry reclaims it once nothing references the wrapper.\nconst preparedFinalizer = new FinalizationRegistry<DuckDBPreparedStatement>((prepared) => {\n prepared.destroySync();\n});\n\n// Wraps one already-prepared DuckDB statement: prepare() is the only async step, so run/get/all rebind and\n// re-execute the same native statement, matching how callers like graph/traverse.ts's ring loop reuse one Statement across several calls.\nclass DuckdbStatement implements Statement {\n private prepared: DuckDBPreparedStatement;\n\n constructor(prepared: DuckDBPreparedStatement) {\n this.prepared = prepared;\n preparedFinalizer.register(this, prepared);\n }\n\n async run(...params: unknown[]): Promise<RunResult> {\n if (params.length > 0) this.prepared.bind(params as DuckDBValue[]);\n const result = await this.prepared.run();\n return { changes: result.rowsChanged, lastInsertRowid: 0 };\n }\n\n async get(...params: unknown[]): Promise<unknown> {\n const rows = await this.all(...params);\n return rows[0];\n }\n\n async all(...params: unknown[]): Promise<unknown[]> {\n if (params.length > 0) this.prepared.bind(params as DuckDBValue[]);\n const reader = await this.prepared.runAndReadAll();\n return (reader.getRowObjectsJS() as Array<Record<string, unknown>>).map(storeRowToJs);\n }\n\n async *iterate(...params: unknown[]): AsyncIterable<unknown> {\n // Never called on the portable Connection/Statement surface today (only Store.raw streams);\n // materializing keeps this a correct, if eager, AsyncIterable.\n yield* await this.all(...params);\n }\n\n columns(): Array<{ name: string }> {\n const out: Array<{ name: string }> = [];\n for (let i = 0; i < this.prepared.columnCount; i++) out.push({ name: this.prepared.columnName(i) });\n return out;\n }\n\n setReadBigInts(_enabled: boolean): void {\n // No-op: in-range ints are already numbers here (storeRowToJs converts what getRowObjectsJS\n // hands back as BigInt), so the node:sqlite throw-at-step-time problem does not exist.\n }\n}\n\nexport function createConnection(duckdb: DuckDBConnection): Connection {\n const conn: Connection = {\n async exec(sql: string): Promise<void> {\n await duckdb.run(sql);\n },\n async prepare(sql: string): Promise<Statement> {\n return new DuckdbStatement(await duckdb.prepare(sql));\n },\n // One crossing regardless of row count: rewriteBatch folds recognized shapes into a multi-row statement (batch.ts),\n // else falls back to a bind-and-run loop, one call either way. Joins the caller's transaction when there is one, else opens its own.\n async runBatch(sql: string, paramRows: unknown[][]): Promise<void> {\n if (paramRows.length === 0) return;\n await withTransaction(conn, async () => {\n const rewritten = rewriteBatch(sql, paramRows.length);\n if (rewritten) {\n const stmt = await duckdb.prepare(rewritten.sql);\n try {\n stmt.bind(paramRows.flat() as DuckDBValue[]);\n await stmt.run();\n } finally {\n stmt.destroySync();\n }\n return;\n }\n const stmt = await duckdb.prepare(sql);\n try {\n for (const row of paramRows) {\n stmt.bind(row as DuckDBValue[]);\n await stmt.run();\n }\n } finally {\n stmt.destroySync();\n }\n });\n },\n };\n return conn;\n}\n"],"names":["createConnection","storeRowToJs","storeValueToJs","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","value","row","out","Object","entries","key","preparedFinalizer","FinalizationRegistry","prepared","destroySync","DuckdbStatement","register","run","params","result","length","bind","changes","rowsChanged","lastInsertRowid","get","rows","all","reader","runAndReadAll","getRowObjectsJS","map","iterate","columns","i","columnCount","push","name","columnName","setReadBigInts","_enabled","duckdb","conn","exec","sql","prepare","runBatch","paramRows","withTransaction","rewritten","stmt","rewriteBatch","flat"],"mappings":"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"}
|
|
@@ -161,14 +161,8 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
161
161
|
if (n === "Map" || n === "Set") return Array.from(n);
|
|
162
162
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
163
163
|
}
|
|
164
|
-
// DuckDB's frontmatter columns are VARIANT (reconcile.ts
|
|
165
|
-
//
|
|
166
|
-
// native type instead (spike-verified against mapValue()'s actual bind shapes: a bound JS bigint
|
|
167
|
-
// always boxes as INT128, a non-integer JS number always boxes as DOUBLE, a string as VARCHAR --
|
|
168
|
-
// literal SQL values box narrower, e.g. INTEGER/DECIMAL(p, s), which this still covers since a
|
|
169
|
-
// query issued through `sense sql` can produce either). Mapped onto the shared integer/real/text
|
|
170
|
-
// vocabulary sqlite/turso's typeof() already emits natively; an unrecognized name throws rather
|
|
171
|
-
// than being silently called text (no-silent-modes).
|
|
164
|
+
// DuckDB's frontmatter columns are VARIANT (reconcile.ts), so typeof() always reports VARIANT; variant_typeof() reports the boxed native
|
|
165
|
+
// type instead -- INT128, DOUBLE, VARCHAR, or a literal's narrower type like DECIMAL(p, s) -- mapped onto sqlite/turso's integer/real/text vocabulary; an unrecognized name throws (no-silent-modes).
|
|
172
166
|
function classifyVariantType(name, field) {
|
|
173
167
|
if (/^U?INT\d+$/.test(name)) return 'integer';
|
|
174
168
|
if (name === 'FLOAT' || name === 'DOUBLE' || name.startsWith('DECIMAL(')) return 'real';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/fieldStats.ts"],"sourcesContent":["import { quoteIdent } from '../shared.ts';\nimport type { Connection, FieldStat } from '../types.ts';\n\n// DuckDB's frontmatter columns are VARIANT (reconcile.ts
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/fieldStats.ts"],"sourcesContent":["import { quoteIdent } from '../shared.ts';\nimport type { Connection, FieldStat } from '../types.ts';\n\n// DuckDB's frontmatter columns are VARIANT (reconcile.ts), so typeof() always reports VARIANT; variant_typeof() reports the boxed native\n// type instead -- INT128, DOUBLE, VARCHAR, or a literal's narrower type like DECIMAL(p, s) -- mapped onto sqlite/turso's integer/real/text vocabulary; an unrecognized name throws (no-silent-modes).\nfunction classifyVariantType(name: string, field: string): 'integer' | 'real' | 'text' {\n if (/^U?INT\\d+$/.test(name)) return 'integer';\n if (name === 'FLOAT' || name === 'DOUBLE' || name.startsWith('DECIMAL(')) return 'real';\n if (name === 'VARCHAR') return 'text';\n throw new Error(`duckdb: variant_typeof() returned unrecognized type \"${name}\" for column \"${field}\"`);\n}\n\n// One aggregate query, one result row: COUNT + string_agg(DISTINCT variant_typeof()) per column keeps this O(columns), not O(rows x columns).\n// '|' is the separator, not ',': DECIMAL's display name (\"DECIMAL(4, 2)\") contains a comma.\nexport async function fieldStats(conn: Connection, columns: string[], scopeWhere: string): Promise<FieldStat[]> {\n const exprs = columns.map((name, i) => {\n const quoted = quoteIdent(name);\n return `COUNT(${quoted}) AS n${i}, string_agg(DISTINCT variant_typeof(${quoted}), '|') FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const stmt = await conn.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`);\n const row = (await stmt.get()) as Record<string, number | string | null>;\n return columns.map((name, i) => {\n const raw = ((row[`t${i}`] as string) ?? '').split('|').filter(Boolean);\n const types = [...new Set(raw.map((t) => classifyVariantType(t, name)))].sort();\n return { field: name, coverage: Number(row[`n${i}`] ?? 0), type: types.join(',') };\n });\n}\n"],"names":["fieldStats","classifyVariantType","name","field","test","startsWith","Error","conn","columns","scopeWhere","exprs","stmt","row","map","i","quoted","quoteIdent","prepare","join","get","raw","split","filter","Boolean","types","Set","t","sort","coverage","Number","type"],"mappings":";;;;+BAcsBA;;;eAAAA;;;wBAdK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG3B,yIAAyI;AACzI,sMAAsM;AACtM,SAASC,oBAAoBC,IAAY,EAAEC,KAAa;IACtD,IAAI,aAAaC,IAAI,CAACF,OAAO,OAAO;IACpC,IAAIA,SAAS,WAAWA,SAAS,YAAYA,KAAKG,UAAU,CAAC,aAAa,OAAO;IACjF,IAAIH,SAAS,WAAW,OAAO;IAC/B,MAAM,IAAII,MAAM,AAAC,wDAA4EH,OAArBD,MAAK,kBAAsB,OAANC,OAAM;AACrG;AAIO,SAAeH,WAAWO,IAAgB,EAAEC,OAAiB,EAAEC,UAAkB;;YAChFC,OAIAC,MACAC;;;;oBALAF,QAAQF,QAAQK,GAAG,CAAC,SAACX,MAAMY;wBAC/B,IAAMC,SAASC,IAAAA,oBAAU,EAACd;wBAC1B,OAAO,AAAC,SAAuBY,OAAfC,QAAO,UAAiDA,OAAzCD,GAAE,yCAAsEC,OAA/BA,QAAO,0BAAmDD,OAA3BC,QAAO,sBAAsB,OAAFD;oBACpI;oBACa;;wBAAMP,KAAKU,OAAO,CAAC,AAAC,UAA8CR,OAArCC,MAAMQ,IAAI,CAAC,OAAM,sBAA+B,OAAXT;;;oBAAzEE,OAAO;oBACA;;wBAAMA,KAAKQ,GAAG;;;oBAArBP,MAAO;oBACb;;wBAAOJ,QAAQK,GAAG,CAAC,SAACX,MAAMY;gCACVF,OAEyBA;4BAFvC,IAAMQ,MAAM,EAAER,QAAAA,GAAG,CAAC,AAAC,IAAK,OAAFE,GAAI,cAAZF,mBAAAA,QAA2B,IAAIS,KAAK,CAAC,KAAKC,MAAM,CAACC;4BAC/D,IAAMC,QAAQ,AAAC,qBAAG,IAAIC,IAAIL,IAAIP,GAAG,CAAC,SAACa;uCAAMzB,oBAAoByB,GAAGxB;iCAASyB,IAAI;4BAC7E,OAAO;gCAAExB,OAAOD;gCAAM0B,UAAUC,QAAOjB,SAAAA,GAAG,CAAC,AAAC,IAAK,OAAFE,GAAI,cAAZF,oBAAAA,SAAgB;gCAAIkB,MAAMN,MAAMN,IAAI,CAAC;4BAAK;wBACnF;;;;IACF"}
|
|
@@ -178,19 +178,15 @@ var FIELDS = [
|
|
|
178
178
|
'summary',
|
|
179
179
|
'text'
|
|
180
180
|
];
|
|
181
|
-
// Mirrors sqlite's bm25(content, 10.0, 5.0, 1.0, ...) column-weight intent; DuckDB's
|
|
182
|
-
//
|
|
183
|
-
// separately and combined here.
|
|
181
|
+
// Mirrors sqlite's bm25(content, 10.0, 5.0, 1.0, ...) column-weight intent; DuckDB's match_bm25 has no
|
|
182
|
+
// field_weights argument (verified 1.5.5), so each field is scored separately and combined here.
|
|
184
183
|
var FIELD_WEIGHT = {
|
|
185
184
|
title: 10,
|
|
186
185
|
summary: 5,
|
|
187
186
|
text: 1
|
|
188
187
|
};
|
|
189
|
-
|
|
190
|
-
//
|
|
191
|
-
// otherwise silently treat as literal terms instead of honoring (PRINCIPLES: no-silent-modes).
|
|
192
|
-
// Checked against terms with quoted spans blanked -- those go through contains() and are
|
|
193
|
-
// supported.
|
|
188
|
+
// FTS5 operator syntax (sqlite.org/fts5.html sec. 3) that words/substrings below would otherwise silently
|
|
189
|
+
// treat as literal terms (PRINCIPLES: no-silent-modes). Checked with quoted spans blanked; those go through contains() and are supported.
|
|
194
190
|
var FTS5_OPERATORS = [
|
|
195
191
|
{
|
|
196
192
|
label: 'prefix query',
|
|
@@ -241,9 +237,8 @@ function unsupportedOperator(terms) {
|
|
|
241
237
|
}
|
|
242
238
|
return null;
|
|
243
239
|
}
|
|
244
|
-
// A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index
|
|
245
|
-
//
|
|
246
|
-
// and any author-quoted phrase, any script -- go through contains() instead of the fts index.
|
|
240
|
+
// A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index the whole run as one token
|
|
241
|
+
// (same gap FTS5 has without the `_seg` sidecar), so such runs -- and any author-quoted phrase, any script -- go through contains() instead.
|
|
247
242
|
function splitTerms(terms) {
|
|
248
243
|
var words = [];
|
|
249
244
|
var substrings = [];
|
|
@@ -251,7 +246,7 @@ function splitTerms(terms) {
|
|
|
251
246
|
var phrase = inner.trim();
|
|
252
247
|
if (phrase.length === 0) return ' ';
|
|
253
248
|
substrings.push(phrase);
|
|
254
|
-
if (!
|
|
249
|
+
if (!(0, _segmentts.hasUnspacedRun)(phrase)) {
|
|
255
250
|
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
256
251
|
try {
|
|
257
252
|
for(var _iterator = phrase.split(/\s+/)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
@@ -280,7 +275,7 @@ function splitTerms(terms) {
|
|
|
280
275
|
for(var _iterator = withoutPhrases.split(/\s+/)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
281
276
|
var tok = _step.value;
|
|
282
277
|
if (tok.length === 0) continue;
|
|
283
|
-
if (
|
|
278
|
+
if ((0, _segmentts.hasUnspacedRun)(tok)) substrings.push(tok);
|
|
284
279
|
else words.push(tok);
|
|
285
280
|
}
|
|
286
281
|
} catch (err) {
|
|
@@ -302,9 +297,8 @@ function splitTerms(terms) {
|
|
|
302
297
|
substrings: substrings
|
|
303
298
|
};
|
|
304
299
|
}
|
|
305
|
-
// No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this
|
|
306
|
-
//
|
|
307
|
-
// when content actually changed since (see FtsIndexState / duckdb/store.ts's markStale()).
|
|
300
|
+
// No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this pays the full rebuild -- but only
|
|
301
|
+
// once per store instance's first lexical query, and only when content actually changed since (FtsIndexState / store.ts's markStale()).
|
|
308
302
|
function ensureFtsFresh(conn, state) {
|
|
309
303
|
return _async_to_generator(function() {
|
|
310
304
|
return _ts_generator(this, function(_state) {
|
|
@@ -326,10 +320,8 @@ function ensureFtsFresh(conn, state) {
|
|
|
326
320
|
return _ts_generator(this, function(_state) {
|
|
327
321
|
switch(_state.label){
|
|
328
322
|
case 0:
|
|
329
|
-
// stopwords='none': sqlite's porter/unicode61 tokenizer never removes stopwords either
|
|
330
|
-
// (
|
|
331
|
-
// silently drop common query words (e.g. "and") from the index but not from match_bm25's
|
|
332
|
-
// conjunctive gate, making a bare multi-word query that contains one match nothing.
|
|
323
|
+
// stopwords='none': sqlite's porter/unicode61 tokenizer never removes stopwords either (verified 1.5.5); the fts extension's
|
|
324
|
+
// default 571-word list would otherwise drop common words (e.g. "and") from the index but not match_bm25's conjunctive gate, breaking multi-word matches.
|
|
333
325
|
return [
|
|
334
326
|
4,
|
|
335
327
|
conn.exec("PRAGMA create_fts_index('content', 'path', 'title', 'summary', 'text', stopwords='none', overwrite=1)")
|
|
@@ -354,13 +346,13 @@ function ensureFtsFresh(conn, state) {
|
|
|
354
346
|
});
|
|
355
347
|
})();
|
|
356
348
|
}
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
// emitted -- positional `?` binding requires that order to match the assembled SQL text.
|
|
349
|
+
// Field-weighted fragments (title 10 / summary 5 / text 1), with params emitted in the exact left-to-right order the assembled SQL needs for positional `?` binding.
|
|
350
|
+
// score/gate params stay in separate arrays until joined at the end, since the SQL puts every score part before every gate part; sharing one array would shift binds once a query has both words and substrings (PRINCIPLES: no-silent-modes).
|
|
360
351
|
function buildScoreAndGate(words, substrings) {
|
|
361
352
|
var scoreParts = [];
|
|
362
353
|
var gateParts = [];
|
|
363
|
-
var
|
|
354
|
+
var scoreParams = [];
|
|
355
|
+
var gateParams = [];
|
|
364
356
|
if (words.length > 0) {
|
|
365
357
|
var wordQuery = words.join(' ');
|
|
366
358
|
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
@@ -368,7 +360,7 @@ function buildScoreAndGate(words, substrings) {
|
|
|
368
360
|
for(var _iterator = FIELDS[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
369
361
|
var field = _step.value;
|
|
370
362
|
scoreParts.push("".concat(FIELD_WEIGHT[field], '.0 * COALESCE(fts_main_content.match_bm25(content."path", ?, fields := \'').concat(field, "', conjunctive := false), 0)"));
|
|
371
|
-
|
|
363
|
+
scoreParams.push(wordQuery);
|
|
372
364
|
}
|
|
373
365
|
} catch (err) {
|
|
374
366
|
_didIteratorError = true;
|
|
@@ -385,7 +377,7 @@ function buildScoreAndGate(words, substrings) {
|
|
|
385
377
|
}
|
|
386
378
|
}
|
|
387
379
|
gateParts.push('fts_main_content.match_bm25(content."path", ?, conjunctive := true) IS NOT NULL');
|
|
388
|
-
|
|
380
|
+
gateParams.push(wordQuery);
|
|
389
381
|
}
|
|
390
382
|
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
391
383
|
try {
|
|
@@ -397,7 +389,7 @@ function buildScoreAndGate(words, substrings) {
|
|
|
397
389
|
for(var _iterator2 = FIELDS[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
|
|
398
390
|
var field1 = _step2.value;
|
|
399
391
|
scoreParts.push("".concat(FIELD_WEIGHT[field1], ".0 * CAST(contains(lower(content.").concat(field1, "), ?) AS INTEGER)"));
|
|
400
|
-
|
|
392
|
+
scoreParams.push(needle);
|
|
401
393
|
}
|
|
402
394
|
} catch (err) {
|
|
403
395
|
_didIteratorError2 = true;
|
|
@@ -414,7 +406,7 @@ function buildScoreAndGate(words, substrings) {
|
|
|
414
406
|
}
|
|
415
407
|
}
|
|
416
408
|
gateParts.push("contains(lower(content.title) || ' ' || lower(content.summary) || ' ' || lower(content.text), ?)");
|
|
417
|
-
|
|
409
|
+
gateParams.push(needle);
|
|
418
410
|
}
|
|
419
411
|
} catch (err) {
|
|
420
412
|
_didIteratorError1 = true;
|
|
@@ -433,7 +425,7 @@ function buildScoreAndGate(words, substrings) {
|
|
|
433
425
|
return {
|
|
434
426
|
scoreSql: scoreParts.join(' + '),
|
|
435
427
|
gateSql: gateParts.join(' AND '),
|
|
436
|
-
params:
|
|
428
|
+
params: _to_consumable_array(scoreParams).concat(_to_consumable_array(gateParams))
|
|
437
429
|
};
|
|
438
430
|
}
|
|
439
431
|
function queryLexical(conn, terms, opts, state) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/lexical.ts"],"sourcesContent":["// D1: fts BM25 for ranking, contains() scans for exact substring / phrase verification /\n// unspaced scripts, JS excerpts for every doc (no snippet()).\nimport { SenseError } from '../../errors.ts';\nimport { UNSPACED_SCRIPTS } from '../../text/segment.ts';\nimport { withTransaction } from '../transaction.ts';\nimport type { Connection, LexicalHit, LexicalQueryOptions } from '../types.ts';\n\nconst FIELDS = ['title', 'summary', 'text'] as const;\ntype Field = (typeof FIELDS)[number];\n// Mirrors sqlite's bm25(content, 10.0, 5.0, 1.0, ...) column-weight intent; DuckDB's\n// match_bm25 has no field_weights argument (verified 1.5.5), so each field is scored\n// separately and combined here.\nconst FIELD_WEIGHT: Record<Field, number> = { title: 10, summary: 5, text: 1 };\n\nconst UNSPACED_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');\n\n// FTS5 operator syntax (sqlite.org/fts5.html sec. 3) that words/substrings below would\n// otherwise silently treat as literal terms instead of honoring (PRINCIPLES: no-silent-modes).\n// Checked against terms with quoted spans blanked -- those go through contains() and are\n// supported.\nconst FTS5_OPERATORS: Array<{ label: string; re: RegExp }> = [\n { label: 'prefix query', re: /[\\p{L}\\p{N}_]+\\*(?=\\s|$)/u },\n { label: 'boolean operator', re: /(?:^|\\s)(?:AND|OR|NOT)(?=\\s|$)/ },\n { label: 'NEAR operator', re: /(?:^|\\s)NEAR\\b/ },\n { label: 'initial-token operator', re: /(?:^|\\s)\\^\\S+/ },\n { label: 'column filter', re: /(?:^|\\s)[\\p{L}_]\\w*\\s*:/u },\n];\n\nfunction unsupportedOperator(terms: string): { label: string; token: string } | null {\n const withoutPhrases = terms.replace(/\"[^\"]*\"/g, ' ');\n for (const { label, re } of FTS5_OPERATORS) {\n const m = withoutPhrases.match(re);\n if (m) return { label, token: m[0].trim() };\n }\n return null;\n}\n\ninterface FtsIndexState {\n stale: boolean;\n}\n\n// A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index\n// the whole run as one token (same gap FTS5 has without the `_seg` sidecar), so such runs --\n// and any author-quoted phrase, any script -- go through contains() instead of the fts index.\nfunction splitTerms(terms: string): { words: string[]; substrings: string[] } {\n const words: string[] = [];\n const substrings: string[] = [];\n const withoutPhrases = terms.replace(/\"([^\"]*)\"/g, (_m, inner: string) => {\n const phrase = inner.trim();\n if (phrase.length === 0) return ' ';\n substrings.push(phrase);\n if (!UNSPACED_RUN.test(phrase)) for (const w of phrase.split(/\\s+/)) if (w.length > 0) words.push(w);\n return ' ';\n });\n for (const tok of withoutPhrases.split(/\\s+/)) {\n if (tok.length === 0) continue;\n if (UNSPACED_RUN.test(tok)) substrings.push(tok);\n else words.push(tok);\n }\n return { words, substrings };\n}\n\n// No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this\n// pays the full rebuild -- but only once per store instance's first lexical query, and only\n// when content actually changed since (see FtsIndexState / duckdb/store.ts's markStale()).\nasync function ensureFtsFresh(conn: Connection, state: FtsIndexState): Promise<void> {\n if (!state.stale) return;\n await conn.exec('INSTALL fts; LOAD fts;');\n await withTransaction(conn, async () => {\n // stopwords='none': sqlite's porter/unicode61 tokenizer never removes stopwords either\n // (verified 1.5.5), and the fts extension's default 571-word English list would otherwise\n // silently drop common query words (e.g. \"and\") from the index but not from match_bm25's\n // conjunctive gate, making a bare multi-word query that contains one match nothing.\n await conn.exec(`PRAGMA create_fts_index('content', 'path', 'title', 'summary', 'text', stopwords='none', overwrite=1)`);\n });\n state.stale = false;\n}\n\n// Per-word-term and per-substring-term SQL fragments, field-weighted the same way for both\n// (title 10 / summary 5 / text 1), plus the params in the exact left-to-right order they are\n// emitted -- positional `?` binding requires that order to match the assembled SQL text.\nfunction buildScoreAndGate(words: string[], substrings: string[]): { scoreSql: string; gateSql: string; params: unknown[] } {\n const scoreParts: string[] = [];\n const gateParts: string[] = [];\n const params: unknown[] = [];\n\n if (words.length > 0) {\n const wordQuery = words.join(' ');\n for (const field of FIELDS) {\n scoreParts.push(`${FIELD_WEIGHT[field]}.0 * COALESCE(fts_main_content.match_bm25(content.\"path\", ?, fields := '${field}', conjunctive := false), 0)`);\n params.push(wordQuery);\n }\n gateParts.push(`fts_main_content.match_bm25(content.\"path\", ?, conjunctive := true) IS NOT NULL`);\n params.push(wordQuery);\n }\n\n for (const raw of substrings) {\n const needle = raw.toLowerCase();\n for (const field of FIELDS) {\n scoreParts.push(`${FIELD_WEIGHT[field]}.0 * CAST(contains(lower(content.${field}), ?) AS INTEGER)`);\n params.push(needle);\n }\n gateParts.push(`contains(lower(content.title) || ' ' || lower(content.summary) || ' ' || lower(content.text), ?)`);\n params.push(needle);\n }\n\n return { scoreSql: scoreParts.join(' + '), gateSql: gateParts.join(' AND '), params };\n}\n\n// Ranked word-match query, scoped by the caller-built SQL fragments (same fragments sqlite's\n// queryLexical takes). `hit` is always NULL: no snippet() equivalent exists, so every row goes\n// through the caller's JS excerpt fallback (commands/search.ts) rather than a second excerpt path.\nexport async function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, state: FtsIndexState): Promise<LexicalHit[]> {\n const unsupported = unsupportedOperator(terms);\n if (unsupported !== null) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"duckdb\" does not implement FTS5's ${unsupported.label} (\"${unsupported.token}\") in this build; rephrase \"${terms.trim()}\" without it, or set \"store\" to \"sqlite\" in this tree's config to search it as written`);\n }\n const { whereJoin, whereCond, scopeCond, limit } = opts;\n const { words, substrings } = splitTerms(terms);\n if (words.length === 0 && substrings.length === 0) return [];\n\n if (words.length > 0) await ensureFtsFresh(conn, state);\n\n const { scoreSql, gateSql, params } = buildScoreAndGate(words, substrings);\n const sql = `SELECT path, NULL AS hit FROM (\n SELECT content.\"path\" AS path, (${scoreSql}) AS score\n FROM content\n ${whereJoin}\n WHERE ${gateSql}\n ${whereCond} ${scopeCond}\n ) sq ORDER BY score DESC, path LIMIT ?`;\n const stmt = await conn.prepare(sql);\n return (await stmt.all(...params, limit)) as unknown as LexicalHit[];\n}\n\n// One instance per store: `stale` starts true (a fresh connection cannot know whether an\n// on-disk fts schema still matches `content`), and store.ts's reconcile wrapper flips it back\n// to true whenever content changes, so the next query rebuilds before ranking against it.\nexport function createLexicalIndex(conn: Connection): { query: (terms: string, opts: LexicalQueryOptions) => Promise<LexicalHit[]>; markStale: () => void } {\n const state: FtsIndexState = { stale: true };\n return {\n query: (terms, opts) => queryLexical(conn, terms, opts, state),\n markStale: () => {\n state.stale = true;\n },\n };\n}\n"],"names":["createLexicalIndex","queryLexical","FIELDS","FIELD_WEIGHT","title","summary","text","UNSPACED_RUN","RegExp","UNSPACED_SCRIPTS","FTS5_OPERATORS","label","re","unsupportedOperator","terms","withoutPhrases","replace","m","match","token","trim","splitTerms","words","substrings","_m","inner","phrase","length","push","test","split","w","tok","ensureFtsFresh","conn","state","stale","exec","withTransaction","buildScoreAndGate","scoreParts","gateParts","params","wordQuery","join","field","raw","needle","toLowerCase","scoreSql","gateSql","opts","stmt","unsupported","whereJoin","whereCond","scopeCond","limit","sql","SenseError","prepare","all","query","markStale"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/lexical.ts"],"sourcesContent":["// D1: fts BM25 for ranking, contains() scans for exact substring / phrase verification /\n// unspaced scripts, JS excerpts for every doc (no snippet()).\nimport { SenseError } from '../../errors.ts';\nimport { hasUnspacedRun } from '../../text/segment.ts';\nimport { withTransaction } from '../transaction.ts';\nimport type { Connection, LexicalHit, LexicalQueryOptions } from '../types.ts';\n\nconst FIELDS = ['title', 'summary', 'text'] as const;\ntype Field = (typeof FIELDS)[number];\n// Mirrors sqlite's bm25(content, 10.0, 5.0, 1.0, ...) column-weight intent; DuckDB's match_bm25 has no\n// field_weights argument (verified 1.5.5), so each field is scored separately and combined here.\nconst FIELD_WEIGHT: Record<Field, number> = { title: 10, summary: 5, text: 1 };\n\n// FTS5 operator syntax (sqlite.org/fts5.html sec. 3) that words/substrings below would otherwise silently\n// treat as literal terms (PRINCIPLES: no-silent-modes). Checked with quoted spans blanked; those go through contains() and are supported.\nconst FTS5_OPERATORS: Array<{ label: string; re: RegExp }> = [\n { label: 'prefix query', re: /[\\p{L}\\p{N}_]+\\*(?=\\s|$)/u },\n { label: 'boolean operator', re: /(?:^|\\s)(?:AND|OR|NOT)(?=\\s|$)/ },\n { label: 'NEAR operator', re: /(?:^|\\s)NEAR\\b/ },\n { label: 'initial-token operator', re: /(?:^|\\s)\\^\\S+/ },\n { label: 'column filter', re: /(?:^|\\s)[\\p{L}_]\\w*\\s*:/u },\n];\n\nfunction unsupportedOperator(terms: string): { label: string; token: string } | null {\n const withoutPhrases = terms.replace(/\"[^\"]*\"/g, ' ');\n for (const { label, re } of FTS5_OPERATORS) {\n const m = withoutPhrases.match(re);\n if (m) return { label, token: m[0].trim() };\n }\n return null;\n}\n\ninterface FtsIndexState {\n stale: boolean;\n}\n\n// A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index the whole run as one token\n// (same gap FTS5 has without the `_seg` sidecar), so such runs -- and any author-quoted phrase, any script -- go through contains() instead.\nfunction splitTerms(terms: string): { words: string[]; substrings: string[] } {\n const words: string[] = [];\n const substrings: string[] = [];\n const withoutPhrases = terms.replace(/\"([^\"]*)\"/g, (_m, inner: string) => {\n const phrase = inner.trim();\n if (phrase.length === 0) return ' ';\n substrings.push(phrase);\n if (!hasUnspacedRun(phrase)) for (const w of phrase.split(/\\s+/)) if (w.length > 0) words.push(w);\n return ' ';\n });\n for (const tok of withoutPhrases.split(/\\s+/)) {\n if (tok.length === 0) continue;\n if (hasUnspacedRun(tok)) substrings.push(tok);\n else words.push(tok);\n }\n return { words, substrings };\n}\n\n// No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this pays the full rebuild -- but only\n// once per store instance's first lexical query, and only when content actually changed since (FtsIndexState / store.ts's markStale()).\nasync function ensureFtsFresh(conn: Connection, state: FtsIndexState): Promise<void> {\n if (!state.stale) return;\n await conn.exec('INSTALL fts; LOAD fts;');\n await withTransaction(conn, async () => {\n // stopwords='none': sqlite's porter/unicode61 tokenizer never removes stopwords either (verified 1.5.5); the fts extension's\n // default 571-word list would otherwise drop common words (e.g. \"and\") from the index but not match_bm25's conjunctive gate, breaking multi-word matches.\n await conn.exec(`PRAGMA create_fts_index('content', 'path', 'title', 'summary', 'text', stopwords='none', overwrite=1)`);\n });\n state.stale = false;\n}\n\n// Field-weighted fragments (title 10 / summary 5 / text 1), with params emitted in the exact left-to-right order the assembled SQL needs for positional `?` binding.\n// score/gate params stay in separate arrays until joined at the end, since the SQL puts every score part before every gate part; sharing one array would shift binds once a query has both words and substrings (PRINCIPLES: no-silent-modes).\nfunction buildScoreAndGate(words: string[], substrings: string[]): { scoreSql: string; gateSql: string; params: unknown[] } {\n const scoreParts: string[] = [];\n const gateParts: string[] = [];\n const scoreParams: unknown[] = [];\n const gateParams: unknown[] = [];\n\n if (words.length > 0) {\n const wordQuery = words.join(' ');\n for (const field of FIELDS) {\n scoreParts.push(`${FIELD_WEIGHT[field]}.0 * COALESCE(fts_main_content.match_bm25(content.\"path\", ?, fields := '${field}', conjunctive := false), 0)`);\n scoreParams.push(wordQuery);\n }\n gateParts.push(`fts_main_content.match_bm25(content.\"path\", ?, conjunctive := true) IS NOT NULL`);\n gateParams.push(wordQuery);\n }\n\n for (const raw of substrings) {\n const needle = raw.toLowerCase();\n for (const field of FIELDS) {\n scoreParts.push(`${FIELD_WEIGHT[field]}.0 * CAST(contains(lower(content.${field}), ?) AS INTEGER)`);\n scoreParams.push(needle);\n }\n gateParts.push(`contains(lower(content.title) || ' ' || lower(content.summary) || ' ' || lower(content.text), ?)`);\n gateParams.push(needle);\n }\n\n return { scoreSql: scoreParts.join(' + '), gateSql: gateParts.join(' AND '), params: [...scoreParams, ...gateParams] };\n}\n\n// Ranked word-match query, scoped by the caller-built SQL fragments (same fragments sqlite's queryLexical takes).\n// `hit` is always NULL: no snippet() equivalent exists, so every row goes through the caller's JS excerpt fallback (commands/search.ts).\nexport async function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, state: FtsIndexState): Promise<LexicalHit[]> {\n const unsupported = unsupportedOperator(terms);\n if (unsupported !== null) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"duckdb\" does not implement FTS5's ${unsupported.label} (\"${unsupported.token}\") in this build; rephrase \"${terms.trim()}\" without it, or set \"store\" to \"sqlite\" in this tree's config to search it as written`);\n }\n const { whereJoin, whereCond, scopeCond, limit } = opts;\n const { words, substrings } = splitTerms(terms);\n if (words.length === 0 && substrings.length === 0) return [];\n\n if (words.length > 0) await ensureFtsFresh(conn, state);\n\n const { scoreSql, gateSql, params } = buildScoreAndGate(words, substrings);\n const sql = `SELECT path, NULL AS hit FROM (\n SELECT content.\"path\" AS path, (${scoreSql}) AS score\n FROM content\n ${whereJoin}\n WHERE ${gateSql}\n ${whereCond} ${scopeCond}\n ) sq ORDER BY score DESC, path LIMIT ?`;\n const stmt = await conn.prepare(sql);\n return (await stmt.all(...params, limit)) as unknown as LexicalHit[];\n}\n\n// One instance per store: `stale` starts true (a fresh connection can't know whether an on-disk fts schema matches `content`),\n// and store.ts's reconcile wrapper flips it back to true whenever content changes, so the next query rebuilds first.\nexport function createLexicalIndex(conn: Connection): { query: (terms: string, opts: LexicalQueryOptions) => Promise<LexicalHit[]>; markStale: () => void } {\n const state: FtsIndexState = { stale: true };\n return {\n query: (terms, opts) => queryLexical(conn, terms, opts, state),\n markStale: () => {\n state.stale = true;\n },\n };\n}\n"],"names":["createLexicalIndex","queryLexical","FIELDS","FIELD_WEIGHT","title","summary","text","FTS5_OPERATORS","label","re","unsupportedOperator","terms","withoutPhrases","replace","m","match","token","trim","splitTerms","words","substrings","_m","inner","phrase","length","push","hasUnspacedRun","split","w","tok","ensureFtsFresh","conn","state","stale","exec","withTransaction","buildScoreAndGate","scoreParts","gateParts","scoreParams","gateParams","wordQuery","join","field","raw","needle","toLowerCase","scoreSql","gateSql","params","opts","stmt","unsupported","whereJoin","whereCond","scopeCond","limit","sql","SenseError","prepare","all","query","markStale"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/native.ts"],"sourcesContent":["import { loadOrInstall, type NativeDescriptor, packageNodeModules } from '../native.ts';\n\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\n\nconst DUCKDB: NativeDescriptor = { store: 'duckdb', pkg: DUCKDB_PACKAGE, sizeHint: '~110MB' };\n\nlet duckdbApiPromise: Promise<typeof import('@duckdb/node-api')> | undefined;\n\n// The one accessor every consumer of @duckdb/node-api
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/native.ts"],"sourcesContent":["import { loadOrInstall, type NativeDescriptor, packageNodeModules } from '../native.ts';\n\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\n\nconst DUCKDB: NativeDescriptor = { store: 'duckdb', pkg: DUCKDB_PACKAGE, sizeHint: '~110MB' };\n\nlet duckdbApiPromise: Promise<typeof import('@duckdb/node-api')> | undefined;\n\n// The one accessor every consumer of @duckdb/node-api must go through: after a fresh install in this process, Node keeps returning\n// the pre-install \"not found\" miss to any new bare-specifier lookup of the same path, so caching the resolved module here means only the first caller ever resolves it.\nexport function duckdbApi(): Promise<typeof import('@duckdb/node-api')> {\n if (!duckdbApiPromise) duckdbApiPromise = loadOrInstall<typeof import('@duckdb/node-api')>(DUCKDB, packageNodeModules());\n return duckdbApiPromise;\n}\n"],"names":["DUCKDB_PACKAGE","duckdbApi","DUCKDB","store","pkg","sizeHint","duckdbApiPromise","loadOrInstall","packageNodeModules"],"mappings":";;;;;;;;;;;QAEaA;eAAAA;;QAQGC;eAAAA;;;wBAVyD;AAElE,IAAMD,iBAAiB;AAE9B,IAAME,SAA2B;IAAEC,OAAO;IAAUC,KAAKJ;IAAgBK,UAAU;AAAS;AAE5F,IAAIC;AAIG,SAASL;IACd,IAAI,CAACK,kBAAkBA,mBAAmBC,IAAAA,uBAAa,EAAoCL,QAAQM,IAAAA,4BAAkB;IACrH,OAAOF;AACT"}
|
|
@@ -22,11 +22,12 @@ _export(exports, {
|
|
|
22
22
|
var _nodefs = require("node:fs");
|
|
23
23
|
var _nodepath = require("node:path");
|
|
24
24
|
var _indexts = require("../../config/index.js");
|
|
25
|
+
var _handoffts = require("../../embed/handoff.js");
|
|
25
26
|
var _typests = require("../../embed/types.js");
|
|
26
27
|
var _errorsts = require("../../errors.js");
|
|
27
28
|
var _indexts1 = require("../../features/index.js");
|
|
29
|
+
var _cachets = require("../cache.js");
|
|
28
30
|
var _sharedts = require("../shared.js");
|
|
29
|
-
var _opents = require("../sqlite/open.js");
|
|
30
31
|
var _connectionts = require("./connection.js");
|
|
31
32
|
var _nativets = require("./native.js");
|
|
32
33
|
var _reconcilets = require("./reconcile.js");
|
|
@@ -165,11 +166,8 @@ function _ts_generator(thisArg, body) {
|
|
|
165
166
|
}
|
|
166
167
|
var DB_FILENAME = 'cache.duckdb';
|
|
167
168
|
var SCHEMA_VERSION = '2';
|
|
168
|
-
// `content` is a plain table (not FTS-virtual):
|
|
169
|
-
//
|
|
170
|
-
// contains() substring/phrase/unspaced-script verification either way. No tokenizer resolution
|
|
171
|
-
// -- this store always uses the fts extension's default (porter) stemmer, unlike sqlite's
|
|
172
|
-
// configurable `content.tokenize` (not read here; D1 does not extend that knob to DuckDB).
|
|
169
|
+
// `content` is a plain table (not FTS-virtual): the fts index is built over it lazily, only when a lexical query runs (lexical.ts), and read directly for contains() verification either way.
|
|
170
|
+
// No tokenizer resolution: this store always uses the fts extension's default (porter) stemmer; sqlite's configurable `content.tokenize` is not read here.
|
|
173
171
|
function ensureSchema(conn, cfg) {
|
|
174
172
|
return _async_to_generator(function() {
|
|
175
173
|
var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, feature, err;
|
|
@@ -360,10 +358,8 @@ function connect(cfg) {
|
|
|
360
358
|
})
|
|
361
359
|
];
|
|
362
360
|
case 5:
|
|
363
|
-
// On-disk files default to an older storage format for cross-version compatibility, which
|
|
364
|
-
//
|
|
365
|
-
// v1.5.0"); this store's dynamic frontmatter columns need VARIANT (see reconcile.ts), so
|
|
366
|
-
// the format floor is pinned explicitly. :memory: databases are unaffected either way.
|
|
361
|
+
// On-disk files default to an older storage format for cross-version compatibility, which rejects VARIANT columns ("VARIANT
|
|
362
|
+
// columns are not supported in storage versions prior to v1.5.0"); this store's dynamic frontmatter columns need VARIANT (reconcile.ts), so the floor is pinned explicitly.
|
|
367
363
|
instance = _state.sent();
|
|
368
364
|
return [
|
|
369
365
|
4,
|
|
@@ -427,7 +423,7 @@ function connect(cfg) {
|
|
|
427
423
|
closed = true;
|
|
428
424
|
duckdb.disconnectSync();
|
|
429
425
|
instance.closeSync();
|
|
430
|
-
(0,
|
|
426
|
+
(0, _cachets.clearCache)(cfg);
|
|
431
427
|
return [
|
|
432
428
|
2,
|
|
433
429
|
connect(cfg)
|
|
@@ -474,7 +470,7 @@ function connect(cfg) {
|
|
|
474
470
|
}
|
|
475
471
|
function openDuckdb(cfg) {
|
|
476
472
|
return _async_to_generator(function() {
|
|
477
|
-
var _ref, instance, duckdb, conn, resolvedCfg, dbPath, parsed, warnings;
|
|
473
|
+
var _ref, instance, duckdb, conn, resolvedCfg, dbPath, parsed, warnings, store;
|
|
478
474
|
return _ts_generator(this, function(_state) {
|
|
479
475
|
switch(_state.label){
|
|
480
476
|
case 0:
|
|
@@ -484,10 +480,13 @@ function openDuckdb(cfg) {
|
|
|
484
480
|
];
|
|
485
481
|
case 1:
|
|
486
482
|
_ref = _state.sent(), instance = _ref.instance, duckdb = _ref.duckdb, conn = _ref.conn, resolvedCfg = _ref.cfg, dbPath = _ref.dbPath, parsed = _ref.parsed, warnings = _ref.warnings;
|
|
483
|
+
store = (0, _storets.createStore)(instance, duckdb, conn, resolvedCfg, resolvedCfg.baseDir);
|
|
484
|
+
// reconcile ran before this object existed, so its chunk text is keyed by the connection.
|
|
485
|
+
(0, _handoffts.rekeyChunkText)(conn, store);
|
|
487
486
|
return [
|
|
488
487
|
2,
|
|
489
488
|
{
|
|
490
|
-
store:
|
|
489
|
+
store: store,
|
|
491
490
|
cfg: resolvedCfg,
|
|
492
491
|
dbPath: dbPath,
|
|
493
492
|
parsed: parsed,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/open.ts"],"sourcesContent":["import { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { DuckDBConnection, DuckDBInstance } from '@duckdb/node-api';\nimport type { Config, ResolvedConfig } from '../../config/index.ts';\nimport { featureSignature, STATE_DIR } from '../../config/index.ts';\nimport { STORE_DIMS } from '../../embed/types.ts';\nimport { SenseError } from '../../errors.ts';\nimport { activeFeatures, FEATURES } from '../../features/index.ts';\nimport { getMeta, setMeta } from '../shared.ts';\nimport { clearCache } from '../sqlite/open.ts';\nimport type { Connection, Store } from '../types.ts';\nimport { createConnection } from './connection.ts';\nimport { DUCKDB_PACKAGE, duckdbApi } from './native.ts';\nimport { reconcile } from './reconcile.ts';\nimport { registerFunctions } from './sql-functions.ts';\nimport { createStore } from './store.ts';\n\nexport const DB_FILENAME = 'cache.duckdb';\n// Independent of sqlite's SCHEMA_VERSION -- the two stores' cache shapes evolve separately\n// (VARIANT frontmatter columns instead of untyped ones), and the store name already joins the\n// feature signature (see featureSignature), so a config's `store` key switching cleanly\n// rebuilds rather than reusing the other engine's stale cache shape.\nexport const SCHEMA_VERSION = '2';\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// `content` is a plain table (not FTS-virtual): D1 builds DuckDB's fts index over it lazily,\n// only when a lexical query actually runs (see duckdb/lexical.ts), and reads it directly for\n// contains() substring/phrase/unspaced-script verification either way. No tokenizer resolution\n// -- this store always uses the fts extension's default (porter) stemmer, unlike sqlite's\n// configurable `content.tokenize` (not read here; D1 does not extend that knob to DuckDB).\nasync function ensureSchema(conn: Connection, cfg: Config): Promise<void> {\n await conn.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" DOUBLE, \"_ctime\" DOUBLE, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n await conn.exec(`CREATE TABLE IF NOT EXISTS content (\"path\" TEXT PRIMARY KEY, title TEXT, summary TEXT, text TEXT)`);\n await conn.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n await conn.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) {\n // Native FLOAT[STORE_DIMS] instead of the embed feature's engine-neutral BLOB+scale DDL\n // (see duckdb/vectors.ts). `scale` is kept, unused, so the feature's shared reconcile-time\n // INSERT/DELETE (src/features/embed.ts) still names a column that exists on both stores.\n if (feature.name === 'embed') {\n await conn.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector FLOAT[${STORE_DIMS}], PRIMARY KEY (\"path\", chunk))`);\n continue;\n }\n await feature.schema(conn);\n }\n if ((await getMeta(conn, 'schema_version')) === null) await setMeta(conn, 'schema_version', SCHEMA_VERSION);\n if ((await getMeta(conn, 'features')) === null) await setMeta(conn, 'features', featureSignature(cfg, FEATURES));\n}\n\nasync function connect(cfg: ResolvedConfig): Promise<{ instance: DuckDBInstance; duckdb: DuckDBConnection; conn: Connection; cfg: ResolvedConfig; dbPath: string; parsed: number; warnings: string[] }> {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n // Dynamic, not a top-level import: sqlite trees must never even attempt to resolve this\n // optional peer dependency, so nothing in this store's module graph imports it as a value\n // until a duckdb tree is actually opened (types-only imports elsewhere are erased and cost\n // nothing either way). Installed on first use if missing, and shared with sql-functions.ts\n // and vectors.ts (see native.ts's duckdbApi).\n let DuckDBInstance: typeof import('@duckdb/node-api').DuckDBInstance;\n try {\n ({ DuckDBInstance } = await duckdbApi());\n } catch (err) {\n if (err instanceof SenseError) throw err;\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" needs the ${DUCKDB_PACKAGE} package (${(err as Error).message})`);\n }\n\n let duckdb: DuckDBConnection;\n let instance: DuckDBInstance | undefined;\n try {\n // On-disk files default to an older storage format for cross-version compatibility, which\n // rejects VARIANT columns (\"VARIANT columns are not supported in storage versions prior to\n // v1.5.0\"); this store's dynamic frontmatter columns need VARIANT (see reconcile.ts), so\n // the format floor is pinned explicitly. :memory: databases are unaffected either way.\n instance = await DuckDBInstance.create(dbPath, { storage_compatibility_version: 'v1.5.0' });\n duckdb = await instance.connect();\n } catch (err) {\n // create() may have succeeded before connect() failed: close it, or its WAL stays open.\n instance?.closeSync();\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" failed to open ${dbPath}: ${(err as Error).message}`);\n }\n // A throw below (schema, reconcile's COLUMN_LIMIT) would leak the open instance, whose WAL\n // then locks the .duckdb file undeletable on Windows. closed marks the rebuild branch.\n let closed = false;\n try {\n await registerFunctions(duckdb);\n const conn = createConnection(duckdb);\n\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 features = await getMeta(conn, 'features');\n const wantFeatures = featureSignature(cfg, FEATURES);\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Reconcile only reparses changed files, so a stale cache can't be patched incrementally --\n // rebuild instead, same as sqlite's open() (see its comment for why this is announced).\n const reason = version !== null && version !== SCHEMA_VERSION ? 'cache format changed (new sensemaking version)' : 'config change (features, tokenizer, or presets)';\n console.error(`sense: ${reason}; rebuilding the index`);\n // Close before clearCache deletes the files underneath the still-open instance.\n closed = true;\n duckdb.disconnectSync();\n instance.closeSync();\n clearCache(cfg);\n return connect(cfg);\n }\n\n await ensureSchema(conn, cfg);\n\n const { parsed, warnings } = await reconcile(conn, cfg, cfg.baseDir);\n return { instance, duckdb, conn, cfg, dbPath, parsed, warnings };\n } catch (err) {\n if (!closed) {\n duckdb.disconnectSync();\n instance?.closeSync();\n }\n throw err;\n }\n}\n\nexport async function openDuckdb(cfg: ResolvedConfig): Promise<OpenResult> {\n const { instance, duckdb, conn, cfg: resolvedCfg, dbPath, parsed, warnings } = await connect(cfg);\n return { store: createStore(instance, duckdb, conn, resolvedCfg, resolvedCfg.baseDir), cfg: resolvedCfg, dbPath, parsed, warnings };\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","openDuckdb","ensureSchema","conn","cfg","feature","exec","activeFeatures","name","STORE_DIMS","schema","getMeta","setMeta","featureSignature","FEATURES","connect","stateDir","dbPath","DuckDBInstance","err","duckdb","instance","closed","version","features","wantFeatures","reason","parsed","warnings","join","baseDir","STATE_DIR","mkdirSync","recursive","duckdbApi","SenseError","DUCKDB_PACKAGE","message","create","storage_compatibility_version","closeSync","registerFunctions","createConnection","console","error","disconnectSync","clearCache","reconcile","resolvedCfg","store","createStore"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/open.ts"],"sourcesContent":["import { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { DuckDBConnection, DuckDBInstance } from '@duckdb/node-api';\nimport type { Config, ResolvedConfig } from '../../config/index.ts';\nimport { featureSignature, STATE_DIR } from '../../config/index.ts';\nimport { rekeyChunkText } from '../../embed/handoff.ts';\nimport { STORE_DIMS } from '../../embed/types.ts';\nimport { SenseError } from '../../errors.ts';\nimport { activeFeatures, FEATURES } from '../../features/index.ts';\nimport { clearCache } from '../cache.ts';\nimport { getMeta, setMeta } from '../shared.ts';\nimport type { Connection, Store } from '../types.ts';\nimport { createConnection } from './connection.ts';\nimport { DUCKDB_PACKAGE, duckdbApi } from './native.ts';\nimport { reconcile } from './reconcile.ts';\nimport { registerFunctions } from './sql-functions.ts';\nimport { createStore } from './store.ts';\n\nexport const DB_FILENAME = 'cache.duckdb';\n// Independent of sqlite's SCHEMA_VERSION -- the two stores' cache shapes evolve separately (VARIANT frontmatter columns vs untyped).\n// The store name already joins the feature signature, so switching a config's `store` key rebuilds rather than reusing the other engine's cache.\nexport const SCHEMA_VERSION = '2';\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// `content` is a plain table (not FTS-virtual): the fts index is built over it lazily, only when a lexical query runs (lexical.ts), and read directly for contains() verification either way.\n// No tokenizer resolution: this store always uses the fts extension's default (porter) stemmer; sqlite's configurable `content.tokenize` is not read here.\nasync function ensureSchema(conn: Connection, cfg: Config): Promise<void> {\n await conn.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" DOUBLE, \"_ctime\" DOUBLE, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n await conn.exec(`CREATE TABLE IF NOT EXISTS content (\"path\" TEXT PRIMARY KEY, title TEXT, summary TEXT, text TEXT)`);\n await conn.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n await conn.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) {\n // Native FLOAT[STORE_DIMS] instead of the embed feature's engine-neutral BLOB+scale DDL (vectors.ts). `scale` is kept, unused,\n // so the feature's shared reconcile-time INSERT/DELETE (features/embed.ts) names a column that exists on both stores.\n if (feature.name === 'embed') {\n await conn.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector FLOAT[${STORE_DIMS}], PRIMARY KEY (\"path\", chunk))`);\n continue;\n }\n await feature.schema(conn);\n }\n if ((await getMeta(conn, 'schema_version')) === null) await setMeta(conn, 'schema_version', SCHEMA_VERSION);\n if ((await getMeta(conn, 'features')) === null) await setMeta(conn, 'features', featureSignature(cfg, FEATURES));\n}\n\nasync function connect(cfg: ResolvedConfig): Promise<{ instance: DuckDBInstance; duckdb: DuckDBConnection; conn: Connection; cfg: ResolvedConfig; dbPath: string; parsed: number; warnings: string[] }> {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n // Dynamic, not a top-level import: sqlite trees must never attempt to resolve this optional peer dependency, so nothing imports\n // it as a value until a duckdb tree opens (types-only imports are erased). Installed on first use if missing, shared with sql-functions.ts and vectors.ts via native.ts's duckdbApi.\n let DuckDBInstance: typeof import('@duckdb/node-api').DuckDBInstance;\n try {\n ({ DuckDBInstance } = await duckdbApi());\n } catch (err) {\n if (err instanceof SenseError) throw err;\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" needs the ${DUCKDB_PACKAGE} package (${(err as Error).message})`);\n }\n\n let duckdb: DuckDBConnection;\n let instance: DuckDBInstance | undefined;\n try {\n // On-disk files default to an older storage format for cross-version compatibility, which rejects VARIANT columns (\"VARIANT\n // columns are not supported in storage versions prior to v1.5.0\"); this store's dynamic frontmatter columns need VARIANT (reconcile.ts), so the floor is pinned explicitly.\n instance = await DuckDBInstance.create(dbPath, { storage_compatibility_version: 'v1.5.0' });\n duckdb = await instance.connect();\n } catch (err) {\n // create() may have succeeded before connect() failed: close it, or its WAL stays open.\n instance?.closeSync();\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" failed to open ${dbPath}: ${(err as Error).message}`);\n }\n // A throw below (schema, reconcile's COLUMN_LIMIT) would leak the open instance, whose WAL\n // then locks the .duckdb file undeletable on Windows. closed marks the rebuild branch.\n let closed = false;\n try {\n await registerFunctions(duckdb);\n const conn = createConnection(duckdb);\n\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 features = await getMeta(conn, 'features');\n const wantFeatures = featureSignature(cfg, FEATURES);\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Reconcile only reparses changed files, so a stale cache can't be patched incrementally --\n // rebuild instead, same as sqlite's open() (see its comment for why this is announced).\n const reason = version !== null && version !== SCHEMA_VERSION ? 'cache format changed (new sensemaking version)' : 'config change (features, tokenizer, or presets)';\n console.error(`sense: ${reason}; rebuilding the index`);\n // Close before clearCache deletes the files underneath the still-open instance.\n closed = true;\n duckdb.disconnectSync();\n instance.closeSync();\n clearCache(cfg);\n return connect(cfg);\n }\n\n await ensureSchema(conn, cfg);\n\n const { parsed, warnings } = await reconcile(conn, cfg, cfg.baseDir);\n return { instance, duckdb, conn, cfg, dbPath, parsed, warnings };\n } catch (err) {\n if (!closed) {\n duckdb.disconnectSync();\n instance?.closeSync();\n }\n throw err;\n }\n}\n\nexport async function openDuckdb(cfg: ResolvedConfig): Promise<OpenResult> {\n const { instance, duckdb, conn, cfg: resolvedCfg, dbPath, parsed, warnings } = await connect(cfg);\n const store = createStore(instance, duckdb, conn, resolvedCfg, resolvedCfg.baseDir);\n // reconcile ran before this object existed, so its chunk text is keyed by the connection.\n rekeyChunkText(conn, store);\n return { store: store, cfg: resolvedCfg, dbPath, parsed, warnings };\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","openDuckdb","ensureSchema","conn","cfg","feature","exec","activeFeatures","name","STORE_DIMS","schema","getMeta","setMeta","featureSignature","FEATURES","connect","stateDir","dbPath","DuckDBInstance","err","duckdb","instance","closed","version","features","wantFeatures","reason","parsed","warnings","join","baseDir","STATE_DIR","mkdirSync","recursive","duckdbApi","SenseError","DUCKDB_PACKAGE","message","create","storage_compatibility_version","closeSync","registerFunctions","createConnection","console","error","disconnectSync","clearCache","reconcile","resolvedCfg","store","createStore","rekeyChunkText"],"mappings":"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"}
|