latticesql 1.2.6 → 1.3.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.
- package/README.md +139 -0
- package/dist/cli.js +281 -10
- package/dist/index.cjs +279 -4
- package/dist/index.d.cts +205 -1
- package/dist/index.d.ts +205 -1
- package/dist/index.js +283 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { join as join2 } from "path";
|
|
|
6
6
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
7
7
|
|
|
8
8
|
// src/render/writer.ts
|
|
9
|
-
import { writeFileSync, mkdirSync, renameSync, existsSync, readFileSync } from "fs";
|
|
9
|
+
import { writeFileSync, mkdirSync, renameSync, copyFileSync, unlinkSync, existsSync, readFileSync } from "fs";
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import { dirname, join } from "path";
|
|
12
12
|
import { tmpdir } from "os";
|
|
@@ -19,7 +19,16 @@ function atomicWrite(filePath, content) {
|
|
|
19
19
|
if (currentHash === newHash) return false;
|
|
20
20
|
const tmp = join(tmpdir(), `lattice-${randomBytes(8).toString("hex")}.tmp`);
|
|
21
21
|
writeFileSync(tmp, content, "utf8");
|
|
22
|
-
|
|
22
|
+
try {
|
|
23
|
+
renameSync(tmp, filePath);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err.code === "EXDEV") {
|
|
26
|
+
copyFileSync(tmp, filePath);
|
|
27
|
+
unlinkSync(tmp);
|
|
28
|
+
} else {
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
23
32
|
return true;
|
|
24
33
|
}
|
|
25
34
|
function existingHash(filePath) {
|
|
@@ -329,7 +338,56 @@ var Sanitizer = class {
|
|
|
329
338
|
|
|
330
339
|
// src/render/engine.ts
|
|
331
340
|
import { join as join4, basename, isAbsolute, resolve } from "path";
|
|
332
|
-
import { mkdirSync as mkdirSync2, existsSync as existsSync4, copyFileSync } from "fs";
|
|
341
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync4, copyFileSync as copyFileSync2 } from "fs";
|
|
342
|
+
|
|
343
|
+
// src/render/token-budget.ts
|
|
344
|
+
function estimateTokens(text) {
|
|
345
|
+
return Math.ceil(text.length / 4);
|
|
346
|
+
}
|
|
347
|
+
function applyTokenBudget(rows, renderFn, budget, prioritizeBy) {
|
|
348
|
+
const fullContent = renderFn(rows);
|
|
349
|
+
if (estimateTokens(fullContent) <= budget) return fullContent;
|
|
350
|
+
if (rows.length === 0) return fullContent;
|
|
351
|
+
const prioritized = [...rows];
|
|
352
|
+
if (typeof prioritizeBy === "function") {
|
|
353
|
+
prioritized.sort(prioritizeBy);
|
|
354
|
+
} else if (typeof prioritizeBy === "string") {
|
|
355
|
+
const col = prioritizeBy;
|
|
356
|
+
prioritized.sort((a, b) => {
|
|
357
|
+
const va = a[col];
|
|
358
|
+
const vb = b[col];
|
|
359
|
+
if (va == null && vb == null) return 0;
|
|
360
|
+
if (va == null) return 1;
|
|
361
|
+
if (vb == null) return -1;
|
|
362
|
+
if (va < vb) return 1;
|
|
363
|
+
if (va > vb) return -1;
|
|
364
|
+
return 0;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
let lo = 0;
|
|
368
|
+
let hi = prioritized.length;
|
|
369
|
+
let bestContent = "";
|
|
370
|
+
let bestCount = 0;
|
|
371
|
+
while (lo < hi) {
|
|
372
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
373
|
+
const content = renderFn(prioritized.slice(0, mid));
|
|
374
|
+
if (estimateTokens(content) <= budget) {
|
|
375
|
+
bestContent = content;
|
|
376
|
+
bestCount = mid;
|
|
377
|
+
lo = mid;
|
|
378
|
+
if (lo === hi) break;
|
|
379
|
+
} else {
|
|
380
|
+
hi = mid - 1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (bestCount === 0) {
|
|
384
|
+
bestContent = renderFn([]);
|
|
385
|
+
}
|
|
386
|
+
const tokens = estimateTokens(bestContent);
|
|
387
|
+
return bestContent + `
|
|
388
|
+
|
|
389
|
+
[truncated: ${bestCount} of ${rows.length} rows rendered, ~${tokens} tokens]`;
|
|
390
|
+
}
|
|
333
391
|
|
|
334
392
|
// src/render/entity-query.ts
|
|
335
393
|
var SAFE_COL_RE = /^[a-zA-Z0-9_]+$/;
|
|
@@ -664,7 +722,7 @@ ${tmpl.perRow.body(row)}
|
|
|
664
722
|
|
|
665
723
|
// src/lifecycle/cleanup.ts
|
|
666
724
|
import { join as join3 } from "path";
|
|
667
|
-
import { existsSync as existsSync3, readdirSync, unlinkSync, rmdirSync, statSync } from "fs";
|
|
725
|
+
import { existsSync as existsSync3, readdirSync, unlinkSync as unlinkSync2, rmdirSync, statSync } from "fs";
|
|
668
726
|
function cleanupEntityContexts(outputDir, entityContexts, currentSlugsByTable, manifest, options = {}, newManifest) {
|
|
669
727
|
const result = {
|
|
670
728
|
directoriesRemoved: [],
|
|
@@ -706,7 +764,7 @@ function cleanupEntityContexts(outputDir, entityContexts, currentSlugsByTable, m
|
|
|
706
764
|
if (globalProtected.has(filename)) continue;
|
|
707
765
|
const filePath = join3(entityDir, filename);
|
|
708
766
|
if (!existsSync3(filePath)) continue;
|
|
709
|
-
if (!options.dryRun)
|
|
767
|
+
if (!options.dryRun) unlinkSync2(filePath);
|
|
710
768
|
options.onOrphan?.(filePath, "file");
|
|
711
769
|
result.filesRemoved.push(filePath);
|
|
712
770
|
}
|
|
@@ -751,7 +809,7 @@ function cleanupEntityContexts(outputDir, entityContexts, currentSlugsByTable, m
|
|
|
751
809
|
if (globalProtected.has(filename)) continue;
|
|
752
810
|
const filePath = join3(entityDir, filename);
|
|
753
811
|
if (!existsSync3(filePath)) continue;
|
|
754
|
-
if (!options.dryRun)
|
|
812
|
+
if (!options.dryRun) unlinkSync2(filePath);
|
|
755
813
|
options.onOrphan?.(filePath, "file");
|
|
756
814
|
result.filesRemoved.push(filePath);
|
|
757
815
|
}
|
|
@@ -765,9 +823,11 @@ function cleanupEntityContexts(outputDir, entityContexts, currentSlugsByTable, m
|
|
|
765
823
|
var RenderEngine = class {
|
|
766
824
|
_schema;
|
|
767
825
|
_adapter;
|
|
768
|
-
|
|
826
|
+
_getTaskContext;
|
|
827
|
+
constructor(schema, adapter, getTaskContext) {
|
|
769
828
|
this._schema = schema;
|
|
770
829
|
this._adapter = adapter;
|
|
830
|
+
this._getTaskContext = getTaskContext ?? (() => "");
|
|
771
831
|
}
|
|
772
832
|
async render(outputDir) {
|
|
773
833
|
const start = Date.now();
|
|
@@ -775,8 +835,38 @@ var RenderEngine = class {
|
|
|
775
835
|
const counters = { skipped: 0 };
|
|
776
836
|
for (const [name, def] of this._schema.getTables()) {
|
|
777
837
|
let rows = this._schema.queryTable(this._adapter, name);
|
|
838
|
+
if (def.relevanceFilter) {
|
|
839
|
+
const ctx = this._getTaskContext();
|
|
840
|
+
rows = rows.filter((row) => def.relevanceFilter(row, ctx));
|
|
841
|
+
}
|
|
778
842
|
if (def.filter) rows = def.filter(rows);
|
|
779
|
-
|
|
843
|
+
if (def.rewardTracking) {
|
|
844
|
+
if (def.pruneBelow !== void 0) {
|
|
845
|
+
const threshold = def.pruneBelow;
|
|
846
|
+
const toPrune = rows.filter(
|
|
847
|
+
(r) => r._reward_count > 0 && r._reward_total < threshold
|
|
848
|
+
);
|
|
849
|
+
if (toPrune.length > 0) {
|
|
850
|
+
for (const r of toPrune) {
|
|
851
|
+
const pkCol = this._schema.getPrimaryKey(name)[0] ?? "id";
|
|
852
|
+
this._adapter.run(
|
|
853
|
+
`UPDATE "${name}" SET deleted_at = datetime('now') WHERE "${pkCol}" = ?`,
|
|
854
|
+
[r[pkCol]]
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
rows = rows.filter(
|
|
858
|
+
(r) => r._reward_count === 0 || r._reward_total >= threshold
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (!def.prioritizeBy) {
|
|
863
|
+
rows.sort((a, b) => (b._reward_total ?? 0) - (a._reward_total ?? 0));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (def.enrich) {
|
|
867
|
+
for (const fn of def.enrich) rows = fn(rows);
|
|
868
|
+
}
|
|
869
|
+
const content = def.tokenBudget ? applyTokenBudget(rows, def.render, def.tokenBudget, def.prioritizeBy) : def.render(rows);
|
|
780
870
|
const filePath = join4(outputDir, def.outputFile);
|
|
781
871
|
if (atomicWrite(filePath, content)) {
|
|
782
872
|
filesWritten.push(filePath);
|
|
@@ -895,7 +985,7 @@ var RenderEngine = class {
|
|
|
895
985
|
const destPath = join4(entityDir, basename(absPath));
|
|
896
986
|
if (!existsSync4(destPath)) {
|
|
897
987
|
try {
|
|
898
|
-
|
|
988
|
+
copyFileSync2(absPath, destPath);
|
|
899
989
|
filesWritten.push(destPath);
|
|
900
990
|
} catch {
|
|
901
991
|
}
|
|
@@ -1236,6 +1326,14 @@ var WritebackPipeline = class {
|
|
|
1236
1326
|
if (store.isSeen(filePath, key)) continue;
|
|
1237
1327
|
store.markSeen(filePath, key);
|
|
1238
1328
|
}
|
|
1329
|
+
if (def.validate) {
|
|
1330
|
+
const result = await def.validate(entry);
|
|
1331
|
+
const threshold = def.rejectBelow ?? 0;
|
|
1332
|
+
if (!result.pass || result.score < threshold) {
|
|
1333
|
+
def.onReject?.(entry, result);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1239
1337
|
await def.persist(entry, filePath);
|
|
1240
1338
|
processed++;
|
|
1241
1339
|
}
|
|
@@ -1642,6 +1740,73 @@ function resolveEncryptedColumns(encrypted, allColumns) {
|
|
|
1642
1740
|
return new Set(allColumns.filter((c) => !SKIP_COLUMNS.has(c)));
|
|
1643
1741
|
}
|
|
1644
1742
|
|
|
1743
|
+
// src/search/embeddings.ts
|
|
1744
|
+
var EMBEDDINGS_TABLE = "_lattice_embeddings";
|
|
1745
|
+
function ensureEmbeddingsTable(adapter) {
|
|
1746
|
+
adapter.run(`CREATE TABLE IF NOT EXISTS "${EMBEDDINGS_TABLE}" (
|
|
1747
|
+
"table_name" TEXT NOT NULL,
|
|
1748
|
+
"row_pk" TEXT NOT NULL,
|
|
1749
|
+
"embedding" TEXT NOT NULL,
|
|
1750
|
+
PRIMARY KEY ("table_name", "row_pk")
|
|
1751
|
+
)`);
|
|
1752
|
+
}
|
|
1753
|
+
async function storeEmbedding(adapter, table, pk, row, config) {
|
|
1754
|
+
const text = config.fields.map((f) => {
|
|
1755
|
+
const v = row[f];
|
|
1756
|
+
return v == null ? "" : String(v);
|
|
1757
|
+
}).filter((s) => s.length > 0).join(" ");
|
|
1758
|
+
if (text.length === 0) return;
|
|
1759
|
+
const vector = await config.embed(text);
|
|
1760
|
+
adapter.run(
|
|
1761
|
+
`INSERT OR REPLACE INTO "${EMBEDDINGS_TABLE}" ("table_name", "row_pk", "embedding") VALUES (?, ?, ?)`,
|
|
1762
|
+
[table, pk, JSON.stringify(vector)]
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
function removeEmbedding(adapter, table, pk) {
|
|
1766
|
+
adapter.run(
|
|
1767
|
+
`DELETE FROM "${EMBEDDINGS_TABLE}" WHERE "table_name" = ? AND "row_pk" = ?`,
|
|
1768
|
+
[table, pk]
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
function cosineSimilarity(a, b) {
|
|
1772
|
+
const len = Math.min(a.length, b.length);
|
|
1773
|
+
let dot = 0;
|
|
1774
|
+
let magA = 0;
|
|
1775
|
+
let magB = 0;
|
|
1776
|
+
for (let i = 0; i < len; i++) {
|
|
1777
|
+
dot += a[i] * b[i];
|
|
1778
|
+
magA += a[i] * a[i];
|
|
1779
|
+
magB += b[i] * b[i];
|
|
1780
|
+
}
|
|
1781
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
1782
|
+
return denom === 0 ? 0 : dot / denom;
|
|
1783
|
+
}
|
|
1784
|
+
async function searchByEmbedding(adapter, table, queryText, config, topK, minScore, pkColumn = "id") {
|
|
1785
|
+
const queryVector = await config.embed(queryText);
|
|
1786
|
+
const stored = adapter.all(
|
|
1787
|
+
`SELECT "row_pk", "embedding" FROM "${EMBEDDINGS_TABLE}" WHERE "table_name" = ?`,
|
|
1788
|
+
[table]
|
|
1789
|
+
);
|
|
1790
|
+
const scored = [];
|
|
1791
|
+
for (const entry of stored) {
|
|
1792
|
+
const vec = JSON.parse(entry.embedding);
|
|
1793
|
+
const score = cosineSimilarity(queryVector, vec);
|
|
1794
|
+
if (score >= minScore) {
|
|
1795
|
+
scored.push({ pk: entry.row_pk, score });
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1799
|
+
const topResults = scored.slice(0, topK);
|
|
1800
|
+
const results = [];
|
|
1801
|
+
for (const { pk, score } of topResults) {
|
|
1802
|
+
const row = adapter.get(`SELECT * FROM "${table}" WHERE "${pkColumn}" = ?`, [pk]);
|
|
1803
|
+
if (row) {
|
|
1804
|
+
results.push({ row, score });
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return results;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1645
1810
|
// src/lattice.ts
|
|
1646
1811
|
var Lattice = class {
|
|
1647
1812
|
_adapter;
|
|
@@ -1660,6 +1825,8 @@ var Lattice = class {
|
|
|
1660
1825
|
_encryptedTableColumns = /* @__PURE__ */ new Map();
|
|
1661
1826
|
/** Raw encryption key passphrase from constructor options. */
|
|
1662
1827
|
_encryptionKeyRaw;
|
|
1828
|
+
/** Current task context string for relevance filtering. */
|
|
1829
|
+
_taskContext = "";
|
|
1663
1830
|
_auditHandlers = [];
|
|
1664
1831
|
_renderHandlers = [];
|
|
1665
1832
|
_writebackHandlers = [];
|
|
@@ -1686,7 +1853,7 @@ var Lattice = class {
|
|
|
1686
1853
|
this._adapter = new SQLiteAdapter(dbPath, adapterOpts);
|
|
1687
1854
|
this._schema = new SchemaManager();
|
|
1688
1855
|
this._sanitizer = new Sanitizer(options.security);
|
|
1689
|
-
this._render = new RenderEngine(this._schema, this._adapter);
|
|
1856
|
+
this._render = new RenderEngine(this._schema, this._adapter, () => this._taskContext);
|
|
1690
1857
|
this._reverseSync = new ReverseSyncEngine(this._schema, this._adapter);
|
|
1691
1858
|
this._loop = new SyncLoop(this._render);
|
|
1692
1859
|
this._writeback = new WritebackPipeline();
|
|
@@ -1710,8 +1877,10 @@ var Lattice = class {
|
|
|
1710
1877
|
// -------------------------------------------------------------------------
|
|
1711
1878
|
define(table, def) {
|
|
1712
1879
|
this._assertNotInit("define");
|
|
1880
|
+
const columns = def.rewardTracking ? { ...def.columns, _reward_total: "REAL DEFAULT 0", _reward_count: "INTEGER DEFAULT 0" } : def.columns;
|
|
1713
1881
|
const compiledDef = {
|
|
1714
1882
|
...def,
|
|
1883
|
+
columns,
|
|
1715
1884
|
render: def.render ? compileRender(
|
|
1716
1885
|
def,
|
|
1717
1886
|
table,
|
|
@@ -1757,6 +1926,10 @@ var Lattice = class {
|
|
|
1757
1926
|
const rows = this._adapter.all(`PRAGMA table_info("${tableName}")`);
|
|
1758
1927
|
this._columnCache.set(tableName, new Set(rows.map((r) => r.name)));
|
|
1759
1928
|
}
|
|
1929
|
+
const hasEmbeddings = [...this._schema.getTables().values()].some((d) => d.embeddings);
|
|
1930
|
+
if (hasEmbeddings) {
|
|
1931
|
+
ensureEmbeddingsTable(this._adapter);
|
|
1932
|
+
}
|
|
1760
1933
|
this._setupEncryption();
|
|
1761
1934
|
this._initialized = true;
|
|
1762
1935
|
return Promise.resolve();
|
|
@@ -1786,6 +1959,21 @@ var Lattice = class {
|
|
|
1786
1959
|
this._initialized = false;
|
|
1787
1960
|
}
|
|
1788
1961
|
// -------------------------------------------------------------------------
|
|
1962
|
+
// Task context (for relevance filtering)
|
|
1963
|
+
// -------------------------------------------------------------------------
|
|
1964
|
+
/**
|
|
1965
|
+
* Set the current task context string. Tables with a `relevanceFilter`
|
|
1966
|
+
* will use this value to filter rows before rendering.
|
|
1967
|
+
*/
|
|
1968
|
+
setTaskContext(context) {
|
|
1969
|
+
this._taskContext = context;
|
|
1970
|
+
return this;
|
|
1971
|
+
}
|
|
1972
|
+
/** Return the current task context string. */
|
|
1973
|
+
getTaskContext() {
|
|
1974
|
+
return this._taskContext;
|
|
1975
|
+
}
|
|
1976
|
+
// -------------------------------------------------------------------------
|
|
1789
1977
|
// Encryption helpers
|
|
1790
1978
|
// -------------------------------------------------------------------------
|
|
1791
1979
|
_setupEncryption() {
|
|
@@ -1860,6 +2048,7 @@ var Lattice = class {
|
|
|
1860
2048
|
const pkValue = rawPk != null ? String(rawPk) : "";
|
|
1861
2049
|
this._sanitizer.emitAudit(table, "insert", pkValue);
|
|
1862
2050
|
this._fireWriteHooks(table, "insert", rowWithPk, pkValue);
|
|
2051
|
+
this._syncEmbedding(table, "insert", rowWithPk, pkValue);
|
|
1863
2052
|
return Promise.resolve(pkValue);
|
|
1864
2053
|
}
|
|
1865
2054
|
/**
|
|
@@ -1927,6 +2116,11 @@ var Lattice = class {
|
|
|
1927
2116
|
const auditId = typeof id === "string" ? id : JSON.stringify(id);
|
|
1928
2117
|
this._sanitizer.emitAudit(table, "update", auditId);
|
|
1929
2118
|
this._fireWriteHooks(table, "update", sanitized, auditId, Object.keys(sanitized));
|
|
2119
|
+
const def = this._schema.getTables().get(table);
|
|
2120
|
+
if (def?.embeddings) {
|
|
2121
|
+
const fullRow = this._adapter.get(`SELECT * FROM "${table}" WHERE ${clause}`, pkParams);
|
|
2122
|
+
if (fullRow) this._syncEmbedding(table, "update", fullRow, auditId);
|
|
2123
|
+
}
|
|
1930
2124
|
return Promise.resolve();
|
|
1931
2125
|
}
|
|
1932
2126
|
/**
|
|
@@ -1948,6 +2142,7 @@ var Lattice = class {
|
|
|
1948
2142
|
const auditId = typeof id === "string" ? id : JSON.stringify(id);
|
|
1949
2143
|
this._sanitizer.emitAudit(table, "delete", auditId);
|
|
1950
2144
|
this._fireWriteHooks(table, "delete", { id: auditId }, auditId);
|
|
2145
|
+
this._syncEmbedding(table, "delete", {}, auditId);
|
|
1951
2146
|
return Promise.resolve();
|
|
1952
2147
|
}
|
|
1953
2148
|
get(table, id) {
|
|
@@ -2331,6 +2526,65 @@ var Lattice = class {
|
|
|
2331
2526
|
const ms = unit === "h" ? num * 36e5 : unit === "d" ? num * 864e5 : num * 6e4;
|
|
2332
2527
|
return new Date(Date.now() - ms).toISOString();
|
|
2333
2528
|
}
|
|
2529
|
+
// -------------------------------------------------------------------------
|
|
2530
|
+
// Reward tracking
|
|
2531
|
+
// -------------------------------------------------------------------------
|
|
2532
|
+
/**
|
|
2533
|
+
* Update reward scores for a row. The total reward is recalculated as
|
|
2534
|
+
* the running average across all reward calls. Requires `rewardTracking`
|
|
2535
|
+
* on the table definition.
|
|
2536
|
+
*/
|
|
2537
|
+
reward(table, id, scores) {
|
|
2538
|
+
const notInit = this._notInitError();
|
|
2539
|
+
if (notInit) return notInit;
|
|
2540
|
+
const def = this._schema.getTables().get(table);
|
|
2541
|
+
if (!def?.rewardTracking) {
|
|
2542
|
+
return Promise.reject(
|
|
2543
|
+
new Error(`Table "${table}" does not have rewardTracking enabled`)
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
const vals = Object.values(scores);
|
|
2547
|
+
if (vals.length === 0) return Promise.resolve();
|
|
2548
|
+
const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
2549
|
+
const { clause, params: pkParams } = this._pkWhere(table, id);
|
|
2550
|
+
this._adapter.run(
|
|
2551
|
+
`UPDATE "${table}" SET "_reward_total" = ("_reward_total" * "_reward_count" + ?) / ("_reward_count" + 1), "_reward_count" = "_reward_count" + 1 WHERE ${clause}`,
|
|
2552
|
+
[avg, ...pkParams]
|
|
2553
|
+
);
|
|
2554
|
+
return Promise.resolve();
|
|
2555
|
+
}
|
|
2556
|
+
// -------------------------------------------------------------------------
|
|
2557
|
+
// Semantic search
|
|
2558
|
+
// -------------------------------------------------------------------------
|
|
2559
|
+
/**
|
|
2560
|
+
* Search for rows by semantic similarity. Requires `embeddings` config
|
|
2561
|
+
* on the table definition.
|
|
2562
|
+
*
|
|
2563
|
+
* @param table - Table to search
|
|
2564
|
+
* @param query - Natural-language query text
|
|
2565
|
+
* @param opts - Search options (topK, minScore)
|
|
2566
|
+
* @returns Matching rows with similarity scores, sorted best-first.
|
|
2567
|
+
*/
|
|
2568
|
+
async search(table, query, opts = {}) {
|
|
2569
|
+
const notInit = this._notInitError();
|
|
2570
|
+
if (notInit) return notInit;
|
|
2571
|
+
const def = this._schema.getTables().get(table);
|
|
2572
|
+
if (!def?.embeddings) {
|
|
2573
|
+
return Promise.reject(
|
|
2574
|
+
new Error(`Table "${table}" does not have embeddings configured`)
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
const pkCol = this._schema.getPrimaryKey(table)[0] ?? "id";
|
|
2578
|
+
return searchByEmbedding(
|
|
2579
|
+
this._adapter,
|
|
2580
|
+
table,
|
|
2581
|
+
query,
|
|
2582
|
+
def.embeddings,
|
|
2583
|
+
opts.topK ?? 10,
|
|
2584
|
+
opts.minScore ?? 0,
|
|
2585
|
+
pkCol
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2334
2588
|
query(table, opts = {}) {
|
|
2335
2589
|
const notInit = this._notInitError();
|
|
2336
2590
|
if (notInit) return notInit;
|
|
@@ -2589,6 +2843,23 @@ var Lattice = class {
|
|
|
2589
2843
|
}
|
|
2590
2844
|
}
|
|
2591
2845
|
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Update or remove the embedding for a row.
|
|
2848
|
+
* No-op if the table doesn't have `embeddings` configured.
|
|
2849
|
+
*/
|
|
2850
|
+
_syncEmbedding(table, op, row, pk) {
|
|
2851
|
+
const def = this._schema.getTables().get(table);
|
|
2852
|
+
if (!def?.embeddings) return;
|
|
2853
|
+
if (op === "delete") {
|
|
2854
|
+
removeEmbedding(this._adapter, table, pk);
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
storeEmbedding(this._adapter, table, pk, row, def.embeddings).catch((err) => {
|
|
2858
|
+
for (const h of this._errorHandlers) {
|
|
2859
|
+
h(err instanceof Error ? err : new Error(String(err)));
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2592
2863
|
_notInitError() {
|
|
2593
2864
|
if (!this._initialized) {
|
|
2594
2865
|
return Promise.reject(
|
|
@@ -3129,6 +3400,7 @@ export {
|
|
|
3129
3400
|
InMemoryStateStore,
|
|
3130
3401
|
Lattice,
|
|
3131
3402
|
READ_ONLY_HEADER,
|
|
3403
|
+
applyTokenBudget,
|
|
3132
3404
|
applyWriteEntry,
|
|
3133
3405
|
autoUpdate,
|
|
3134
3406
|
contentHash,
|
|
@@ -3138,6 +3410,7 @@ export {
|
|
|
3138
3410
|
deriveKey,
|
|
3139
3411
|
encrypt,
|
|
3140
3412
|
entityFileNames,
|
|
3413
|
+
estimateTokens,
|
|
3141
3414
|
fixSchemaConflicts,
|
|
3142
3415
|
frontmatter,
|
|
3143
3416
|
generateEntryId,
|