@an5/adapters 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +133 -2
  2. package/dist/an5Adapter.d.ts +37 -0
  3. package/dist/an5Adapter.js +138 -35
  4. package/dist/base/types.d.ts +14 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +8 -1
  7. package/dist/sqlite/browserEngine.d.ts +13 -0
  8. package/dist/sqlite/browserEngine.js +13 -0
  9. package/dist/sqlite/engine.d.ts +5 -0
  10. package/dist/sqlite/engine.js +10 -0
  11. package/dist/sqlite/index.d.ts +1 -0
  12. package/dist/sqlite/index.js +1 -0
  13. package/dist/sqlite/vector.d.ts +155 -0
  14. package/dist/sqlite/vector.js +436 -0
  15. package/dotnet/An5Adapter.cs +85 -27
  16. package/dotnet/Base/Core.cs +14 -0
  17. package/dotnet/Mssql/MssqlEngine.cs +44 -1
  18. package/dotnet/Postgres/PostgresEngine.cs +2 -0
  19. package/dotnet/Sqlite/SqliteEngine.cs +82 -2
  20. package/dotnet/Sqlite/SqliteVectors.cs +401 -0
  21. package/golang/an5_adapter.go +84 -32
  22. package/golang/base/vectors.go +263 -0
  23. package/golang/sqlite/vector.go +248 -0
  24. package/golang/sqlite_vector_test.go +288 -0
  25. package/golang/table_client.go +67 -3
  26. package/java/pom.xml +177 -0
  27. package/java/src/main/java/an5/adapters/An5Adapter.java +374 -0
  28. package/java/src/main/java/an5/adapters/An5Aggregate.java +116 -0
  29. package/java/src/main/java/an5/adapters/An5GroupBy.java +97 -0
  30. package/java/src/main/java/an5/adapters/An5Query.java +100 -0
  31. package/java/src/main/java/an5/adapters/An5TableClient.java +1031 -0
  32. package/java/src/main/java/an5/adapters/An5ViewClient.java +107 -0
  33. package/java/src/main/java/an5/adapters/base/Dialect.java +103 -0
  34. package/java/src/main/java/an5/adapters/base/FieldMeta.java +42 -0
  35. package/java/src/main/java/an5/adapters/base/JdbcUrls.java +201 -0
  36. package/java/src/main/java/an5/adapters/base/Metadata.java +195 -0
  37. package/java/src/main/java/an5/adapters/base/RelationDef.java +26 -0
  38. package/java/src/main/java/an5/adapters/base/SqlBuilder.java +422 -0
  39. package/java/src/main/java/an5/adapters/base/SqliteVectors.java +375 -0
  40. package/java/src/main/java/an5/adapters/base/Vectors.java +123 -0
  41. package/java/src/test/java/An5AdaptersSmoke.java +733 -0
  42. package/java/src/test/java/Json.java +174 -0
  43. package/java/src/test/java/QuerySemantics.java +111 -0
  44. package/kotlin/build.gradle.kts +145 -0
  45. package/kotlin/settings.gradle.kts +10 -0
  46. package/kotlin/src/main/kotlin/an5/adapters/Aggregate.kt +89 -0
  47. package/kotlin/src/main/kotlin/an5/adapters/An5.kt +91 -0
  48. package/kotlin/src/main/kotlin/an5/adapters/Query.kt +130 -0
  49. package/kotlin/src/main/kotlin/an5/adapters/Rows.kt +166 -0
  50. package/kotlin/src/main/kotlin/an5/adapters/TableClient.kt +169 -0
  51. package/kotlin/src/main/kotlin/an5/adapters/Types.kt +77 -0
  52. package/kotlin/src/test/kotlin/an5/adapters/An5Smoke.kt +329 -0
  53. package/native/sqlite/CMakeLists.txt +12 -0
  54. package/native/sqlite/README.md +117 -0
  55. package/native/sqlite/an5_vector.c +209 -0
  56. package/package.json +33 -7
  57. package/python/an5_adapter.py +37 -6
  58. package/python/base/__init__.py +15 -0
  59. package/python/base/vectors.py +434 -0
  60. package/python/sqlite/__init__.py +2 -2
  61. package/python/sqlite/provider.py +21 -1
  62. package/python/table_client.py +129 -59
  63. package/rust/src/adapter.rs +342 -24
  64. package/rust/src/base/mod.rs +7 -0
  65. package/rust/src/base/vectors.rs +293 -0
  66. package/rust/src/lib.rs +3 -3
  67. package/scripts/sqlite-native-build.js +32 -0
  68. package/swift/Package.swift +22 -0
  69. package/swift/Sources/An5Adapters/An5Adapter.swift +160 -0
  70. package/swift/Sources/An5Adapters/Metadata.swift +183 -0
  71. package/swift/Sources/An5Adapters/Query.swift +105 -0
  72. package/swift/Sources/An5Adapters/Row+Accessors.swift +129 -0
  73. package/swift/Sources/An5Adapters/SQLBuilder.swift +359 -0
  74. package/swift/Sources/An5Adapters/SQLiteDriver.swift +301 -0
  75. package/swift/Sources/An5Adapters/SqliteVectors.swift +243 -0
  76. package/swift/Sources/An5Adapters/TableClient.swift +671 -0
  77. package/swift/Sources/An5Adapters/Types.swift +127 -0
  78. package/swift/Sources/An5Adapters/Vectors.swift +86 -0
  79. package/swift/Sources/An5Adapters/ViewClient.swift +50 -0
  80. package/swift/Sources/CSQLite/module.modulemap +8 -0
  81. package/swift/Sources/CSQLite/shim.h +9 -0
  82. package/swift/Tests/An5AdaptersTests/QuerySemanticsTests.swift +51 -0
  83. package/swift/Tests/An5AdaptersTests/RuntimeSmokeTests.swift +488 -0
  84. package/typescript/src/an5Adapter.ts +146 -29
  85. package/typescript/src/base/types.ts +15 -0
  86. package/typescript/src/index.ts +13 -0
  87. package/typescript/src/sqlite/browserEngine.ts +24 -0
  88. package/typescript/src/sqlite/engine.ts +13 -0
  89. package/typescript/src/sqlite/index.ts +1 -1
  90. package/typescript/src/sqlite/vector.ts +541 -0
  91. package/golang/sqlite/sqlite.go +0 -14
