taladb 0.11.2 → 0.11.4

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.
@@ -1,3 +1,89 @@
1
+ // src/vector-client.ts
2
+ function integer(value, name, min = 0, max = 4294967295) {
3
+ if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${name} must be an integer between ${min} and ${max}`);
4
+ }
5
+ function vectorValues(vector) {
6
+ const values = Array.from(vector);
7
+ if (values.length === 0 || values.some((v) => !Number.isFinite(v))) throw new Error("vector must contain finite numbers");
8
+ return values;
9
+ }
10
+ function queryOptions(options) {
11
+ if (options.efSearch !== void 0) integer(options.efSearch, "efSearch", 1);
12
+ if (options.offset !== void 0) integer(options.offset, "offset");
13
+ if (options.groupSize !== void 0) integer(options.groupSize, "groupSize", 1);
14
+ if (options.oversampling !== void 0) integer(options.oversampling, "oversampling", 1, 100);
15
+ if (options.scoreThreshold !== void 0 && !Number.isFinite(options.scoreThreshold)) throw new Error("scoreThreshold must be finite");
16
+ return options;
17
+ }
18
+ function abortError() {
19
+ const error = new Error("Vector index rebuild cancelled");
20
+ error.name = "AbortError";
21
+ return error;
22
+ }
23
+ function createVectorClient(send) {
24
+ const client = {
25
+ searchVectors: async (field, vector, topK, filter, options = {}) => {
26
+ integer(topK, "topK");
27
+ return send({ op: "search", field, query: vectorValues(vector), topK, filter, options: queryOptions(options) });
28
+ },
29
+ findWithin: (field, vector, scoreThreshold, filter) => client.searchVectors(field, vector, 4294967295, filter, { mode: "exact", scoreThreshold }),
30
+ vectorIndexStatus: (field) => send({ op: "status", field }),
31
+ beginVectorBuild: (field, options) => send({ op: "beginBuild", field, options }),
32
+ stepVectorBuild: async (field, id, batchSize = 32) => {
33
+ integer(batchSize, "batchSize", 1, 1024);
34
+ return send({ op: "stepBuild", field, id, batchSize });
35
+ },
36
+ cancelVectorBuild: (field, id) => send({ op: "cancelBuild", field, id }),
37
+ rebuildVectorIndex: async (field, options = {}) => {
38
+ const { signal, onProgress, batchSize = 32, ...graphOptions } = options;
39
+ integer(batchSize, "batchSize", 1, 1024);
40
+ if (signal?.aborted) throw abortError();
41
+ let progress = await client.beginVectorBuild(field, Object.keys(graphOptions).length ? graphOptions : void 0);
42
+ try {
43
+ onProgress?.(progress);
44
+ while (progress.state === "building") {
45
+ await new Promise((resolve) => setTimeout(resolve, 0));
46
+ if (signal?.aborted) throw abortError();
47
+ progress = await client.stepVectorBuild(field, progress.id, batchSize);
48
+ onProgress?.(progress);
49
+ }
50
+ if (progress.state === "failed") throw new Error(progress.error ?? "Vector rebuild failed");
51
+ if (progress.state === "cancelled") throw abortError();
52
+ return progress;
53
+ } catch (error) {
54
+ if (progress.state === "building") await client.cancelVectorBuild(field, progress.id);
55
+ throw error;
56
+ }
57
+ },
58
+ measureVectorRecall: async (field, queries, topK, filter, options = {}) => {
59
+ integer(topK, "topK", 1);
60
+ return send({ op: "recall", field, queries: queries.map(vectorValues), topK, filter, options: queryOptions(options) });
61
+ }
62
+ };
63
+ return client;
64
+ }
65
+ function vectorIndexRequest(field, options) {
66
+ integer(options.dimensions, "dimensions", 1);
67
+ if (options.metric && !["cosine", "dot", "euclidean"].includes(options.metric)) throw new Error("invalid vector metric");
68
+ if (options.indexType && !["flat", "hnsw"].includes(options.indexType)) throw new Error("invalid vector indexType");
69
+ if (options.quantization && options.quantization !== "none" && options.indexType !== "hnsw") throw new Error("quantization requires an HNSW index");
70
+ if (options.indexType === "hnsw") {
71
+ const m = options.hnswM ?? 32;
72
+ const efConstruction = options.hnswEfConstruction ?? 200;
73
+ integer(m, "hnswM", 2, 128);
74
+ integer(efConstruction, "hnswEfConstruction", m, 1e5);
75
+ if (options.metric === "dot") throw new Error("HNSW requires cosine or euclidean");
76
+ if (options.quantization === "binary" && options.metric && options.metric !== "cosine") throw new Error("binary quantization requires cosine");
77
+ }
78
+ return {
79
+ op: "create",
80
+ field,
81
+ dimensions: options.dimensions,
82
+ metric: options.metric,
83
+ options: options.indexType === "hnsw" ? { m: options.hnswM ?? 32, efConstruction: options.hnswEfConstruction ?? 200, quantization: options.quantization ?? "none" } : null
84
+ };
85
+ }
86
+
1
87
  // src/config.browser.ts
2
88
  var ENDPOINT_FIELDS = [
3
89
  "endpoint",
@@ -710,7 +796,9 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
710
796
  }
711
797
  function wrapCollection(name, opts) {
712
798
  const s = JSON.stringify;
799
+ const vectorCommand = async (request) => JSON.parse(await proxy.send("vectorCommand", { collection: name, requestJson: JSON.stringify(request) }));
713
800
  const wrapped = {
801
+ ...createVectorClient(vectorCommand),
714
802
  insert: (doc) => proxy.send("insert", { collection: name, docJson: s(doc) }),
715
803
  insertMany: async (docs) => {
716
804
  const json = await proxy.send("insertMany", {
@@ -762,25 +850,21 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
762
850
  dropCompoundIndex: (fields) => proxy.send("dropCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
763
851
  createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
764
852
  dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
765
- createVectorIndex: (field, options) => {
766
- if (options.indexType === "hnsw") return Promise.reject(new Error("HNSW vector indexes are not available in the browser (requires native threads). Use Node.js or React Native."));
767
- return proxy.send("createVectorIndex", {
768
- collection: name,
769
- field,
770
- dimensions: options.dimensions,
771
- metric: options.metric,
772
- indexType: null,
773
- hnswM: null,
774
- hnswEfConstruction: null
775
- });
853
+ createVectorIndex: async (field, options) => {
854
+ const request = vectorIndexRequest(field, options);
855
+ await vectorCommand({ ...request, deferBuild: true });
856
+ if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
776
857
  },
777
858
  dropVectorIndex: (field) => proxy.send("dropVectorIndex", { collection: name, field }),
778
- upgradeVectorIndex: (_field) => Promise.reject(new Error("HNSW vector indexes are not available in the browser (requires native threads). Use Node.js or React Native.")),
859
+ upgradeVectorIndex: async (field) => {
860
+ await createVectorClient(vectorCommand).rebuildVectorIndex(field);
861
+ },
779
862
  listIndexes: async () => {
780
863
  const json = await proxy.send("listIndexes", { collection: name });
781
864
  return JSON.parse(json);
782
865
  },
783
- findNearest: async (field, vector, topK, filter) => {
866
+ findNearest: async (field, vector, topK, filter, options) => {
867
+ if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
784
868
  const json = await proxy.send("findNearest", {
785
869
  collection: name,
786
870
  field,
@@ -838,7 +922,7 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
838
922
  function nudgedPoller(collection, fetchJson, callback, onError) {
839
923
  let active = true;
840
924
  let lastJson = "";
841
- let lastGeneration = -1;
925
+ let lastGeneration = null;
842
926
  let generationSupported = true;
843
927
  let timer = null;
844
928
  let running = false;
@@ -903,6 +987,7 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
903
987
  await proxy.send("flush");
904
988
  },
905
989
  isPrimary: () => proxy.send("isPrimary"),
990
+ storageInfo: () => proxy.send("capabilities"),
906
991
  close: async () => {
907
992
  channel?.close();
908
993
  try {
@@ -915,14 +1000,16 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
915
1000
  listCollectionNames: async () => JSON.parse(await proxy.send("listCollections"))
916
1001
  };
917
1002
  if (migrations?.length) {
918
- await runMigrations(
919
- handle,
920
- async () => proxy.send("userVersion"),
921
- async (v) => {
922
- await proxy.send("setUserVersion", { version: v });
923
- },
924
- migrations
925
- );
1003
+ await navigator.locks.request(`taladb:migrations:${dbName}`, async () => {
1004
+ await runMigrations(
1005
+ handle,
1006
+ async () => proxy.send("userVersion"),
1007
+ async (v) => {
1008
+ await proxy.send("setUserVersion", { version: v });
1009
+ },
1010
+ migrations
1011
+ );
1012
+ });
926
1013
  }
927
1014
  return handle;
928
1015
  }
@@ -938,7 +1025,9 @@ async function createNodeDB(dbName, webhook, config, passphrase, migrations) {
938
1025
  const db = TalaDBNode.open(dbName, configJson, passphrase ?? null);
939
1026
  function wrapCollection(name, opts) {
940
1027
  const col = db.collection(name);
1028
+ const vectorCommand = async (request) => col.vectorCommandAsync ? col.vectorCommandAsync(request) : col.vectorCommand(request);
941
1029
  const wrapped = {
1030
+ ...createVectorClient(vectorCommand),
942
1031
  insert: async (doc) => col.insertAsync ? col.insertAsync(doc) : col.insert(doc),
943
1032
  insertMany: async (docs) => col.insertManyAsync ? col.insertManyAsync(docs) : col.insertMany(docs),
944
1033
  find: async (filter) => col.findAsync ? col.findAsync(filter ?? null) : col.find(filter ?? null),
@@ -955,14 +1044,19 @@ async function createNodeDB(dbName, webhook, config, passphrase, migrations) {
955
1044
  dropCompoundIndex: async (fields) => col.dropCompoundIndex(fields),
956
1045
  createFtsIndex: async (field) => col.createFtsIndex(field),
957
1046
  dropFtsIndex: async (field) => col.dropFtsIndex(field),
958
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
1047
+ createVectorIndex: async (field, options) => {
1048
+ const request = vectorIndexRequest(field, options);
1049
+ await vectorCommand({ ...request, deferBuild: true });
1050
+ if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
1051
+ },
959
1052
  dropVectorIndex: async (field) => col.dropVectorIndex(field),
960
1053
  upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
961
1054
  listIndexes: async () => {
962
1055
  const json = col.listIndexes();
963
1056
  return JSON.parse(json);
964
1057
  },
965
- findNearest: async (field, vector, topK, filter) => {
1058
+ findNearest: async (field, vector, topK, filter, options) => {
1059
+ if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
966
1060
  const raw = await col.findNearest(field, vector, topK, filter ?? null);
967
1061
  return raw;
968
1062
  },
@@ -1010,78 +1104,109 @@ async function createNativeDB(_dbName, webhook, migrations) {
1010
1104
  );
1011
1105
  }
1012
1106
  const native = maybeNative;
1107
+ let nativeQueue = Promise.resolve();
1108
+ let queued = 0;
1109
+ let closed = false;
1110
+ const call = (op, ...args) => {
1111
+ if (queued >= 128) return Promise.reject(new Error("TalaDB native request queue is full"));
1112
+ queued++;
1113
+ const result = nativeQueue.then(async () => {
1114
+ if (closed) throw new Error("TalaDB database is closed");
1115
+ if (op === "close") {
1116
+ closed = true;
1117
+ native.close();
1118
+ return;
1119
+ }
1120
+ if (op === "findNearest" && native.findNearestAsync) {
1121
+ return native.findNearestAsync(...args);
1122
+ }
1123
+ if (native.callAsync) return native.callAsync(op, args);
1124
+ if (op === "find" && native.findAsync) {
1125
+ return native.findAsync(...args);
1126
+ }
1127
+ const method = native[op];
1128
+ if (!method) throw new Error(`${op} requires a newer @taladb/react-native binary; rebuild the native module`);
1129
+ return method(...args);
1130
+ });
1131
+ nativeQueue = result.catch(() => {
1132
+ }).finally(() => {
1133
+ queued--;
1134
+ });
1135
+ return result;
1136
+ };
1013
1137
  function wrapCollection(name, opts) {
1138
+ const vectorCommand = async (request) => call("vectorCommand", name, request);
1014
1139
  const wrapped = {
1015
- insert: async (doc) => native.insert(name, doc),
1016
- insertMany: async (docs) => native.insertMany(name, docs),
1017
- find: async (filter) => native.find(name, filter ?? {}),
1018
- findOne: async (filter) => native.findOne(name, filter ?? {}),
1019
- updateOne: async (filter, update) => native.updateOne(name, filter, update),
1020
- updateMany: async (filter, update) => native.updateMany(name, filter, update),
1021
- deleteOne: async (filter) => native.deleteOne(name, filter),
1022
- deleteMany: async (filter) => native.deleteMany(name, filter),
1023
- count: async (filter) => native.count(name, filter ?? {}),
1024
- aggregate: async (pipeline) => native.aggregate(name, pipeline),
1025
- createIndex: async (field) => native.createIndex(name, field),
1026
- dropIndex: async (field) => native.dropIndex(name, field),
1027
- createCompoundIndex: async (fields) => native.createCompoundIndex(name, fields),
1028
- dropCompoundIndex: async (fields) => native.dropCompoundIndex(name, fields),
1029
- createFtsIndex: async (field) => native.createFtsIndex(name, field),
1030
- dropFtsIndex: async (field) => native.dropFtsIndex(name, field),
1140
+ ...createVectorClient(vectorCommand),
1141
+ insert: async (doc) => await call("insert", name, doc),
1142
+ insertMany: async (docs) => await call("insertMany", name, docs),
1143
+ find: async (filter) => await call("find", name, filter ?? {}),
1144
+ findOne: async (filter) => await call("findOne", name, filter ?? {}),
1145
+ updateOne: async (filter, update) => await call("updateOne", name, filter, update),
1146
+ updateMany: async (filter, update) => await call("updateMany", name, filter, update),
1147
+ deleteOne: async (filter) => await call("deleteOne", name, filter),
1148
+ deleteMany: async (filter) => await call("deleteMany", name, filter),
1149
+ count: async (filter) => await call("count", name, filter ?? {}),
1150
+ aggregate: async (pipeline) => await call("aggregate", name, pipeline),
1151
+ createIndex: async (field) => await call("createIndex", name, field),
1152
+ dropIndex: async (field) => await call("dropIndex", name, field),
1153
+ createCompoundIndex: async (fields) => await call("createCompoundIndex", name, fields),
1154
+ dropCompoundIndex: async (fields) => await call("dropCompoundIndex", name, fields),
1155
+ createFtsIndex: async (field) => await call("createFtsIndex", name, field),
1156
+ dropFtsIndex: async (field) => await call("dropFtsIndex", name, field),
1031
1157
  createVectorIndex: async (field, options) => {
1032
- const opts2 = {};
1033
- if (options.metric) opts2.metric = options.metric;
1034
- if (options.hnswM || options.hnswEfConstruction) {
1035
- opts2.hnsw = { m: options.hnswM, efConstruction: options.hnswEfConstruction };
1036
- }
1037
- return native.createVectorIndex(name, field, options.dimensions, opts2);
1158
+ const request = vectorIndexRequest(field, options);
1159
+ await vectorCommand({ ...request, deferBuild: true });
1160
+ if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
1038
1161
  },
1039
- dropVectorIndex: async (field) => native.dropVectorIndex(name, field),
1040
- upgradeVectorIndex: async (field) => native.upgradeVectorIndex(name, field),
1041
- // The JSI HostObject does not expose index introspection yet; return a
1042
- // correctly-shaped empty result rather than `{}` cast to the interface.
1043
- listIndexes: async () => ({ btree: [], fts: [], vector: [] }),
1044
- findNearest: async (field, vector, topK, filter) => {
1045
- const raw = native.findNearest(name, field, vector, topK, filter ?? null);
1162
+ dropVectorIndex: async (field) => await call("dropVectorIndex", name, field),
1163
+ upgradeVectorIndex: async (field) => await call("upgradeVectorIndex", name, field),
1164
+ listIndexes: async () => call("listIndexes", name),
1165
+ findNearest: async (field, vector, topK, filter, options) => {
1166
+ if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
1167
+ const raw = await call("findNearest", name, field, vector, topK, filter ?? null);
1046
1168
  return raw;
1047
1169
  },
1048
1170
  searchText: async (field, query, topK, filter, options) => {
1049
- if (!native.searchText) {
1171
+ if (!native.callAsync && !native.searchText) {
1050
1172
  throw new Error("searchText requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
1051
1173
  }
1052
- return native.searchText(name, field, query, topK, filter ?? null, options ?? null);
1174
+ return await call("searchText", name, field, query, topK, filter ?? null, options ?? null);
1053
1175
  },
1054
1176
  hybridSearch: async (text, vector, topK, filter, options) => {
1055
- if (!native.hybridSearch) {
1177
+ if (!native.callAsync && !native.hybridSearch) {
1056
1178
  throw new Error("hybridSearch requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
1057
1179
  }
1058
- return native.hybridSearch(name, text.textField, text.text, vector.vectorField, vector.vector, topK, filter ?? null, options ?? null);
1180
+ return await call("hybridSearch", name, text.textField, text.text, vector.vectorField, vector.vector, topK, filter ?? null, options ?? null);
1059
1181
  },
1060
- subscribe: (filter, callback, onError) => makePoller(async () => native.find(name, filter ?? {}), callback, onError),
1061
- subscribeAggregate: (pipeline, callback, onError) => makePoller(async () => native.aggregate(name, pipeline), callback, onError)
1182
+ subscribe: (filter, callback, onError) => makePoller(async () => await call("find", name, filter ?? {}), callback, onError),
1183
+ subscribeAggregate: (pipeline, callback, onError) => makePoller(async () => await call("aggregate", name, pipeline), callback, onError)
1062
1184
  };
1063
1185
  return decorateCollection(wrapped, name, opts, webhook);
1064
1186
  }
1065
1187
  const handle = {
1066
1188
  collection: (name, opts) => wrapCollection(name, opts),
1067
- compact: async () => native.compact(),
1068
- close: async () => native.close(),
1069
- flush: native.flush ? async () => {
1070
- native.flush();
1189
+ compact: async () => await call("compact"),
1190
+ close: async () => await call("close"),
1191
+ flush: native.callAsync || native.flush ? async () => {
1192
+ await call("flush");
1193
+ } : void 0,
1194
+ rebuildVectorIndexes: native.callAsync || native.rebuildVectorIndexes ? async () => {
1195
+ await call("rebuildVectorIndexes");
1071
1196
  } : void 0,
1072
1197
  // One process owns the file — there is no other tab to defer to.
1073
1198
  isPrimary: async () => true
1074
1199
  };
1075
1200
  if (migrations?.length) {
1076
- if (typeof native.userVersion !== "function" || typeof native.setUserVersion !== "function") {
1201
+ if (!native.callAsync && (typeof native.userVersion !== "function" || typeof native.setUserVersion !== "function")) {
1077
1202
  throw new Error(
1078
1203
  "openDB({ migrations }) is not available on this @taladb/react-native binary yet (the JSI HostObject does not expose userVersion/setUserVersion). Update the native module."
1079
1204
  );
1080
1205
  }
1081
1206
  await runMigrations(
1082
1207
  handle,
1083
- async () => native.userVersion(),
1084
- async (v) => native.setUserVersion(v),
1208
+ async () => call("userVersion"),
1209
+ async (v) => call("setUserVersion", v),
1085
1210
  migrations
1086
1211
  );
1087
1212
  }
@@ -1156,6 +1281,7 @@ function attachWebhook(db, webhook) {
1156
1281
  export {
1157
1282
  TalaDbValidationError,
1158
1283
  applySchema,
1284
+ createVectorClient,
1159
1285
  createWebhookDispatcher,
1160
1286
  decorateCollection,
1161
1287
  deriveDocId,
package/dist/index.d.mts CHANGED
@@ -1,3 +1,81 @@
1
+ /** Shared vector API for Node, browser workers, and the React Native job executor. */
2
+ type VectorQuantization = 'none' | 'scalar' | 'binary';
3
+ interface VectorGraphOptions {
4
+ m?: number;
5
+ efConstruction?: number;
6
+ quantization?: VectorQuantization;
7
+ }
8
+ interface VectorQueryOptions {
9
+ mode?: 'auto' | 'exact' | 'ann';
10
+ efSearch?: number;
11
+ scoreThreshold?: number;
12
+ offset?: number;
13
+ groupBy?: string;
14
+ groupSize?: number;
15
+ oversampling?: number;
16
+ }
17
+ interface VectorBuildProgress {
18
+ id: string;
19
+ state: 'building' | 'ready' | 'cancelled' | 'failed';
20
+ processed: number;
21
+ total: number;
22
+ revision: number;
23
+ error: string | null;
24
+ }
25
+ interface VectorRebuildOptions extends VectorGraphOptions {
26
+ /** Insertions per native/worker call (1–1024); defaults to 32 for mobile responsiveness. */
27
+ batchSize?: number;
28
+ signal?: AbortSignal;
29
+ onProgress?: (progress: VectorBuildProgress) => void;
30
+ }
31
+ interface VectorIndexStatus {
32
+ field: string;
33
+ state: 'flat' | 'ready' | 'stale' | 'rebuildRequired';
34
+ persistent: boolean;
35
+ indexedVectors: number;
36
+ totalVectors: number;
37
+ deletedNodes: number;
38
+ revision: number;
39
+ indexRevision: number | null;
40
+ options: Required<VectorGraphOptions> | null;
41
+ build: VectorBuildProgress | null;
42
+ }
43
+ interface VectorQueryResult<T> {
44
+ hits: {
45
+ document: T;
46
+ score: number;
47
+ }[];
48
+ execution: {
49
+ path: 'exact' | 'hnsw';
50
+ reason: string;
51
+ revision: number;
52
+ efSearch: number | null;
53
+ distanceComputations: number;
54
+ };
55
+ /** Offset pagination uses live snapshots. Writes between pages can change ordering. */
56
+ nextOffset: number | null;
57
+ }
58
+ interface VectorRecall {
59
+ recallAtK: number;
60
+ queries: number;
61
+ topK: number;
62
+ exactMs: number;
63
+ annMs: number;
64
+ }
65
+ interface VectorClient<T> {
66
+ searchVectors(field: string, vector: ArrayLike<number>, topK: number, filter?: Record<string, unknown>, options?: VectorQueryOptions): Promise<VectorQueryResult<T>>;
67
+ /** Exact range search. Returns every matching document above the score threshold. */
68
+ findWithin(field: string, vector: ArrayLike<number>, scoreThreshold: number, filter?: Record<string, unknown>): Promise<VectorQueryResult<T>>;
69
+ vectorIndexStatus(field: string): Promise<VectorIndexStatus>;
70
+ rebuildVectorIndex(field: string, options?: VectorRebuildOptions): Promise<VectorBuildProgress>;
71
+ beginVectorBuild(field: string, options?: VectorGraphOptions): Promise<VectorBuildProgress>;
72
+ stepVectorBuild(field: string, id: string, batchSize?: number): Promise<VectorBuildProgress>;
73
+ cancelVectorBuild(field: string, id: string): Promise<VectorBuildProgress>;
74
+ measureVectorRecall(field: string, queries: ArrayLike<number>[], topK: number, filter?: Record<string, unknown>, options?: VectorQueryOptions): Promise<VectorRecall>;
75
+ }
76
+ /** Binding adapter used by TalaDB's Node, browser, and React Native packages. */
77
+ declare function createVectorClient<T>(send: (request: Record<string, unknown>) => Promise<any>): VectorClient<T>;
78
+
1
79
  /** The three mutation kinds a webhook reports, and the verb each one uses. */
2
80
  type WebhookOp = 'insert' | 'update' | 'delete';
3
81
  interface WebhookConfig {
@@ -105,13 +183,15 @@ interface VectorIndexOptions {
105
183
  * Index algorithm. Defaults to `"flat"` (exact brute-force).
106
184
  * Use `"hnsw"` for approximate nearest-neighbour search — much faster on
107
185
  * large collections at the cost of occasional missed results.
108
- * Requires the `vector-hnsw` feature to be compiled in.
186
+ * Supported on browser, Node.js and React Native.
109
187
  */
110
188
  indexType?: 'flat' | 'hnsw';
111
- /** HNSW connectivity parameter M (default 16). Higher = better recall, more memory. */
189
+ /** HNSW connectivity parameter M. Supported range is 2–128; defaults to 32. */
112
190
  hnswM?: number;
113
191
  /** HNSW build-time quality parameter ef_construction (default 200). */
114
192
  hnswEfConstruction?: number;
193
+ /** Graph compression; originals remain available for exact rescoring. */
194
+ quantization?: VectorQuantization;
115
195
  }
116
196
  /** Describes the indexes that exist on a collection. */
117
197
  interface CollectionIndexInfo {
@@ -482,7 +562,7 @@ type AggregatePipeline<T extends Document = Document> = AggregateStage<T>[];
482
562
  type InsertDoc<T extends Document> = Omit<T, '_id'> & {
483
563
  _id?: string;
484
564
  };
485
- interface Collection<T extends Document = Document> {
565
+ interface Collection<T extends Document = Document> extends VectorClient<T> {
486
566
  insert(doc: InsertDoc<T>): Promise<string>;
487
567
  insertMany(docs: InsertDoc<T>[]): Promise<string[]>;
488
568
  find(filter?: Filter<T>): Promise<T[]>;
@@ -600,7 +680,7 @@ interface Collection<T extends Document = Document> {
600
680
  *
601
681
  * After calling this, `findNearest` uses approximate nearest-neighbour
602
682
  * search which is significantly faster on large collections.
603
- * Requires the `vector-hnsw` feature to be compiled in; no-op otherwise.
683
+ * Promotes flat/legacy indexes and compacts persistent graphs.
604
684
  */
605
685
  upgradeVectorIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
606
686
  /**
@@ -620,7 +700,7 @@ interface Collection<T extends Document = Document> {
620
700
  * locale: 'en',
621
701
  * });
622
702
  */
623
- findNearest(field: keyof Omit<T, '_id'> & string, vector: number[], topK: number, filter?: Filter<T>): Promise<VectorSearchResult<T>[]>;
703
+ findNearest(field: keyof Omit<T, '_id'> & string, vector: number[], topK: number, filter?: Filter<T>, options?: VectorQueryOptions): Promise<VectorSearchResult<T>[]>;
624
704
  /**
625
705
  * Subscribe to live query results. The callback receives a full snapshot of
626
706
  * matching documents immediately and again after every write that could
@@ -681,33 +761,26 @@ interface TalaDB {
681
761
  */
682
762
  flush?(): Promise<void>;
683
763
  /**
684
- * Whether this tab's writes land authoritatively, without being forwarded to
685
- * another tab.
686
- *
687
- * **Browser only.** The first tab to open a database becomes the primary and
688
- * owns the storage; later tabs run an in-memory copy and forward their writes
689
- * to it over BroadcastChannel. Both the OPFS owner and — where OPFS is
690
- * unavailable the tab that publishes the IndexedDB snapshot report `true`.
691
- *
692
- * Use it for work that must not run in more than one tab at a time, or that
693
- * depends on reading its own writes back immediately: a background queue
694
- * drainer, a scheduled cleanup pass, an outbound sync loop. A secondary tab
695
- * sees other tabs' writes up to ~500 ms late, and its own writes only once
696
- * the primary has applied them.
697
- *
698
- * Primary status changes during a session — closing the owning tab promotes
699
- * another — so re-check it rather than caching the answer.
700
- *
701
- * Always `true` on Node.js and React Native, where a single process owns the
702
- * database. May be absent on older `@taladb/web` builds — treat absence as
703
- * `true`.
704
- *
705
- * @example
706
- * if (await db.isPrimary?.() ?? true) {
707
- * await drainOutbox();
708
- * }
764
+ * Rebuild all configured HNSW graphs for maintenance. Graphs persist across
765
+ * restarts; startup rebuilding is unnecessary. Prefer per-collection
766
+ * rebuildVectorIndex for bounded batches, progress and cancellation.
767
+ */
768
+ rebuildVectorIndexes?(): Promise<void>;
769
+ /**
770
+ * Whether this browser tab owns the database storage. All tabs execute reads
771
+ * and writes through that owner and await its result. Ownership can change
772
+ * after the owning tab closes. Node.js and React Native return true.
709
773
  */
710
774
  isPrimary?(): Promise<boolean>;
775
+ /** Actual browser storage/durability capabilities, including fallback errors. */
776
+ storageInfo?(): Promise<{
777
+ storage: 'opfs' | 'indexeddb';
778
+ durableWrites: boolean;
779
+ maxSnapshotBytes: number | null;
780
+ storageError: string | null;
781
+ hnsw: boolean;
782
+ owner: boolean;
783
+ }>;
711
784
  /**
712
785
  * Change-webhook delivery counters, when the webhook is enabled. All zero
713
786
  * (and `pending: 0`) when it is not.
@@ -945,4 +1018,4 @@ interface OpenDBOptions {
945
1018
  */
946
1019
  declare function openDB(dbName?: string, options?: OpenDBOptions): Promise<TalaDB>;
947
1020
 
948
- export { type AggregatePipeline, type AggregateStage, type Collection, type CollectionIndexInfo, type CollectionOptions, type Document, type DurabilityConfig, type Filter, type HybridSearchOptions, type HybridSearchResult, type InsertDoc, type Migration, type OpenDBOptions, type Schema, type TalaDB, type TalaDbConfig, TalaDbValidationError, type TextSearchOptions, type TextSearchResult, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, type WebhookConfig, type WebhookDispatcher, type WebhookEvent, type WebhookOp, type WebhookStats, applySchema, createWebhookDispatcher, decorateCollection, deriveDocId, isDocId, openDB, runMigrations, validateWebhookConfig };
1021
+ export { type AggregatePipeline, type AggregateStage, type Collection, type CollectionIndexInfo, type CollectionOptions, type Document, type DurabilityConfig, type Filter, type HybridSearchOptions, type HybridSearchResult, type InsertDoc, type Migration, type OpenDBOptions, type Schema, type TalaDB, type TalaDbConfig, TalaDbValidationError, type TextSearchOptions, type TextSearchResult, type Update, type Value, type VectorBuildProgress, type VectorClient, type VectorGraphOptions, type VectorIndexOptions, type VectorIndexStatus, type VectorMetric, type VectorQuantization, type VectorQueryOptions, type VectorQueryResult, type VectorRebuildOptions, type VectorRecall, type VectorSearchResult, type WebhookConfig, type WebhookDispatcher, type WebhookEvent, type WebhookOp, type WebhookStats, applySchema, createVectorClient, createWebhookDispatcher, decorateCollection, deriveDocId, isDocId, openDB, runMigrations, validateWebhookConfig };