@taladb/react-native 0.10.2 → 0.11.1

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/src/index.tsx CHANGED
@@ -1,71 +1,32 @@
1
- /**
2
- * TalaDB React Native — public JS API.
3
- *
4
- * Usage:
5
- * ```ts
6
- * import { TalaDBModule, openDB } from '@taladb/react-native';
7
- *
8
- * // In App.tsx / index.js (once, at startup)
9
- * await TalaDBModule.initialize('myapp.db');
10
- *
11
- * // Anywhere in the app — same API as browser
12
- * const db = openDB('myapp.db');
13
- * const users = db.collection<User>('users');
14
- * const id = users.insert({ name: 'Alice', age: 30 });
15
- * ```
16
- *
17
- * All operations are **synchronous** via JSI — no async/await needed
18
- * after initialization.
19
- */
1
+ /** TalaDB React Native public API. CRUD is synchronous through the JSI host. */
2
+ import {
3
+ createWebhookDispatcher,
4
+ type WebhookConfig,
5
+ type WebhookDispatcher,
6
+ type WebhookEvent,
7
+ type WebhookStats,
8
+ } from 'taladb';
20
9
  import NativeTalaDB from './NativeTalaDB';
21
10
 
22
- // ---------------------------------------------------------------------------
23
- // Module-level helpers
24
- // ---------------------------------------------------------------------------
25
-
26
11
  export const TalaDBModule = {
27
- /**
28
- * Open (or create) the database. Call once at app startup.
29
- *
30
- * @param configJson Optional JSON-serialised `TalaDbConfig` for HTTP push
31
- * sync. Example: `JSON.stringify({ sync: { enabled: true,
32
- * endpoint: 'https://api.example.com/events' } })`.
33
- */
12
+ /** Open the native database. The config controls durability/encryption only. */
34
13
  initialize: (dbName: string, configJson?: string) =>
35
14
  NativeTalaDB.initialize(dbName, configJson),
36
- /** Close the database gracefully. */
37
15
  close: () => NativeTalaDB.close(),
38
- /** HTTP push events dropped by backpressure or failed after retries. */
39
- syncStatus: () => native().syncStatus(),
40
- /** Wait for HTTP push work accepted before this call. */
41
- flushSync: (timeoutMs = 5000) => native().flushSync(timeoutMs),
42
16
  };
43
17
 
44
- // ---------------------------------------------------------------------------
45
- // Collection handle
46
- // ---------------------------------------------------------------------------
47
-
48
18
  export interface Document {
49
19
  _id?: string;
50
20
  [key: string]: unknown;
51
21
  }
52
22
 
23
+ export type InsertDocument<T extends Document> = Omit<T, '_id'> & { _id?: string };
53
24
  export type Filter = Record<string, unknown>;
54
25
  export type Update = Record<string, unknown>;
55
26
 
