taladb 0.7.3 → 0.7.5

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.
@@ -0,0 +1,366 @@
1
+ // src/config.browser.ts
2
+ var ENDPOINT_FIELDS = [
3
+ "endpoint",
4
+ "insert_endpoint",
5
+ "update_endpoint",
6
+ "delete_endpoint"
7
+ ];
8
+ function validateConfig(config) {
9
+ const sync = config.sync;
10
+ if (!sync) return;
11
+ for (const key of ENDPOINT_FIELDS) {
12
+ const url = sync[key];
13
+ if (url !== void 0 && !url.startsWith("http://") && !url.startsWith("https://")) {
14
+ throw new Error(
15
+ `TalaDB config: invalid endpoint URL "${url}" \u2014 must start with http:// or https://`
16
+ );
17
+ }
18
+ }
19
+ }
20
+ async function loadConfig(_configPath) {
21
+ return {};
22
+ }
23
+
24
+ // src/index.ts
25
+ var TalaDbValidationError = class extends Error {
26
+ constructor(cause, context) {
27
+ const label = context ? ` (${context})` : "";
28
+ const msg = cause instanceof Error ? cause.message : String(cause);
29
+ super(`TalaDB schema validation failed${label}: ${msg}`);
30
+ this.cause = cause;
31
+ this.name = "TalaDbValidationError";
32
+ }
33
+ };
34
+ function applySchema(col, options) {
35
+ const { schema, validateOnRead = false } = options;
36
+ if (!schema) return col;
37
+ function parseWrite(doc, label) {
38
+ try {
39
+ return schema.parse(doc);
40
+ } catch (err) {
41
+ throw new TalaDbValidationError(err, label);
42
+ }
43
+ }
44
+ function parseRead(doc) {
45
+ try {
46
+ return schema.parse(doc);
47
+ } catch (err) {
48
+ throw new TalaDbValidationError(err, "read");
49
+ }
50
+ }
51
+ return {
52
+ ...col,
53
+ insert: async (doc) => {
54
+ parseWrite(doc, "insert");
55
+ return col.insert(doc);
56
+ },
57
+ insertMany: async (docs) => {
58
+ docs.forEach((doc, i) => parseWrite(doc, `insertMany[${i}]`));
59
+ return col.insertMany(docs);
60
+ },
61
+ find: validateOnRead ? async (filter) => {
62
+ const docs = await col.find(filter);
63
+ return docs.map((d) => parseRead(d));
64
+ } : col.find.bind(col),
65
+ findOne: validateOnRead ? async (filter) => {
66
+ const doc = await col.findOne(filter);
67
+ return doc === null ? null : parseRead(doc);
68
+ } : col.findOne.bind(col)
69
+ };
70
+ }
71
+ function detectPlatform() {
72
+ if (globalThis.nativeCallSyncHook !== void 0) {
73
+ return "react-native";
74
+ }
75
+ if (globalThis.window !== void 0 && typeof navigator !== "undefined") {
76
+ return "browser";
77
+ }
78
+ return "node";
79
+ }
80
+ var WorkerProxy = class {
81
+ constructor(port) {
82
+ this.pending = /* @__PURE__ */ new Map();
83
+ this.nextId = 1;
84
+ this.port = port;
85
+ this.port.onmessage = (e) => {
86
+ const { id, result, error } = e.data;
87
+ const p = this.pending.get(id);
88
+ if (p) {
89
+ this.pending.delete(id);
90
+ if (error === void 0) p.resolve(result);
91
+ else p.reject(new Error(error));
92
+ }
93
+ };
94
+ this.port.start?.();
95
+ }
96
+ send(op, args = {}) {
97
+ return new Promise((resolve, reject) => {
98
+ const id = this.nextId++;
99
+ this.pending.set(id, { resolve, reject });
100
+ this.port.postMessage({ id, op, ...args });
101
+ });
102
+ }
103
+ };
104
+ function makePoller(findFn, callback) {
105
+ let active = true;
106
+ let lastJson = "";
107
+ const poll = async () => {
108
+ if (!active) return;
109
+ try {
110
+ const docs = await findFn();
111
+ const json = JSON.stringify(docs);
112
+ if (json !== lastJson) {
113
+ lastJson = json;
114
+ callback(docs);
115
+ }
116
+ } catch {
117
+ }
118
+ if (active) setTimeout(poll, 300);
119
+ };
120
+ poll();
121
+ return () => {
122
+ active = false;
123
+ };
124
+ }
125
+ async function createBrowserDB(dbName, config) {
126
+ const workerUrl = new URL("@taladb/web/worker/taladb.worker.js", import.meta.url);
127
+ const worker = new Worker(workerUrl, { type: "module", name: "taladb" });
128
+ const proxy = new WorkerProxy(worker);
129
+ const configJson = config !== void 0 ? JSON.stringify(config) : void 0;
130
+ await proxy.send("init", { dbName, configJson });
131
+ const nudgeCallbacks = /* @__PURE__ */ new Set();
132
+ let channel = null;
133
+ if (typeof BroadcastChannel !== "undefined") {
134
+ channel = new BroadcastChannel(`taladb:${dbName}`);
135
+ channel.onmessage = (e) => {
136
+ if (e.data === "taladb:changed") {
137
+ for (const nudge of nudgeCallbacks) nudge();
138
+ }
139
+ };
140
+ }
141
+ function wrapCollection(name, opts) {
142
+ const s = JSON.stringify;
143
+ const wrapped = {
144
+ insert: (doc) => proxy.send("insert", { collection: name, docJson: s(doc) }),
145
+ insertMany: async (docs) => {
146
+ const json = await proxy.send("insertMany", {
147
+ collection: name,
148
+ docsJson: s(docs)
149
+ });
150
+ return JSON.parse(json);
151
+ },
152
+ find: async (filter) => {
153
+ const json = await proxy.send("find", {
154
+ collection: name,
155
+ filterJson: filter ? s(filter) : "null"
156
+ });
157
+ return JSON.parse(json);
158
+ },
159
+ findOne: async (filter) => {
160
+ const json = await proxy.send("findOne", {
161
+ collection: name,
162
+ filterJson: filter ? s(filter) : "null"
163
+ });
164
+ return JSON.parse(json);
165
+ },
166
+ updateOne: (filter, update) => proxy.send("updateOne", {
167
+ collection: name,
168
+ filterJson: s(filter),
169
+ updateJson: s(update)
170
+ }),
171
+ updateMany: (filter, update) => proxy.send("updateMany", {
172
+ collection: name,
173
+ filterJson: s(filter),
174
+ updateJson: s(update)
175
+ }),
176
+ deleteOne: (filter) => proxy.send("deleteOne", { collection: name, filterJson: s(filter) }),
177
+ deleteMany: (filter) => proxy.send("deleteMany", { collection: name, filterJson: s(filter) }),
178
+ count: (filter) => proxy.send("count", {
179
+ collection: name,
180
+ filterJson: filter ? s(filter) : "null"
181
+ }),
182
+ createIndex: (field) => proxy.send("createIndex", { collection: name, field }),
183
+ dropIndex: (field) => proxy.send("dropIndex", { collection: name, field }),
184
+ createFtsIndex: (field) => proxy.send("createFtsIndex", { collection: name, field }),
185
+ dropFtsIndex: (field) => proxy.send("dropFtsIndex", { collection: name, field }),
186
+ createVectorIndex: (field, options) => {
187
+ 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."));
188
+ return proxy.send("createVectorIndex", {
189
+ collection: name,
190
+ field,
191
+ dimensions: options.dimensions,
192
+ metric: options.metric,
193
+ indexType: null,
194
+ hnswM: null,
195
+ hnswEfConstruction: null
196
+ });
197
+ },
198
+ dropVectorIndex: (field) => proxy.send("dropVectorIndex", { collection: name, field }),
199
+ upgradeVectorIndex: (_field) => Promise.reject(new Error("HNSW vector indexes are not available in the browser (requires native threads). Use Node.js or React Native.")),
200
+ listIndexes: async () => {
201
+ const json = await proxy.send("listIndexes", { collection: name });
202
+ return JSON.parse(json);
203
+ },
204
+ findNearest: async (field, vector, topK, filter) => {
205
+ const json = await proxy.send("findNearest", {
206
+ collection: name,
207
+ field,
208
+ queryJson: JSON.stringify(vector),
209
+ topK,
210
+ filterJson: filter ? JSON.stringify(filter) : "null"
211
+ });
212
+ return JSON.parse(json);
213
+ },
214
+ subscribe: (filter, callback) => {
215
+ let active = true;
216
+ let lastJson = "[]";
217
+ let timer = null;
218
+ const poll = async () => {
219
+ if (!active) return;
220
+ if (timer !== null) {
221
+ clearTimeout(timer);
222
+ timer = null;
223
+ }
224
+ try {
225
+ const json = await proxy.send("find", {
226
+ collection: name,
227
+ filterJson: filter ? s(filter) : "null"
228
+ });
229
+ if (json !== lastJson) {
230
+ lastJson = json;
231
+ callback(JSON.parse(json));
232
+ }
233
+ } catch {
234
+ }
235
+ if (active) timer = setTimeout(poll, 300);
236
+ };
237
+ nudgeCallbacks.add(poll);
238
+ poll();
239
+ return () => {
240
+ active = false;
241
+ nudgeCallbacks.delete(poll);
242
+ if (timer !== null) {
243
+ clearTimeout(timer);
244
+ timer = null;
245
+ }
246
+ };
247
+ }
248
+ };
249
+ return opts ? applySchema(wrapped, opts) : wrapped;
250
+ }
251
+ return {
252
+ collection: (name, opts) => wrapCollection(name, opts),
253
+ compact: () => proxy.send("compact"),
254
+ close: async () => {
255
+ channel?.close();
256
+ await proxy.send("close");
257
+ worker.terminate();
258
+ }
259
+ };
260
+ }
261
+ async function createNodeDB(dbName, config) {
262
+ const { TalaDBNode } = await import("@taladb/node");
263
+ const configJson = config !== void 0 ? JSON.stringify(config) : null;
264
+ const db = TalaDBNode.open(dbName, configJson);
265
+ function wrapCollection(name, opts) {
266
+ const col = db.collection(name);
267
+ const wrapped = {
268
+ insert: async (doc) => col.insert(doc),
269
+ insertMany: async (docs) => col.insertMany(docs),
270
+ find: async (filter) => col.find(filter ?? null),
271
+ findOne: async (filter) => col.findOne(filter) ?? null,
272
+ updateOne: async (filter, update) => col.updateOne(filter, update),
273
+ updateMany: async (filter, update) => col.updateMany(filter, update),
274
+ deleteOne: async (filter) => col.deleteOne(filter),
275
+ deleteMany: async (filter) => col.deleteMany(filter),
276
+ count: async (filter) => col.count(filter ?? null),
277
+ createIndex: async (field) => col.createIndex(field),
278
+ dropIndex: async (field) => col.dropIndex(field),
279
+ createFtsIndex: async (field) => col.createFtsIndex(field),
280
+ dropFtsIndex: async (field) => col.dropFtsIndex(field),
281
+ createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
282
+ dropVectorIndex: async (field) => col.dropVectorIndex(field),
283
+ upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
284
+ listIndexes: async () => {
285
+ const json = col.listIndexes();
286
+ return JSON.parse(json);
287
+ },
288
+ findNearest: async (field, vector, topK, filter) => {
289
+ const raw = await col.findNearest(field, vector, topK, filter ?? null);
290
+ return raw;
291
+ },
292
+ subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? null), callback)
293
+ };
294
+ return opts ? applySchema(wrapped, opts) : wrapped;
295
+ }
296
+ return {
297
+ collection: (name, opts) => wrapCollection(name, opts),
298
+ compact: async () => db.compact(),
299
+ close: async () => {
300
+ }
301
+ };
302
+ }
303
+ async function createNativeDB(_dbName) {
304
+ const maybeNative = globalThis.__TalaDB__;
305
+ if (!maybeNative) {
306
+ throw new Error(
307
+ "@taladb/react-native JSI HostObject not found. Did you call TalaDBModule.initialize() in your app entry point?"
308
+ );
309
+ }
310
+ const native = maybeNative;
311
+ function wrapCollection(name, opts) {
312
+ const col = native.collection(name);
313
+ const wrapped = {
314
+ insert: async (doc) => col.insert(doc),
315
+ insertMany: async (docs) => col.insertMany(docs),
316
+ find: async (filter) => col.find(filter ?? {}),
317
+ findOne: async (filter) => col.findOne(filter ?? {}),
318
+ updateOne: async (filter, update) => col.updateOne(filter, update),
319
+ updateMany: async (filter, update) => col.updateMany(filter, update),
320
+ deleteOne: async (filter) => col.deleteOne(filter),
321
+ deleteMany: async (filter) => col.deleteMany(filter),
322
+ count: async (filter) => col.count(filter ?? {}),
323
+ createIndex: async (field) => col.createIndex(field),
324
+ dropIndex: async (field) => col.dropIndex(field),
325
+ createFtsIndex: async (field) => col.createFtsIndex(field),
326
+ dropFtsIndex: async (field) => col.dropFtsIndex(field),
327
+ createVectorIndex: async (field, options) => col.createVectorIndex(field, options.dimensions, options.metric ?? null, options.indexType ?? null, options.hnswM ?? null, options.hnswEfConstruction ?? null),
328
+ dropVectorIndex: async (field) => col.dropVectorIndex(field),
329
+ upgradeVectorIndex: async (field) => col.upgradeVectorIndex(field),
330
+ listIndexes: async () => JSON.parse(col.listIndexes()),
331
+ findNearest: async (field, vector, topK, filter) => {
332
+ const raw = col.findNearest(field, vector, topK, filter ?? null);
333
+ return raw;
334
+ },
335
+ subscribe: (filter, callback) => makePoller(async () => col.find(filter ?? {}), callback)
336
+ };
337
+ return opts ? applySchema(wrapped, opts) : wrapped;
338
+ }
339
+ return {
340
+ collection: (name, opts) => wrapCollection(name, opts),
341
+ compact: async () => native.compact(),
342
+ close: async () => native.close()
343
+ };
344
+ }
345
+ async function openDB(dbName = "taladb.db", options) {
346
+ let resolvedConfig;
347
+ if (options?.config !== void 0) {
348
+ validateConfig(options.config);
349
+ resolvedConfig = options.config;
350
+ } else {
351
+ resolvedConfig = await loadConfig(options?.configPath);
352
+ }
353
+ const platform = detectPlatform();
354
+ switch (platform) {
355
+ case "browser":
356
+ return createBrowserDB(dbName, resolvedConfig);
357
+ case "react-native":
358
+ return createNativeDB(dbName);
359
+ case "node":
360
+ return createNodeDB(dbName, resolvedConfig);
361
+ }
362
+ }
363
+ export {
364
+ TalaDbValidationError,
365
+ openDB
366
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taladb",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
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",
@@ -8,6 +8,7 @@
8
8
  "sideEffects": false,
9
9
  "exports": {
10
10
  ".": {
11
+ "react-native": "./dist/index.react-native.mjs",
11
12
  "browser": "./dist/index.browser.mjs",
12
13
  "types": "./dist/index.d.ts",
13
14
  "import": "./dist/index.mjs",
@@ -60,8 +61,12 @@
60
61
  "typescript": "^5.9.3",
61
62
  "vitest": "^3.2.0"
62
63
  },
63
- "optionalDependencies": {
64
+ "peerDependencies": {
64
65
  "@taladb/web": "workspace:*",
65
66
  "@taladb/node": "workspace:*"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "@taladb/web": { "optional": true },
70
+ "@taladb/node": { "optional": true }
66
71
  }
67
72
  }