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.d.ts
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
|
-
*
|
|
186
|
+
* Supported on browser, Node.js and React Native.
|
|
109
187
|
*/
|
|
110
188
|
indexType?: 'flat' | 'hnsw';
|
|
111
|
-
/** HNSW connectivity parameter M
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
TalaDbValidationError: () => TalaDbValidationError,
|
|
34
34
|
applySchema: () => applySchema,
|
|
35
|
+
createVectorClient: () => createVectorClient,
|
|
35
36
|
createWebhookDispatcher: () => createWebhookDispatcher,
|
|
36
37
|
decorateCollection: () => decorateCollection,
|
|
37
38
|
deriveDocId: () => deriveDocId,
|
|
@@ -42,6 +43,92 @@ __export(index_exports, {
|
|
|
42
43
|
});
|
|
43
44
|
module.exports = __toCommonJS(index_exports);
|
|
44
45
|
|
|
46
|
+
// src/vector-client.ts
|
|
47
|
+
function integer(value, name, min = 0, max = 4294967295) {
|
|
48
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${name} must be an integer between ${min} and ${max}`);
|
|
49
|
+
}
|
|
50
|
+
function vectorValues(vector) {
|
|
51
|
+
const values = Array.from(vector);
|
|
52
|
+
if (values.length === 0 || values.some((v) => !Number.isFinite(v))) throw new Error("vector must contain finite numbers");
|
|
53
|
+
return values;
|
|
54
|
+
}
|
|
55
|
+
function queryOptions(options) {
|
|
56
|
+
if (options.efSearch !== void 0) integer(options.efSearch, "efSearch", 1);
|
|
57
|
+
if (options.offset !== void 0) integer(options.offset, "offset");
|
|
58
|
+
if (options.groupSize !== void 0) integer(options.groupSize, "groupSize", 1);
|
|
59
|
+
if (options.oversampling !== void 0) integer(options.oversampling, "oversampling", 1, 100);
|
|
60
|
+
if (options.scoreThreshold !== void 0 && !Number.isFinite(options.scoreThreshold)) throw new Error("scoreThreshold must be finite");
|
|
61
|
+
return options;
|
|
62
|
+
}
|
|
63
|
+
function abortError() {
|
|
64
|
+
const error = new Error("Vector index rebuild cancelled");
|
|
65
|
+
error.name = "AbortError";
|
|
66
|
+
return error;
|
|
67
|
+
}
|
|
68
|
+
function createVectorClient(send) {
|
|
69
|
+
const client = {
|
|
70
|
+
searchVectors: async (field, vector, topK, filter, options = {}) => {
|
|
71
|
+
integer(topK, "topK");
|
|
72
|
+
return send({ op: "search", field, query: vectorValues(vector), topK, filter, options: queryOptions(options) });
|
|
73
|
+
},
|
|
74
|
+
findWithin: (field, vector, scoreThreshold, filter) => client.searchVectors(field, vector, 4294967295, filter, { mode: "exact", scoreThreshold }),
|
|
75
|
+
vectorIndexStatus: (field) => send({ op: "status", field }),
|
|
76
|
+
beginVectorBuild: (field, options) => send({ op: "beginBuild", field, options }),
|
|
77
|
+
stepVectorBuild: async (field, id, batchSize = 32) => {
|
|
78
|
+
integer(batchSize, "batchSize", 1, 1024);
|
|
79
|
+
return send({ op: "stepBuild", field, id, batchSize });
|
|
80
|
+
},
|
|
81
|
+
cancelVectorBuild: (field, id) => send({ op: "cancelBuild", field, id }),
|
|
82
|
+
rebuildVectorIndex: async (field, options = {}) => {
|
|
83
|
+
const { signal, onProgress, batchSize = 32, ...graphOptions } = options;
|
|
84
|
+
integer(batchSize, "batchSize", 1, 1024);
|
|
85
|
+
if (signal?.aborted) throw abortError();
|
|
86
|
+
let progress = await client.beginVectorBuild(field, Object.keys(graphOptions).length ? graphOptions : void 0);
|
|
87
|
+
try {
|
|
88
|
+
onProgress?.(progress);
|
|
89
|
+
while (progress.state === "building") {
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
91
|
+
if (signal?.aborted) throw abortError();
|
|
92
|
+
progress = await client.stepVectorBuild(field, progress.id, batchSize);
|
|
93
|
+
onProgress?.(progress);
|
|
94
|
+
}
|
|
95
|
+
if (progress.state === "failed") throw new Error(progress.error ?? "Vector rebuild failed");
|
|
96
|
+
if (progress.state === "cancelled") throw abortError();
|
|
97
|
+
return progress;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (progress.state === "building") await client.cancelVectorBuild(field, progress.id);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
measureVectorRecall: async (field, queries, topK, filter, options = {}) => {
|
|
104
|
+
integer(topK, "topK", 1);
|
|
105
|
+
return send({ op: "recall", field, queries: queries.map(vectorValues), topK, filter, options: queryOptions(options) });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return client;
|
|
109
|
+
}
|
|
110
|
+
function vectorIndexRequest(field, options) {
|
|
111
|
+
integer(options.dimensions, "dimensions", 1);
|
|
112
|
+
if (options.metric && !["cosine", "dot", "euclidean"].includes(options.metric)) throw new Error("invalid vector metric");
|
|
113
|
+
if (options.indexType && !["flat", "hnsw"].includes(options.indexType)) throw new Error("invalid vector indexType");
|
|
114
|
+
if (options.quantization && options.quantization !== "none" && options.indexType !== "hnsw") throw new Error("quantization requires an HNSW index");
|
|
115
|
+
if (options.indexType === "hnsw") {
|
|
116
|
+
const m = options.hnswM ?? 32;
|
|
117
|
+
const efConstruction = options.hnswEfConstruction ?? 200;
|
|
118
|
+
integer(m, "hnswM", 2, 128);
|
|
119
|
+
integer(efConstruction, "hnswEfConstruction", m, 1e5);
|
|
120
|
+
if (options.metric === "dot") throw new Error("HNSW requires cosine or euclidean");
|
|
121
|
+
if (options.quantization === "binary" && options.metric && options.metric !== "cosine") throw new Error("binary quantization requires cosine");
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
op: "create",
|
|
125
|
+
field,
|
|
126
|
+
dimensions: options.dimensions,
|
|
127
|
+
metric: options.metric,
|
|
128
|
+
options: options.indexType === "hnsw" ? { m: options.hnswM ?? 32, efConstruction: options.hnswEfConstruction ?? 200, quantization: options.quantization ?? "none" } : null
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
45
132
|
// src/webhook.ts
|
|
46
133
|
var METHOD = {
|
|
47
134
|
insert: "POST",
|
|
@@ -784,7 +871,9 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
784
871
|
}
|
|
785
872
|
function wrapCollection(name, opts) {
|
|
786
873
|
const s = JSON.stringify;
|
|
874
|
+
const vectorCommand = async (request) => JSON.parse(await proxy.send("vectorCommand", { collection: name, requestJson: JSON.stringify(request) }));
|
|
787
875
|
const wrapped = {
|
|
876
|
+
...createVectorClient(vectorCommand),
|
|
788
877
|
insert: (doc) => proxy.send("insert", { collection: name, docJson: s(doc) }),
|
|
789
878
|
insertMany: async (docs) => {
|
|
790
879
|
const json = await proxy.send("insertMany", {
|
|
@@ -836,25 +925,21 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
836
925
|
dropCompoundIndex: (fields) => proxy.send("dropCompoundIndex", { collection: name, fieldsJson: JSON.stringify(fields) }),
|
|
837
926
|
createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
|
|
838
927
|
dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
|
|
839
|
-
createVectorIndex: (field, options) => {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
field,
|
|
844
|
-
dimensions: options.dimensions,
|
|
845
|
-
metric: options.metric,
|
|
846
|
-
indexType: null,
|
|
847
|
-
hnswM: null,
|
|
848
|
-
hnswEfConstruction: null
|
|
849
|
-
});
|
|
928
|
+
createVectorIndex: async (field, options) => {
|
|
929
|
+
const request = vectorIndexRequest(field, options);
|
|
930
|
+
await vectorCommand({ ...request, deferBuild: true });
|
|
931
|
+
if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
|
|
850
932
|
},
|
|
851
933
|
dropVectorIndex: (field) => proxy.send("dropVectorIndex", { collection: name, field }),
|
|
852
|
-
upgradeVectorIndex: (
|
|
934
|
+
upgradeVectorIndex: async (field) => {
|
|
935
|
+
await createVectorClient(vectorCommand).rebuildVectorIndex(field);
|
|
936
|
+
},
|
|
853
937
|
listIndexes: async () => {
|
|
854
938
|
const json = await proxy.send("listIndexes", { collection: name });
|
|
855
939
|
return JSON.parse(json);
|
|
856
940
|
},
|
|
857
|
-
findNearest: async (field, vector, topK, filter) => {
|
|
941
|
+
findNearest: async (field, vector, topK, filter, options) => {
|
|
942
|
+
if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
|
|
858
943
|
const json = await proxy.send("findNearest", {
|
|
859
944
|
collection: name,
|
|
860
945
|
field,
|
|
@@ -912,7 +997,7 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
912
997
|
function nudgedPoller(collection, fetchJson, callback, onError) {
|
|
913
998
|
let active = true;
|
|
914
999
|
let lastJson = "";
|
|
915
|
-
let lastGeneration =
|
|
1000
|
+
let lastGeneration = null;
|
|
916
1001
|
let generationSupported = true;
|
|
917
1002
|
let timer = null;
|
|
918
1003
|
let running = false;
|
|
@@ -977,6 +1062,7 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
977
1062
|
await proxy.send("flush");
|
|
978
1063
|
},
|
|
979
1064
|
isPrimary: () => proxy.send("isPrimary"),
|
|
1065
|
+
storageInfo: () => proxy.send("capabilities"),
|
|
980
1066
|
close: async () => {
|
|
981
1067
|
channel?.close();
|
|
982
1068
|
try {
|
|
@@ -989,14 +1075,16 @@ async function createBrowserDB(dbName, webhook, config, passphrase, migrations)
|
|
|
989
1075
|
listCollectionNames: async () => JSON.parse(await proxy.send("listCollections"))
|
|
990
1076
|
};
|
|
991
1077
|
if (migrations?.length) {
|
|
992
|
-
await
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1078
|
+
await navigator.locks.request(`taladb:migrations:${dbName}`, async () => {
|
|
1079
|
+
await runMigrations(
|
|
1080
|
+
handle,
|
|
1081
|
+
async () => proxy.send("userVersion"),
|
|
1082
|
+
async (v) => {
|
|
1083
|
+
await proxy.send("setUserVersion", { version: v });
|
|
1084
|
+
},
|
|
1085
|
+
migrations
|
|
1086
|
+
);
|
|
1087
|
+
});
|
|
1000
1088
|
}
|
|
1001
1089
|
return handle;
|
|
1002
1090
|
}
|
|
@@ -1012,7 +1100,9 @@ async function createNodeDB(dbName, webhook, config, passphrase, migrations) {
|
|
|
1012
1100
|
const db = TalaDBNode.open(dbName, configJson, passphrase ?? null);
|
|
1013
1101
|
function wrapCollection(name, opts) {
|
|
1014
1102
|
const col = db.collection(name);
|
|
1103
|
+
const vectorCommand = async (request) => col.vectorCommandAsync ? col.vectorCommandAsync(request) : col.vectorCommand(request);
|
|
1015
1104
|
const wrapped = {
|
|
1105
|
+
...createVectorClient(vectorCommand),
|
|
1016
1106
|
insert: async (doc) => col.insertAsync ? col.insertAsync(doc) : col.insert(doc),
|
|
1017
1107
|
insertMany: async (docs) => col.insertManyAsync ? col.insertManyAsync(docs) : col.insertMany(docs),
|
|
1018
1108
|
find: async (filter) => col.findAsync ? col.findAsync(filter ?? null) : col.find(filter ?? null),
|
|
@@ -1029,14 +1119,19 @@ async function createNodeDB(dbName, webhook, config, passphrase, migrations) {
|
|
|
1029
1119
|
dropCompoundIndex: async (fields) => col.dropCompoundIndex(fields),
|
|
1030
1120
|
createFtsIndex: async (field) => col.createFtsIndex(field),
|
|
1031
1121
|
dropFtsIndex: async (field) => col.dropFtsIndex(field),
|
|
1032
|
-
createVectorIndex: async (field, options) =>
|
|
1122
|
+
createVectorIndex: async (field, options) => {
|
|
1123
|
+
const request = vectorIndexRequest(field, options);
|
|
1124
|
+
await vectorCommand({ ...request, deferBuild: true });
|
|
1125
|
+
if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
|
|
1126
|
+
},
|
|
1033
1127
|
dropVectorIndex: async (field) => col.dropVectorIndex(field),
|
|
1034
1128
|
upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
|
|
1035
1129
|
listIndexes: async () => {
|
|
1036
1130
|
const json = col.listIndexes();
|
|
1037
1131
|
return JSON.parse(json);
|
|
1038
1132
|
},
|
|
1039
|
-
findNearest: async (field, vector, topK, filter) => {
|
|
1133
|
+
findNearest: async (field, vector, topK, filter, options) => {
|
|
1134
|
+
if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
|
|
1040
1135
|
const raw = await col.findNearest(field, vector, topK, filter ?? null);
|
|
1041
1136
|
return raw;
|
|
1042
1137
|
},
|
|
@@ -1084,78 +1179,109 @@ async function createNativeDB(_dbName, webhook, migrations) {
|
|
|
1084
1179
|
);
|
|
1085
1180
|
}
|
|
1086
1181
|
const native = maybeNative;
|
|
1182
|
+
let nativeQueue = Promise.resolve();
|
|
1183
|
+
let queued = 0;
|
|
1184
|
+
let closed = false;
|
|
1185
|
+
const call = (op, ...args) => {
|
|
1186
|
+
if (queued >= 128) return Promise.reject(new Error("TalaDB native request queue is full"));
|
|
1187
|
+
queued++;
|
|
1188
|
+
const result = nativeQueue.then(async () => {
|
|
1189
|
+
if (closed) throw new Error("TalaDB database is closed");
|
|
1190
|
+
if (op === "close") {
|
|
1191
|
+
closed = true;
|
|
1192
|
+
native.close();
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (op === "findNearest" && native.findNearestAsync) {
|
|
1196
|
+
return native.findNearestAsync(...args);
|
|
1197
|
+
}
|
|
1198
|
+
if (native.callAsync) return native.callAsync(op, args);
|
|
1199
|
+
if (op === "find" && native.findAsync) {
|
|
1200
|
+
return native.findAsync(...args);
|
|
1201
|
+
}
|
|
1202
|
+
const method = native[op];
|
|
1203
|
+
if (!method) throw new Error(`${op} requires a newer @taladb/react-native binary; rebuild the native module`);
|
|
1204
|
+
return method(...args);
|
|
1205
|
+
});
|
|
1206
|
+
nativeQueue = result.catch(() => {
|
|
1207
|
+
}).finally(() => {
|
|
1208
|
+
queued--;
|
|
1209
|
+
});
|
|
1210
|
+
return result;
|
|
1211
|
+
};
|
|
1087
1212
|
function wrapCollection(name, opts) {
|
|
1213
|
+
const vectorCommand = async (request) => call("vectorCommand", name, request);
|
|
1088
1214
|
const wrapped = {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1215
|
+
...createVectorClient(vectorCommand),
|
|
1216
|
+
insert: async (doc) => await call("insert", name, doc),
|
|
1217
|
+
insertMany: async (docs) => await call("insertMany", name, docs),
|
|
1218
|
+
find: async (filter) => await call("find", name, filter ?? {}),
|
|
1219
|
+
findOne: async (filter) => await call("findOne", name, filter ?? {}),
|
|
1220
|
+
updateOne: async (filter, update) => await call("updateOne", name, filter, update),
|
|
1221
|
+
updateMany: async (filter, update) => await call("updateMany", name, filter, update),
|
|
1222
|
+
deleteOne: async (filter) => await call("deleteOne", name, filter),
|
|
1223
|
+
deleteMany: async (filter) => await call("deleteMany", name, filter),
|
|
1224
|
+
count: async (filter) => await call("count", name, filter ?? {}),
|
|
1225
|
+
aggregate: async (pipeline) => await call("aggregate", name, pipeline),
|
|
1226
|
+
createIndex: async (field) => await call("createIndex", name, field),
|
|
1227
|
+
dropIndex: async (field) => await call("dropIndex", name, field),
|
|
1228
|
+
createCompoundIndex: async (fields) => await call("createCompoundIndex", name, fields),
|
|
1229
|
+
dropCompoundIndex: async (fields) => await call("dropCompoundIndex", name, fields),
|
|
1230
|
+
createFtsIndex: async (field) => await call("createFtsIndex", name, field),
|
|
1231
|
+
dropFtsIndex: async (field) => await call("dropFtsIndex", name, field),
|
|
1105
1232
|
createVectorIndex: async (field, options) => {
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
if (options.
|
|
1109
|
-
opts2.hnsw = { m: options.hnswM, efConstruction: options.hnswEfConstruction };
|
|
1110
|
-
}
|
|
1111
|
-
return native.createVectorIndex(name, field, options.dimensions, opts2);
|
|
1233
|
+
const request = vectorIndexRequest(field, options);
|
|
1234
|
+
await vectorCommand({ ...request, deferBuild: true });
|
|
1235
|
+
if (request.options) await createVectorClient(vectorCommand).rebuildVectorIndex(field, request.options);
|
|
1112
1236
|
},
|
|
1113
|
-
dropVectorIndex: async (field) =>
|
|
1114
|
-
upgradeVectorIndex: async (field) =>
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const raw = native.findNearest(name, field, vector, topK, filter ?? null);
|
|
1237
|
+
dropVectorIndex: async (field) => await call("dropVectorIndex", name, field),
|
|
1238
|
+
upgradeVectorIndex: async (field) => await call("upgradeVectorIndex", name, field),
|
|
1239
|
+
listIndexes: async () => call("listIndexes", name),
|
|
1240
|
+
findNearest: async (field, vector, topK, filter, options) => {
|
|
1241
|
+
if (options) return (await createVectorClient(vectorCommand).searchVectors(field, vector, topK, filter, options)).hits;
|
|
1242
|
+
const raw = await call("findNearest", name, field, vector, topK, filter ?? null);
|
|
1120
1243
|
return raw;
|
|
1121
1244
|
},
|
|
1122
1245
|
searchText: async (field, query, topK, filter, options) => {
|
|
1123
|
-
if (!native.searchText) {
|
|
1246
|
+
if (!native.callAsync && !native.searchText) {
|
|
1124
1247
|
throw new Error("searchText requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
|
|
1125
1248
|
}
|
|
1126
|
-
return
|
|
1249
|
+
return await call("searchText", name, field, query, topK, filter ?? null, options ?? null);
|
|
1127
1250
|
},
|
|
1128
1251
|
hybridSearch: async (text, vector, topK, filter, options) => {
|
|
1129
|
-
if (!native.hybridSearch) {
|
|
1252
|
+
if (!native.callAsync && !native.hybridSearch) {
|
|
1130
1253
|
throw new Error("hybridSearch requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
|
|
1131
1254
|
}
|
|
1132
|
-
return
|
|
1255
|
+
return await call("hybridSearch", name, text.textField, text.text, vector.vectorField, vector.vector, topK, filter ?? null, options ?? null);
|
|
1133
1256
|
},
|
|
1134
|
-
subscribe: (filter, callback, onError) => makePoller(async () =>
|
|
1135
|
-
subscribeAggregate: (pipeline, callback, onError) => makePoller(async () =>
|
|
1257
|
+
subscribe: (filter, callback, onError) => makePoller(async () => await call("find", name, filter ?? {}), callback, onError),
|
|
1258
|
+
subscribeAggregate: (pipeline, callback, onError) => makePoller(async () => await call("aggregate", name, pipeline), callback, onError)
|
|
1136
1259
|
};
|
|
1137
1260
|
return decorateCollection(wrapped, name, opts, webhook);
|
|
1138
1261
|
}
|
|
1139
1262
|
const handle = {
|
|
1140
1263
|
collection: (name, opts) => wrapCollection(name, opts),
|
|
1141
|
-
compact: async () =>
|
|
1142
|
-
close: async () =>
|
|
1143
|
-
flush: native.flush ? async () => {
|
|
1144
|
-
|
|
1264
|
+
compact: async () => await call("compact"),
|
|
1265
|
+
close: async () => await call("close"),
|
|
1266
|
+
flush: native.callAsync || native.flush ? async () => {
|
|
1267
|
+
await call("flush");
|
|
1268
|
+
} : void 0,
|
|
1269
|
+
rebuildVectorIndexes: native.callAsync || native.rebuildVectorIndexes ? async () => {
|
|
1270
|
+
await call("rebuildVectorIndexes");
|
|
1145
1271
|
} : void 0,
|
|
1146
1272
|
// One process owns the file — there is no other tab to defer to.
|
|
1147
1273
|
isPrimary: async () => true
|
|
1148
1274
|
};
|
|
1149
1275
|
if (migrations?.length) {
|
|
1150
|
-
if (typeof native.userVersion !== "function" || typeof native.setUserVersion !== "function") {
|
|
1276
|
+
if (!native.callAsync && (typeof native.userVersion !== "function" || typeof native.setUserVersion !== "function")) {
|
|
1151
1277
|
throw new Error(
|
|
1152
1278
|
"openDB({ migrations }) is not available on this @taladb/react-native binary yet (the JSI HostObject does not expose userVersion/setUserVersion). Update the native module."
|
|
1153
1279
|
);
|
|
1154
1280
|
}
|
|
1155
1281
|
await runMigrations(
|
|
1156
1282
|
handle,
|
|
1157
|
-
async () =>
|
|
1158
|
-
async (v) =>
|
|
1283
|
+
async () => call("userVersion"),
|
|
1284
|
+
async (v) => call("setUserVersion", v),
|
|
1159
1285
|
migrations
|
|
1160
1286
|
);
|
|
1161
1287
|
}
|
|
@@ -1231,6 +1357,7 @@ function attachWebhook(db, webhook) {
|
|
|
1231
1357
|
0 && (module.exports = {
|
|
1232
1358
|
TalaDbValidationError,
|
|
1233
1359
|
applySchema,
|
|
1360
|
+
createVectorClient,
|
|
1234
1361
|
createWebhookDispatcher,
|
|
1235
1362
|
decorateCollection,
|
|
1236
1363
|
deriveDocId,
|