@@ -27,6 +27,15 @@ import {
27
27
  type NBaseVectorClient,
28
28
  } from './nbase';
29
29
  import { bindParam, buildOrderBy, parseWhere, quote } from './base/sql';
30
+ import {
31
+ decodeVector,
32
+ isVectorField,
33
+ needsVectorEncoding,
34
+ runSqliteVectorSearch,
35
+ vectorDistance,
36
+ type SqliteVectorSupport,
37
+ type VectorStrategyPreference,
38
+ } from './sqlite/vector';
30
39
  import {
31
40
  createAdapterProxy,
32
41
  getFieldsForModel,
@@ -72,7 +81,10 @@ function isDuplicateKeyError(error: unknown, dialect: Dialect): boolean {
72
81
  function appendUpdateSet(sets: string[], params: Record<string, any>, col: string, val: any, dialect: Dialect): void {
73
82
  const quoted = quote(col, dialect);
74
83
  const safeCol = sanitizeParamName(col);
75
- if (val && typeof val === 'object' && !(val instanceof Date)) {
84
+ // `TypedArray.prototype.set` is the copy method, not the update operator, so a
85
+ // vector written as a float32 BLOB must not be read as `{ set: ... }`.
86
+ const isLiteral = Array.isArray(val) || ArrayBuffer.isView(val) || val instanceof ArrayBuffer;
87
+ if (val && typeof val === 'object' && !(val instanceof Date) && !isLiteral) {
76
88
  if (val.increment !== undefined) {
77
89
  const p = bindParam(params, `s_${safeCol}_inc`, val.increment);
78
90
  sets.push(`${quoted} = ${quoted} + @${p}`);
@@ -386,6 +398,22 @@ export class An5Adapter {
386
398
  return (await this.requireEngine()).exec<T>(query, params);
387
399
  }
388
400
 
401
+ /**
402
+ * The SQLite engine's vector hooks, or null when this is not a SQLite adapter.
403
+ * `vectorSearch` needs them to load sqlite-vec and register the distance
404
+ * functions; a connection with no hooks still searches in memory.
405
+ */
406
+ async sqliteVectorSupport(): Promise<SqliteVectorSupport | null> {
407
+ if (this.sheetsAdapter) return null;
408
+ const engine = await this.requireEngine();
409
+ return engine.dialect === 'sqlite' ? (engine.vectorSupport ?? null) : null;
410
+ }
411
+
412
+ /** `vectorStrategy` from the config, so a project can pin one strategy. */
413
+ get vectorStrategy(): VectorStrategyPreference | undefined {
414
+ return this._engineConfig?.vectorStrategy;
415
+ }
416
+
389
417
  /** INTERNAL: used by AdapterTableClient for DML statements needing row count */
390
418
  async _executeRaw(query: string, params?: Record<string, any>): Promise<number> {
391
419
  return (await this.requireEngine()).executeRaw(query, params);
@@ -732,6 +760,14 @@ export class AdapterTableClient<T = any> {
732
760
  public get dialect(): Dialect { return this.adapter.dialect; }
733
761
 
734
762
  public get tableName(): string {
763
+ const t = this.mappedTableName;
764
+ if (t.startsWith('[') || t.startsWith('"') || t.startsWith('`')) return t;
765
+ if (t.includes('.')) return t.split('.').map(p => quote(p, this.dialect)).join('.');
766
+ return quote(t, this.dialect);
767
+ }
768
+
769
+ /** The table name this model maps to, before any quoting is applied. */
770
+ private get mappedTableName(): string {
735
771
  const name = this.modelName;
736
772
  let t = name;
737
773
  const modelToTable = getModelToTable();
@@ -744,9 +780,17 @@ export class AdapterTableClient<T = any> {
744
780
  if (modelToTable[lower]) t = modelToTable[lower];
745
781
  }
746
782
  }
747
- if (t.startsWith('[') || t.startsWith('"') || t.startsWith('`')) return t;
748
- if (t.includes('.')) return t.split('.').map(p => quote(p, this.dialect)).join('.');
749
- return quote(t, this.dialect);
783
+ return t;
784
+ }
785
+
786
+ /**
787
+ * The bare table name, for helpers that take a string literal. `@@map` values
788
+ * may be pre-quoted or schema-qualified, neither of which `pragma_table_info`
789
+ * accepts, so the last segment is unwrapped here.
790
+ */
791
+ public get rawTableName(): string {
792
+ const last = this.mappedTableName.split('.').pop() ?? this.mappedTableName;
793
+ return last.replace(/^[\s]*[\[\]"`]+|[\[\]"`]+[\s]*$/g, '');
750
794
  }
751
795
 
752
796
  private get nolock(): string {
@@ -770,6 +814,53 @@ export class AdapterTableClient<T = any> {
770
814
  return this.adapter._executeRaw(query, params);
771
815
  }
772
816
 
817
+ /**
818
+ * Encode every `VECTOR(n)` value on its way into SQLite.
819
+ *
820
+ * The column holds a float32 BLOB, which is a third of the size of the JSON
821
+ * text it replaces and is what sqlite-vec reads. Values that already arrived
822
+ * as text or bytes are left alone, so rows written by an older version stay
823
+ * readable.
824
+ */
825
+ private encodeVectorFields(data: Record<string, any>): Record<string, any> {
826
+ if (this.dialect !== 'sqlite') return data;
827
+ const fields = getFieldsForModel(this.modelName);
828
+ const vectorFields = Object.keys(fields).filter((name) => isVectorField(fields[name]));
829
+ if (vectorFields.length === 0) return data;
830
+ const { encodeVector } = require('./sqlite/vector');
831
+ const out = { ...data };
832
+ for (const name of vectorFields) {
833
+ const value = out[name];
834
+ if (needsVectorEncoding(value)) {
835
+ const parsed = Array.from(value as ArrayLike<number>);
836
+ if (parsed.length > 0) out[name] = encodeVector(parsed);
837
+ }
838
+ }
839
+ return out;
840
+ }
841
+
842
+ /** Decode `VECTOR(n)` columns back into `number[]` after a read. */
843
+ private decodeVectorFields<T>(rows: T[]): T[] {
844
+ if (this.dialect !== 'sqlite' || !Array.isArray(rows) || rows.length === 0) return rows;
845
+ const fields = getFieldsForModel(this.modelName);
846
+ const vectorFields = Object.keys(fields).filter((name) => isVectorField(fields[name]));
847
+ if (vectorFields.length === 0) return rows;
848
+ return rows.map((row: any) => {
849
+ if (!row || typeof row !== 'object') return row;
850
+ let changed = false;
851
+ const out: any = { ...row };
852
+ for (const name of vectorFields) {
853
+ if (!(name in out)) continue;
854
+ const decoded = decodeVector(out[name]);
855
+ if (decoded) {
856
+ out[name] = decoded;
857
+ changed = true;
858
+ }
859
+ }
860
+ return changed ? out : row;
861
+ });
862
+ }
863
+
773
864
  async findMany(args?: { where?: any; orderBy?: any; skip?: number; take?: number; select?: any; include?: any }): Promise<T[]> {
774
865
  const params: Record<string, any> = {};
775
866
  const whereCtx = this.whereContext();
@@ -825,7 +916,7 @@ export class AdapterTableClient<T = any> {
825
916
  if (orderSql) query += ` ${orderSql}`;
826
917
  }
827
918
 
828
- const rows = await this.doExec<T>(query, params);
919
+ const rows = this.decodeVectorFields(await this.doExec<T>(query, params));
829
920
 
830
921
  if (args?.include) {
831
922
  await resolveIncludes(this.modelName, rows, args.include, this.adapter);
@@ -875,7 +966,7 @@ export class AdapterTableClient<T = any> {
875
966
  const fields = getFieldsForModel(this.modelName);
876
967
  const idFieldName = resolveIdField(fields);
877
968
 
878
- const data: any = { ...rawData };
969
+ const data: any = this.encodeVectorFields(rawData);
879
970
  if (idFieldName) {
880
971
  const fieldDef: any = fields[idFieldName];
881
972
  const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || fieldDef?.type || '');
@@ -954,9 +1045,10 @@ export class AdapterTableClient<T = any> {
954
1045
 
955
1046
  for (let r = 0; r < args.data.length; r++) {
956
1047
  const row = args.data[r] as any;
1048
+ const encoded = this.encodeVectorFields(row);
957
1049
  const vals = firstCols.map(col => {
958
1050
  const p = `r${r}_${col}`;
959
- params[p] = row[col] ?? null;
1051
+ params[p] = encoded[col] ?? null;
960
1052
  return `@${p}`;
961
1053
  });
962
1054
  rowPlaceholders.push(`(${vals.join(', ')})`);
@@ -988,13 +1080,14 @@ export class AdapterTableClient<T = any> {
988
1080
  }
989
1081
  }
990
1082
 
1083
+ const data = this.encodeVectorFields(rawData);
991
1084
  const setCols = Object.keys(rawData).filter(k => rawData[k] !== undefined);
992
1085
  if (setCols.length > 0) {
993
1086
  const params: Record<string, any> = {};
994
1087
  const whereSql = parseWhere(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
995
1088
  const sets: string[] = [];
996
1089
  for (const col of setCols) {
997
- appendUpdateSet(sets, params, col, rawData[col], this.dialect);
1090
+ appendUpdateSet(sets, params, col, data[col], this.dialect);
998
1091
  }
999
1092
  if (sets.length > 0) {
1000
1093
  const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
@@ -1050,11 +1143,12 @@ export class AdapterTableClient<T = any> {
1050
1143
  async updateMany(args: { where?: any; data: Partial<T> }): Promise<{ count: number }> {
1051
1144
  const params: Record<string, any> = {};
1052
1145
  const whereSql = parseWhere(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
1146
+ const data = this.encodeVectorFields(args.data as Record<string, any>);
1053
1147
  const setCols = Object.keys(args.data).filter(k => (args.data as any)[k] !== undefined);
1054
1148
  const sets: string[] = [];
1055
1149
 
1056
1150
  for (const col of setCols) {
1057
- appendUpdateSet(sets, params, col, (args.data as any)[col], this.dialect);
1151
+ appendUpdateSet(sets, params, col, data[col], this.dialect);
1058
1152
  }
1059
1153
 
1060
1154
  if (sets.length === 0) return { count: 0 };
@@ -1410,6 +1504,28 @@ export class AdapterTableClient<T = any> {
1410
1504
  const msg = String(err?.message || '').toLowerCase();
1411
1505
  if (!msg.includes('vector') && !msg.includes('operator does not exist')) throw err;
1412
1506
  }
1507
+ } else if (this.dialect === 'sqlite') {
1508
+ // SQLite has no vector type, so the ranking runs through sqlite-vec, a
1509
+ // driver function, or json_each — whichever this connection supports.
1510
+ const whereSql = parseWhere(this.modelName, args.where, params, this.dialect, '', this.whereContext());
1511
+ const support = await (this.adapter as any).sqliteVectorSupport?.() ?? null;
1512
+ const ranked = await runSqliteVectorSearch({
1513
+ exec: (sql, p) => this.doExec<any>(sql, p),
1514
+ vectorSupport: support,
1515
+ table: this.tableName,
1516
+ rawTable: this.rawTableName,
1517
+ column: col,
1518
+ rawColumn: vectorField,
1519
+ vector: args.vector,
1520
+ metric,
1521
+ take,
1522
+ tail: whereSql ? ` WHERE ${whereSql}` : '',
1523
+ params,
1524
+ preference: (this.adapter as any).vectorStrategy,
1525
+ });
1526
+ if (ranked) return this.decodeVectorFields(ranked.rows) as (T & { distance: number })[];
1527
+ // No in-database strategy: score the column in the client instead.
1528
+ return this.scoreVectorsInMemory(args, metric, take, vectorField);
1413
1529
  } else if (this.dialect === 'mssql') {
1414
1530
  const distFn = metric === 'cosine' ? 'cosine' : metric === 'euclidean' ? 'euclidean' : 'dot';
1415
1531
  const whereSql = parseWhere(this.modelName, args.where, params, this.dialect, '', this.whereContext());
@@ -1436,30 +1552,31 @@ export class AdapterTableClient<T = any> {
1436
1552
  }
1437
1553
 
1438
1554
  // 2. Fallback: in-memory similarity computation when native vector support is unavailable
1555
+ return this.scoreVectorsInMemory(args, metric, take, vectorField);
1556
+ }
1557
+
1558
+ /**
1559
+ * Rank the vector column in the client. This is the last resort for every
1560
+ * provider, and the only option on SQLite connections that offer neither
1561
+ * sqlite-vec, a driver function nor JSON1.
1562
+ *
1563
+ * The whole table is loaded, so it is only reasonable for a table small
1564
+ * enough to fit in memory.
1565
+ */
1566
+ private async scoreVectorsInMemory(
1567
+ args: { vector: number[]; where?: any },
1568
+ metric: 'cosine' | 'euclidean' | 'dot',
1569
+ take: number,
1570
+ vectorField = 'embedding',
1571
+ ): Promise<(T & { distance: number })[]> {
1439
1572
  const rows = await this.findMany({ where: args.where });
1440
1573
 
1441
1574
  const scored: { row: T; dist: number }[] = [];
1442
1575
  for (const row of rows) {
1443
- const raw = (row as any)[vectorField];
1444
- if (!raw) continue;
1445
- let vec: number[] = [];
1446
- try { vec = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { continue; }
1447
- if (!Array.isArray(vec) || vec.length !== args.vector.length) continue;
1448
-
1449
- let dot = 0, m1 = 0, m2 = 0;
1450
- for (let i = 0; i < args.vector.length; i++) {
1451
- const av = args.vector[i] ?? 0;
1452
- const bv = vec[i] ?? 0;
1453
- dot += av * bv;
1454
- m1 += av ** 2;
1455
- m2 += bv ** 2;
1456
- }
1457
- const cosine = m1 && m2 ? dot / (Math.sqrt(m1) * Math.sqrt(m2)) : 0;
1458
- const dist = metric === 'cosine'
1459
- ? 1 - cosine
1460
- : metric === 'dot'
1461
- ? -dot
1462
- : Math.sqrt(args.vector.reduce((s, v, i) => s + (v - (vec[i] ?? 0)) ** 2, 0));
1576
+ const vec = decodeVector((row as any)[vectorField]);
1577
+ if (!vec) continue;
1578
+ const dist = vectorDistance(args.vector, vec, metric);
1579
+ if (dist === null) continue;
1463
1580
  scored.push({ row, dist });
1464
1581
  }
1465
1582
  scored.sort((a, b) => a.dist - b.dist);
@@ -1,3 +1,5 @@
1
+ import type { SqliteVectorSupport, VectorStrategyPreference } from '../sqlite/vector';
2
+
1
3
  export type Dialect = 'mssql' | 'postgres' | 'mysql' | 'sqlite' | 'googlesheets';
2
4
 
3
5
  export interface An5AdapterConfig {
@@ -8,6 +10,17 @@ export interface An5AdapterConfig {
8
10
  * run in NBase while the rows stay in `connectionString`'s database.
9
11
  */
10
12
  nbase?: string;
13
+ /**
14
+ * Path to sqlite-vec or the AN5 native vector extension. On SQLite it is loaded before the
15
+ * vector capabilities are probed, so `vectorSearch` ranks inside the database
16
+ * instead of loading the table into memory. Other providers ignore it.
17
+ */
18
+ sqliteVec?: string;
19
+ /**
20
+ * Pin the SQLite vector search strategy. `auto` (the default) probes for
21
+ * sqlite-vec, a driver function and JSON1, and uses the first that works.
22
+ */
23
+ vectorStrategy?: VectorStrategyPreference;
11
24
  engine?: QueryEngine;
12
25
  db?: any;
13
26
  driver?: any;
@@ -30,4 +43,6 @@ export interface QueryEngine {
30
43
  connect(): Promise<void>;
31
44
  disconnect(): Promise<void>;
32
45
  beginTransaction(): Promise<TransactionHandle>;
46
+ /** SQLite only: extension loading and distance-function registration. */
47
+ vectorSupport?: SqliteVectorSupport;
33
48
  }
@@ -29,6 +29,19 @@ export type {
29
29
  AdapterMetadata,
30
30
  Dialect,
31
31
  } from './an5Adapter';
32
+ export {
33
+ encodeVector,
34
+ decodeVector,
35
+ vectorDistance,
36
+ isVectorField,
37
+ AN5_VECTOR_FUNCTIONS,
38
+ SQLITE_VEC_FUNCTIONS,
39
+ } from './sqlite/vector';
40
+ export type {
41
+ SqliteVectorStrategy,
42
+ VectorMetric,
43
+ VectorStrategyPreference,
44
+ } from './sqlite/vector';
32
45
  export type { TypedAn5Adapter, AdapterAPI } from './typed';
33
46
  export {
34
47
  getLlmConfig, setLlmConfig,
@@ -1,4 +1,5 @@
1
1
  import type { Dialect, QueryEngine, TransactionHandle } from '../base/types';
2
+ import { SqliteVectorSupport } from './vector';
2
3
 
3
4
  // ─── Browser SQLite Driver & Config ──────────────────────────────────────────────────
4
5
 
@@ -13,18 +14,41 @@ export interface SqliteBrowserConfig {
13
14
  driver?: SqliteDriver;
14
15
  exec?: <T = any>(query: string, params?: Record<string, any>) => Promise<T[]> | T[];
15
16
  executeRaw?: (query: string, params?: Record<string, any>) => Promise<number> | number;
17
+ /** Path to the sqlite-vec extension, for WASM builds that allow loading one. */
18
+ sqliteVec?: string;
19
+ /** Pin the vector search strategy instead of probing for one. */
20
+ vectorStrategy?: 'auto' | 'sqlite-vec' | 'udf' | 'sql' | 'memory';
21
+ /** Register a scalar function with the driver, when its API allows it. */
22
+ registerFunction?: (name: string, fn: (...args: any[]) => any) => void;
16
23
  }
17
24
 
18
25
  // ─── Browser SQLite Engine ──────────────────────────────────────────────────────────
19
26
 
20
27
  export class SqliteBrowserEngine implements QueryEngine {
21
28
  dialect: Dialect = 'sqlite';
29
+ /**
30
+ * A WASM build cannot register a JS callback as an SQL function, so this only
31
+ * reaches the `sql` strategy (json_each in plain SQL) and the in-memory one.
32
+ * A custom `registerFunction` hook unlocks `udf` as well.
33
+ */
34
+ readonly vectorSupport: SqliteVectorSupport;
22
35
  private db: any = null;
23
36
  private driver: SqliteDriver | null = null;
24
37
  private customExec: ((query: string, params?: Record<string, any>) => Promise<any[]> | any[]) | null = null;
25
38
  private customExecuteRaw: ((query: string, params?: Record<string, any>) => Promise<number> | number) | null = null;
26
39
 
27
40
  constructor(config?: SqliteBrowserConfig | any) {
41
+ this.vectorSupport = new SqliteVectorSupport({
42
+ native: () => this.db,
43
+ registerFunction: (name, fn) => {
44
+ const register = config?.registerFunction;
45
+ if (typeof register === 'function') return register(name, fn);
46
+ const db = this.db;
47
+ if (db && typeof db.create_function === 'function') return db.create_function(name, fn);
48
+ throw new Error('this SQLite build cannot register functions');
49
+ },
50
+ }, { sqliteVec: config?.sqliteVec, vectorStrategy: config?.vectorStrategy });
51
+
28
52
  if (!config) return;
29
53
 
30
54
  if (config.exec && typeof config.exec === 'function') {
@@ -1,4 +1,5 @@
1
1
  import type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from '../base/types';
2
+ import { SqliteVectorSupport } from './vector';
2
3
 
3
4
  // ─── SQLite Engine ─────────────────────────────────────────────────────────────────
4
5
 
@@ -7,6 +8,8 @@ try { BetterSqlite3 = require('better-sqlite3'); } catch { }
7
8
 
8
9
  export class SqliteEngine implements QueryEngine {
9
10
  dialect: Dialect = 'sqlite';
11
+ /** Registers `an5_vec_*` and loads sqlite-vec for in-database vector search. */
12
+ readonly vectorSupport: SqliteVectorSupport;
10
13
  private db: any = null;
11
14
  private readonly filePath: string;
12
15
 
@@ -16,6 +19,15 @@ export class SqliteEngine implements QueryEngine {
16
19
  .replace(/^sqlite:\/\/\//i, '/')
17
20
  .replace(/^sqlite:\/\//i, '')
18
21
  .replace(/^sqlite:/i, '');
22
+ this.vectorSupport = new SqliteVectorSupport({
23
+ native: () => this.getDb(),
24
+ registerFunction: (name, fn) => this.getDb().function(name, { deterministic: true }, fn),
25
+ }, { sqliteVec: adapterConfig.sqliteVec, vectorStrategy: adapterConfig.vectorStrategy });
26
+ }
27
+
28
+ /** The better-sqlite3 handle, needed for extension loading. */
29
+ native(): any {
30
+ return this.getDb();
19
31
  }
20
32
 
21
33
  private getDb(): any {
@@ -42,6 +54,7 @@ export class SqliteEngine implements QueryEngine {
42
54
 
43
55
  async disconnect(): Promise<void> {
44
56
  if (this.db) { this.db.close(); this.db = null; }
57
+ this.vectorSupport.reset();
45
58
  }
46
59
 
47
60
  async beginTransaction(): Promise<TransactionHandle> {
@@ -1,3 +1,3 @@
1
1
  export * from './engine';
2
2
  export * from './browserEngine';
3
-
3
+ export * from './vector';