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.
- package/dist/index.browser.mjs +192 -66
- package/dist/index.d.mts +104 -31
- package/dist/index.d.ts +104 -31
- package/dist/index.js +193 -66
- package/dist/index.mjs +192 -66
- package/dist/index.react-native.mjs +192 -66
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -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/webhook.ts
|
|
2
88
|
var METHOD = {
|
|
3
89
|
insert: "POST",
|
|
@@ -739,7 +825,9 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
739
825
|
}
|
|
740
826
|
function wrapCollection(name, opts) {
|
|
741
827
|
const s = JSON.stringify;
|
|
828
|
+
const vectorCommand = async (request) => JSON.parse(await proxy.send("vectorCommand", { collection: name, requestJson: JSON.stringify(request) }));
|
|
742
829
|
const wrapped = {
|
|
830
|
+
...createVectorClient(vectorCommand),
|
|
743
831
|
insert: (doc) => proxy.send("insert", { collection: name, docJson: s(doc) }),
|
|
744
832
|
insertMany: async (docs) => {
|
|
745
833
|
const json = await proxy.send("insertMany", {
|
|
@@ -791,25 +879,21 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
791
879
|
dropCompoundIndex: (fields) => proxy.send("dropCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
|
|
792
880
|
createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
|
|
793
881
|
dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
|
|
794
|
-
createVectorIndex: (field, options) => {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
field,
|
|
799
|
-
dimensions: options.dimensions,
|
|
800
|
-
metric: options.metric,
|
|
801
|
-
indexType: null,
|
|
802
|
-
hnswM: null,
|
|
803
|
-
hnswEfConstruction: null
|
|
804
|
-
});
|
|
882
|
+
createVectorIndex: async (field, options) => {
|
|
883
|
+
const request = vectorIndexRequest(field, options);
|
|
884
|
+
await vectorCommand({ ...request, deferBuild: true });
|
|
885
|
+
if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
|
|
805
886
|
},
|
|
806
887
|
dropVectorIndex: (field) => proxy.send("dropVectorIndex", { collection: name, field }),
|
|
807
|
-
upgradeVectorIndex: (
|
|
888
|
+
upgradeVectorIndex: async (field) => {
|
|
889
|
+
await createVectorClient(vectorCommand).rebuildVectorIndex(field);
|
|
890
|
+
},
|
|
808
891
|
listIndexes: async () => {
|
|
809
892
|
const json = await proxy.send("listIndexes", { collection: name });
|
|
810
893
|
return JSON.parse(json);
|
|
811
894
|
},
|
|
812
|
-
findNearest: async (field, vector, topK, filter) => {
|
|
895
|
+
findNearest: async (field, vector, topK, filter, options) => {
|
|
896
|
+
if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
|
|
813
897
|
const json = await proxy.send("findNearest", {
|
|
814
898
|
collection: name,
|
|
815
899
|
field,
|
|
@@ -867,7 +951,7 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
867
951
|
function nudgedPoller(collection, fetchJson, callback, onError) {
|
|
868
952
|
let active = true;
|
|
869
953
|
let lastJson = "";
|
|
870
|
-
let lastGeneration =
|
|
954
|
+
let lastGeneration = null;
|
|
871
955
|
let generationSupported = true;
|
|
872
956
|
let timer = null;
|
|
873
957
|
let running = false;
|
|
@@ -932,6 +1016,7 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
932
1016
|
await proxy.send("flush");
|
|
933
1017
|
},
|
|
934
1018
|
isPrimary: () => proxy.send("isPrimary"),
|
|
1019
|
+
storageInfo: () => proxy.send("capabilities"),
|
|
935
1020
|
close: async () => {
|
|
936
1021
|
channel?.close();
|
|
937
1022
|
try {
|
|
@@ -944,14 +1029,16 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
944
1029
|
listCollectionNames: async () => JSON.parse(await proxy.send("listCollections"))
|
|
945
1030
|
};
|
|
946
1031
|
if (migrations?.length) {
|
|
947
|
-
await
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1032
|
+
await navigator.locks.request(`taladb:migrations:${dbName}`, async () => {
|
|
1033
|
+
await runMigrations(
|
|
1034
|
+
handle,
|
|
1035
|
+
async () => proxy.send("userVersion"),
|
|
1036
|
+
async (v) => {
|
|
1037
|
+
await proxy.send("setUserVersion", { version: v });
|
|
1038
|
+
},
|
|
1039
|
+
migrations
|
|
1040
|
+
);
|
|
1041
|
+
});
|
|
955
1042
|
}
|
|
956
1043
|
return handle;
|
|
957
1044
|
}
|
|
@@ -967,7 +1054,9 @@ async function createNodeDB(dbName, webhook, config, passphrase, migrations) {
|
|
|
967
1054
|
const db = TalaDBNode.open(dbName, configJson, passphrase ?? null);
|
|
968
1055
|
function wrapCollection(name, opts) {
|
|
969
1056
|
const col = db.collection(name);
|
|
1057
|
+
const vectorCommand = async (request) => col.vectorCommandAsync ? col.vectorCommandAsync(request) : col.vectorCommand(request);
|
|
970
1058
|
const wrapped = {
|
|
1059
|
+
...createVectorClient(vectorCommand),
|
|
971
1060
|
insert: async (doc) => col.insertAsync ? col.insertAsync(doc) : col.insert(doc),
|
|
972
1061
|
insertMany: async (docs) => col.insertManyAsync ? col.insertManyAsync(docs) : col.insertMany(docs),
|
|
973
1062
|
find: async (filter) => col.findAsync ? col.findAsync(filter ?? null) : col.find(filter ?? null),
|
|
@@ -984,14 +1073,19 @@ async function createNodeDB(dbName, webhook, config, passphrase, migrations) {
|
|
|
984
1073
|
dropCompoundIndex: async (fields) => col.dropCompoundIndex(fields),
|
|
985
1074
|
createFtsIndex: async (field) => col.createFtsIndex(field),
|
|
986
1075
|
dropFtsIndex: async (field) => col.dropFtsIndex(field),
|
|
987
|
-
createVectorIndex: async (field, options) =>
|
|
1076
|
+
createVectorIndex: async (field, options) => {
|
|
1077
|
+
const request = vectorIndexRequest(field, options);
|
|
1078
|
+
await vectorCommand({ ...request, deferBuild: true });
|
|
1079
|
+
if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
|
|
1080
|
+
},
|
|
988
1081
|
dropVectorIndex: async (field) => col.dropVectorIndex(field),
|
|
989
1082
|
upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
|
|
990
1083
|
listIndexes: async () => {
|
|
991
1084
|
const json = col.listIndexes();
|
|
992
1085
|
return JSON.parse(json);
|
|
993
1086
|
},
|
|
994
|
-
findNearest: async (field, vector, topK, filter) => {
|
|
1087
|
+
findNearest: async (field, vector, topK, filter, options) => {
|
|
1088
|
+
if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
|
|
995
1089
|
const raw = await col.findNearest(field, vector, topK, filter ?? null);
|
|
996
1090
|
return raw;
|
|
997
1091
|
},
|
|
@@ -1039,78 +1133,109 @@ async function createNativeDB(_dbName, webhook, migrations) {
|
|
|
1039
1133
|
);
|
|
1040
1134
|
}
|
|
1041
1135
|
const native = maybeNative;
|
|
1136
|
+
let nativeQueue = Promise.resolve();
|
|
1137
|
+
let queued = 0;
|
|
1138
|
+
let closed = false;
|
|
1139
|
+
const call = (op, ...args) => {
|
|
1140
|
+
if (queued >= 128) return Promise.reject(new Error("TalaDB native request queue is full"));
|
|
1141
|
+
queued++;
|
|
1142
|
+
const result = nativeQueue.then(async () => {
|
|
1143
|
+
if (closed) throw new Error("TalaDB database is closed");
|
|
1144
|
+
if (op === "close") {
|
|
1145
|
+
closed = true;
|
|
1146
|
+
native.close();
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (op === "findNearest" && native.findNearestAsync) {
|
|
1150
|
+
return native.findNearestAsync(...args);
|
|
1151
|
+
}
|
|
1152
|
+
if (native.callAsync) return native.callAsync(op, args);
|
|
1153
|
+
if (op === "find" && native.findAsync) {
|
|
1154
|
+
return native.findAsync(...args);
|
|
1155
|
+
}
|
|
1156
|
+
const method = native[op];
|
|
1157
|
+
if (!method) throw new Error(`${op} requires a newer @taladb/react-native binary; rebuild the native module`);
|
|
1158
|
+
return method(...args);
|
|
1159
|
+
});
|
|
1160
|
+
nativeQueue = result.catch(() => {
|
|
1161
|
+
}).finally(() => {
|
|
1162
|
+
queued--;
|
|
1163
|
+
});
|
|
1164
|
+
return result;
|
|
1165
|
+
};
|
|
1042
1166
|
function wrapCollection(name, opts) {
|
|
1167
|
+
const vectorCommand = async (request) => call("vectorCommand", name, request);
|
|
1043
1168
|
const wrapped = {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1169
|
+
...createVectorClient(vectorCommand),
|
|
1170
|
+
insert: async (doc) => await call("insert", name, doc),
|
|
1171
|
+
insertMany: async (docs) => await call("insertMany", name, docs),
|
|
1172
|
+
find: async (filter) => await call("find", name, filter ?? {}),
|
|
1173
|
+
findOne: async (filter) => await call("findOne", name, filter ?? {}),
|
|
1174
|
+
updateOne: async (filter, update) => await call("updateOne", name, filter, update),
|
|
1175
|
+
updateMany: async (filter, update) => await call("updateMany", name, filter, update),
|
|
1176
|
+
deleteOne: async (filter) => await call("deleteOne", name, filter),
|
|
1177
|
+
deleteMany: async (filter) => await call("deleteMany", name, filter),
|
|
1178
|
+
count: async (filter) => await call("count", name, filter ?? {}),
|
|
1179
|
+
aggregate: async (pipeline) => await call("aggregate", name, pipeline),
|
|
1180
|
+
createIndex: async (field) => await call("createIndex", name, field),
|
|
1181
|
+
dropIndex: async (field) => await call("dropIndex", name, field),
|
|
1182
|
+
createCompoundIndex: async (fields) => await call("createCompoundIndex", name, fields),
|
|
1183
|
+
dropCompoundIndex: async (fields) => await call("dropCompoundIndex", name, fields),
|
|
1184
|
+
createFtsIndex: async (field) => await call("createFtsIndex", name, field),
|
|
1185
|
+
dropFtsIndex: async (field) => await call("dropFtsIndex", name, field),
|
|
1060
1186
|
createVectorIndex: async (field, options) => {
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
if (options.
|
|
1064
|
-
opts2.hnsw = { m: options.hnswM, efConstruction: options.hnswEfConstruction };
|
|
1065
|
-
}
|
|
1066
|
-
return native.createVectorIndex(name, field, options.dimensions, opts2);
|
|
1187
|
+
const request = vectorIndexRequest(field, options);
|
|
1188
|
+
await vectorCommand({ ...request, deferBuild: true });
|
|
1189
|
+
if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
|
|
1067
1190
|
},
|
|
1068
|
-
dropVectorIndex: async (field) =>
|
|
1069
|
-
upgradeVectorIndex: async (field) =>
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const raw = native.findNearest(name, field, vector, topK, filter ?? null);
|
|
1191
|
+
dropVectorIndex: async (field) => await call("dropVectorIndex", name, field),
|
|
1192
|
+
upgradeVectorIndex: async (field) => await call("upgradeVectorIndex", name, field),
|
|
1193
|
+
listIndexes: async () => call("listIndexes", name),
|
|
1194
|
+
findNearest: async (field, vector, topK, filter, options) => {
|
|
1195
|
+
if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
|
|
1196
|
+
const raw = await call("findNearest", name, field, vector, topK, filter ?? null);
|
|
1075
1197
|
return raw;
|
|
1076
1198
|
},
|
|
1077
1199
|
searchText: async (field, query, topK, filter, options) => {
|
|
1078
|
-
if (!native.searchText) {
|
|
1200
|
+
if (!native.callAsync && !native.searchText) {
|
|
1079
1201
|
throw new Error("searchText requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
|
|
1080
1202
|
}
|
|
1081
|
-
return
|
|
1203
|
+
return await call("searchText", name, field, query, topK, filter ?? null, options ?? null);
|
|
1082
1204
|
},
|
|
1083
1205
|
hybridSearch: async (text, vector, topK, filter, options) => {
|
|
1084
|
-
if (!native.hybridSearch) {
|
|
1206
|
+
if (!native.callAsync && !native.hybridSearch) {
|
|
1085
1207
|
throw new Error("hybridSearch requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
|
|
1086
1208
|
}
|
|
1087
|
-
return
|
|
1209
|
+
return await call("hybridSearch", name, text.textField, text.text, vector.vectorField, vector.vector, topK, filter ?? null, options ?? null);
|
|
1088
1210
|
},
|
|
1089
|
-
subscribe: (filter, callback, onError) => makePoller(async () =>
|
|
1090
|
-
subscribeAggregate: (pipeline, callback, onError) => makePoller(async () =>
|
|
1211
|
+
subscribe: (filter, callback, onError) => makePoller(async () => await call("find", name, filter ?? {}), callback, onError),
|
|
1212
|
+
subscribeAggregate: (pipeline, callback, onError) => makePoller(async () => await call("aggregate", name, pipeline), callback, onError)
|
|
1091
1213
|
};
|
|
1092
1214
|
return decorateCollection(wrapped, name, opts, webhook);
|
|
1093
1215
|
}
|
|
1094
1216
|
const handle = {
|
|
1095
1217
|
collection: (name, opts) => wrapCollection(name, opts),
|
|
1096
|
-
compact: async () =>
|
|
1097
|
-
close: async () =>
|
|
1098
|
-
flush: native.flush ? async () => {
|
|
1099
|
-
|
|
1218
|
+
compact: async () => await call("compact"),
|
|
1219
|
+
close: async () => await call("close"),
|
|
1220
|
+
flush: native.callAsync || native.flush ? async () => {
|
|
1221
|
+
await call("flush");
|
|
1222
|
+
} : void 0,
|
|
1223
|
+
rebuildVectorIndexes: native.callAsync || native.rebuildVectorIndexes ? async () => {
|
|
1224
|
+
await call("rebuildVectorIndexes");
|
|
1100
1225
|
} : void 0,
|
|
1101
1226
|
// One process owns the file — there is no other tab to defer to.
|
|
1102
1227
|
isPrimary: async () => true
|
|
1103
1228
|
};
|
|
1104
1229
|
if (migrations?.length) {
|
|
1105
|
-
if (typeof native.userVersion !== "function" || typeof native.setUserVersion !== "function") {
|
|
1230
|
+
if (!native.callAsync && (typeof native.userVersion !== "function" || typeof native.setUserVersion !== "function")) {
|
|
1106
1231
|
throw new Error(
|
|
1107
1232
|
"openDB({ migrations }) is not available on this @taladb/react-native binary yet (the JSI HostObject does not expose userVersion/setUserVersion). Update the native module."
|
|
1108
1233
|
);
|
|
1109
1234
|
}
|
|
1110
1235
|
await runMigrations(
|
|
1111
1236
|
handle,
|
|
1112
|
-
async () =>
|
|
1113
|
-
async (v) =>
|
|
1237
|
+
async () => call("userVersion"),
|
|
1238
|
+
async (v) => call("setUserVersion", v),
|
|
1114
1239
|
migrations
|
|
1115
1240
|
);
|
|
1116
1241
|
}
|
|
@@ -1185,6 +1310,7 @@ function attachWebhook(db, webhook) {
|
|
|
1185
1310
|
export {
|
|
1186
1311
|
TalaDbValidationError,
|
|
1187
1312
|
applySchema,
|
|
1313
|
+
createVectorClient,
|
|
1188
1314
|
createWebhookDispatcher,
|
|
1189
1315
|
decorateCollection,
|
|
1190
1316
|
deriveDocId,
|