@fgv/ts-agent-memory-sqlite-vec 5.1.0-50 → 5.1.0-51
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 +26 -1
- package/dist/packlets/sqlite-vec-index/connection.js +54 -0
- package/dist/packlets/sqlite-vec-index/connection.js.map +1 -0
- package/dist/packlets/sqlite-vec-index/model.js.map +1 -1
- package/dist/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js +89 -6
- package/dist/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js.map +1 -1
- package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +50 -3
- package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -1
- package/dist/ts-agent-memory-sqlite-vec.d.ts +163 -7
- package/lib/packlets/sqlite-vec-index/connection.d.ts +42 -0
- package/lib/packlets/sqlite-vec-index/connection.d.ts.map +1 -0
- package/lib/packlets/sqlite-vec-index/connection.js +91 -0
- package/lib/packlets/sqlite-vec-index/connection.js.map +1 -0
- package/lib/packlets/sqlite-vec-index/model.d.ts +92 -0
- package/lib/packlets/sqlite-vec-index/model.d.ts.map +1 -1
- package/lib/packlets/sqlite-vec-index/model.js.map +1 -1
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.d.ts +38 -6
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.d.ts.map +1 -1
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js +89 -6
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js.map +1 -1
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts +34 -4
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts.map +1 -1
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +50 -3
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqliteVecVectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/sqliteVecVectorIndex.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAGL,aAAa,EACb,IAAI,EACJ,cAAc,EACd,OAAO,EACP,iBAAiB,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAaL,aAAa,EACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGvE,iDAAiD;AACjD,MAAM,kBAAkB,GAAW,gBAAgB,CAAC;AAEpD,yGAAyG;AACzG,MAAM,aAAa,GAAW,0BAA0B,CAAC;AAQzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,OAAO,oBAAoB;IAQ/B,YAAoB,EAA0B,EAAE,KAAa,EAAE,SAA6B;QAC1F,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;IACtE,CAAC;IAED,yEAAyE;IACzE,IAAW,IAAI;QACb,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,8EAA8E;QAC9E,+EAA+E;QAC/E,+EAA+E;QAC/E,8EAA8E;QAC9E,4EAA4E;QAC5E,8EAA8E;QAC9E,mBAAmB;QACnB,OAAO,MAAM,CAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAA6B,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;;;;;;OASG;IACI,MAAM,CAAC,MAAM,CAAC,MAAyC;;QAC5D,MAAM,KAAK,GAAW,MAAA,MAAM,CAAC,SAAS,mCAAI,kBAAkB,CAAC;QAC7D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,iCAAiC,KAAK,kCAAkC,CAAC,CAAC,CAAC;QACzG,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CACpB,aAAa,CAAC,GAAG,EAAE;YACjB,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC/B,MAAM,SAAS,GAAuB,oBAAoB,CAAC,sBAAsB,CAC/E,MAAM,CAAC,QAAQ,EACf,KAAK,CACN,CAAC;YACF,OAAO,IAAI,oBAAoB,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,2CAA2C,CAAC,EAAE,CAAC,CAC1E,CAAC;IACJ,CAAC;IAED,qCAAqC;IAC9B,GAAG,CAAC,MAAmB,EAAE,MAAoB;QAClD,MAAM,GAAG,GAAW,aAAa,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,6BAA6B,GAAG,iBAAiB,CAAC,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACvE,OAAO,OAAO,CAAC,OAAO,CACpB,IAAI,CACF,6BAA6B,GAAG,gBAAgB,MAAM,CAAC,MAAM,mCAAmC,IAAI,CAAC,UAAU,EAAE,CAClH,CACF,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CACpB,aAAa,CAAC,GAAG,EAAE;YACjB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC9B,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACjC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;gBAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/D,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,6BAA6B,GAAG,MAAM,CAAC,EAAE,CAAC,CACrE,CAAC;IACJ,CAAC;IAED,qCAAqC;IAC9B,GAAG,CAAC,MAAmB;QAC5B,OAAO,OAAO,CAAC,OAAO,CACpB,aAAa,CAAC,GAAG,EAAE;YACjB,yEAAyE;YACzE,qEAAqE;YACrE,iCAAiC;YACjC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC9B,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,SAAS,CAAC;QAClE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,+BAA+B,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CACzF,CAAC;IACJ,CAAC;IAED,wCAAwC;IACjC,MAAM,CAAC,MAAmB;QAC/B,OAAO,OAAO,CAAC,OAAO,CACpB,aAAa,CAAC,GAAG,EAAE;YACjB,uEAAuE;YACvE,qCAAqC;YACrC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;YAChD,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gCAAgC,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAC1F,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,KAAK,CAAC,OAAO,CAClB,MAA2B,EAC3B,KAAqB,EACrB,OAA+B;;QAE/B,MAAM,OAAO,GAAY,CAAC,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,aAAa,mCAAI,MAAM,CAAC,KAAK,MAAM,CAAC;QACvE,6EAA6E;QAC7E,6CAA6C;QAC7C,MAAM,MAAM,GAAiC,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACnF,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;YACvB,wEAAwE;YACxE,4EAA4E;YAC5E,0EAA0E;YAC1E,6EAA6E;YAC7E,sEAAsE;YACtE,OAAO,cAAc,CAAC,iDAAiD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,OAAO,GAAiB,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5C,IAAI,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;YACxB,oEAAoE;YACpE,OAAO,cAAc,CAAC,oDAAoD,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/F,CAAC;QACD,MAAM,OAAO,GAAsB,IAAI,GAAG,EAAgB,CAAC;QAC3D,MAAM,QAAQ,GAAsB,IAAI,GAAG,EAAgB,CAAC;QAC5D,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,4EAA4E;QAC5E,MAAM,MAAM,GAAG,GAAyB,EAAE,CAAC,CAAC;YAC1C,OAAO;YACP,QAAQ;YACR,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;YAC/B,OAAO;SACR,CAAC,CAAC;QACH,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAS,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC/C,mEAAmE;YACnE,0EAA0E;YAC1E,2DAA2D;YAC3D,MAAM,QAAQ,GAAqC,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAChG,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;gBACzB,MAAM,KAAK,GAAW,oCAAoC,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,aACpF,QAAQ,CAAC,OACX,EAAE,CAAC;gBACH,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,cAAc,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC1E,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC/C,SAAS;YACX,CAAC;YACD,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACjC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,MAAM,KAAK,GAAmB,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC5E,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;gBACtB,MAAM,KAAK,GAAW,yBAAyB,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,cAAc,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC1E,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC/C,SAAS;YACX,CAAC;YACD,wEAAwE;YACxE,2EAA2E;YAC3E,6EAA6E;YAC7E,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IAED;;;;;;;OAOG;IACK,MAAM;QACZ,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QACD,6EAA6E;QAC7E,yEAAyE;QACzE,uCAAuC;QACvC,OAAO,aAAa,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAChG,OAAO,CAAC,IAAI,CAAC,CACd,CAAC;IACJ,CAAC;IAED,uCAAuC;IAChC,KAAK,CAAC,MAAoB,EAAE,IAAY;QAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC3C,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC,OAAO,CACpB,IAAI,CACF,iCAAiC,MAAM,CAAC,MAAM,mCAAmC,IAAI,CAAC,UAAU,EAAE,CACnG,CACF,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CACpB,aAAa,CAAiC,GAAG,EAAE;YACjD,MAAM,IAAI,GAA2B,IAAI,CAAC,MAAO,CAAC,KAAK,CAAC,GAAG,CACzD,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,EACpC,IAAI,CACqB,CAAC;YAC5B,0EAA0E;YAC1E,8EAA8E;YAC9E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACxB,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;gBACtD,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,QAAQ;aACxB,CAAC,CAAC,CAAC;QACN,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,+BAA+B,CAAC,EAAE,CAAC,CAC9D,CAAC;IACJ,CAAC;IAED,sEAAsE;IAC9D,YAAY,CAAC,SAAiB;QACpC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,uCAAuC,IAAI,CAAC,MAAM,eAAe;YAC/D,gDAAgD,SAAS,2BAA2B,CACvF,CAAC;IACJ,CAAC;IAED,4EAA4E;IACpE,QAAQ;QACd,MAAM,GAAG,GAA4B,IAAI,CAAC,GAAG,CAAC,OAAO,CACnD,gBAAgB,IAAI,CAAC,MAAM,wBAAwB,CACpD,CAAC;QACF,MAAM,GAAG,GAA4B,IAAI,CAAC,GAAG,CAAC,OAAO,CACnD,gBAAgB,IAAI,CAAC,MAAM,wCAAwC,CACpE,CAAC;QACF,wEAAwE;QACxE,kDAAkD;QAClD,MAAM,UAAU,GACd,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,GAAW,EAAE,IAAgB,EAAE,EAAE;YACrD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACb,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACL,OAAO;YACL,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,CAAC,GAAW,EAAE,IAAgB,EAAQ,EAAE;gBAC/C,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACxB,CAAC;YACD,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CACrB,qCAAqC,IAAI,CAAC,MAAM,qCAAqC,CACtF;YACD,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,8BAA8B,IAAI,CAAC,MAAM,GAAG,CAAC;YACrE,8EAA8E;YAC9E,sCAAsC;YACtC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,IAAI,CAAC,MAAM,gCAAgC,CAAC;SACrF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,sBAAsB,CAAC,EAA0B,EAAE,KAAa;QAC7E,MAAM,GAAG,GAAgC,EAAE;aACxC,OAAO,CAAC,iEAAiE,CAAC;aAC1E,GAAG,CAAC,KAAK,CAAgC,CAAC;QAC7C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,GAA4B,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACvE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,sHAAsH;IAC9G,MAAM,CAAC,OAAO,CAAC,MAAoB;QACzC,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,SAAS,CAAC,GAAW;QAClC,MAAM,GAAG,GAAW,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO;YACL,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAA8B;YACrD,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAwB;SAC9C,CAAC;IACJ,CAAC;CACF","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { load as loadSqliteVec } from 'sqlite-vec';\nimport {\n DetailedResult,\n Result,\n captureResult,\n fail,\n failWithDetail,\n succeed,\n succeedWithDetail\n} from '@fgv/ts-utils';\nimport {\n IEdgeTarget,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorIndex,\n IVectorQueryHit,\n IVectorRebuildOptions,\n IVectorRebuildReport,\n Kind,\n MemoryEmbedder,\n MemoryId,\n MemoryScopeKey,\n edgeTargetKey\n} from '@fgv/ts-agent-memory';\nimport { invokeHook, tally, withRollbackNote } from './rebuildHelpers';\nimport { ISqliteVecVectorIndexCreateParams } from './model';\n\n/** Default name for the `vec0` virtual table. */\nconst DEFAULT_TABLE_NAME: string = 'memory_vectors';\n\n/** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */\nconst IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** One KNN row as returned by the `vec0` MATCH query. */\ninterface IKnnRow {\n readonly target_key: string;\n readonly distance: number;\n}\n\n/**\n * A persistent, `sqlite-vec`-backed `IVectorIndex` for `@fgv/ts-agent-memory`.\n *\n * @remarks\n * This is the **durable** counterpart to the in-memory `InMemoryCosineIndex`:\n * embeddings live in a `sqlite-vec` `vec0` virtual table inside a `better-sqlite3`\n * database, so they survive a process restart. A consumer that wires this index\n * into `FileTreeMemoryStore` (instead of the in-memory index) opens an existing\n * vault **without re-embedding it** — the vectors are already on disk. New writes\n * still flow through the store's incremental embed-on-write path; there is no core\n * store change.\n *\n * The index is keyed by the canonical `edgeTargetKey` of each record's\n * scope-qualified `(scope, id)` address (a `TEXT PRIMARY KEY` on the `vec0` table),\n * so two records that share a filename stem across scopes never collide. The\n * dimension is established by the first `add` (the `vec0` column is fixed-width) and\n * recovered from the table schema when a persistent file is reopened; every later\n * `add`/`query` must match it or fail loudly, exactly as the in-memory index does.\n * Similarity is cosine (`distance_metric=cosine`): the returned `score` is\n * `1 - cosineDistance`, i.e. cosine similarity in `[-1, 1]`, higher = more similar —\n * byte-for-byte the same scoring contract as `InMemoryCosineIndex`.\n *\n * Query is a brute-force `vec0` KNN scan (not an ANN structure): correct and\n * durable, appropriate for the same \"thousands of records\" regime the in-memory\n * index targets. Large-N ANN indexing is explicitly out of scope — see the README.\n *\n * The `better-sqlite3` `Database` is consumer-owned (bring-your-own): this index\n * loads the `sqlite-vec` extension onto it and reads/writes the table, but never\n * opens or closes the connection.\n * @public\n */\nexport class SqliteVecVectorIndex implements IVectorIndex {\n private readonly _db: BetterSqlite3.Database;\n private readonly _table: string;\n /** The dimension of every stored vector; `undefined` until the table exists (first `add` or a reopened non-empty file). */\n private _dimension: number | undefined;\n /** Prepared statements; created once the table exists (established or recovered). */\n private _stmts: ISqliteVecStatements | undefined;\n\n private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {\n this._db = db;\n this._table = table;\n this._dimension = dimension;\n this._stmts = dimension === undefined ? undefined : this._prepare();\n }\n\n /** The number of vectors currently held. Zero before the first `add`. */\n public get size(): number {\n if (this._stmts === undefined) {\n return 0;\n }\n // `Number(...)` narrows the count in case the consumer enabled better-sqlite3\n // safe-integer mode (`db.defaultSafeIntegers(true)`), which returns `count(*)`\n // as a `bigint`. Without it a `bigint` leaks through a `number`-typed contract\n // member — and now through `IIndexCoverage.indexSize`, which is also declared\n // `number`, so the coverage report would carry a value of the wrong runtime\n // type. `SqliteVecFragmentIndex`'s two counts have always converted; this one\n // was the outlier.\n return Number((this._stmts.count.get() as { c: number | bigint }).c);\n }\n\n /**\n * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied\n * `better-sqlite3` connection and, if the vector table already exists (a reopened\n * persistent file), recovers its established dimension so no re-embedding is\n * needed on open.\n *\n * @param params - See {@link ISqliteVecVectorIndexCreateParams}.\n * @returns `Success` with the index, or `Failure` if the table name is not a\n * simple identifier or the extension fails to load.\n */\n public static create(params: ISqliteVecVectorIndexCreateParams): Promise<Result<SqliteVecVectorIndex>> {\n const table: string = params.tableName ?? DEFAULT_TABLE_NAME;\n if (!IDENTIFIER_RE.test(table)) {\n return Promise.resolve(fail(`sqlite-vec index: table name '${table}' is not a simple SQL identifier`));\n }\n return Promise.resolve(\n captureResult(() => {\n loadSqliteVec(params.database);\n const dimension: number | undefined = SqliteVecVectorIndex._readExistingDimension(\n params.database,\n table\n );\n return new SqliteVecVectorIndex(params.database, table, dimension);\n }).withErrorFormat((e) => `sqlite-vec index: failed to initialize: ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.add} */\n public add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>> {\n const key: string = edgeTargetKey(target);\n if (vector.length === 0) {\n return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));\n }\n if (this._dimension !== undefined && vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: cannot add '${key}': dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult(() => {\n if (this._stmts === undefined) {\n this._createTable(vector.length);\n this._dimension = vector.length;\n this._stmts = this._prepare();\n }\n this._stmts.replace(key, SqliteVecVectorIndex._toBlob(vector));\n return key;\n }).withErrorFormat((e) => `vector index: cannot add '${key}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(\n captureResult(() => {\n // Before any add has created the table there is nothing held, which is a\n // truthful `false` rather than an error — same posture as `remove`'s\n // idempotence and `size`'s zero.\n if (this._stmts === undefined) {\n return false;\n }\n return this._stmts.has.get(edgeTargetKey(target)) !== undefined;\n }).withErrorFormat((e) => `vector index: cannot check '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n return Promise.resolve(\n captureResult(() => {\n // Idempotent: removing a target with no embedding (or before any `add`\n // created the table) still succeeds.\n if (this._stmts !== undefined) {\n this._stmts.delete.run(edgeTargetKey(target));\n }\n return target;\n }).withErrorFormat((e) => `vector index: cannot remove '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /**\n * Re-embed every record from `source` and rebuild the persisted index — see\n * `IVectorIndex.rebuild` for the mode semantics, which this implementation\n * matches exactly.\n *\n * @remarks\n * **Not atomic, and cannot be.** `better-sqlite3` transactions are synchronous,\n * so one cannot span the `await embed(...)` calls this loop makes — unlike\n * {@link SqliteVecVectorIndex.add}, which wraps its delete-then-insert. The\n * `'fail'` / `'skip'` modes therefore cover only failures JavaScript can catch:\n * a process kill mid-rebuild leaves the table holding neither the old index nor\n * the complete new one, and the remedy is to run `rebuild` again.\n */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: MemoryEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IVectorRebuildReport, IVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n // `source` is consumer-supplied, so a throw or rejection becomes a `Failure`\n // here rather than escaping as an exception.\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE any clear: a failed list is no evidence about the\n // vectors already held, and no re-embedding has been attempted, so there is\n // no half-rebuilt state to protect against. Clearing here would destroy a\n // healthy persisted index over a transient read error. No report either, for\n // the same reason — there is nothing this call disturbed to describe.\n return failWithDetail(`vector index rebuild: failed to list records: ${listed.message}`);\n }\n const cleared: Result<true> = this._clear();\n if (cleared.isFailure()) {\n // Also nothing established: the table still holds whatever it held.\n return failWithDetail(`vector index rebuild: failed to clear the index: ${cleared.message}`);\n }\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Absent stays absent — only the source knows whether it filtered anything.\n const report = (): IVectorRebuildReport => ({\n indexed,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Likewise capture-wrapped: an embedder that throws mid-loop would\n // otherwise escape past the `'fail'` rollback below, leaving this DURABLE\n // table holding a partial index that survives the process.\n const embedded: Result<Float32Array | undefined> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `vector index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (embedded.value === undefined) {\n tally(declined, kind);\n continue;\n }\n const added: Result<string> = await this.add(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `vector index rebuild: ${added.message}`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // Tallied in the loop rather than read back off `size` at the end. That\n // `COUNT` was also the only fallible step in assembling the report, so the\n // per-kind tally removes a failure path as well as a rounding of the answer.\n tally(indexed, kind);\n }\n return succeedWithDetail(report());\n }\n\n /**\n * **Empties the rows; does NOT release the table's declared dimension.** That\n * is a `vec0` constraint rather than a choice — the dimension is schema, and\n * there is no `ALTER TABLE` for it — so a rebuild at a new dimension fails\n * here where it would succeed on the in-memory sibling, which forgets its\n * dimension on reset. Changing dimension needs a drop-and-re-index; see the\n * note on `IVectorIndex.rebuild`.\n */\n private _clear(): Result<true> {\n if (this._stmts === undefined) {\n return succeed(true);\n }\n // Capture-wrapped like `add` / `remove` / `query`: a closed connection or an\n // I/O error here is a `Failure`, not an exception thrown out of a method\n // whose signature promises a `Result`.\n return captureResult(() => this._db.prepare(`DELETE FROM \"${this._table}\"`).run()).onSuccess(() =>\n succeed(true)\n );\n }\n\n /** {@inheritDoc IVectorIndex.query} */\n public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._stmts === undefined) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult<ReadonlyArray<IVectorQueryHit>>(() => {\n const rows: ReadonlyArray<IKnnRow> = this._stmts!.query.all(\n SqliteVecVectorIndex._toBlob(vector),\n topK\n ) as ReadonlyArray<IKnnRow>;\n // sqlite-vec returns rows in ascending distance (nearest first); score is\n // `1 - cosineDistance` = cosine similarity, so descending score is preserved.\n return rows.map((row) => ({\n target: SqliteVecVectorIndex._parseKey(row.target_key),\n score: 1 - row.distance\n }));\n }).withErrorFormat((e) => `vector index: query failed: ${e}`)\n );\n }\n\n /** Create the `vec0` virtual table with the established dimension. */\n private _createTable(dimension: number): void {\n this._db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS \"${this._table}\" USING vec0(` +\n `target_key TEXT PRIMARY KEY, embedding float[${dimension}] distance_metric=cosine)`\n );\n }\n\n /** Prepare the statements the index reuses. Requires the table to exist. */\n private _prepare(): ISqliteVecStatements {\n const del: BetterSqlite3.Statement = this._db.prepare(\n `DELETE FROM \"${this._table}\" WHERE target_key = ?`\n );\n const ins: BetterSqlite3.Statement = this._db.prepare(\n `INSERT INTO \"${this._table}\"(target_key, embedding) VALUES (?, ?)`\n );\n // vec0 rejects INSERT OR REPLACE on a TEXT primary key, so replace is a\n // delete-then-insert inside a single transaction.\n const replaceTxn: BetterSqlite3.Transaction<(key: string, blob: Uint8Array) => void> =\n this._db.transaction((key: string, blob: Uint8Array) => {\n del.run(key);\n ins.run(key, blob);\n });\n return {\n delete: del,\n replace: (key: string, blob: Uint8Array): void => {\n replaceTxn(key, blob);\n },\n query: this._db.prepare(\n `SELECT target_key, distance FROM \"${this._table}\" WHERE embedding MATCH ? AND k = ?`\n ),\n count: this._db.prepare(`SELECT count(*) AS c FROM \"${this._table}\"`),\n // `LIMIT 1` rather than a count: membership needs existence, not cardinality,\n // and vec0 can stop at the first row.\n has: this._db.prepare(`SELECT 1 FROM \"${this._table}\" WHERE target_key = ? LIMIT 1`)\n };\n }\n\n /**\n * Recover the established dimension of an existing `vec0` table from its stored\n * `CREATE VIRTUAL TABLE` SQL (`float[<n>]`). Returns `undefined` when the table\n * does not exist yet (a fresh database — dimension is set by the first `add`).\n */\n private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {\n const row: { sql: string } | undefined = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .get(table) as { sql: string } | undefined;\n if (row === undefined) {\n return undefined;\n }\n const match: RegExpMatchArray | null = row.sql.match(/float\\[(\\d+)\\]/);\n return match === null ? undefined : Number(match[1]);\n }\n\n /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */\n private static _toBlob(vector: Float32Array): Uint8Array {\n return new Uint8Array(Float32Array.from(vector).buffer);\n }\n\n /**\n * Reverse `edgeTargetKey` — the canonical key is `scope\\0id` with NUL\n * excluded from both components, so the first NUL splits it unambiguously.\n */\n private static _parseKey(key: string): IEdgeTarget {\n const nul: number = key.indexOf('\\0');\n return {\n scope: key.slice(0, nul) as unknown as MemoryScopeKey,\n id: key.slice(nul + 1) as unknown as MemoryId\n };\n }\n}\n\n/** The prepared statements / helpers the index reuses once its table exists. */\ninterface ISqliteVecStatements {\n readonly delete: BetterSqlite3.Statement;\n readonly replace: (key: string, blob: Uint8Array) => void;\n readonly query: BetterSqlite3.Statement;\n readonly count: BetterSqlite3.Statement;\n readonly has: BetterSqlite3.Statement;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sqliteVecVectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/sqliteVecVectorIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { load as loadSqliteVec } from 'sqlite-vec';\nimport {\n DetailedResult,\n Result,\n captureResult,\n fail,\n failWithDetail,\n succeed,\n succeedWithDetail\n} from '@fgv/ts-utils';\nimport {\n IEdgeTarget,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorIndex,\n IVectorQueryHit,\n IVectorRebuildOptions,\n IVectorRebuildReport,\n Kind,\n MemoryEmbedder,\n MemoryId,\n MemoryScopeKey,\n edgeTargetKey\n} from '@fgv/ts-agent-memory';\nimport { invokeHook, tally, withRollbackNote } from './rebuildHelpers';\nimport { closeOwnedConnection, openOwnedConnection } from './connection';\nimport {\n ISqliteVecVectorIndexCreateParams,\n ISqliteVecVectorIndexHandle,\n ISqliteVecVectorIndexOpenParams\n} from './model';\n\n/** Default name for the `vec0` virtual table. */\nconst DEFAULT_TABLE_NAME: string = 'memory_vectors';\n\n/** Package-facing prefix for this class's failure messages. */\nconst LABEL: string = 'sqlite-vec index';\n\n/** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */\nconst IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** One KNN row as returned by the `vec0` MATCH query. */\ninterface IKnnRow {\n readonly target_key: string;\n readonly distance: number;\n}\n\n/**\n * A persistent, `sqlite-vec`-backed `IVectorIndex` for `@fgv/ts-agent-memory`.\n *\n * @remarks\n * This is the **durable** counterpart to the in-memory `InMemoryCosineIndex`:\n * embeddings live in a `sqlite-vec` `vec0` virtual table inside a `better-sqlite3`\n * database, so they survive a process restart. A consumer that wires this index\n * into `FileTreeMemoryStore` (instead of the in-memory index) opens an existing\n * vault **without re-embedding it** — the vectors are already on disk. New writes\n * still flow through the store's incremental embed-on-write path; there is no core\n * store change.\n *\n * The index is keyed by the canonical `edgeTargetKey` of each record's\n * scope-qualified `(scope, id)` address (a `TEXT PRIMARY KEY` on the `vec0` table),\n * so two records that share a filename stem across scopes never collide. The\n * dimension is established by the first `add` (the `vec0` column is fixed-width) and\n * recovered from the table schema when a persistent file is reopened; every later\n * `add`/`query` must match it or fail loudly, exactly as the in-memory index does.\n * Similarity is cosine (`distance_metric=cosine`): the returned `score` is\n * `1 - cosineDistance`, i.e. cosine similarity in `[-1, 1]`, higher = more similar —\n * byte-for-byte the same scoring contract as `InMemoryCosineIndex`.\n *\n * Query is a brute-force `vec0` KNN scan (not an ANN structure): correct and\n * durable, appropriate for the same \"thousands of records\" regime the in-memory\n * index targets. Large-N ANN indexing is explicitly out of scope — see the README.\n *\n * **Connection ownership depends on which factory you use.** With\n * {@link SqliteVecVectorIndex.create} the `Database` is consumer-owned\n * (bring-your-own): this index loads the `sqlite-vec` extension onto it and\n * reads/writes the table, but never opens or closes the connection — and that is\n * the seam for backing a record index and a fragment index with one connection.\n * With {@link SqliteVecVectorIndex.open} this package opens the file itself and\n * hands back a handle carrying the disposer for the connection it created.\n * @public\n */\nexport class SqliteVecVectorIndex implements IVectorIndex {\n private readonly _db: BetterSqlite3.Database;\n private readonly _table: string;\n /** The dimension of every stored vector; `undefined` until the table exists (first `add` or a reopened non-empty file). */\n private _dimension: number | undefined;\n /** Prepared statements; created once the table exists (established or recovered). */\n private _stmts: ISqliteVecStatements | undefined;\n\n private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {\n this._db = db;\n this._table = table;\n this._dimension = dimension;\n this._stmts = dimension === undefined ? undefined : this._prepare();\n }\n\n /** The number of vectors currently held. Zero before the first `add`. */\n public get size(): number {\n if (this._stmts === undefined) {\n return 0;\n }\n // `Number(...)` narrows the count in case the consumer enabled better-sqlite3\n // safe-integer mode (`db.defaultSafeIntegers(true)`), which returns `count(*)`\n // as a `bigint`. Without it a `bigint` leaks through a `number`-typed contract\n // member — and now through `IIndexCoverage.indexSize`, which is also declared\n // `number`, so the coverage report would carry a value of the wrong runtime\n // type. `SqliteVecFragmentIndex`'s two counts have always converted; this one\n // was the outlier.\n return Number((this._stmts.count.get() as { c: number | bigint }).c);\n }\n\n /**\n * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied\n * `better-sqlite3` connection and, if the vector table already exists (a reopened\n * persistent file), recovers its established dimension so no re-embedding is\n * needed on open.\n *\n * @param params - See {@link ISqliteVecVectorIndexCreateParams}.\n * @returns `Success` with the index, or `Failure` if the table name is not a\n * simple identifier or the extension fails to load.\n */\n public static create(params: ISqliteVecVectorIndexCreateParams): Promise<Result<SqliteVecVectorIndex>> {\n const table: string = params.tableName ?? DEFAULT_TABLE_NAME;\n if (!IDENTIFIER_RE.test(table)) {\n return Promise.resolve(fail(`sqlite-vec index: table name '${table}' is not a simple SQL identifier`));\n }\n return Promise.resolve(\n captureResult(() => {\n loadSqliteVec(params.database);\n const dimension: number | undefined = SqliteVecVectorIndex._readExistingDimension(\n params.database,\n table\n );\n return new SqliteVecVectorIndex(params.database, table, dimension);\n }).withErrorFormat((e) => `sqlite-vec index: failed to initialize: ${e}`)\n );\n }\n\n /**\n * Path-based factory. Opens the database file itself and returns the index\n * together with a disposer for the connection it created.\n *\n * @remarks\n * The convenience over {@link SqliteVecVectorIndex.create} is that the consumer\n * neither value-imports `better-sqlite3` nor re-establishes `Result` discipline\n * around a constructor that throws — this is the one place the package leaked its\n * own dependency into consumer source.\n *\n * **Use `create` instead when one connection must back more than one index** (a\n * record index and a fragment index in the same file, the intended shared-handle\n * case). Two `open` calls on one path give two independent connections, not a\n * shared one.\n *\n * If initialization fails after the file is opened, the connection is closed\n * before returning, so a failed `open` does not leak the descriptor it created.\n * Should that close *itself* fail — the connection is then genuinely leaked — the\n * returned message says so rather than hiding it.\n *\n * @param params - See {@link ISqliteVecVectorIndexOpenParams}.\n * @returns `Success` with a {@link ISqliteVecVectorIndexHandle}, or `Failure` if\n * the driver could not be loaded, the file could not be opened, the table name is\n * not a simple identifier, or the extension fails to load.\n */\n public static async open(\n params: ISqliteVecVectorIndexOpenParams\n ): Promise<Result<ISqliteVecVectorIndexHandle>> {\n return (await openOwnedConnection(params.path, LABEL)).thenOnSuccess(async (database) =>\n (await SqliteVecVectorIndex.create({ database, tableName: params.tableName }))\n .onFailure((message) =>\n // This call opened the connection, so a failure to initialize on top of it\n // must not leave the file handle behind. A close that ALSO fails is said out\n // loud rather than swallowed — the same reasoning, and the same helper, as\n // `withRollbackNote`: silently discarding it would make the \"a failed open\n // leaks nothing\" guarantee untrue exactly when it stopped holding, with no\n // way for a caller to detect it.\n fail(withRollbackNote(message, closeOwnedConnection(database, LABEL)))\n )\n .onSuccess((index) =>\n succeed({\n index,\n close: () => closeOwnedConnection(database, LABEL)\n })\n )\n );\n }\n\n /** {@inheritDoc IVectorIndex.add} */\n public add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>> {\n const key: string = edgeTargetKey(target);\n if (vector.length === 0) {\n return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));\n }\n if (this._dimension !== undefined && vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: cannot add '${key}': dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult(() => {\n if (this._stmts === undefined) {\n this._createTable(vector.length);\n this._dimension = vector.length;\n this._stmts = this._prepare();\n }\n this._stmts.replace(key, SqliteVecVectorIndex._toBlob(vector));\n return key;\n }).withErrorFormat((e) => `vector index: cannot add '${key}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(\n captureResult(() => {\n // Before any add has created the table there is nothing held, which is a\n // truthful `false` rather than an error — same posture as `remove`'s\n // idempotence and `size`'s zero.\n if (this._stmts === undefined) {\n return false;\n }\n return this._stmts.has.get(edgeTargetKey(target)) !== undefined;\n }).withErrorFormat((e) => `vector index: cannot check '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n return Promise.resolve(\n captureResult(() => {\n // Idempotent: removing a target with no embedding (or before any `add`\n // created the table) still succeeds.\n if (this._stmts !== undefined) {\n this._stmts.delete.run(edgeTargetKey(target));\n }\n return target;\n }).withErrorFormat((e) => `vector index: cannot remove '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /**\n * Re-embed every record from `source` and rebuild the persisted index — see\n * `IVectorIndex.rebuild` for the mode semantics, which this implementation\n * matches exactly.\n *\n * @remarks\n * **Not atomic, and cannot be.** `better-sqlite3` transactions are synchronous,\n * so one cannot span the `await embed(...)` calls this loop makes — unlike\n * {@link SqliteVecVectorIndex.add}, which wraps its delete-then-insert. The\n * `'fail'` / `'skip'` modes therefore cover only failures JavaScript can catch:\n * a process kill mid-rebuild leaves the table holding neither the old index nor\n * the complete new one, and the remedy is to run `rebuild` again.\n */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: MemoryEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IVectorRebuildReport, IVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n // `source` is consumer-supplied, so a throw or rejection becomes a `Failure`\n // here rather than escaping as an exception.\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE any clear: a failed list is no evidence about the\n // vectors already held, and no re-embedding has been attempted, so there is\n // no half-rebuilt state to protect against. Clearing here would destroy a\n // healthy persisted index over a transient read error. No report either, for\n // the same reason — there is nothing this call disturbed to describe.\n return failWithDetail(`vector index rebuild: failed to list records: ${listed.message}`);\n }\n const cleared: Result<true> = this._clear();\n if (cleared.isFailure()) {\n // Also nothing established: the table still holds whatever it held.\n return failWithDetail(`vector index rebuild: failed to clear the index: ${cleared.message}`);\n }\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Absent stays absent — only the source knows whether it filtered anything.\n const report = (): IVectorRebuildReport => ({\n indexed,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Likewise capture-wrapped: an embedder that throws mid-loop would\n // otherwise escape past the `'fail'` rollback below, leaving this DURABLE\n // table holding a partial index that survives the process.\n const embedded: Result<Float32Array | undefined> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `vector index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (embedded.value === undefined) {\n tally(declined, kind);\n continue;\n }\n const added: Result<string> = await this.add(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `vector index rebuild: ${added.message}`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // Tallied in the loop rather than read back off `size` at the end. That\n // `COUNT` was also the only fallible step in assembling the report, so the\n // per-kind tally removes a failure path as well as a rounding of the answer.\n tally(indexed, kind);\n }\n return succeedWithDetail(report());\n }\n\n /**\n * **Empties the rows; does NOT release the table's declared dimension.** That\n * is a `vec0` constraint rather than a choice — the dimension is schema, and\n * there is no `ALTER TABLE` for it — so a rebuild at a new dimension fails\n * here where it would succeed on the in-memory sibling, which forgets its\n * dimension on reset. Changing dimension needs a drop-and-re-index; see the\n * note on `IVectorIndex.rebuild`.\n */\n private _clear(): Result<true> {\n if (this._stmts === undefined) {\n return succeed(true);\n }\n // Capture-wrapped like `add` / `remove` / `query`: a closed connection or an\n // I/O error here is a `Failure`, not an exception thrown out of a method\n // whose signature promises a `Result`.\n return captureResult(() => this._db.prepare(`DELETE FROM \"${this._table}\"`).run()).onSuccess(() =>\n succeed(true)\n );\n }\n\n /** {@inheritDoc IVectorIndex.query} */\n public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._stmts === undefined) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult<ReadonlyArray<IVectorQueryHit>>(() => {\n const rows: ReadonlyArray<IKnnRow> = this._stmts!.query.all(\n SqliteVecVectorIndex._toBlob(vector),\n topK\n ) as ReadonlyArray<IKnnRow>;\n // sqlite-vec returns rows in ascending distance (nearest first); score is\n // `1 - cosineDistance` = cosine similarity, so descending score is preserved.\n return rows.map((row) => ({\n target: SqliteVecVectorIndex._parseKey(row.target_key),\n score: 1 - row.distance\n }));\n }).withErrorFormat((e) => `vector index: query failed: ${e}`)\n );\n }\n\n /** Create the `vec0` virtual table with the established dimension. */\n private _createTable(dimension: number): void {\n this._db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS \"${this._table}\" USING vec0(` +\n `target_key TEXT PRIMARY KEY, embedding float[${dimension}] distance_metric=cosine)`\n );\n }\n\n /** Prepare the statements the index reuses. Requires the table to exist. */\n private _prepare(): ISqliteVecStatements {\n const del: BetterSqlite3.Statement = this._db.prepare(\n `DELETE FROM \"${this._table}\" WHERE target_key = ?`\n );\n const ins: BetterSqlite3.Statement = this._db.prepare(\n `INSERT INTO \"${this._table}\"(target_key, embedding) VALUES (?, ?)`\n );\n // vec0 rejects INSERT OR REPLACE on a TEXT primary key, so replace is a\n // delete-then-insert inside a single transaction.\n const replaceTxn: BetterSqlite3.Transaction<(key: string, blob: Uint8Array) => void> =\n this._db.transaction((key: string, blob: Uint8Array) => {\n del.run(key);\n ins.run(key, blob);\n });\n return {\n delete: del,\n replace: (key: string, blob: Uint8Array): void => {\n replaceTxn(key, blob);\n },\n query: this._db.prepare(\n `SELECT target_key, distance FROM \"${this._table}\" WHERE embedding MATCH ? AND k = ?`\n ),\n count: this._db.prepare(`SELECT count(*) AS c FROM \"${this._table}\"`),\n // `LIMIT 1` rather than a count: membership needs existence, not cardinality,\n // and vec0 can stop at the first row.\n has: this._db.prepare(`SELECT 1 FROM \"${this._table}\" WHERE target_key = ? LIMIT 1`)\n };\n }\n\n /**\n * Recover the established dimension of an existing `vec0` table from its stored\n * `CREATE VIRTUAL TABLE` SQL (`float[<n>]`). Returns `undefined` when the table\n * does not exist yet (a fresh database — dimension is set by the first `add`).\n */\n private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {\n const row: { sql: string } | undefined = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .get(table) as { sql: string } | undefined;\n if (row === undefined) {\n return undefined;\n }\n const match: RegExpMatchArray | null = row.sql.match(/float\\[(\\d+)\\]/);\n return match === null ? undefined : Number(match[1]);\n }\n\n /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */\n private static _toBlob(vector: Float32Array): Uint8Array {\n return new Uint8Array(Float32Array.from(vector).buffer);\n }\n\n /**\n * Reverse `edgeTargetKey` — the canonical key is `scope\\0id` with NUL\n * excluded from both components, so the first NUL splits it unambiguously.\n */\n private static _parseKey(key: string): IEdgeTarget {\n const nul: number = key.indexOf('\\0');\n return {\n scope: key.slice(0, nul) as unknown as MemoryScopeKey,\n id: key.slice(nul + 1) as unknown as MemoryId\n };\n }\n}\n\n/** The prepared statements / helpers the index reuses once its table exists. */\ninterface ISqliteVecStatements {\n readonly delete: BetterSqlite3.Statement;\n readonly replace: (key: string, blob: Uint8Array) => void;\n readonly query: BetterSqlite3.Statement;\n readonly count: BetterSqlite3.Statement;\n readonly has: BetterSqlite3.Statement;\n}\n"]}
|
|
@@ -3,6 +3,7 @@ import { DetailedResult } from '@fgv/ts-utils';
|
|
|
3
3
|
import { FragmentEmbedder } from '@fgv/ts-agent-memory';
|
|
4
4
|
import { IEdgeTarget } from '@fgv/ts-agent-memory';
|
|
5
5
|
import { IEmbeddedFragment } from '@fgv/ts-agent-memory';
|
|
6
|
+
import { IFragmentQueryOptions } from '@fgv/ts-agent-memory';
|
|
6
7
|
import { IFragmentVectorIndex } from '@fgv/ts-agent-memory';
|
|
7
8
|
import { IFragmentVectorRebuildReport } from '@fgv/ts-agent-memory';
|
|
8
9
|
import { IMemoryRecordSource } from '@fgv/ts-agent-memory';
|
|
@@ -37,6 +38,47 @@ export declare interface ISqliteVecFragmentIndexCreateParams {
|
|
|
37
38
|
readonly tableName?: string;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* An index plus the connection {@link SqliteVecFragmentIndex.open} opened for it.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export declare interface ISqliteVecFragmentIndexHandle {
|
|
47
|
+
/** The index, ready to use. */
|
|
48
|
+
readonly index: SqliteVecFragmentIndex;
|
|
49
|
+
/**
|
|
50
|
+
* Closes the connection **this `open` call created**. Idempotent — a second
|
|
51
|
+
* `close` succeeds rather than failing. See
|
|
52
|
+
* {@link ISqliteVecVectorIndexHandle.close} for why the disposer lives here
|
|
53
|
+
* rather than on the index class.
|
|
54
|
+
*/
|
|
55
|
+
close(): Result<true>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parameters for {@link SqliteVecFragmentIndex.open}.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
export declare interface ISqliteVecFragmentIndexOpenParams {
|
|
63
|
+
/**
|
|
64
|
+
* Filesystem path to the database file, opened by this package rather than by
|
|
65
|
+
* the consumer. Created if it does not exist, exactly as `better-sqlite3` would.
|
|
66
|
+
* `':memory:'` is accepted and yields an owned ephemeral connection.
|
|
67
|
+
*
|
|
68
|
+
* **Two `open` calls on one path produce two independent connections, not a
|
|
69
|
+
* shared one** — see {@link ISqliteVecVectorIndexOpenParams.path}. To put a
|
|
70
|
+
* fragment index and a record index on one connection, open it yourself and pass
|
|
71
|
+
* it to both `create` methods.
|
|
72
|
+
*/
|
|
73
|
+
readonly path: string;
|
|
74
|
+
/**
|
|
75
|
+
* Name of the `vec0` virtual table that holds the fragment embeddings. Must be a
|
|
76
|
+
* simple SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to
|
|
77
|
+
* `'memory_fragments'`.
|
|
78
|
+
*/
|
|
79
|
+
readonly tableName?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
40
82
|
/**
|
|
41
83
|
* Parameters for {@link SqliteVecVectorIndex.create}.
|
|
42
84
|
* @public
|
|
@@ -61,6 +103,58 @@ export declare interface ISqliteVecVectorIndexCreateParams {
|
|
|
61
103
|
readonly tableName?: string;
|
|
62
104
|
}
|
|
63
105
|
|
|
106
|
+
/**
|
|
107
|
+
* An index plus the connection {@link SqliteVecVectorIndex.open} opened for it.
|
|
108
|
+
*
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
111
|
+
export declare interface ISqliteVecVectorIndexHandle {
|
|
112
|
+
/** The index, ready to use. */
|
|
113
|
+
readonly index: SqliteVecVectorIndex;
|
|
114
|
+
/**
|
|
115
|
+
* Closes the connection **this `open` call created**. Idempotent — a second
|
|
116
|
+
* `close` succeeds rather than failing.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* The disposer travels on this handle rather than on the index class because an
|
|
120
|
+
* index built by `create` holds a connection the **consumer** owns, and must stay
|
|
121
|
+
* incapable of closing it. A `close()` method meaningful on some instances and
|
|
122
|
+
* forbidden on others would be a lie in the type; here, only the caller that
|
|
123
|
+
* caused the connection to exist is handed the means to end it.
|
|
124
|
+
*
|
|
125
|
+
* The index is unusable afterwards — every operation on it will fail against a
|
|
126
|
+
* closed connection.
|
|
127
|
+
*/
|
|
128
|
+
close(): Result<true>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parameters for {@link SqliteVecVectorIndex.open}.
|
|
133
|
+
* @public
|
|
134
|
+
*/
|
|
135
|
+
export declare interface ISqliteVecVectorIndexOpenParams {
|
|
136
|
+
/**
|
|
137
|
+
* Filesystem path to the database file, opened by this package rather than by
|
|
138
|
+
* the consumer. Created if it does not exist, exactly as `better-sqlite3` would.
|
|
139
|
+
* `':memory:'` is accepted and yields an owned ephemeral connection.
|
|
140
|
+
*
|
|
141
|
+
* **Two `open` calls on one path produce two independent connections, not a
|
|
142
|
+
* shared one.** That is legal in SQLite and has a different locking story than
|
|
143
|
+
* the single-connection case — writes contend, and a reader can see a
|
|
144
|
+
* `SQLITE_BUSY`. To put a record index and a fragment index on one connection
|
|
145
|
+
* (the intended shared-handle case), open the connection yourself and pass it to
|
|
146
|
+
* both `create` methods.
|
|
147
|
+
*/
|
|
148
|
+
readonly path: string;
|
|
149
|
+
/**
|
|
150
|
+
* Name of the `vec0` virtual table that holds the embeddings. Must be a simple
|
|
151
|
+
* SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to `'memory_vectors'`.
|
|
152
|
+
* Supply a distinct name to hold more than one independent index in a single
|
|
153
|
+
* database file.
|
|
154
|
+
*/
|
|
155
|
+
readonly tableName?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
64
158
|
/**
|
|
65
159
|
* A persistent, `sqlite-vec`-backed `IFragmentVectorIndex` (from
|
|
66
160
|
* `@fgv/ts-agent-memory`) — the fragment-granular sibling of
|
|
@@ -100,9 +194,13 @@ export declare interface ISqliteVecVectorIndexCreateParams {
|
|
|
100
194
|
* cosine (`score = 1 - cosineDistance`), byte-identical to the in-memory index.
|
|
101
195
|
* Large-N ANN indexing is explicitly out of scope, same regime as the record index.
|
|
102
196
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
197
|
+
* **Connection ownership depends on which factory you use.** With
|
|
198
|
+
* {@link SqliteVecFragmentIndex.create} the `Database` is consumer-owned
|
|
199
|
+
* (bring-your-own): this index loads the `sqlite-vec` extension onto it and
|
|
200
|
+
* reads/writes the table, but never opens or closes the connection — and that is
|
|
201
|
+
* the seam for backing this index and a record index with one connection. With
|
|
202
|
+
* {@link SqliteVecFragmentIndex.open} this package opens the file itself and hands
|
|
203
|
+
* back a handle carrying the disposer for the connection it created.
|
|
106
204
|
* @public
|
|
107
205
|
*/
|
|
108
206
|
export declare class SqliteVecFragmentIndex implements IFragmentVectorIndex {
|
|
@@ -131,6 +229,34 @@ export declare class SqliteVecFragmentIndex implements IFragmentVectorIndex {
|
|
|
131
229
|
* drop-and-re-index — `vec0` cannot be altered in place).
|
|
132
230
|
*/
|
|
133
231
|
static create(params: ISqliteVecFragmentIndexCreateParams): Promise<Result<SqliteVecFragmentIndex>>;
|
|
232
|
+
/**
|
|
233
|
+
* Path-based factory. Opens the database file itself and returns the index
|
|
234
|
+
* together with a disposer for the connection it created.
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* The fragment-granular sibling of {@link SqliteVecVectorIndex.open}, and present
|
|
238
|
+
* for the same reason: a consumer doing sub-document retrieval only would
|
|
239
|
+
* otherwise still value-import `better-sqlite3` and hand-roll a `captureResult`
|
|
240
|
+
* around a constructor that throws.
|
|
241
|
+
*
|
|
242
|
+
* **Use `create` instead when one connection must back both a fragment index and
|
|
243
|
+
* a record index** — the intended shared-handle case. Two `open` calls on one path
|
|
244
|
+
* give two independent connections, not a shared one.
|
|
245
|
+
*
|
|
246
|
+
* If initialization fails after the file is opened, the connection is closed
|
|
247
|
+
* before returning, so a failed `open` does not leak the descriptor it created.
|
|
248
|
+
* Should that close *itself* fail — the connection is then genuinely leaked — the
|
|
249
|
+
* returned message says so rather than hiding it. That includes the
|
|
250
|
+
* auxiliary-column mismatch failure, which is reported by `create` only after the
|
|
251
|
+
* file is open.
|
|
252
|
+
*
|
|
253
|
+
* @param params - See {@link ISqliteVecFragmentIndexOpenParams}.
|
|
254
|
+
* @returns `Success` with a {@link ISqliteVecFragmentIndexHandle}, or `Failure` if
|
|
255
|
+
* the driver could not be loaded, the file could not be opened, the table name is
|
|
256
|
+
* not a simple identifier, the extension fails to load, or the existing table was
|
|
257
|
+
* written by a version with a different auxiliary-column set.
|
|
258
|
+
*/
|
|
259
|
+
static open(params: ISqliteVecFragmentIndexOpenParams): Promise<Result<ISqliteVecFragmentIndexHandle>>;
|
|
134
260
|
/** {@inheritDoc IFragmentVectorIndex.addFragments} */
|
|
135
261
|
addFragments(target: IEdgeTarget, fragments: ReadonlyArray<IEmbeddedFragment>): Promise<Result<number>>;
|
|
136
262
|
/** {@inheritDoc IFragmentVectorIndex.remove} */
|
|
@@ -149,7 +275,7 @@ export declare class SqliteVecFragmentIndex implements IFragmentVectorIndex {
|
|
|
149
275
|
*/
|
|
150
276
|
private _clear;
|
|
151
277
|
/** {@inheritDoc IFragmentVectorIndex.query} */
|
|
152
|
-
query(vector: Float32Array, topK: number,
|
|
278
|
+
query(vector: Float32Array, topK: number, options?: IFragmentQueryOptions): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
|
|
153
279
|
/**
|
|
154
280
|
* Create the fragment `vec0` virtual table with the established dimension. The
|
|
155
281
|
* auxiliary columns must stay in sync with `AUXILIARY_COLUMNS`, which
|
|
@@ -248,9 +374,13 @@ export declare class SqliteVecFragmentIndex implements IFragmentVectorIndex {
|
|
|
248
374
|
* durable, appropriate for the same "thousands of records" regime the in-memory
|
|
249
375
|
* index targets. Large-N ANN indexing is explicitly out of scope — see the README.
|
|
250
376
|
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
377
|
+
* **Connection ownership depends on which factory you use.** With
|
|
378
|
+
* {@link SqliteVecVectorIndex.create} the `Database` is consumer-owned
|
|
379
|
+
* (bring-your-own): this index loads the `sqlite-vec` extension onto it and
|
|
380
|
+
* reads/writes the table, but never opens or closes the connection — and that is
|
|
381
|
+
* the seam for backing a record index and a fragment index with one connection.
|
|
382
|
+
* With {@link SqliteVecVectorIndex.open} this package opens the file itself and
|
|
383
|
+
* hands back a handle carrying the disposer for the connection it created.
|
|
254
384
|
* @public
|
|
255
385
|
*/
|
|
256
386
|
export declare class SqliteVecVectorIndex implements IVectorIndex {
|
|
@@ -274,6 +404,32 @@ export declare class SqliteVecVectorIndex implements IVectorIndex {
|
|
|
274
404
|
* simple identifier or the extension fails to load.
|
|
275
405
|
*/
|
|
276
406
|
static create(params: ISqliteVecVectorIndexCreateParams): Promise<Result<SqliteVecVectorIndex>>;
|
|
407
|
+
/**
|
|
408
|
+
* Path-based factory. Opens the database file itself and returns the index
|
|
409
|
+
* together with a disposer for the connection it created.
|
|
410
|
+
*
|
|
411
|
+
* @remarks
|
|
412
|
+
* The convenience over {@link SqliteVecVectorIndex.create} is that the consumer
|
|
413
|
+
* neither value-imports `better-sqlite3` nor re-establishes `Result` discipline
|
|
414
|
+
* around a constructor that throws — this is the one place the package leaked its
|
|
415
|
+
* own dependency into consumer source.
|
|
416
|
+
*
|
|
417
|
+
* **Use `create` instead when one connection must back more than one index** (a
|
|
418
|
+
* record index and a fragment index in the same file, the intended shared-handle
|
|
419
|
+
* case). Two `open` calls on one path give two independent connections, not a
|
|
420
|
+
* shared one.
|
|
421
|
+
*
|
|
422
|
+
* If initialization fails after the file is opened, the connection is closed
|
|
423
|
+
* before returning, so a failed `open` does not leak the descriptor it created.
|
|
424
|
+
* Should that close *itself* fail — the connection is then genuinely leaked — the
|
|
425
|
+
* returned message says so rather than hiding it.
|
|
426
|
+
*
|
|
427
|
+
* @param params - See {@link ISqliteVecVectorIndexOpenParams}.
|
|
428
|
+
* @returns `Success` with a {@link ISqliteVecVectorIndexHandle}, or `Failure` if
|
|
429
|
+
* the driver could not be loaded, the file could not be opened, the table name is
|
|
430
|
+
* not a simple identifier, or the extension fails to load.
|
|
431
|
+
*/
|
|
432
|
+
static open(params: ISqliteVecVectorIndexOpenParams): Promise<Result<ISqliteVecVectorIndexHandle>>;
|
|
277
433
|
/** {@inheritDoc IVectorIndex.add} */
|
|
278
434
|
add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>>;
|
|
279
435
|
/** {@inheritDoc IVectorIndex.has} */
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
import { Result } from '@fgv/ts-utils';
|
|
3
|
+
/**
|
|
4
|
+
* Opens a `better-sqlite3` connection this package owns.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* **The value import of `better-sqlite3` lives here, and it is deliberately
|
|
8
|
+
* lazy.** Every other module in this package imports the driver as `import type`
|
|
9
|
+
* only, so merely importing `@fgv/ts-agent-memory-sqlite-vec` has never loaded the
|
|
10
|
+
* native binding — a property the path-based factories must not cost consumers who
|
|
11
|
+
* only ever call `create()`. A static import would move the native load (and its
|
|
12
|
+
* failure mode) to package-load time for everyone. The dynamic `import()` keeps it
|
|
13
|
+
* at the one call that actually needs a connection.
|
|
14
|
+
*
|
|
15
|
+
* Taking this import is the entire point of the path-based factories: it is the
|
|
16
|
+
* only place this wrapper leaked its own dependency into consumer source.
|
|
17
|
+
*
|
|
18
|
+
* @param path - Filesystem path to the database file. `':memory:'` is accepted and
|
|
19
|
+
* yields an owned ephemeral connection.
|
|
20
|
+
* @param label - Package-facing prefix for the failure message.
|
|
21
|
+
* @returns `Success` with a connection this package owns, or `Failure` if the
|
|
22
|
+
* driver could not be loaded or the file could not be opened.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare function openOwnedConnection(path: string, label: string): Promise<Result<BetterSqlite3.Database>>;
|
|
26
|
+
/**
|
|
27
|
+
* Closes a connection this package opened.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* Only ever called on a connection produced by {@link openOwnedConnection}. A
|
|
31
|
+
* connection supplied through a `create()` param is the consumer's and is never
|
|
32
|
+
* closed here — that asymmetry is the reason `close` travels on the handle
|
|
33
|
+
* returned by `open` rather than sitting on the index class.
|
|
34
|
+
*
|
|
35
|
+
* `better-sqlite3`'s own `close()` is safe to call on an already-closed
|
|
36
|
+
* connection, so a second `close()` on the same handle succeeds rather than
|
|
37
|
+
* failing.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export declare function closeOwnedConnection(db: BetterSqlite3.Database, label: string): Result<true>;
|
|
42
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/connection.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,aAAa,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAqC,MAAM,eAAe,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAMzC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAK5F"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.openOwnedConnection = openOwnedConnection;
|
|
41
|
+
exports.closeOwnedConnection = closeOwnedConnection;
|
|
42
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
43
|
+
/**
|
|
44
|
+
* Opens a `better-sqlite3` connection this package owns.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* **The value import of `better-sqlite3` lives here, and it is deliberately
|
|
48
|
+
* lazy.** Every other module in this package imports the driver as `import type`
|
|
49
|
+
* only, so merely importing `@fgv/ts-agent-memory-sqlite-vec` has never loaded the
|
|
50
|
+
* native binding — a property the path-based factories must not cost consumers who
|
|
51
|
+
* only ever call `create()`. A static import would move the native load (and its
|
|
52
|
+
* failure mode) to package-load time for everyone. The dynamic `import()` keeps it
|
|
53
|
+
* at the one call that actually needs a connection.
|
|
54
|
+
*
|
|
55
|
+
* Taking this import is the entire point of the path-based factories: it is the
|
|
56
|
+
* only place this wrapper leaked its own dependency into consumer source.
|
|
57
|
+
*
|
|
58
|
+
* @param path - Filesystem path to the database file. `':memory:'` is accepted and
|
|
59
|
+
* yields an owned ephemeral connection.
|
|
60
|
+
* @param label - Package-facing prefix for the failure message.
|
|
61
|
+
* @returns `Success` with a connection this package owns, or `Failure` if the
|
|
62
|
+
* driver could not be loaded or the file could not be opened.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
async function openOwnedConnection(path, label) {
|
|
66
|
+
return (await (0, ts_utils_1.captureAsyncResult)(async () => (await Promise.resolve().then(() => __importStar(require('better-sqlite3')))).default))
|
|
67
|
+
.withErrorFormat((m) => `${label}: failed to load the 'better-sqlite3' driver: ${m}`)
|
|
68
|
+
.onSuccess((driver) => (0, ts_utils_1.captureResult)(() => new driver(path)).withErrorFormat((m) => `${label}: failed to open '${path}': ${m}`));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Closes a connection this package opened.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Only ever called on a connection produced by {@link openOwnedConnection}. A
|
|
75
|
+
* connection supplied through a `create()` param is the consumer's and is never
|
|
76
|
+
* closed here — that asymmetry is the reason `close` travels on the handle
|
|
77
|
+
* returned by `open` rather than sitting on the index class.
|
|
78
|
+
*
|
|
79
|
+
* `better-sqlite3`'s own `close()` is safe to call on an already-closed
|
|
80
|
+
* connection, so a second `close()` on the same handle succeeds rather than
|
|
81
|
+
* failing.
|
|
82
|
+
*
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
function closeOwnedConnection(db, label) {
|
|
86
|
+
return (0, ts_utils_1.captureResult)(() => {
|
|
87
|
+
db.close();
|
|
88
|
+
return true;
|
|
89
|
+
}).withErrorFormat((m) => `${label}: failed to close the connection: ${m}`);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/connection.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BH,kDASC;AAiBD,oDAKC;AAvDD,4CAA0E;AAE1E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,KAAK,UAAU,mBAAmB,CACvC,IAAY,EACZ,KAAa;IAEb,OAAO,CAAC,MAAM,IAAA,6BAAkB,EAAC,KAAK,IAAI,EAAE,CAAC,CAAC,wDAAa,gBAAgB,GAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACpF,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,iDAAiD,CAAC,EAAE,CAAC;SACpF,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CACpB,IAAA,wBAAa,EAAC,GAAG,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,qBAAqB,IAAI,MAAM,CAAC,EAAE,CAAC,CACzG,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,oBAAoB,CAAC,EAA0B,EAAE,KAAa;IAC5E,OAAO,IAAA,wBAAa,EAAO,GAAG,EAAE;QAC9B,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,qCAAqC,CAAC,EAAE,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { Result, captureAsyncResult, captureResult } from '@fgv/ts-utils';\n\n/**\n * Opens a `better-sqlite3` connection this package owns.\n *\n * @remarks\n * **The value import of `better-sqlite3` lives here, and it is deliberately\n * lazy.** Every other module in this package imports the driver as `import type`\n * only, so merely importing `@fgv/ts-agent-memory-sqlite-vec` has never loaded the\n * native binding — a property the path-based factories must not cost consumers who\n * only ever call `create()`. A static import would move the native load (and its\n * failure mode) to package-load time for everyone. The dynamic `import()` keeps it\n * at the one call that actually needs a connection.\n *\n * Taking this import is the entire point of the path-based factories: it is the\n * only place this wrapper leaked its own dependency into consumer source.\n *\n * @param path - Filesystem path to the database file. `':memory:'` is accepted and\n * yields an owned ephemeral connection.\n * @param label - Package-facing prefix for the failure message.\n * @returns `Success` with a connection this package owns, or `Failure` if the\n * driver could not be loaded or the file could not be opened.\n * @internal\n */\nexport async function openOwnedConnection(\n path: string,\n label: string\n): Promise<Result<BetterSqlite3.Database>> {\n return (await captureAsyncResult(async () => (await import('better-sqlite3')).default))\n .withErrorFormat((m) => `${label}: failed to load the 'better-sqlite3' driver: ${m}`)\n .onSuccess((driver) =>\n captureResult(() => new driver(path)).withErrorFormat((m) => `${label}: failed to open '${path}': ${m}`)\n );\n}\n\n/**\n * Closes a connection this package opened.\n *\n * @remarks\n * Only ever called on a connection produced by {@link openOwnedConnection}. A\n * connection supplied through a `create()` param is the consumer's and is never\n * closed here — that asymmetry is the reason `close` travels on the handle\n * returned by `open` rather than sitting on the index class.\n *\n * `better-sqlite3`'s own `close()` is safe to call on an already-closed\n * connection, so a second `close()` on the same handle succeeds rather than\n * failing.\n *\n * @internal\n */\nexport function closeOwnedConnection(db: BetterSqlite3.Database, label: string): Result<true> {\n return captureResult<true>(() => {\n db.close();\n return true;\n }).withErrorFormat((m) => `${label}: failed to close the connection: ${m}`);\n}\n"]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
import type { Result } from '@fgv/ts-utils';
|
|
3
|
+
import type { SqliteVecFragmentIndex } from './sqliteVecFragmentIndex';
|
|
4
|
+
import type { SqliteVecVectorIndex } from './sqliteVecVectorIndex';
|
|
2
5
|
/**
|
|
3
6
|
* Parameters for {@link SqliteVecVectorIndex.create}.
|
|
4
7
|
* @public
|
|
@@ -22,6 +25,95 @@ export interface ISqliteVecVectorIndexCreateParams {
|
|
|
22
25
|
*/
|
|
23
26
|
readonly tableName?: string;
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Parameters for {@link SqliteVecVectorIndex.open}.
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export interface ISqliteVecVectorIndexOpenParams {
|
|
33
|
+
/**
|
|
34
|
+
* Filesystem path to the database file, opened by this package rather than by
|
|
35
|
+
* the consumer. Created if it does not exist, exactly as `better-sqlite3` would.
|
|
36
|
+
* `':memory:'` is accepted and yields an owned ephemeral connection.
|
|
37
|
+
*
|
|
38
|
+
* **Two `open` calls on one path produce two independent connections, not a
|
|
39
|
+
* shared one.** That is legal in SQLite and has a different locking story than
|
|
40
|
+
* the single-connection case — writes contend, and a reader can see a
|
|
41
|
+
* `SQLITE_BUSY`. To put a record index and a fragment index on one connection
|
|
42
|
+
* (the intended shared-handle case), open the connection yourself and pass it to
|
|
43
|
+
* both `create` methods.
|
|
44
|
+
*/
|
|
45
|
+
readonly path: string;
|
|
46
|
+
/**
|
|
47
|
+
* Name of the `vec0` virtual table that holds the embeddings. Must be a simple
|
|
48
|
+
* SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to `'memory_vectors'`.
|
|
49
|
+
* Supply a distinct name to hold more than one independent index in a single
|
|
50
|
+
* database file.
|
|
51
|
+
*/
|
|
52
|
+
readonly tableName?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* An index plus the connection {@link SqliteVecVectorIndex.open} opened for it.
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
export interface ISqliteVecVectorIndexHandle {
|
|
60
|
+
/** The index, ready to use. */
|
|
61
|
+
readonly index: SqliteVecVectorIndex;
|
|
62
|
+
/**
|
|
63
|
+
* Closes the connection **this `open` call created**. Idempotent — a second
|
|
64
|
+
* `close` succeeds rather than failing.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* The disposer travels on this handle rather than on the index class because an
|
|
68
|
+
* index built by `create` holds a connection the **consumer** owns, and must stay
|
|
69
|
+
* incapable of closing it. A `close()` method meaningful on some instances and
|
|
70
|
+
* forbidden on others would be a lie in the type; here, only the caller that
|
|
71
|
+
* caused the connection to exist is handed the means to end it.
|
|
72
|
+
*
|
|
73
|
+
* The index is unusable afterwards — every operation on it will fail against a
|
|
74
|
+
* closed connection.
|
|
75
|
+
*/
|
|
76
|
+
close(): Result<true>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parameters for {@link SqliteVecFragmentIndex.open}.
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
export interface ISqliteVecFragmentIndexOpenParams {
|
|
83
|
+
/**
|
|
84
|
+
* Filesystem path to the database file, opened by this package rather than by
|
|
85
|
+
* the consumer. Created if it does not exist, exactly as `better-sqlite3` would.
|
|
86
|
+
* `':memory:'` is accepted and yields an owned ephemeral connection.
|
|
87
|
+
*
|
|
88
|
+
* **Two `open` calls on one path produce two independent connections, not a
|
|
89
|
+
* shared one** — see {@link ISqliteVecVectorIndexOpenParams.path}. To put a
|
|
90
|
+
* fragment index and a record index on one connection, open it yourself and pass
|
|
91
|
+
* it to both `create` methods.
|
|
92
|
+
*/
|
|
93
|
+
readonly path: string;
|
|
94
|
+
/**
|
|
95
|
+
* Name of the `vec0` virtual table that holds the fragment embeddings. Must be a
|
|
96
|
+
* simple SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to
|
|
97
|
+
* `'memory_fragments'`.
|
|
98
|
+
*/
|
|
99
|
+
readonly tableName?: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* An index plus the connection {@link SqliteVecFragmentIndex.open} opened for it.
|
|
103
|
+
*
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
export interface ISqliteVecFragmentIndexHandle {
|
|
107
|
+
/** The index, ready to use. */
|
|
108
|
+
readonly index: SqliteVecFragmentIndex;
|
|
109
|
+
/**
|
|
110
|
+
* Closes the connection **this `open` call created**. Idempotent — a second
|
|
111
|
+
* `close` succeeds rather than failing. See
|
|
112
|
+
* {@link ISqliteVecVectorIndexHandle.close} for why the disposer lives here
|
|
113
|
+
* rather than on the index class.
|
|
114
|
+
*/
|
|
115
|
+
close(): Result<true>;
|
|
116
|
+
}
|
|
25
117
|
/**
|
|
26
118
|
* Parameters for {@link SqliteVecFragmentIndex.create}.
|
|
27
119
|
* @public
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/model.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,aAAa,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/model.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,aAAa,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;GAGG;AACH,MAAM,WAAW,iCAAiC;IAChD;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC;IAE1C;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IAErC;;;;;;;;;;;;;OAaG;IACH,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,iCAAiC;IAChD;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IAEvC;;;;;OAKG;IACH,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC;IAE1C;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/model.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\n/**\n * Parameters for {@link SqliteVecVectorIndex.create}.\n * @public\n */\nexport interface ISqliteVecVectorIndexCreateParams {\n /**\n * A `better-sqlite3` `Database` the consumer owns (bring-your-own, mirroring\n * the boundary-package convention). The consumer opens it (`new Database(path)`\n * for a persistent file, or `new Database(':memory:')` for an ephemeral index)\n * and owns its lifecycle — this index never closes it. `create` loads the\n * `sqlite-vec` extension onto the connection and, if the vector table already\n * exists (a reopened persistent file), recovers its established dimension so no\n * re-embedding is required on open.\n */\n readonly database: BetterSqlite3.Database;\n\n /**\n * Name of the `vec0` virtual table that holds the embeddings. Must be a simple\n * SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to `'memory_vectors'`.\n * Supply a distinct name to hold more than one independent index in a single\n * database file.\n */\n readonly tableName?: string;\n}\n\n/**\n * Parameters for {@link SqliteVecFragmentIndex.create}.\n * @public\n */\nexport interface ISqliteVecFragmentIndexCreateParams {\n /**\n * A `better-sqlite3` `Database` the consumer owns (bring-your-own, mirroring\n * {@link ISqliteVecVectorIndexCreateParams.database}). The consumer opens it and\n * owns its lifecycle — this index never closes it. `create` loads the\n * `sqlite-vec` extension onto the connection and, if the fragment table already\n * exists (a reopened persistent file), recovers its established dimension so no\n * re-embedding is required on open.\n */\n readonly database: BetterSqlite3.Database;\n\n /**\n * Name of the `vec0` virtual table that holds the fragment embeddings. Must be a\n * simple SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to\n * `'memory_fragments'`. Supply a distinct name (distinct from any record-level\n * {@link SqliteVecVectorIndex} table) to hold more than one independent index in\n * a single database file.\n */\n readonly tableName?: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/model.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport type { Result } from '@fgv/ts-utils';\nimport type { SqliteVecFragmentIndex } from './sqliteVecFragmentIndex';\nimport type { SqliteVecVectorIndex } from './sqliteVecVectorIndex';\n\n/**\n * Parameters for {@link SqliteVecVectorIndex.create}.\n * @public\n */\nexport interface ISqliteVecVectorIndexCreateParams {\n /**\n * A `better-sqlite3` `Database` the consumer owns (bring-your-own, mirroring\n * the boundary-package convention). The consumer opens it (`new Database(path)`\n * for a persistent file, or `new Database(':memory:')` for an ephemeral index)\n * and owns its lifecycle — this index never closes it. `create` loads the\n * `sqlite-vec` extension onto the connection and, if the vector table already\n * exists (a reopened persistent file), recovers its established dimension so no\n * re-embedding is required on open.\n */\n readonly database: BetterSqlite3.Database;\n\n /**\n * Name of the `vec0` virtual table that holds the embeddings. Must be a simple\n * SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to `'memory_vectors'`.\n * Supply a distinct name to hold more than one independent index in a single\n * database file.\n */\n readonly tableName?: string;\n}\n\n/**\n * Parameters for {@link SqliteVecVectorIndex.open}.\n * @public\n */\nexport interface ISqliteVecVectorIndexOpenParams {\n /**\n * Filesystem path to the database file, opened by this package rather than by\n * the consumer. Created if it does not exist, exactly as `better-sqlite3` would.\n * `':memory:'` is accepted and yields an owned ephemeral connection.\n *\n * **Two `open` calls on one path produce two independent connections, not a\n * shared one.** That is legal in SQLite and has a different locking story than\n * the single-connection case — writes contend, and a reader can see a\n * `SQLITE_BUSY`. To put a record index and a fragment index on one connection\n * (the intended shared-handle case), open the connection yourself and pass it to\n * both `create` methods.\n */\n readonly path: string;\n\n /**\n * Name of the `vec0` virtual table that holds the embeddings. Must be a simple\n * SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to `'memory_vectors'`.\n * Supply a distinct name to hold more than one independent index in a single\n * database file.\n */\n readonly tableName?: string;\n}\n\n/**\n * An index plus the connection {@link SqliteVecVectorIndex.open} opened for it.\n *\n * @public\n */\nexport interface ISqliteVecVectorIndexHandle {\n /** The index, ready to use. */\n readonly index: SqliteVecVectorIndex;\n\n /**\n * Closes the connection **this `open` call created**. Idempotent — a second\n * `close` succeeds rather than failing.\n *\n * @remarks\n * The disposer travels on this handle rather than on the index class because an\n * index built by `create` holds a connection the **consumer** owns, and must stay\n * incapable of closing it. A `close()` method meaningful on some instances and\n * forbidden on others would be a lie in the type; here, only the caller that\n * caused the connection to exist is handed the means to end it.\n *\n * The index is unusable afterwards — every operation on it will fail against a\n * closed connection.\n */\n close(): Result<true>;\n}\n\n/**\n * Parameters for {@link SqliteVecFragmentIndex.open}.\n * @public\n */\nexport interface ISqliteVecFragmentIndexOpenParams {\n /**\n * Filesystem path to the database file, opened by this package rather than by\n * the consumer. Created if it does not exist, exactly as `better-sqlite3` would.\n * `':memory:'` is accepted and yields an owned ephemeral connection.\n *\n * **Two `open` calls on one path produce two independent connections, not a\n * shared one** — see {@link ISqliteVecVectorIndexOpenParams.path}. To put a\n * fragment index and a record index on one connection, open it yourself and pass\n * it to both `create` methods.\n */\n readonly path: string;\n\n /**\n * Name of the `vec0` virtual table that holds the fragment embeddings. Must be a\n * simple SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to\n * `'memory_fragments'`.\n */\n readonly tableName?: string;\n}\n\n/**\n * An index plus the connection {@link SqliteVecFragmentIndex.open} opened for it.\n *\n * @public\n */\nexport interface ISqliteVecFragmentIndexHandle {\n /** The index, ready to use. */\n readonly index: SqliteVecFragmentIndex;\n\n /**\n * Closes the connection **this `open` call created**. Idempotent — a second\n * `close` succeeds rather than failing. See\n * {@link ISqliteVecVectorIndexHandle.close} for why the disposer lives here\n * rather than on the index class.\n */\n close(): Result<true>;\n}\n\n/**\n * Parameters for {@link SqliteVecFragmentIndex.create}.\n * @public\n */\nexport interface ISqliteVecFragmentIndexCreateParams {\n /**\n * A `better-sqlite3` `Database` the consumer owns (bring-your-own, mirroring\n * {@link ISqliteVecVectorIndexCreateParams.database}). The consumer opens it and\n * owns its lifecycle — this index never closes it. `create` loads the\n * `sqlite-vec` extension onto the connection and, if the fragment table already\n * exists (a reopened persistent file), recovers its established dimension so no\n * re-embedding is required on open.\n */\n readonly database: BetterSqlite3.Database;\n\n /**\n * Name of the `vec0` virtual table that holds the fragment embeddings. Must be a\n * simple SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to\n * `'memory_fragments'`. Supply a distinct name (distinct from any record-level\n * {@link SqliteVecVectorIndex} table) to hold more than one independent index in\n * a single database file.\n */\n readonly tableName?: string;\n}\n"]}
|