@taladb/react-native 0.11.3 → 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.
@@ -4,6 +4,18 @@
4
4
  * This file is the Codegen source. It defines the native interface that
5
5
  * both the iOS JSI HostObject (TalaDB.mm) and the Android JNI bridge
6
6
  * (TalaDBModule.kt) must implement.
7
+ *
8
+ * # This is not the public API
9
+ *
10
+ * Only `initialize` and `close` are ever called through the TurboModule. Every
11
+ * other method below is a stub that exists so Codegen sees a complete surface;
12
+ * at runtime JS goes straight to the JSI HostObject at `global.__TalaDB__`.
13
+ *
14
+ * So do **not** add methods here to expose new functionality — each one
15
+ * generates an abstract member that `TalaDBModule.kt` and `TalaDB.mm` must
16
+ * override, and forgetting either breaks the native build for a stub nothing
17
+ * calls. The real surface is registered in `cpp/TalaDBHostObject.cpp` and typed
18
+ * by `JsiTalaDB` in `index.tsx`; extend those two together.
7
19
  */
8
20
  import type { TurboModule } from 'react-native';
9
21
  import { TurboModuleRegistry } from 'react-native';
package/src/index.tsx CHANGED
@@ -1,6 +1,11 @@
1
1
  /** TalaDB React Native public API. CRUD is synchronous through the JSI host. */
