taladb 0.2.11 → 0.4.0

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.d.mts CHANGED
@@ -5,6 +5,26 @@ interface VectorIndexOptions {
5
5
  dimensions: number;
6
6
  /** Similarity metric. Defaults to `"cosine"`. */
7
7
  metric?: VectorMetric;
8
+ /**
9
+ * Index algorithm. Defaults to `"flat"` (exact brute-force).
10
+ * Use `"hnsw"` for approximate nearest-neighbour search — much faster on
11
+ * large collections at the cost of occasional missed results.
12
+ * Requires the `vector-hnsw` feature to be compiled in.
13
+ */
14
+ indexType?: 'flat' | 'hnsw';
15
+ /** HNSW connectivity parameter M (default 16). Higher = better recall, more memory. */
16
+ hnswM?: number;
17
+ /** HNSW build-time quality parameter ef_construction (default 200). */
18
+ hnswEfConstruction?: number;
19
+ }
20
+ /** Describes the indexes that exist on a collection. */
21
+ interface CollectionIndexInfo {
22
+ /** B-tree indexes (created with `createIndex`). */
23
+ btree: string[];
24
+ /** Full-text search indexes (created with `createFtsIndex`). */
25
+ fts: string[];
26
+ /** Vector indexes (created with `createVectorIndex`). */
27
+ vector: string[];
8
28
  }
9
29
  /** A single result returned by `Collection.findNearest`. */