56
27
  export interface Collection<T extends Document = Document> {
57
- insert(doc: Omit<T, '_id'>): string;
58
- insertMany(docs: Omit<T, '_id'>[]): string[];
59
- /**
60
- * Upsert many documents **by `_id`**, in one commit. Requires an `_id` on every
61
- * document — for replicated rows that comes from `deriveDocId(collection, key)`.
62
- *
63
- * `origin: 'remote'` marks rows as replicated in from an authoritative origin so
64
- * they are never replicated back out; it defaults to `'local'`.
65
- */
66
- replaceManyWithIds(docs: T[], origin?: 'local' | 'remote'): string[];
67
- /** Delete many documents by id, in one commit. Returns the number removed. */
68
- deleteManyWithIds(ids: string[], origin?: 'local' | 'remote'): number;
28
+ insert(doc: InsertDocument<T>): string;
29
+ insertMany(docs: InsertDocument<T>[]): string[];
69
30
  find(filter?: Filter): T[];
70
31
  findOne(filter: Filter): T | null;
71
32
  updateOne(filter: Filter, update: Update): boolean;
@@ -79,26 +40,21 @@ export interface Collection<T extends Document = Document> {
79
40
  dropFtsIndex(field: string): void;
80
41
  }
81
42
 
43
+ export interface OpenDBOptions {
44
+ /** Runtime-agnostic outbound change webhook, delivered with global `fetch`. */
45
+ webhook?: WebhookConfig;
46
+ }
47
+
82
48
  export interface DB {
83
49
  collection<T extends Document = Document>(name: string): Collection<T>;
84
- syncStatus(): { dropped: number; failed: number };
85
- flushSync(timeoutMs?: number): boolean;
50
+ webhookStats(): WebhookStats;
51
+ flushWebhook(timeoutMs?: number): Promise<boolean>;
86
52
  close(): Promise<void>;
87
53
  }
88
54
 
89
55
  interface JsiTalaDB {
90
56
  insert(collection: string, doc: Object): string;
91
57
  insertMany(collection: string, docs: Object[]): string[];
92
- replaceManyWithIds(
93
- collection: string,
94
- docs: Object[],
95
- origin: 'local' | 'remote',
96
- ): string[];
97
- deleteManyWithIds(
98
- collection: string,
99
- ids: string[],
100
- origin: 'local' | 'remote',
101
- ): number;
102
58
  find(collection: string, filter: Object | null): Object[];
103
59
  findOne(collection: string, filter: Object | null): Object | null;
104
60
  updateOne(collection: string, filter: Object, update: Object): boolean;
@@ -110,8 +66,6 @@ interface JsiTalaDB {
110
66
  dropIndex(collection: string, field: string): void;
111
67
  createFtsIndex(collection: string, field: string): void;
112
68
  dropFtsIndex(collection: string, field: string): void;
113
- syncStatus(): { dropped: number; failed: number };
114
- flushSync(timeoutMs?: number): boolean;
115
69
  }
116
70
 
117
71
  function native(): JsiTalaDB {
@@ -120,31 +74,102 @@ function native(): JsiTalaDB {
120
74
  return host;
121
75
  }
122
76
 
123
- // ---------------------------------------------------------------------------
124
- // openDB — synchronous DB handle (after initialize() has been called)
125
- // ---------------------------------------------------------------------------
77
+ const EMPTY_STATS: WebhookStats = { pending: 0, delivered: 0, failed: 0, dropped: 0 };
78
+
79
+ function emitPostImages<T extends Document>(
80
+ webhook: WebhookDispatcher | null,
81
+ collection: string,
82
+ ids: string[],
83
+ op: 'insert' | 'update',
84
+ committedAt: number,
85
+ ): void {
86
+ if (!webhook?.reports(collection) || ids.length === 0) return;
87
+ const docs = native().find(collection, { _id: { $in: ids } }) as T[];
88
+ const byId = new Map(docs.map((doc) => [doc._id, doc]));
89
+ for (const id of ids) {
90
+ const document = byId.get(id);
91
+ if (document) webhook.emit({ op, collection, id, document, committedAt } as WebhookEvent);
92
+ }
93
+ }
94
+
95
+ function emitDeletes<T extends Document>(
96
+ webhook: WebhookDispatcher | null,
97
+ collection: string,
98
+ docs: T[],
99
+ committedAt: number,
100
+ ): void {
101
+ if (!webhook?.reports(collection)) return;
102
+ for (const document of docs) {
103
+ if (typeof document._id !== 'string') continue;
104
+ // DELETE carries the pre-image: after commit there is no post-image to read.
105
+ webhook.emit({
106
+ op: 'delete',
107
+ collection,
108
+ id: document._id,
109
+ document,
110
+ committedAt,
111
+ } as WebhookEvent);
112
+ }
113
+ }
126
114
 
127
- /**
128
- * Get a synchronous DB handle for the given database name.
129
- *
130
- * `TalaDBModule.initialize(dbName)` **must** have been awaited before calling
131
- * this function.
132
- */
133
- function collection<T extends Document>(colName: string): Collection<T> {
115
+ function collection<T extends Document>(
116
+ colName: string,
117
+ webhook: WebhookDispatcher | null,
118
+ ): Collection<T> {
134
119
  return {
135
- insert: (doc) => native().insert(colName, doc as Object),
136
- insertMany: (docs) => native().insertMany(colName, docs as Object[]),
137
- replaceManyWithIds: (docs, origin = 'local') =>
138
- native().replaceManyWithIds(colName, docs as Object[], origin),
139
- deleteManyWithIds: (ids, origin = 'local') =>
140
- native().deleteManyWithIds(colName, ids, origin),
141
- find: (filter?) => native().find(colName, filter ?? null) as T[],
120
+ insert(doc) {
121
+ const id = native().insert(colName, doc as Object);
122
+ const committedAt = Date.now();
123
+ emitPostImages<T>(webhook, colName, [id], 'insert', committedAt);
124
+ return id;
125
+ },
126
+ insertMany(docs) {
127
+ const ids = native().insertMany(colName, docs as Object[]);
128
+ const committedAt = Date.now();
129
+ emitPostImages<T>(webhook, colName, ids, 'insert', committedAt);
130
+ return ids;
131
+ },
132
+ find: (filter) => native().find(colName, filter ?? null) as T[],
142
133
  findOne: (filter) => native().findOne(colName, filter) as T | null,
143
- updateOne: (filter, update) => native().updateOne(colName, filter, update),
144
- updateMany: (filter, update) => native().updateMany(colName, filter, update),
145
- deleteOne: (filter) => native().deleteOne(colName, filter),
146
- deleteMany: (filter) => native().deleteMany(colName, filter),
147
- count: (filter?) => native().count(colName, filter ?? null),
134
+ updateOne(filter, update) {
135
+ const before = native().findOne(colName, filter) as T | null;
136
+ const changed = native().updateOne(colName, filter, update);
137
+ const committedAt = Date.now();
138
+ if (changed && typeof before?._id === 'string') {
139
+ emitPostImages<T>(webhook, colName, [before._id], 'update', committedAt);
140
+ }
141
+ return changed;
142
+ },
143
+ updateMany(filter, update) {
144
+ const before = native().find(colName, filter) as T[];
145
+ const changed = native().updateMany(colName, filter, update);
146
+ const committedAt = Date.now();
147
+ if (changed > 0) {
148
+ emitPostImages<T>(
149
+ webhook,
150
+ colName,
151
+ before.map((doc) => doc._id).filter((id): id is string => typeof id === 'string'),
152
+ 'update',
153
+ committedAt,
154
+ );
155
+ }
156
+ return changed;
157
+ },
158
+ deleteOne(filter) {
159
+ const before = native().findOne(colName, filter) as T | null;
160
+ const deleted = native().deleteOne(colName, filter);
161
+ const committedAt = Date.now();
162
+ if (deleted && before) emitDeletes(webhook, colName, [before], committedAt);
163
+ return deleted;
164
+ },
165
+ deleteMany(filter) {
166
+ const before = native().find(colName, filter) as T[];
167
+ const deleted = native().deleteMany(colName, filter);
168
+ const committedAt = Date.now();
169
+ if (deleted > 0) emitDeletes(webhook, colName, before, committedAt);
170
+ return deleted;
171
+ },
172
+ count: (filter) => native().count(colName, filter ?? null),
148
173
  createIndex: (field) => native().createIndex(colName, field),
149
174
  dropIndex: (field) => native().dropIndex(colName, field),
150
175
  createFtsIndex: (field) => native().createFtsIndex(colName, field),
@@ -152,11 +177,16 @@ function collection<T extends Document>(colName: string): Collection<T> {
152
177
  };
153
178
  }
154
179
 
155
- export function openDB(_dbName: string): DB {
180
+ /** Get a synchronous DB handle after `TalaDBModule.initialize(dbName)`. */
181
+ export function openDB(_dbName: string, options?: OpenDBOptions): DB {
182
+ const webhook = createWebhookDispatcher(options?.webhook);
156
183
  return {
157
- collection,
158
- syncStatus: () => native().syncStatus(),
159
- flushSync: (timeoutMs = 5000) => native().flushSync(timeoutMs),
160
- close: () => NativeTalaDB.close(),
184
+ collection: <T extends Document>(name: string) => collection<T>(name, webhook),
185
+ webhookStats: () => webhook?.stats() ?? { ...EMPTY_STATS },
186
+ flushWebhook: (timeoutMs) => webhook?.flush(timeoutMs) ?? Promise.resolve(true),
187
+ close: async () => {
188
+ await webhook?.flush();
189
+ await NativeTalaDB.close();
190
+ },
161
191
  };
162
192
  }
File without changes
@@ -1,2 +0,0 @@
1
- #Thu May 14 01:47:43 PST 2026
2
- gradle.version=8.9
File without changes
File without changes