2
2
  import {
3
3
  createWebhookDispatcher,
4
+ createVectorClient,
5
+ type VectorClient,
6
+ type HybridSearchOptions,
7
+ type TextSearchOptions,
8
+ type VectorMetric,
4
9
  type WebhookConfig,
5
10
  type WebhookDispatcher,
6
11
  type WebhookEvent,
@@ -8,6 +13,13 @@ import {
8
13
  } from 'taladb';
9
14
  import NativeTalaDB from './NativeTalaDB';
10
15
 
16
+ export type {
17
+ HybridSearchOptions,
18
+ TextSearchOptions,
19
+ VectorMetric,
20
+ VectorClient, VectorQueryOptions, VectorRebuildOptions, VectorBuildProgress, VectorIndexStatus, VectorQueryResult, VectorRecall, VectorGraphOptions, VectorQuantization,
21
+ } from 'taladb';
22
+
11
23
  export const TalaDBModule = {
12
24
  /** Open the native database. The config controls durability/encryption only. */
13
25
  initialize: (dbName: string, configJson?: string) =>
@@ -24,7 +36,75 @@ export type InsertDocument<T extends Document> = Omit<T, '_id'> & { _id?: string
24
36
  export type Filter = Record<string, unknown>;
25
37
  export type Update = Record<string, unknown>;
26
38
 
27
- export interface Collection<T extends Document = Document> {
39
+ /** A query vector. `Float32Array` takes a zero-copy path across JSI. */
40
+ export type QueryVector = Float32Array | number[];
41
+
42
+ /** An ordered MongoDB-style aggregation pipeline. */
43
+ export type AggregatePipeline = Record<string, unknown>[];
44
+
45
+ /** A single result from `findNearest`. */
46
+ export interface VectorSearchResult<T extends Document = Document> {
47
+ document: T;
48
+ /**
49
+ * Similarity score — higher means more similar. Range depends on the
50
+ * metric: cosine ∈ [-1,1], dot ∈ ℝ, euclidean ∈ (0,1].
51
+ */
52
+ score: number;
53
+ }
54
+
55
+ /** A single result from `searchText`. */
56
+ export interface TextSearchResult<T extends Document = Document> {
57
+ document: T;
58
+ /**
59
+ * BM25 relevance — higher is more relevant. Unbounded above and only
60
+ * meaningful for ordering within one query's result set.
61
+ */
62
+ score: number;
63
+ }
64
+
65
+ /** A single result from `hybridSearch`. */
66
+ export interface HybridSearchResult<T extends Document = Document> {
67
+ document: T;
68
+ /**
69
+ * Fused reciprocal-rank-fusion score. Small by construction and meaningful
70
+ * only as an ordering within one result set — never a similarity or a
71
+ * confidence.
72
+ */
73
+ score: number;
74
+ /** Zero-based rank in the text ranking, or `null` if text did not return it. */
75
+ textRank: number | null;
76
+ /** Zero-based rank in the vector ranking, or `null` if vector did not return it. */
77
+ vectorRank: number | null;
78
+ }
79
+
80
+ /**
81
+ * HNSW build parameters.
82
+ *
83
+ * The native binding validates these values and reports malformed options.
84
+ * `ef_construction` is retained for compatibility with the direct native API;
85
+ * the shared asynchronous vector API uses `efConstruction`.
86
+ */
87
+ export interface HnswBuildOptions {
88
+ /** Bi-directional links per node (2–128). */
89
+ m: number;
90
+ /** Build-time quality (ef during construction). Must be ≥ `m`. Typically 200. */
91
+ ef_construction: number;
92
+ quantization?: import("taladb").VectorQuantization;
93
+ }
94
+
95
+ export interface VectorIndexOptions {
96
+ /** Similarity metric. Defaults to `"cosine"`. */
97
+ metric?: VectorMetric;
98
+ /**
99
+ * Supply this to build an HNSW (approximate) index instead of a flat one.
100
+ *
101
+ * Nodes and links persist in the database and update with document writes.
102
+ * Reopening requires no warm-up.
103
+ */
104
+ hnsw?: HnswBuildOptions;
105
+ }
106
+
107
+ export interface Collection<T extends Document = Document> extends VectorClient<T> {
28
108
  insert(doc: InsertDocument<T>): string;
29
109
  insertMany(docs: InsertDocument<T>[]): string[];
30
110
  find(filter?: Filter): T[];
@@ -38,6 +118,95 @@ export interface Collection<T extends Document = Document> {
38
118
  dropIndex(field: string): void;
39
119
  createFtsIndex(field: string): void;
40
120
  dropFtsIndex(field: string): void;
121
+
122
+ /**
123
+ * Find documents on a background thread. Prefer this over `find` for scans
124
+ * large enough to be felt as a dropped frame — every synchronous method here
125
+ * runs on the JS thread.
126
+ */
127
+ findAsync(filter?: Filter): Promise<T[]>;
128
+
129
+ /**
130
+ * Run a MongoDB-style aggregation pipeline: `$match`, `$group`, `$sort`,
131
+ * `$skip`, `$limit`, `$project`, with `$sum` / `$avg` / `$min` / `$max` /
132
+ * `$count` accumulators. `$match` uses an index where one exists.
133
+ *
134
+ * @example
135
+ * const [{ total }] = repairs.aggregate<{ total: number }>([
136
+ * { $match: { thingId } },
137
+ * { $group: { _id: null, total: { $sum: '$cost' } } },
138
+ * ]);
139
+ */
140
+ aggregate<R extends Document = Document>(pipeline: AggregatePipeline): R[];
141
+
142
+ /**
143
+ * Rank documents by BM25 keyword relevance. OR semantics — a document
144
+ * matching more of the query scores higher. Requires an FTS index on `field`.
145
+ *
146
+ * @example
147
+ * const hits = notes.searchText('body', 'chain replacement', 5);
148
+ */
149
+ searchText(
150
+ field: string,
151
+ query: string,
152
+ topK: number,
153
+ filter?: Filter | null,
154
+ options?: TextSearchOptions,
155
+ ): TextSearchResult<T>[];
156
+
157
+ /**
158
+ * Rank by keyword relevance (BM25) **and** vector similarity, then fuse the
159
+ * two rankings with reciprocal rank fusion.
160
+ *
161
+ * The retrievers fail differently — keyword search misses paraphrases,
162
+ * vector search misses exact identifiers and rare proper nouns — so fusing
163
+ * them recovers both. Requires an FTS index on `textField` and a vector
164
+ * index on `vectorField`. `filter` applies to both retrievers before ranking.
165
+ */
166
+ hybridSearch(
167
+ text: { textField: string; text: string },
168
+ vector: { vectorField: string; vector: QueryVector },
169
+ topK: number,
170
+ filter?: Filter | null,
171
+ options?: HybridSearchOptions,
172
+ ): HybridSearchResult<T>[];
173
+
174
+ /**
175
+ * Nearest neighbours by vector similarity.
176
+ *
177
+ * Passing a `filter` always takes the exact path: the graph cannot be
178
+ * traversed under an arbitrary predicate, so matching IDs are scored
179
+ * directly. That is usually what you want for a scoped search — it is exact,
180
+ * and it reads only the vectors that survive the filter.
181
+ */
182
+ findNearest(
183
+ field: string,
184
+ query: QueryVector,
185
+ topK: number,
186
+ filter?: Filter | null,
187
+ ): VectorSearchResult<T>[];
188
+
189
+ /** `findNearest` on a background thread. Prefer it for unfiltered searches. */
190
+ findNearestAsync(
191
+ field: string,
192
+ query: QueryVector,
193
+ topK: number,
194
+ filter?: Filter | null,
195
+ ): Promise<VectorSearchResult<T>[]>;
196
+
197
+ /**
198
+ * Create a vector index on a numeric-array field. `dimensions` is enforced
199
+ * on every insert and every query.
200
+ */
201
+ createVectorIndex(field: string, dimensions: number, options?: VectorIndexOptions): void;
202
+ dropVectorIndex(field: string): void;
203
+
204
+ /**
205
+ * Promote a flat/legacy index or compact a persistent graph. This synchronous
206
+ * call may be expensive; prefer rebuildVectorIndex for background batches,
207
+ * progress and AbortSignal cancellation on mobile.
208
+ */
209
+ upgradeVectorIndex(field: string): void;
41
210
  }
42
211
 
43
212
  export interface OpenDBOptions {
@@ -50,9 +219,48 @@ export interface DB {
50
219
  webhookStats(): WebhookStats;
51
220
  flushWebhook(timeoutMs?: number): Promise<boolean>;
52
221
  close(): Promise<void>;
222
+
223
+ /** Names of every collection that currently holds documents. */
224
+ listCollectionNames(): string[];
225
+
226
+ /**
227
+ * Force a durable sync of everything written so far.
228
+ *
229
+ * Only meaningful when the database was opened with
230
+ * `durability.flush_every_write: false`, which trades an fsync per write for
231
+ * throughput. The default is `true` — every acknowledged write is already
232
+ * durable — so most apps never need this. Batch importers should turn the
233
+ * flag off, import, then call `flush()`.
234
+ */
235
+ flush(): void;
236
+
237
+ /** Reclaim space from deleted documents. Expensive; run it off the hot path. */
238
+ compact(): void;
239
+
240
+ /**
241
+ * Warm every HNSW vector index in the database.
242
+ *
243
+ * Graphs persist across app launches. This is maintenance, not a startup
244
+ * requirement. Prefer per-field rebuildVectorIndex for progress/cancellation.
245
+ */
246
+ rebuildVectorIndexes(): void;
247
+
248
+ /** The application-defined schema version stored in the database file. */
249
+ userVersion(): number;
250
+ setUserVersion(version: number): void;
53
251
  }
54
252
 
253
+ /**
254
+ * The JSI host object installed as `global.__TalaDB__`.
255
+ *
256
+ * This mirrors the methods registered in `cpp/TalaDBHostObject.cpp`, which is
257
+ * the real runtime surface. The TurboModule spec in `NativeTalaDB.ts` is
258
+ * deliberately narrower: every method there generates an abstract member that
259
+ * `TalaDBModule.kt` and `TalaDB.mm` must stub out, and those stubs are never
260
+ * called. Add new methods here, not there.
261
+ */
55
262
  interface JsiTalaDB {
263
+ callAsync(op: string, args: unknown[]): Promise<any>;
56
264
  insert(collection: string, doc: Object): string;
57
265
  insertMany(collection: string, docs: Object[]): string[];
58
266
  find(collection: string, filter: Object | null): Object[];
@@ -66,6 +274,54 @@ interface JsiTalaDB {
66
274
  dropIndex(collection: string, field: string): void;
67
275
  createFtsIndex(collection: string, field: string): void;
68
276
  dropFtsIndex(collection: string, field: string): void;
277
+ findAsync(collection: string, filter: Object | null): Promise<Object[]>;
278
+ aggregate(collection: string, pipeline: Object[]): Object[];
279
+ searchText(
280
+ collection: string,
281
+ field: string,
282
+ query: string,
283
+ topK: number,
284
+ filter: Object | null,
285
+ options?: Object,
286
+ ): Object[];
287
+ hybridSearch(
288
+ collection: string,
289
+ textField: string,
290
+ text: string,
291
+ vectorField: string,
292
+ vector: QueryVector,
293
+ topK: number,
294
+ filter: Object | null,
295
+ options?: Object,
296
+ ): Object[];
297
+ findNearest(
298
+ collection: string,
299
+ field: string,
300
+ query: QueryVector,
301
+ topK: number,
302
+ filter: Object | null,
303
+ ): Object[];
304
+ findNearestAsync(
305
+ collection: string,
306
+ field: string,
307
+ query: QueryVector,
308
+ topK: number,
309
+ filter: Object | null,
310
+ ): Promise<Object[]>;
311
+ createVectorIndex(
312
+ collection: string,
313
+ field: string,
314
+ dimensions: number,
315
+ opts?: Object,
316
+ ): void;
317
+ dropVectorIndex(collection: string, field: string): void;
318
+ upgradeVectorIndex(collection: string, field: string): void;
319
+ listCollectionNames(): string[];
320
+ rebuildVectorIndexes(): void;
321
+ flush(): void;
322
+ compact(): void;
323
+ userVersion(): number;
324
+ setUserVersion(version: number): void;
69
325
  }
70
326
 
71
327
  function native(): JsiTalaDB {
@@ -117,6 +373,7 @@ function collection<T extends Document>(
117
373
  webhook: WebhookDispatcher | null,
118
374
  ): Collection<T> {
119
375
  return {
376
+ ...createVectorClient<T>(request => native().callAsync('vectorCommand', [colName, request])),
120
377
  insert(doc) {
121
378
  const id = native().insert(colName, doc as Object);
122
379
  const committedAt = Date.now();
@@ -174,6 +431,51 @@ function collection<T extends Document>(
174
431
  dropIndex: (field) => native().dropIndex(colName, field),
175
432
  createFtsIndex: (field) => native().createFtsIndex(colName, field),
176
433
  dropFtsIndex: (field) => native().dropFtsIndex(colName, field),
434
+
435
+ findAsync: async (filter) =>
436
+ (await native().findAsync(colName, filter ?? null)) as T[],
437
+
438
+ aggregate: <R extends Document = Document>(pipeline: AggregatePipeline) =>
439
+ native().aggregate(colName, pipeline as Object[]) as R[],
440
+
441
+ searchText: (field, query, topK, filter, options) =>
442
+ native().searchText(
443
+ colName,
444
+ field,
445
+ query,
446
+ topK,
447
+ filter ?? null,
448
+ options as Object | undefined,
449
+ ) as TextSearchResult<T>[],
450
+
451
+ hybridSearch: (text, vector, topK, filter, options) =>
452
+ native().hybridSearch(
453
+ colName,
454
+ text.textField,
455
+ text.text,
456
+ vector.vectorField,
457
+ vector.vector,
458
+ topK,
459
+ filter ?? null,
460
+ options as Object | undefined,
461
+ ) as HybridSearchResult<T>[],
462
+
463
+ findNearest: (field, query, topK, filter) =>
464
+ native().findNearest(colName, field, query, topK, filter ?? null) as VectorSearchResult<T>[],
465
+
466
+ findNearestAsync: async (field, query, topK, filter) =>
467
+ (await native().findNearestAsync(
468
+ colName,
469
+ field,
470
+ query,
471
+ topK,
472
+ filter ?? null,
473
+ )) as VectorSearchResult<T>[],
474
+
475
+ createVectorIndex: (field, dimensions, options) =>
476
+ native().createVectorIndex(colName, field, dimensions, options as Object | undefined),
477
+ dropVectorIndex: (field) => native().dropVectorIndex(colName, field),
478
+ upgradeVectorIndex: (field) => native().upgradeVectorIndex(colName, field),
177
479
  };
178
480
  }
179
481
 
@@ -184,6 +486,12 @@ export function openDB(_dbName: string, options?: OpenDBOptions): DB {
184
486
  collection: <T extends Document>(name: string) => collection<T>(name, webhook),
185
487
  webhookStats: () => webhook?.stats() ?? { ...EMPTY_STATS },
186
488
  flushWebhook: (timeoutMs) => webhook?.flush(timeoutMs) ?? Promise.resolve(true),
489
+ listCollectionNames: () => native().listCollectionNames(),
490
+ flush: () => native().flush(),
491
+ compact: () => native().compact(),
492
+ rebuildVectorIndexes: () => native().rebuildVectorIndexes(),
493
+ userVersion: () => native().userVersion(),
494
+ setUserVersion: (version) => native().setUserVersion(version),
187
495
  close: async () => {
188
496
  await webhook?.flush();
189
497
  await NativeTalaDB.close();