10
30
  interface VectorSearchResult<T extends Document = Document> {
@@ -72,6 +92,25 @@ interface Collection<T extends Document = Document> {
72
92
  count(filter?: Filter<T>): Promise<number>;
73
93
  createIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
74
94
  dropIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
95
+ /**
96
+ * Create a full-text search index on a string field.
97
+ *
98
+ * Enables fast `{ field: { $contains: 'token' } }` queries using an
99
+ * inverted token index instead of a full collection scan.
100
+ *
101
+ * @example
102
+ * await notes.createFtsIndex('body');
103
+ */
104
+ createFtsIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
105
+ /** Drop a full-text search index. */
106
+ dropFtsIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
107
+ /**
108
+ * Return the indexes that currently exist on this collection.
109
+ *
110
+ * @example
111
+ * const { btree, fts, vector } = await notes.listIndexes();
112
+ */
113
+ listIndexes(): Promise<CollectionIndexInfo>;
75
114
  /**
76
115
  * Create a vector index on a numeric-array field.
77
116
  *
@@ -84,6 +123,14 @@ interface Collection<T extends Document = Document> {
84
123
  createVectorIndex(field: keyof Omit<T, '_id'> & string, options: VectorIndexOptions): Promise<void>;
85
124
  /** Drop a vector index. */
86
125
  dropVectorIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
126
+ /**
127
+ * Upgrade a flat vector index to HNSW in-place.
128
+ *
129
+ * After calling this, `findNearest` uses approximate nearest-neighbour
130
+ * search which is significantly faster on large collections.
131
+ * Requires the `vector-hnsw` feature to be compiled in; no-op otherwise.
132
+ */
133
+ upgradeVectorIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
87
134
  /**
88
135
  * Find the `topK` most similar documents to `vector` using a vector index.
89
136
  *
@@ -136,4 +183,4 @@ interface TalaDB {
136
183
  */
137
184
  declare function openDB(dbName?: string): Promise<TalaDB>;
138
185
 
139
- export { type Collection, type Document, type Filter, type TalaDB, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, openDB };
186
+ export { type Collection, type CollectionIndexInfo, type Document, type Filter, type TalaDB, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, openDB };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,26 @@ interface VectorIndexOptions {
5
5
  dimensions: number;
6
6
  /** Similarity metric. Defaults to `"cosine"`. */
7
7
  metric?: VectorMetric;
8
+ /**
9
+ * Index algorithm. Defaults to `"flat"` (exact brute-force).
10
+ * Use `"hnsw"` for approximate nearest-neighbour search — much faster on
11
+ * large collections at the cost of occasional missed results.
12
+ * Requires the `vector-hnsw` feature to be compiled in.
13
+ */
14
+ indexType?: 'flat' | 'hnsw';
15
+ /** HNSW connectivity parameter M (default 16). Higher = better recall, more memory. */
16
+ hnswM?: number;
17
+ /** HNSW build-time quality parameter ef_construction (default 200). */
18
+ hnswEfConstruction?: number;
19
+ }
20
+ /** Describes the indexes that exist on a collection. */
21
+ interface CollectionIndexInfo {
22
+ /** B-tree indexes (created with `createIndex`). */
23
+ btree: string[];
24
+ /** Full-text search indexes (created with `createFtsIndex`). */
25
+ fts: string[];
26
+ /** Vector indexes (created with `createVectorIndex`). */
27
+ vector: string[];
8
28
  }
9
29
  /** A single result returned by `Collection.findNearest`. */
10
30
  interface VectorSearchResult<T extends Document = Document> {
@@ -72,6 +92,25 @@ interface Collection<T extends Document = Document> {
72
92
  count(filter?: Filter<T>): Promise<number>;
73
93
  createIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
74
94
  dropIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
95
+ /**
96
+ * Create a full-text search index on a string field.
97
+ *
98
+ * Enables fast `{ field: { $contains: 'token' } }` queries using an
99
+ * inverted token index instead of a full collection scan.
100
+ *
101
+ * @example
102
+ * await notes.createFtsIndex('body');
103
+ */
104
+ createFtsIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
105
+ /** Drop a full-text search index. */
106
+ dropFtsIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
107
+ /**
108
+ * Return the indexes that currently exist on this collection.
109
+ *
110
+ * @example
111
+ * const { btree, fts, vector } = await notes.listIndexes();
112
+ */
113
+ listIndexes(): Promise<CollectionIndexInfo>;
75
114
  /**
76
115
  * Create a vector index on a numeric-array field.
77
116
  *
@@ -84,6 +123,14 @@ interface Collection<T extends Document = Document> {
84
123
  createVectorIndex(field: keyof Omit<T, '_id'> & string, options: VectorIndexOptions): Promise<void>;
85
124
  /** Drop a vector index. */
86
125
  dropVectorIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
126
+ /**
127
+ * Upgrade a flat vector index to HNSW in-place.
128
+ *
129
+ * After calling this, `findNearest` uses approximate nearest-neighbour
130
+ * search which is significantly faster on large collections.
131
+ * Requires the `vector-hnsw` feature to be compiled in; no-op otherwise.
132
+ */
133
+ upgradeVectorIndex(field: keyof Omit<T, '_id'> & string): Promise<void>;
87
134
  /**
88
135
  * Find the `topK` most similar documents to `vector` using a vector index.
89
136
  *
@@ -136,4 +183,4 @@ interface TalaDB {
136
183
  */
137
184
  declare function openDB(dbName?: string): Promise<TalaDB>;
138
185
 
139
- export { type Collection, type Document, type Filter, type TalaDB, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, openDB };
186
+ export { type Collection, type CollectionIndexInfo, type Document, type Filter, type TalaDB, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, openDB };
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ var WorkerProxy = class {
57
57
  else p.reject(new Error(error));
58
58
  }
59
59
  };
60
- this.port.start();
60
+ this.port.start?.();
61
61
  }
62
62
  send(op, args = {}) {
63
63
  return new Promise((resolve, reject) => {
@@ -67,71 +67,42 @@ var WorkerProxy = class {
67
67
  });
68
68
  }
69
69
  };
70
- async function createInMemoryBrowserDB(_dbName) {
71
- const wasmUrl = new URL("@taladb/web/pkg/taladb_web.js", import_meta.url);
72
- const wasm = await import(
73
- /* @vite-ignore */
74
- wasmUrl.href
75
- );
76
- await wasm.default();
77
- const db = wasm.TalaDBWasm.openInMemory();
78
- function wrapCollection(name) {
79
- const col = db.collection(name);
80
- return {
81
- insert: async (doc) => col.insert(doc),
82
- insertMany: async (docs) => col.insertMany(docs),
83
- find: async (filter) => col.find(filter ?? null),
84
- findOne: async (filter) => col.findOne(filter) ?? null,
85
- updateOne: async (filter, update) => col.updateOne(filter, update),
86
- updateMany: async (filter, update) => col.updateMany(filter, update),
87
- deleteOne: async (filter) => col.deleteOne(filter),
88
- deleteMany: async (filter) => col.deleteMany(filter),
89
- count: async (filter) => col.count(filter ?? null),
90
- createIndex: async (field) => col.createIndex(field),
91
- dropIndex: async (field) => col.dropIndex(field),
92
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null),
93
- dropVectorIndex: async (field) => col.dropVectorIndex(field),
94
- findNearest: async (field, vector, topK, filter) => {
95
- const raw = await col.findNearest(field, vector, topK, filter ?? null);
96
- return raw;
97
- },
98
- subscribe: (filter, callback) => {
99
- let active = true;
100
- let lastJson = "";
101
- const poll = async () => {
102
- if (!active) return;
103
- try {
104
- const docs = await col.find(filter ?? null);
105
- const json = JSON.stringify(docs);
106
- if (json !== lastJson) {
107
- lastJson = json;
108
- callback(docs);
109
- }
110
- } catch {
111
- }
112
- if (active) setTimeout(poll, 300);
113
- };
114
- poll();
115
- return () => {
116
- active = false;
117
- };
70
+ function makePoller(findFn, callback) {
71
+ let active = true;
72
+ let lastJson = "";
73
+ const poll = async () => {
74
+ if (!active) return;
75
+ try {
76
+ const docs = await findFn();
77
+ const json = JSON.stringify(docs);
78
+ if (json !== lastJson) {
79
+ lastJson = json;
80
+ callback(docs);
118
81
  }
119
- };
120
- }
121
- return {
122
- collection: (name) => wrapCollection(name),
123
- close: async () => {
82
+ } catch {
124
83
  }
84
+ if (active) setTimeout(poll, 300);
85
+ };
86
+ poll();
87
+ return () => {
88
+ active = false;
125
89
  };
126
90
  }
127
91
  async function createBrowserDB(dbName) {
128
- if (typeof SharedWorker === "undefined") {
129
- return createInMemoryBrowserDB(dbName);
130
- }
131
92
  const workerUrl = new URL("@taladb/web/worker/taladb.worker.js", import_meta.url);
132
- const worker = new SharedWorker(workerUrl, { type: "module", name: "taladb" });
133
- const proxy = new WorkerProxy(worker.port);
93
+ const worker = new Worker(workerUrl, { type: "module", name: "taladb" });
94
+ const proxy = new WorkerProxy(worker);
134
95
  await proxy.send("init", { dbName });
96
+ const nudgeCallbacks = /* @__PURE__ */ new Set();
97
+ let channel = null;
98
+ if (typeof BroadcastChannel !== "undefined") {
99
+ channel = new BroadcastChannel(`taladb:${dbName}`);
100
+ channel.onmessage = (e) => {
101
+ if (e.data === "taladb:changed") {
102
+ for (const nudge of nudgeCallbacks) nudge();
103
+ }
104
+ };
105
+ }
135
106
  function wrapCollection(name) {
136
107
  const s = JSON.stringify;
137
108
  return {
@@ -175,13 +146,26 @@ async function createBrowserDB(dbName) {
175
146
  }),
176
147
  createIndex: (field) => proxy.send("createIndex", { collection: name, field }),
177
148
  dropIndex: (field) => proxy.send("dropIndex", { collection: name, field }),
178
- createVectorIndex: (field, options) => proxy.send("createVectorIndex", {
179
- collection: name,
180
- field,
181
- dimensions: options.dimensions,
182
- metric: options.metric
183
- }),
149
+ createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
150
+ dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
151
+ createVectorIndex: (field, options) => {
152
+ 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."));
153
+ return proxy.send("createVectorIndex", {
154
+ collection: name,
155
+ field,
156
+ dimensions: options.dimensions,
157
+ metric: options.metric,
158
+ indexType: null,
159
+ hnswM: null,
160
+ hnswEfConstruction: null
161
+ });
162
+ },
184
163
  dropVectorIndex: (field) => proxy.send("dropVectorIndex", { collection: name, field }),
164
+ upgradeVectorIndex: (_field) => Promise.reject(new Error("HNSW vector indexes are not available in the browser (requires native threads). Use Node.js or React Native.")),
165
+ listIndexes: async () => {
166
+ const json = await proxy.send("listIndexes", { collection: name });
167
+ return JSON.parse(json);
168
+ },
185
169
  findNearest: async (field, vector, topK, filter) => {
186
170
  const json = await proxy.send("findNearest", {
187
171
  collection: name,
@@ -194,9 +178,14 @@ async function createBrowserDB(dbName) {
194
178
  },
195
179
  subscribe: (filter, callback) => {
196
180
  let active = true;
197
- let lastJson = "";
181
+ let lastJson = "[]";
182
+ let timer = null;
198
183
  const poll = async () => {
199
184
  if (!active) return;
185
+ if (timer !== null) {
186
+ clearTimeout(timer);
187
+ timer = null;
188
+ }
200
189
  try {
201
190
  const json = await proxy.send("find", {
202
191
  collection: name,
@@ -208,18 +197,28 @@ async function createBrowserDB(dbName) {
208
197
  }
209
198
  } catch {
210
199
  }
211
- if (active) setTimeout(poll, 300);
200
+ if (active) timer = setTimeout(poll, 300);
212
201
  };
202
+ nudgeCallbacks.add(poll);
213
203
  poll();
214
204
  return () => {
215
205
  active = false;
206
+ nudgeCallbacks.delete(poll);
207
+ if (timer !== null) {
208
+ clearTimeout(timer);
209
+ timer = null;
210
+ }
216
211
  };
217
212
  }
218
213
  };
219
214
  }
220
215
  return {
221
216
  collection: (name) => wrapCollection(name),
222
- close: () => proxy.send("close")
217
+ close: async () => {
218
+ channel?.close();
219
+ await proxy.send("close");
220
+ worker.terminate();
221
+ }
223
222
  };
224
223
  }
225
224
  async function createNodeDB(dbName) {
@@ -239,33 +238,20 @@ async function createNodeDB(dbName) {
239
238
  count: async (filter) => col.count(filter ?? null),
240
239
  createIndex: async (field) => col.createIndex(field),
241
240
  dropIndex: async (field) => col.dropIndex(field),
242
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null),
241
+ createFtsIndex: async (field) => col.createFtsIndex(field),
242
+ dropFtsIndex: async (field) => col.dropFtsIndex(field),
243
+ createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
243
244
  dropVectorIndex: async (field) => col.dropVectorIndex(field),
245
+ upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
246
+ listIndexes: async () => {
247
+ const json = col.listIndexes();
248
+ return JSON.parse(json);
249
+ },
244
250
  findNearest: async (field, vector, topK, filter) => {
245
251
  const raw = await col.findNearest(field, vector, topK, filter ?? null);
246
252
  return raw;
247
253
  },
248
- subscribe: (filter, callback) => {
249
- let active = true;
250
- let lastJson = "";
251
- const poll = async () => {
252
- if (!active) return;
253
- try {
254
- const docs = await col.find(filter ?? null);
255
- const json = JSON.stringify(docs);
256
- if (json !== lastJson) {
257
- lastJson = json;
258
- callback(docs);
259
- }
260
- } catch {
261
- }
262
- if (active) setTimeout(poll, 300);
263
- };
264
- poll();
265
- return () => {
266
- active = false;
267
- };
268
- }
254
+ subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? null), callback)
269
255
  };
270
256
  }
271
257
  return {
@@ -296,33 +282,17 @@ async function createNativeDB(_dbName) {
296
282
  count: async (filter) => col.count(filter ?? {}),
297
283
  createIndex: async (field) => col.createIndex(field),
298
284
  dropIndex: async (field) => col.dropIndex(field),
299
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null),
285
+ createFtsIndex: async (field) => col.createFtsIndex(field),
286
+ dropFtsIndex: async (field) => col.dropFtsIndex(field),
287
+ createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
300
288
  dropVectorIndex: async (field) => col.dropVectorIndex(field),
289
+ upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
290
+ listIndexes: async () => JSON.parse(col.listIndexes()),
301
291
  findNearest: async (field, vector, topK, filter) => {
302
292
  const raw = col.findNearest(field, vector, topK, filter ?? null);
303
293
  return raw;
304
294
  },
305
- subscribe: (filter, callback) => {
306
- let active = true;
307
- let lastJson = "";
308
- const poll = () => {
309
- if (!active) return;
310
- try {
311
- const docs = col.find(filter ?? {});
312
- const json = JSON.stringify(docs);
313
- if (json !== lastJson) {
314
- lastJson = json;
315
- callback(docs);
316
- }
317
- } catch {
318
- }
319
- if (active) setTimeout(poll, 300);
320
- };
321
- poll();
322
- return () => {
323
- active = false;
324
- };
325
- }
295
+ subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? {}), callback)
326
296
  };
327
297
  }
328
298
  return {
package/dist/index.mjs CHANGED
@@ -22,7 +22,7 @@ var WorkerProxy = class {
22
22
  else p.reject(new Error(error));
23
23
  }
24
24
  };
25
- this.port.start();
25
+ this.port.start?.();
26
26
  }
27
27
  send(op, args = {}) {
28
28
  return new Promise((resolve, reject) => {
@@ -32,71 +32,42 @@ var WorkerProxy = class {
32
32
  });
33
33
  }
34
34
  };
35
- async function createInMemoryBrowserDB(_dbName) {
36
- const wasmUrl = new URL("@taladb/web/pkg/taladb_web.js", import.meta.url);
37
- const wasm = await import(
38
- /* @vite-ignore */
39
- wasmUrl.href
40
- );
41
- await wasm.default();
42
- const db = wasm.TalaDBWasm.openInMemory();
43
- function wrapCollection(name) {
44
- const col = db.collection(name);
45
- return {
46
- insert: async (doc) => col.insert(doc),
47
- insertMany: async (docs) => col.insertMany(docs),
48
- find: async (filter) => col.find(filter ?? null),
49
- findOne: async (filter) => col.findOne(filter) ?? null,
50
- updateOne: async (filter, update) => col.updateOne(filter, update),
51
- updateMany: async (filter, update) => col.updateMany(filter, update),
52
- deleteOne: async (filter) => col.deleteOne(filter),
53
- deleteMany: async (filter) => col.deleteMany(filter),
54
- count: async (filter) => col.count(filter ?? null),
55
- createIndex: async (field) => col.createIndex(field),
56
- dropIndex: async (field) => col.dropIndex(field),
57
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null),
58
- dropVectorIndex: async (field) => col.dropVectorIndex(field),
59
- findNearest: async (field, vector, topK, filter) => {
60
- const raw = await col.findNearest(field, vector, topK, filter ?? null);
61
- return raw;
62
- },
63
- subscribe: (filter, callback) => {
64
- let active = true;
65
- let lastJson = "";
66
- const poll = async () => {
67
- if (!active) return;
68
- try {
69
- const docs = await col.find(filter ?? null);
70
- const json = JSON.stringify(docs);
71
- if (json !== lastJson) {
72
- lastJson = json;
73
- callback(docs);
74
- }
75
- } catch {
76
- }
77
- if (active) setTimeout(poll, 300);
78
- };
79
- poll();
80
- return () => {
81
- active = false;
82
- };
35
+ function makePoller(findFn, callback) {
36
+ let active = true;
37
+ let lastJson = "";
38
+ const poll = async () => {
39
+ if (!active) return;
40
+ try {
41
+ const docs = await findFn();
42
+ const json = JSON.stringify(docs);
43
+ if (json !== lastJson) {
44
+ lastJson = json;
45
+ callback(docs);
83
46
  }
84
- };
85
- }
86
- return {
87
- collection: (name) => wrapCollection(name),
88
- close: async () => {
47
+ } catch {
89
48
  }
49
+ if (active) setTimeout(poll, 300);
50
+ };
51
+ poll();
52
+ return () => {
53
+ active = false;
90
54
  };
91
55
  }
92
56
  async function createBrowserDB(dbName) {
93
- if (typeof SharedWorker === "undefined") {
94
- return createInMemoryBrowserDB(dbName);
95
- }
96
57
  const workerUrl = new URL("@taladb/web/worker/taladb.worker.js", import.meta.url);
97
- const worker = new SharedWorker(workerUrl, { type: "module", name: "taladb" });
98
- const proxy = new WorkerProxy(worker.port);
58
+ const worker = new Worker(workerUrl, { type: "module", name: "taladb" });
59
+ const proxy = new WorkerProxy(worker);
99
60
  await proxy.send("init", { dbName });
61
+ const nudgeCallbacks = /* @__PURE__ */ new Set();
62
+ let channel = null;
63
+ if (typeof BroadcastChannel !== "undefined") {
64
+ channel = new BroadcastChannel(`taladb:${dbName}`);
65
+ channel.onmessage = (e) => {
66
+ if (e.data === "taladb:changed") {
67
+ for (const nudge of nudgeCallbacks) nudge();
68
+ }
69
+ };
70
+ }
100
71
  function wrapCollection(name) {
101
72
  const s = JSON.stringify;
102
73
  return {
@@ -140,13 +111,26 @@ async function createBrowserDB(dbName) {
140
111
  }),
141
112
  createIndex: (field) => proxy.send("createIndex", { collection: name, field }),
142
113
  dropIndex: (field) => proxy.send("dropIndex", { collection: name, field }),
143
- createVectorIndex: (field, options) => proxy.send("createVectorIndex", {
144
- collection: name,
145
- field,
146
- dimensions: options.dimensions,
147
- metric: options.metric
148
- }),
114
+ createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
115
+ dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
116
+ createVectorIndex: (field, options) => {
117
+ 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."));
118
+ return proxy.send("createVectorIndex", {
119
+ collection: name,
120
+ field,
121
+ dimensions: options.dimensions,
122
+ metric: options.metric,
123
+ indexType: null,
124
+ hnswM: null,
125
+ hnswEfConstruction: null
126
+ });
127
+ },
149
128
  dropVectorIndex: (field) => proxy.send("dropVectorIndex", { collection: name, field }),
129
+ upgradeVectorIndex: (_field) => Promise.reject(new Error("HNSW vector indexes are not available in the browser (requires native threads). Use Node.js or React Native.")),
130
+ listIndexes: async () => {
131
+ const json = await proxy.send("listIndexes", { collection: name });
132
+ return JSON.parse(json);
133
+ },
150
134
  findNearest: async (field, vector, topK, filter) => {
151
135
  const json = await proxy.send("findNearest", {
152
136
  collection: name,
@@ -159,9 +143,14 @@ async function createBrowserDB(dbName) {
159
143
  },
160
144
  subscribe: (filter, callback) => {
161
145
  let active = true;
162
- let lastJson = "";
146
+ let lastJson = "[]";
147
+ let timer = null;
163
148
  const poll = async () => {
164
149
  if (!active) return;
150
+ if (timer !== null) {
151
+ clearTimeout(timer);
152
+ timer = null;
153
+ }
165
154
  try {
166
155
  const json = await proxy.send("find", {
167
156
  collection: name,
@@ -173,18 +162,28 @@ async function createBrowserDB(dbName) {
173
162
  }
174
163
  } catch {
175
164
  }
176
- if (active) setTimeout(poll, 300);
165
+ if (active) timer = setTimeout(poll, 300);
177
166
  };
167
+ nudgeCallbacks.add(poll);
178
168
  poll();
179
169
  return () => {
180
170
  active = false;
171
+ nudgeCallbacks.delete(poll);
172
+ if (timer !== null) {
173
+ clearTimeout(timer);
174
+ timer = null;
175
+ }
181
176
  };
182
177
  }
183
178
  };
184
179
  }
185
180
  return {
186
181
  collection: (name) => wrapCollection(name),
187
- close: () => proxy.send("close")
182
+ close: async () => {
183
+ channel?.close();
184
+ await proxy.send("close");
185
+ worker.terminate();
186
+ }
188
187
  };
189
188
  }
190
189
  async function createNodeDB(dbName) {
@@ -204,33 +203,20 @@ async function createNodeDB(dbName) {
204
203
  count: async (filter) => col.count(filter ?? null),
205
204
  createIndex: async (field) => col.createIndex(field),
206
205
  dropIndex: async (field) => col.dropIndex(field),
207
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null),
206
+ createFtsIndex: async (field) => col.createFtsIndex(field),
207
+ dropFtsIndex: async (field) => col.dropFtsIndex(field),
208
+ createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
208
209
  dropVectorIndex: async (field) => col.dropVectorIndex(field),
210
+ upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
211
+ listIndexes: async () => {
212
+ const json = col.listIndexes();
213
+ return JSON.parse(json);
214
+ },
209
215
  findNearest: async (field, vector, topK, filter) => {
210
216
  const raw = await col.findNearest(field, vector, topK, filter ?? null);
211
217
  return raw;
212
218
  },
213
- subscribe: (filter, callback) => {
214
- let active = true;
215
- let lastJson = "";
216
- const poll = async () => {
217
- if (!active) return;
218
- try {
219
- const docs = await col.find(filter ?? null);
220
- const json = JSON.stringify(docs);
221
- if (json !== lastJson) {
222
- lastJson = json;
223
- callback(docs);
224
- }
225
- } catch {
226
- }
227
- if (active) setTimeout(poll, 300);
228
- };
229
- poll();
230
- return () => {
231
- active = false;
232
- };
233
- }
219
+ subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? null), callback)
234
220
  };
235
221
  }
236
222
  return {
@@ -261,33 +247,17 @@ async function createNativeDB(_dbName) {
261
247
  count: async (filter) => col.count(filter ?? {}),
262
248
  createIndex: async (field) => col.createIndex(field),
263
249
  dropIndex: async (field) => col.dropIndex(field),
264
- createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null),
250
+ createFtsIndex: async (field) => col.createFtsIndex(field),
251
+ dropFtsIndex: async (field) => col.dropFtsIndex(field),
252
+ createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
265
253
  dropVectorIndex: async (field) => col.dropVectorIndex(field),
254
+ upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
255
+ listIndexes: async () => JSON.parse(col.listIndexes()),
266
256
  findNearest: async (field, vector, topK, filter) => {
267
257
  const raw = col.findNearest(field, vector, topK, filter ?? null);
268
258
  return raw;
269
259
  },
270
- subscribe: (filter, callback) => {
271
- let active = true;
272
- let lastJson = "";
273
- const poll = () => {
274
- if (!active) return;
275
- try {
276
- const docs = col.find(filter ?? {});
277
- const json = JSON.stringify(docs);
278
- if (json !== lastJson) {
279
- lastJson = json;
280
- callback(docs);
281
- }
282
- } catch {
283
- }
284
- if (active) setTimeout(poll, 300);
285
- };
286
- poll();
287
- return () => {
288
- active = false;
289
- };
290
- }
260
+ subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? {}), callback)
291
261
  };
292
262
  }
293
263
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taladb",
3
- "version": "0.2.11",
3
+ "version": "0.4.0",
4
4
  "description": "Local-first document and vector database for React, React Native, and Node.js",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",