@xnetjs/data-bridge 0.0.2
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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/chunk-X6F5CPJI.js +386 -0
- package/dist/index.d.ts +1011 -0
- package/dist/index.js +1246 -0
- package/dist/worker/data-worker.d.ts +2 -0
- package/dist/worker/data-worker.js +304 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
import * as _xnetjs_data from '@xnetjs/data';
|
|
2
|
+
import { PropertyBuilder, DefinedSchema, InferCreateProps, NodeState, NodeChangeEvent, ListNodesOptions, NodeStore, SchemaIRI } from '@xnetjs/data';
|
|
3
|
+
import { Awareness } from 'y-protocols/awareness';
|
|
4
|
+
import { Doc } from 'yjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sort direction for ordering results
|
|
8
|
+
*/
|
|
9
|
+
type SortDirection = 'asc' | 'desc';
|
|
10
|
+
/**
|
|
11
|
+
* System fields that can be used for ordering
|
|
12
|
+
*/
|
|
13
|
+
type SystemOrderField = 'createdAt' | 'updatedAt';
|
|
14
|
+
/**
|
|
15
|
+
* Options for querying nodes via the DataBridge.
|
|
16
|
+
* Maps to the filter options used by useQuery.
|
|
17
|
+
*/
|
|
18
|
+
interface QueryOptions<P extends Record<string, PropertyBuilder> = Record<string, PropertyBuilder>> {
|
|
19
|
+
/** Filter by single node ID (makes this a single-node query) */
|
|
20
|
+
nodeId?: string;
|
|
21
|
+
/** Filter conditions (property: value) */
|
|
22
|
+
where?: Partial<InferCreateProps<P>>;
|
|
23
|
+
/** Include soft-deleted nodes */
|
|
24
|
+
includeDeleted?: boolean;
|
|
25
|
+
/** Sort by property or system field */
|
|
26
|
+
orderBy?: {
|
|
27
|
+
[K in keyof InferCreateProps<P> | SystemOrderField]?: SortDirection;
|
|
28
|
+
};
|
|
29
|
+
/** Limit results */
|
|
30
|
+
limit?: number;
|
|
31
|
+
/** Offset for pagination */
|
|
32
|
+
offset?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A subscription to a query result.
|
|
36
|
+
* Compatible with React's useSyncExternalStore pattern.
|
|
37
|
+
*
|
|
38
|
+
* @typeParam P - Property builder type (unused at runtime, for type inference)
|
|
39
|
+
*/
|
|
40
|
+
interface QuerySubscription<P extends Record<string, PropertyBuilder> = Record<string, PropertyBuilder>> {
|
|
41
|
+
/** Get current snapshot (synchronous - reads from cache). Returns null if loading. */
|
|
42
|
+
getSnapshot(): NodeState[] | null;
|
|
43
|
+
/** Subscribe to updates (React will call this) */
|
|
44
|
+
subscribe(callback: () => void): () => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Result of a create operation
|
|
48
|
+
*/
|
|
49
|
+
interface CreateResult {
|
|
50
|
+
node: NodeState;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Result of an update operation
|
|
54
|
+
*/
|
|
55
|
+
interface UpdateResult {
|
|
56
|
+
node: NodeState;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Result of acquiring a Y.Doc for editing.
|
|
60
|
+
* The doc is kept in sync with the data thread via the bridge.
|
|
61
|
+
*/
|
|
62
|
+
interface AcquiredDoc {
|
|
63
|
+
/** The Y.Doc instance (on main thread for TipTap binding) */
|
|
64
|
+
doc: Doc;
|
|
65
|
+
/** Awareness instance for presence/cursors */
|
|
66
|
+
awareness: Awareness;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Sync connection status
|
|
70
|
+
*/
|
|
71
|
+
type SyncStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
72
|
+
/**
|
|
73
|
+
* Configuration for initializing a DataBridge
|
|
74
|
+
*/
|
|
75
|
+
interface DataBridgeConfig {
|
|
76
|
+
/** Database name for storage */
|
|
77
|
+
dbName?: string;
|
|
78
|
+
/** Author's DID for signing changes */
|
|
79
|
+
authorDID: string;
|
|
80
|
+
/** Ed25519 signing key */
|
|
81
|
+
signingKey: Uint8Array;
|
|
82
|
+
/** Signaling server URL for sync */
|
|
83
|
+
signalingUrl?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The DataBridge interface abstracts data access across different platforms.
|
|
87
|
+
*
|
|
88
|
+
* Implementations:
|
|
89
|
+
* - MainThreadBridge: Direct NodeStore access (Phase 0, fallback)
|
|
90
|
+
* - WorkerBridge: Web Worker via Comlink (Phase 1)
|
|
91
|
+
* - IPCBridge: Electron utility process (Phase 2)
|
|
92
|
+
* - NativeBridge: React Native Turbo Module (Phase 5)
|
|
93
|
+
*
|
|
94
|
+
* All implementations provide the same API, allowing React hooks to work
|
|
95
|
+
* identically across all platforms.
|
|
96
|
+
*/
|
|
97
|
+
interface DataBridge {
|
|
98
|
+
/**
|
|
99
|
+
* Create a subscription to a query result.
|
|
100
|
+
* Returns an object compatible with useSyncExternalStore.
|
|
101
|
+
*
|
|
102
|
+
* The subscription loads data asynchronously and updates the cache.
|
|
103
|
+
* getSnapshot() returns null while loading, then the result array.
|
|
104
|
+
*/
|
|
105
|
+
query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
|
|
106
|
+
/**
|
|
107
|
+
* Create a new node.
|
|
108
|
+
*/
|
|
109
|
+
create<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string): Promise<NodeState>;
|
|
110
|
+
/**
|
|
111
|
+
* Update an existing node.
|
|
112
|
+
*/
|
|
113
|
+
update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
|
|
114
|
+
/**
|
|
115
|
+
* Soft-delete a node.
|
|
116
|
+
*/
|
|
117
|
+
delete(nodeId: string): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Restore a soft-deleted node.
|
|
120
|
+
*/
|
|
121
|
+
restore(nodeId: string): Promise<NodeState>;
|
|
122
|
+
/**
|
|
123
|
+
* Acquire a Y.Doc for editing. Returns the doc with current state.
|
|
124
|
+
* The doc receives updates from the data thread via the bridge.
|
|
125
|
+
*
|
|
126
|
+
* Note: Phase 0 (MainThreadBridge) does not implement this method.
|
|
127
|
+
* It will be implemented in Phase 3 when Y.Doc split architecture is added.
|
|
128
|
+
*/
|
|
129
|
+
acquireDoc?(nodeId: string): Promise<AcquiredDoc>;
|
|
130
|
+
/**
|
|
131
|
+
* Release a Y.Doc when no longer editing.
|
|
132
|
+
* The data thread continues syncing in the background.
|
|
133
|
+
*
|
|
134
|
+
* Note: Phase 0 (MainThreadBridge) does not implement this method.
|
|
135
|
+
*/
|
|
136
|
+
releaseDoc?(nodeId: string): void;
|
|
137
|
+
/**
|
|
138
|
+
* Initialize the bridge with configuration.
|
|
139
|
+
* For MainThreadBridge, this is a no-op (already initialized with NodeStore).
|
|
140
|
+
*/
|
|
141
|
+
initialize?(config: DataBridgeConfig): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Clean up resources.
|
|
144
|
+
*/
|
|
145
|
+
destroy(): void;
|
|
146
|
+
/**
|
|
147
|
+
* Current sync status.
|
|
148
|
+
*/
|
|
149
|
+
readonly status: SyncStatus;
|
|
150
|
+
/**
|
|
151
|
+
* Subscribe to status changes.
|
|
152
|
+
*/
|
|
153
|
+
on(event: 'status', handler: (status: SyncStatus) => void): () => void;
|
|
154
|
+
/**
|
|
155
|
+
* Get the underlying NodeStore directly.
|
|
156
|
+
* Only available in MainThreadBridge for backward compatibility.
|
|
157
|
+
* Will be removed in later phases.
|
|
158
|
+
*/
|
|
159
|
+
readonly nodeStore?: _xnetjs_data.NodeStore;
|
|
160
|
+
/**
|
|
161
|
+
* Subscribe to store changes directly.
|
|
162
|
+
* Only available in MainThreadBridge for backward compatibility.
|
|
163
|
+
*/
|
|
164
|
+
subscribeToChanges?(listener: (event: NodeChangeEvent) => void): () => void;
|
|
165
|
+
/**
|
|
166
|
+
* Get a single node by ID directly.
|
|
167
|
+
* Only available in MainThreadBridge for backward compatibility.
|
|
168
|
+
*/
|
|
169
|
+
get?(nodeId: string): Promise<NodeState | null>;
|
|
170
|
+
/**
|
|
171
|
+
* List nodes with options directly.
|
|
172
|
+
* Only available in MainThreadBridge for backward compatibility.
|
|
173
|
+
*/
|
|
174
|
+
list?(options?: ListNodesOptions): Promise<NodeState[]>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Types for worker-main thread communication
|
|
179
|
+
*
|
|
180
|
+
* These types define the contract between the DataWorker running in a
|
|
181
|
+
* Web Worker and the WorkerBridge on the main thread.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Result from acquiring a Y.Doc in the worker.
|
|
186
|
+
* The state is sent as a Uint8Array for efficient transfer.
|
|
187
|
+
*/
|
|
188
|
+
interface WorkerAcquiredDoc {
|
|
189
|
+
/** The node ID this doc belongs to */
|
|
190
|
+
nodeId: string;
|
|
191
|
+
/** Encoded Y.Doc state (Y.encodeStateAsUpdate) */
|
|
192
|
+
state: Uint8Array;
|
|
193
|
+
/** Client ID for this connection */
|
|
194
|
+
clientId: number;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Document update message from worker to main thread
|
|
198
|
+
*/
|
|
199
|
+
interface DocUpdateMessage {
|
|
200
|
+
type: 'doc-update';
|
|
201
|
+
nodeId: string;
|
|
202
|
+
/** Yjs update (Uint8Array) */
|
|
203
|
+
update: Uint8Array;
|
|
204
|
+
/** Origin of the update (e.g., 'remote', 'local') */
|
|
205
|
+
origin: string;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Configuration passed to the worker on initialization
|
|
209
|
+
*/
|
|
210
|
+
interface WorkerConfig {
|
|
211
|
+
/** Database name for IndexedDB */
|
|
212
|
+
dbName: string;
|
|
213
|
+
/** Author's DID for signing changes */
|
|
214
|
+
authorDID: string;
|
|
215
|
+
/** Ed25519 signing key (serialized as array for transfer) */
|
|
216
|
+
signingKey: number[];
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Serialized query options (sent to worker)
|
|
220
|
+
*/
|
|
221
|
+
interface SerializedQueryOptions {
|
|
222
|
+
nodeId?: string;
|
|
223
|
+
where?: Record<string, unknown>;
|
|
224
|
+
includeDeleted?: boolean;
|
|
225
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
226
|
+
limit?: number;
|
|
227
|
+
offset?: number;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Delta update types for incremental cache updates
|
|
231
|
+
*/
|
|
232
|
+
type QueryDelta$1 = {
|
|
233
|
+
type: 'add';
|
|
234
|
+
node: NodeState;
|
|
235
|
+
index: number;
|
|
236
|
+
} | {
|
|
237
|
+
type: 'remove';
|
|
238
|
+
nodeId: string;
|
|
239
|
+
} | {
|
|
240
|
+
type: 'update';
|
|
241
|
+
nodeId: string;
|
|
242
|
+
node: NodeState;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* The API exposed by the DataWorker via Comlink.
|
|
246
|
+
* This is what WorkerBridge calls on the main thread.
|
|
247
|
+
*/
|
|
248
|
+
interface DataWorkerAPI {
|
|
249
|
+
/**
|
|
250
|
+
* Initialize the worker with configuration.
|
|
251
|
+
* Must be called before any other method.
|
|
252
|
+
*/
|
|
253
|
+
initialize(config: WorkerConfig): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Subscribe to a query. Returns initial results.
|
|
256
|
+
* Delta updates are sent via the callback.
|
|
257
|
+
*/
|
|
258
|
+
subscribe(queryId: string, schemaId: string, options: SerializedQueryOptions, onDelta: (delta: QueryDelta$1) => void): Promise<NodeState[]>;
|
|
259
|
+
/**
|
|
260
|
+
* Unsubscribe from a query.
|
|
261
|
+
*/
|
|
262
|
+
unsubscribe(queryId: string): Promise<void>;
|
|
263
|
+
/**
|
|
264
|
+
* Create a new node.
|
|
265
|
+
*/
|
|
266
|
+
create(schemaId: string, data: Record<string, unknown>, id?: string): Promise<NodeState>;
|
|
267
|
+
/**
|
|
268
|
+
* Update an existing node.
|
|
269
|
+
*/
|
|
270
|
+
update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
|
|
271
|
+
/**
|
|
272
|
+
* Delete a node (soft delete).
|
|
273
|
+
*/
|
|
274
|
+
delete(nodeId: string): Promise<void>;
|
|
275
|
+
/**
|
|
276
|
+
* Restore a deleted node.
|
|
277
|
+
*/
|
|
278
|
+
restore(nodeId: string): Promise<NodeState>;
|
|
279
|
+
/**
|
|
280
|
+
* Get a single node by ID.
|
|
281
|
+
*/
|
|
282
|
+
get(nodeId: string): Promise<NodeState | null>;
|
|
283
|
+
/**
|
|
284
|
+
* Acquire a Y.Doc for editing.
|
|
285
|
+
* Returns the current state which should be applied to the main-thread mirror doc.
|
|
286
|
+
*
|
|
287
|
+
* @param nodeId - The node ID to acquire
|
|
288
|
+
* @param onUpdate - Callback for receiving updates from the worker (remote changes)
|
|
289
|
+
* @returns The initial doc state and client ID
|
|
290
|
+
*/
|
|
291
|
+
acquireDoc(nodeId: string, onUpdate: (update: Uint8Array, origin: string) => void): Promise<WorkerAcquiredDoc>;
|
|
292
|
+
/**
|
|
293
|
+
* Release a Y.Doc when no longer editing.
|
|
294
|
+
* The doc stays in the pool for background sync.
|
|
295
|
+
*/
|
|
296
|
+
releaseDoc(nodeId: string): void;
|
|
297
|
+
/**
|
|
298
|
+
* Apply a local update from the main-thread mirror doc to the worker's source-of-truth doc.
|
|
299
|
+
* The worker will broadcast this to the network.
|
|
300
|
+
*
|
|
301
|
+
* @param nodeId - The node ID
|
|
302
|
+
* @param update - The Yjs update (from Y.encodeStateAsUpdate or update event)
|
|
303
|
+
*/
|
|
304
|
+
applyLocalUpdate(nodeId: string, update: Uint8Array): void;
|
|
305
|
+
/**
|
|
306
|
+
* Get current sync status.
|
|
307
|
+
*/
|
|
308
|
+
getStatus(): SyncStatus;
|
|
309
|
+
/**
|
|
310
|
+
* Subscribe to status changes.
|
|
311
|
+
*/
|
|
312
|
+
onStatusChange(handler: (status: SyncStatus) => void): void;
|
|
313
|
+
/**
|
|
314
|
+
* Clean up and close the worker.
|
|
315
|
+
*/
|
|
316
|
+
destroy(): Promise<void>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* MainThreadBridge - Direct NodeStore access implementation
|
|
321
|
+
*
|
|
322
|
+
* Phase 0 implementation that wraps NodeStore directly.
|
|
323
|
+
* Provides the DataBridge interface while keeping current behavior.
|
|
324
|
+
*
|
|
325
|
+
* This is the fallback implementation used when:
|
|
326
|
+
* - Web Workers are not available
|
|
327
|
+
* - During Phase 0 transition period
|
|
328
|
+
* - For testing/development
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Minimal SyncManager interface for Y.Doc acquisition.
|
|
333
|
+
* This avoids a direct dependency on @xnetjs/react's full SyncManager type.
|
|
334
|
+
*/
|
|
335
|
+
interface SyncManagerLike {
|
|
336
|
+
acquire(nodeId: string): Promise<Doc>;
|
|
337
|
+
release(nodeId: string): void;
|
|
338
|
+
getAwareness(nodeId: string): Awareness | null;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* DataBridge implementation that accesses NodeStore directly on the main thread.
|
|
342
|
+
*
|
|
343
|
+
* This is the Phase 0 implementation that maintains current behavior while
|
|
344
|
+
* providing the DataBridge abstraction. Later phases will move operations
|
|
345
|
+
* off the main thread via Web Workers or IPC.
|
|
346
|
+
*/
|
|
347
|
+
declare class MainThreadBridge implements DataBridge {
|
|
348
|
+
private store;
|
|
349
|
+
private cache;
|
|
350
|
+
private statusListeners;
|
|
351
|
+
private storeUnsubscribe;
|
|
352
|
+
private _syncManager;
|
|
353
|
+
constructor(store: NodeStore);
|
|
354
|
+
/**
|
|
355
|
+
* Set the SyncManager for Y.Doc acquisition.
|
|
356
|
+
* This is called by XNetProvider after the SyncManager is created.
|
|
357
|
+
*/
|
|
358
|
+
setSyncManager(syncManager: SyncManagerLike | null): void;
|
|
359
|
+
query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
|
|
360
|
+
/**
|
|
361
|
+
* Load query data from the store and update cache.
|
|
362
|
+
*/
|
|
363
|
+
private loadQuery;
|
|
364
|
+
/**
|
|
365
|
+
* Handle store changes and invalidate affected caches.
|
|
366
|
+
*/
|
|
367
|
+
private handleStoreChange;
|
|
368
|
+
create<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string): Promise<NodeState>;
|
|
369
|
+
update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
|
|
370
|
+
delete(nodeId: string): Promise<void>;
|
|
371
|
+
restore(nodeId: string): Promise<NodeState>;
|
|
372
|
+
/**
|
|
373
|
+
* Acquire a Y.Doc for editing.
|
|
374
|
+
* Delegates to SyncManager if available, otherwise throws.
|
|
375
|
+
*
|
|
376
|
+
* @throws Error if SyncManager is not set
|
|
377
|
+
*/
|
|
378
|
+
acquireDoc(nodeId: string): Promise<AcquiredDoc>;
|
|
379
|
+
/**
|
|
380
|
+
* Release a Y.Doc when no longer editing.
|
|
381
|
+
*/
|
|
382
|
+
releaseDoc(nodeId: string): void;
|
|
383
|
+
destroy(): void;
|
|
384
|
+
get status(): SyncStatus;
|
|
385
|
+
on(event: 'status', handler: (status: SyncStatus) => void): () => void;
|
|
386
|
+
get nodeStore(): NodeStore;
|
|
387
|
+
subscribeToChanges(listener: (event: NodeChangeEvent) => void): () => void;
|
|
388
|
+
get(nodeId: string): Promise<NodeState | null>;
|
|
389
|
+
list(options?: ListNodesOptions): Promise<NodeState[]>;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Create a MainThreadBridge from a NodeStore.
|
|
393
|
+
*/
|
|
394
|
+
declare function createMainThreadBridge(store: NodeStore): MainThreadBridge;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* WorkerBridge - Main thread bridge to DataWorker
|
|
398
|
+
*
|
|
399
|
+
* This bridge runs on the main thread and communicates with the DataWorker
|
|
400
|
+
* running in a Web Worker via Comlink. It provides the same DataBridge
|
|
401
|
+
* interface as MainThreadBridge but offloads all heavy operations.
|
|
402
|
+
*
|
|
403
|
+
* Key features:
|
|
404
|
+
* - Type-safe RPC via Comlink
|
|
405
|
+
* - Query caching with delta updates
|
|
406
|
+
* - useSyncExternalStore-compatible subscriptions
|
|
407
|
+
*/
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* DataBridge implementation that communicates with a Web Worker.
|
|
411
|
+
*
|
|
412
|
+
* All heavy operations (storage, queries, crypto) run in the worker,
|
|
413
|
+
* keeping the main thread free for UI rendering.
|
|
414
|
+
*/
|
|
415
|
+
declare class WorkerBridge implements DataBridge {
|
|
416
|
+
private worker;
|
|
417
|
+
private remote;
|
|
418
|
+
private cache;
|
|
419
|
+
private subscriptions;
|
|
420
|
+
private queryCounter;
|
|
421
|
+
private statusListeners;
|
|
422
|
+
private _status;
|
|
423
|
+
private initialized;
|
|
424
|
+
private mirrorDocs;
|
|
425
|
+
constructor(workerUrl: string | URL);
|
|
426
|
+
/**
|
|
427
|
+
* Initialize the bridge and underlying worker.
|
|
428
|
+
*/
|
|
429
|
+
initialize(config: DataBridgeConfig): Promise<void>;
|
|
430
|
+
query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
|
|
431
|
+
private startWorkerSubscription;
|
|
432
|
+
private applyDelta;
|
|
433
|
+
private serializeOptions;
|
|
434
|
+
create<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string): Promise<NodeState>;
|
|
435
|
+
update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
|
|
436
|
+
delete(nodeId: string): Promise<void>;
|
|
437
|
+
restore(nodeId: string): Promise<NodeState>;
|
|
438
|
+
/**
|
|
439
|
+
* Acquire a Y.Doc for editing.
|
|
440
|
+
*
|
|
441
|
+
* Implements the split Y.Doc pattern:
|
|
442
|
+
* 1. Worker maintains "source of truth" Y.Doc (handles persistence & network sync)
|
|
443
|
+
* 2. Main thread gets a mirror Y.Doc for TipTap binding
|
|
444
|
+
* 3. Updates flow bidirectionally:
|
|
445
|
+
* - Local edits → worker (for persistence & broadcast)
|
|
446
|
+
* - Remote edits → main thread (for rendering)
|
|
447
|
+
*/
|
|
448
|
+
acquireDoc(nodeId: string): Promise<AcquiredDoc>;
|
|
449
|
+
/**
|
|
450
|
+
* Release a Y.Doc when no longer editing.
|
|
451
|
+
* The worker continues syncing in the background.
|
|
452
|
+
*/
|
|
453
|
+
releaseDoc(nodeId: string): void;
|
|
454
|
+
destroy(): void;
|
|
455
|
+
get status(): SyncStatus;
|
|
456
|
+
on(event: 'status', handler: (status: SyncStatus) => void): () => void;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Create a WorkerBridge from a worker URL.
|
|
460
|
+
*
|
|
461
|
+
* @param workerUrl - URL to the data worker script
|
|
462
|
+
* @returns A new WorkerBridge instance (must call initialize() before use)
|
|
463
|
+
*/
|
|
464
|
+
declare function createWorkerBridge(workerUrl: string | URL): WorkerBridge;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* NativeBridge - DataBridge implementation for React Native/Expo
|
|
468
|
+
*
|
|
469
|
+
* Phase 5 implementation that provides the DataBridge interface for React Native.
|
|
470
|
+
*
|
|
471
|
+
* Architecture:
|
|
472
|
+
* - Uses expo-sqlite for storage (runs on native thread)
|
|
473
|
+
* - Uses NodeStore on JS thread (can be moved to JSI in future)
|
|
474
|
+
* - Provides same API as MainThreadBridge/WorkerBridge
|
|
475
|
+
*
|
|
476
|
+
* Future enhancements:
|
|
477
|
+
* - Turbo Module for heavy operations (crypto, queries)
|
|
478
|
+
* - JSI bindings for direct native access
|
|
479
|
+
* - Native WebSocket for sync
|
|
480
|
+
*/
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Interface for native storage adapters (expo-sqlite, etc.)
|
|
484
|
+
* This allows the NativeBridge to work with different storage backends.
|
|
485
|
+
*/
|
|
486
|
+
interface NativeStorageAdapter {
|
|
487
|
+
/** Open the database */
|
|
488
|
+
open(): Promise<void>;
|
|
489
|
+
/** Close the database */
|
|
490
|
+
close(): Promise<void>;
|
|
491
|
+
/** Get a document by ID */
|
|
492
|
+
getDocument(id: string): Promise<{
|
|
493
|
+
content: Uint8Array;
|
|
494
|
+
} | null>;
|
|
495
|
+
/** Set a document */
|
|
496
|
+
setDocument(id: string, content: Uint8Array): Promise<void>;
|
|
497
|
+
/** Delete a document */
|
|
498
|
+
deleteDocument(id: string): Promise<void>;
|
|
499
|
+
/** List all document IDs */
|
|
500
|
+
listDocuments(): Promise<string[]>;
|
|
501
|
+
}
|
|
502
|
+
interface NativeBridgeConfig {
|
|
503
|
+
/** The NodeStore instance to use */
|
|
504
|
+
store: NodeStore;
|
|
505
|
+
/** Optional native storage adapter for Y.Doc persistence */
|
|
506
|
+
storageAdapter?: NativeStorageAdapter;
|
|
507
|
+
/** Signaling server URL for sync (optional, default: no sync) */
|
|
508
|
+
signalingUrl?: string;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* DataBridge implementation for React Native/Expo.
|
|
512
|
+
*
|
|
513
|
+
* This implementation runs on the JS thread but is designed to work with
|
|
514
|
+
* React Native's architecture. Storage operations use expo-sqlite which
|
|
515
|
+
* runs on a native thread.
|
|
516
|
+
*
|
|
517
|
+
* For now, this is similar to MainThreadBridge but structured for RN.
|
|
518
|
+
* Future versions will add Turbo Module integration for off-thread operations.
|
|
519
|
+
*/
|
|
520
|
+
declare class NativeBridge implements DataBridge {
|
|
521
|
+
private store;
|
|
522
|
+
private cache;
|
|
523
|
+
private statusListeners;
|
|
524
|
+
private storeUnsubscribe;
|
|
525
|
+
private storageAdapter?;
|
|
526
|
+
private _status;
|
|
527
|
+
private destroyed;
|
|
528
|
+
constructor(config: NativeBridgeConfig);
|
|
529
|
+
query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
|
|
530
|
+
/**
|
|
531
|
+
* Load query data from the store and update cache.
|
|
532
|
+
*/
|
|
533
|
+
private loadQuery;
|
|
534
|
+
/**
|
|
535
|
+
* Handle store changes and invalidate affected caches.
|
|
536
|
+
*/
|
|
537
|
+
private handleStoreChange;
|
|
538
|
+
create<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string): Promise<NodeState>;
|
|
539
|
+
update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
|
|
540
|
+
delete(nodeId: string): Promise<void>;
|
|
541
|
+
restore(nodeId: string): Promise<NodeState>;
|
|
542
|
+
/**
|
|
543
|
+
* Acquire a Y.Doc for editing.
|
|
544
|
+
*
|
|
545
|
+
* Note: Y.Doc management in React Native is limited compared to web.
|
|
546
|
+
* For now, this throws an error. Future versions will support Y.Doc
|
|
547
|
+
* via native WebSocket sync or JSI bindings.
|
|
548
|
+
*/
|
|
549
|
+
acquireDoc(_nodeId: string): Promise<AcquiredDoc>;
|
|
550
|
+
/**
|
|
551
|
+
* Release a Y.Doc when no longer editing.
|
|
552
|
+
*/
|
|
553
|
+
releaseDoc(_nodeId: string): void;
|
|
554
|
+
/**
|
|
555
|
+
* Initialize the bridge.
|
|
556
|
+
* Opens storage adapter if provided.
|
|
557
|
+
*/
|
|
558
|
+
initialize(_config: DataBridgeConfig): Promise<void>;
|
|
559
|
+
destroy(): void;
|
|
560
|
+
get status(): SyncStatus;
|
|
561
|
+
on(event: 'status', handler: (status: SyncStatus) => void): () => void;
|
|
562
|
+
private setStatus;
|
|
563
|
+
get nodeStore(): NodeStore;
|
|
564
|
+
subscribeToChanges(listener: (event: NodeChangeEvent) => void): () => void;
|
|
565
|
+
get(nodeId: string): Promise<NodeState | null>;
|
|
566
|
+
list(options?: ListNodesOptions): Promise<NodeState[]>;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Create a NativeBridge from a NodeStore.
|
|
570
|
+
*/
|
|
571
|
+
declare function createNativeBridge(config: NativeBridgeConfig): NativeBridge;
|
|
572
|
+
/**
|
|
573
|
+
* Check if we're running in a React Native environment.
|
|
574
|
+
*/
|
|
575
|
+
declare function isReactNative(): boolean;
|
|
576
|
+
/**
|
|
577
|
+
* Check if we're running in Expo.
|
|
578
|
+
*/
|
|
579
|
+
declare function isExpo(): boolean;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Factory functions for creating DataBridge instances
|
|
583
|
+
*
|
|
584
|
+
* Provides platform-aware bridge creation with automatic detection
|
|
585
|
+
* of Web Worker support and appropriate fallbacks.
|
|
586
|
+
*/
|
|
587
|
+
|
|
588
|
+
interface CreateBridgeOptions {
|
|
589
|
+
/**
|
|
590
|
+
* NodeStore instance (required for MainThreadBridge fallback)
|
|
591
|
+
*/
|
|
592
|
+
nodeStore: NodeStore;
|
|
593
|
+
/**
|
|
594
|
+
* Configuration for the bridge
|
|
595
|
+
*/
|
|
596
|
+
config: DataBridgeConfig;
|
|
597
|
+
/**
|
|
598
|
+
* URL to the data worker script.
|
|
599
|
+
* If not provided, MainThreadBridge will be used.
|
|
600
|
+
*
|
|
601
|
+
* In Vite, use import.meta.url to get the worker URL:
|
|
602
|
+
* ```ts
|
|
603
|
+
* // Option 1: Use the package's built worker
|
|
604
|
+
* const workerUrl = new URL('@xnetjs/data-bridge/worker', import.meta.url)
|
|
605
|
+
*
|
|
606
|
+
* // Option 2: Use Vite's ?worker&url import (bundles into your build)
|
|
607
|
+
* import workerUrl from '@xnetjs/data-bridge/dist/worker/data-worker.js?worker&url'
|
|
608
|
+
* ```
|
|
609
|
+
*
|
|
610
|
+
* Note: Vite handles Web Worker bundling automatically. No additional
|
|
611
|
+
* configuration is needed - just pass the URL to WorkerBridge.
|
|
612
|
+
*/
|
|
613
|
+
workerUrl?: URL | string;
|
|
614
|
+
/**
|
|
615
|
+
* Force a specific bridge type.
|
|
616
|
+
* - 'worker': Force WorkerBridge (fails if workers not supported)
|
|
617
|
+
* - 'main-thread': Force MainThreadBridge
|
|
618
|
+
* - 'auto': Auto-detect (default)
|
|
619
|
+
*/
|
|
620
|
+
mode?: 'worker' | 'main-thread' | 'auto';
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Check if Web Workers are supported in the current environment.
|
|
624
|
+
*/
|
|
625
|
+
declare function isWorkerSupported(): boolean;
|
|
626
|
+
/**
|
|
627
|
+
* Check if we're in a Node.js environment (e.g., SSR, tests).
|
|
628
|
+
*/
|
|
629
|
+
declare function isNodeEnvironment(): boolean;
|
|
630
|
+
/**
|
|
631
|
+
* Create a DataBridge with automatic platform detection.
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```ts
|
|
635
|
+
* // In a web app with Vite
|
|
636
|
+
* const bridge = await createDataBridge({
|
|
637
|
+
* nodeStore,
|
|
638
|
+
* config: { authorDID, signingKey },
|
|
639
|
+
* workerUrl: new URL('@xnetjs/data-bridge/worker', import.meta.url)
|
|
640
|
+
* })
|
|
641
|
+
*
|
|
642
|
+
* // In tests or when workers aren't needed
|
|
643
|
+
* const bridge = await createDataBridge({
|
|
644
|
+
* nodeStore,
|
|
645
|
+
* config: { authorDID, signingKey },
|
|
646
|
+
* mode: 'main-thread'
|
|
647
|
+
* })
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
declare function createDataBridge(options: CreateBridgeOptions): Promise<DataBridge>;
|
|
651
|
+
/**
|
|
652
|
+
* Create a MainThreadBridge directly.
|
|
653
|
+
* Use this when you don't need off-main-thread support.
|
|
654
|
+
*/
|
|
655
|
+
declare function createMainThreadBridgeSync(nodeStore: NodeStore): MainThreadBridge;
|
|
656
|
+
/**
|
|
657
|
+
* Create a WorkerBridge directly.
|
|
658
|
+
* The bridge must be initialized before use.
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* const bridge = createWorkerBridgeSync(workerUrl)
|
|
663
|
+
* await bridge.initialize(config)
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
666
|
+
declare function createWorkerBridgeSync(workerUrl: URL | string): WorkerBridge;
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* QueryCache - In-memory cache for query results with LRU eviction
|
|
670
|
+
*
|
|
671
|
+
* Provides:
|
|
672
|
+
* - Fast synchronous access to query results (for useSyncExternalStore)
|
|
673
|
+
* - Subscriber notification on cache updates
|
|
674
|
+
* - Query deduplication via stable query IDs
|
|
675
|
+
* - LRU eviction for memory management
|
|
676
|
+
* - Weak references for inactive subscriptions (automatic cleanup)
|
|
677
|
+
*/
|
|
678
|
+
|
|
679
|
+
interface QueryCacheOptions {
|
|
680
|
+
/** Maximum number of queries to cache (default: 100) */
|
|
681
|
+
maxSize?: number;
|
|
682
|
+
/** Enable weak reference cleanup interval (default: true in browser, false in tests) */
|
|
683
|
+
enableWeakRefCleanup?: boolean;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* In-memory cache for query results with subscriber notification and LRU eviction.
|
|
687
|
+
*/
|
|
688
|
+
declare class QueryCache {
|
|
689
|
+
private cache;
|
|
690
|
+
private maxSize;
|
|
691
|
+
private cleanupInterval;
|
|
692
|
+
constructor(options?: QueryCacheOptions);
|
|
693
|
+
/**
|
|
694
|
+
* Start the interval that cleans up dead weak references.
|
|
695
|
+
*/
|
|
696
|
+
private startWeakRefCleanup;
|
|
697
|
+
/**
|
|
698
|
+
* Stop the weak reference cleanup interval.
|
|
699
|
+
*/
|
|
700
|
+
stopCleanup(): void;
|
|
701
|
+
/**
|
|
702
|
+
* Clean up dead weak references from all cache entries.
|
|
703
|
+
* Returns the number of dead references removed.
|
|
704
|
+
*/
|
|
705
|
+
cleanupDeadWeakRefs(): number;
|
|
706
|
+
/**
|
|
707
|
+
* Compute a stable query ID from schema and options.
|
|
708
|
+
* Same query params should produce the same ID for deduplication.
|
|
709
|
+
*/
|
|
710
|
+
computeQueryId<P extends Record<string, PropertyBuilder>>(schemaId: string, options?: QueryOptions<P>): string;
|
|
711
|
+
/**
|
|
712
|
+
* Get cached data for a query (synchronous for useSyncExternalStore).
|
|
713
|
+
* Updates lastAccessed for LRU tracking.
|
|
714
|
+
*/
|
|
715
|
+
get(queryId: string): NodeState[] | null;
|
|
716
|
+
/**
|
|
717
|
+
* Check if a query is in the cache.
|
|
718
|
+
*/
|
|
719
|
+
has(queryId: string): boolean;
|
|
720
|
+
/**
|
|
721
|
+
* Set cached data for a query and notify subscribers.
|
|
722
|
+
* Triggers LRU eviction if cache exceeds maxSize.
|
|
723
|
+
*/
|
|
724
|
+
set(queryId: string, data: NodeState[], schemaId: SchemaIRI, options: QueryOptions): void;
|
|
725
|
+
/**
|
|
726
|
+
* Initialize a cache entry (called when starting a subscription).
|
|
727
|
+
*/
|
|
728
|
+
initEntry(queryId: string, schemaId: SchemaIRI, options: QueryOptions): void;
|
|
729
|
+
/**
|
|
730
|
+
* Subscribe to cache updates for a query.
|
|
731
|
+
* Uses strong references - callback will not be garbage collected until unsubscribed.
|
|
732
|
+
*/
|
|
733
|
+
subscribe(queryId: string, callback: () => void): () => void;
|
|
734
|
+
/**
|
|
735
|
+
* Subscribe with a weak reference.
|
|
736
|
+
* The callback can be garbage collected if the owning component is unmounted
|
|
737
|
+
* and no other references to the callback exist.
|
|
738
|
+
*
|
|
739
|
+
* This is useful for long-lived subscriptions where you want automatic cleanup
|
|
740
|
+
* without explicit unsubscribe calls. However, for React components, prefer
|
|
741
|
+
* the regular subscribe() with proper cleanup in useEffect.
|
|
742
|
+
*
|
|
743
|
+
* @param queryId - The query to subscribe to
|
|
744
|
+
* @param callback - The callback to invoke on updates
|
|
745
|
+
* @returns Unsubscribe function
|
|
746
|
+
*/
|
|
747
|
+
subscribeWeak(queryId: string, callback: () => void): () => void;
|
|
748
|
+
/**
|
|
749
|
+
* Notify all subscribers of a query that data has changed.
|
|
750
|
+
* Handles both strong and weak subscribers, cleaning up dead weak refs.
|
|
751
|
+
*/
|
|
752
|
+
notifySubscribers(queryId: string): void;
|
|
753
|
+
/**
|
|
754
|
+
* Get the number of active subscribers for a query.
|
|
755
|
+
* Includes both strong and live weak subscribers.
|
|
756
|
+
*/
|
|
757
|
+
getSubscriberCount(queryId: string): number;
|
|
758
|
+
/**
|
|
759
|
+
* Get the number of weak subscribers (including potentially dead ones).
|
|
760
|
+
* Useful for debugging.
|
|
761
|
+
*/
|
|
762
|
+
getWeakSubscriberCount(queryId: string): number;
|
|
763
|
+
/**
|
|
764
|
+
* Remove a query from the cache.
|
|
765
|
+
*/
|
|
766
|
+
delete(queryId: string): void;
|
|
767
|
+
/**
|
|
768
|
+
* Get all query IDs that match a schema.
|
|
769
|
+
*/
|
|
770
|
+
getQueriesForSchema(schemaId: SchemaIRI): string[];
|
|
771
|
+
/**
|
|
772
|
+
* Get the schema IRI for a cached query.
|
|
773
|
+
*/
|
|
774
|
+
getSchemaId(queryId: string): SchemaIRI | undefined;
|
|
775
|
+
/**
|
|
776
|
+
* Get the options for a cached query.
|
|
777
|
+
*/
|
|
778
|
+
getOptions(queryId: string): QueryOptions | undefined;
|
|
779
|
+
/**
|
|
780
|
+
* Clear the entire cache and stop cleanup interval.
|
|
781
|
+
*/
|
|
782
|
+
clear(): void;
|
|
783
|
+
/**
|
|
784
|
+
* Destroy the cache, stopping all cleanup intervals.
|
|
785
|
+
*/
|
|
786
|
+
destroy(): void;
|
|
787
|
+
/**
|
|
788
|
+
* Get the number of cached queries.
|
|
789
|
+
*/
|
|
790
|
+
get size(): number;
|
|
791
|
+
/**
|
|
792
|
+
* Get the maximum cache size.
|
|
793
|
+
*/
|
|
794
|
+
get maxCacheSize(): number;
|
|
795
|
+
/**
|
|
796
|
+
* Check if an entry has any active subscribers (strong or live weak).
|
|
797
|
+
*/
|
|
798
|
+
private hasActiveSubscribers;
|
|
799
|
+
/**
|
|
800
|
+
* Evict least-recently-used entries if cache exceeds maxSize.
|
|
801
|
+
* Only evicts entries with no active subscribers and older than MIN_AGE_FOR_EVICTION.
|
|
802
|
+
*/
|
|
803
|
+
private evictIfNeeded;
|
|
804
|
+
/**
|
|
805
|
+
* Manually trigger eviction (for testing or explicit cleanup).
|
|
806
|
+
*/
|
|
807
|
+
evict(): number;
|
|
808
|
+
/**
|
|
809
|
+
* Filter nodes based on query options.
|
|
810
|
+
*/
|
|
811
|
+
filterNodes<P extends Record<string, PropertyBuilder>>(nodes: NodeState[], options?: QueryOptions<P>): NodeState[];
|
|
812
|
+
/**
|
|
813
|
+
* Sort nodes based on query options.
|
|
814
|
+
*/
|
|
815
|
+
sortNodes<P extends Record<string, PropertyBuilder>>(nodes: NodeState[], options?: QueryOptions<P>): NodeState[];
|
|
816
|
+
/**
|
|
817
|
+
* Apply pagination to nodes.
|
|
818
|
+
*/
|
|
819
|
+
paginateNodes(nodes: NodeState[], options?: QueryOptions): NodeState[];
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Debounce utilities for DataBridge
|
|
824
|
+
*
|
|
825
|
+
* These utilities help reduce message frequency between main thread and worker,
|
|
826
|
+
* particularly useful for high-frequency Y.Doc updates during rapid typing.
|
|
827
|
+
*/
|
|
828
|
+
interface DebounceOptions {
|
|
829
|
+
/** Time in ms to wait before executing */
|
|
830
|
+
wait: number;
|
|
831
|
+
/** Maximum time in ms to wait before forcing execution */
|
|
832
|
+
maxWait?: number;
|
|
833
|
+
/** If true, execute on leading edge instead of trailing */
|
|
834
|
+
leading?: boolean;
|
|
835
|
+
}
|
|
836
|
+
interface DebouncedFunction<T extends (...args: unknown[]) => void> {
|
|
837
|
+
(...args: Parameters<T>): void;
|
|
838
|
+
/** Cancel any pending execution */
|
|
839
|
+
cancel(): void;
|
|
840
|
+
/** Execute immediately if there's a pending call */
|
|
841
|
+
flush(): void;
|
|
842
|
+
/** Check if there's a pending execution */
|
|
843
|
+
pending(): boolean;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Creates a debounced function that delays invoking func until after `wait`
|
|
847
|
+
* milliseconds have elapsed since the last time the debounced function was invoked.
|
|
848
|
+
*
|
|
849
|
+
* Optionally supports a maxWait to ensure execution even during continuous calls.
|
|
850
|
+
*/
|
|
851
|
+
declare function debounce<T extends (...args: unknown[]) => void>(func: T, options: DebounceOptions): DebouncedFunction<T>;
|
|
852
|
+
/**
|
|
853
|
+
* Accumulates Y.Doc updates and batches them into a single merged update.
|
|
854
|
+
* This is more efficient than debouncing individual updates because Yjs
|
|
855
|
+
* can merge multiple updates into one.
|
|
856
|
+
*/
|
|
857
|
+
interface UpdateBatcher {
|
|
858
|
+
/** Add an update to the batch */
|
|
859
|
+
add(update: Uint8Array): void;
|
|
860
|
+
/** Cancel pending batch */
|
|
861
|
+
cancel(): void;
|
|
862
|
+
/** Flush pending updates immediately */
|
|
863
|
+
flush(): void;
|
|
864
|
+
/** Check if there are pending updates */
|
|
865
|
+
pending(): boolean;
|
|
866
|
+
}
|
|
867
|
+
interface UpdateBatcherOptions {
|
|
868
|
+
/** Time in ms to wait before flushing the batch */
|
|
869
|
+
wait: number;
|
|
870
|
+
/** Maximum time in ms before forcing a flush */
|
|
871
|
+
maxWait: number;
|
|
872
|
+
/** Callback to receive the batched update */
|
|
873
|
+
onFlush: (mergedUpdate: Uint8Array) => void;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Creates an update batcher that accumulates Y.Doc updates and merges them.
|
|
877
|
+
*
|
|
878
|
+
* Uses Yjs mergeUpdates to combine multiple updates into one, reducing
|
|
879
|
+
* message count and improving network efficiency.
|
|
880
|
+
*/
|
|
881
|
+
declare function createUpdateBatcher(options: UpdateBatcherOptions): UpdateBatcher;
|
|
882
|
+
/**
|
|
883
|
+
* Delta types for query result changes
|
|
884
|
+
*/
|
|
885
|
+
type QueryDelta = {
|
|
886
|
+
type: 'add';
|
|
887
|
+
node: unknown;
|
|
888
|
+
index: number;
|
|
889
|
+
} | {
|
|
890
|
+
type: 'remove';
|
|
891
|
+
nodeId: string;
|
|
892
|
+
} | {
|
|
893
|
+
type: 'update';
|
|
894
|
+
nodeId: string;
|
|
895
|
+
node: unknown;
|
|
896
|
+
};
|
|
897
|
+
/**
|
|
898
|
+
* Options for creating a delta batcher
|
|
899
|
+
*/
|
|
900
|
+
interface DeltaBatcherOptions {
|
|
901
|
+
/** Time in ms to wait before flushing deltas */
|
|
902
|
+
wait: number;
|
|
903
|
+
/** Maximum time in ms before forcing a flush */
|
|
904
|
+
maxWait: number;
|
|
905
|
+
/** Callback to receive batched deltas */
|
|
906
|
+
onFlush: (deltas: QueryDelta[]) => void;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Batcher for query deltas that coalesces rapid changes.
|
|
910
|
+
*
|
|
911
|
+
* Merging rules:
|
|
912
|
+
* - Multiple updates to same node → keep only latest update
|
|
913
|
+
* - Add then remove same node → both cancel out (no delta)
|
|
914
|
+
* - Remove then add same node → becomes update
|
|
915
|
+
* - Add then update same node → keep add with updated data
|
|
916
|
+
*/
|
|
917
|
+
interface DeltaBatcher {
|
|
918
|
+
/** Add a delta to the batch */
|
|
919
|
+
add(delta: QueryDelta): void;
|
|
920
|
+
/** Cancel pending batch */
|
|
921
|
+
cancel(): void;
|
|
922
|
+
/** Flush pending deltas immediately */
|
|
923
|
+
flush(): void;
|
|
924
|
+
/** Check if there are pending deltas */
|
|
925
|
+
pending(): boolean;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Creates a delta batcher that coalesces rapid query result changes.
|
|
929
|
+
*/
|
|
930
|
+
declare function createDeltaBatcher(options: DeltaBatcherOptions): DeltaBatcher;
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Binary Serialization for NodeState
|
|
934
|
+
*
|
|
935
|
+
* Provides efficient binary encoding for NodeState objects when transferring
|
|
936
|
+
* between main thread and worker via postMessage. This is more efficient than
|
|
937
|
+
* JSON for large datasets with typed arrays.
|
|
938
|
+
*
|
|
939
|
+
* Format:
|
|
940
|
+
* - Uses TextEncoder/TextDecoder for strings
|
|
941
|
+
* - Preserves Uint8Array as-is (transferable)
|
|
942
|
+
* - Compacts property timestamps
|
|
943
|
+
*
|
|
944
|
+
* Note: This is an optimization for large transfers. For small payloads,
|
|
945
|
+
* structured clone (default postMessage behavior) may be faster.
|
|
946
|
+
*/
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Binary encoder for NodeState arrays.
|
|
950
|
+
*
|
|
951
|
+
* Encodes an array of NodeState objects to a single Uint8Array.
|
|
952
|
+
* The result can be transferred via postMessage as a Transferable.
|
|
953
|
+
*/
|
|
954
|
+
declare class NodeStateEncoder {
|
|
955
|
+
private chunks;
|
|
956
|
+
private textEncoder;
|
|
957
|
+
/**
|
|
958
|
+
* Encode an array of NodeState objects.
|
|
959
|
+
*/
|
|
960
|
+
encode(states: NodeState[]): Uint8Array;
|
|
961
|
+
private writeNodeState;
|
|
962
|
+
private writeProperties;
|
|
963
|
+
private writeTimestamps;
|
|
964
|
+
private writeTimestamp;
|
|
965
|
+
private writeValue;
|
|
966
|
+
private writeByte;
|
|
967
|
+
private writeUint32;
|
|
968
|
+
private writeFloat64;
|
|
969
|
+
private writeString;
|
|
970
|
+
private writeUint8Array;
|
|
971
|
+
private finish;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Binary decoder for NodeState arrays.
|
|
975
|
+
*/
|
|
976
|
+
declare class NodeStateDecoder {
|
|
977
|
+
private data;
|
|
978
|
+
private view;
|
|
979
|
+
private offset;
|
|
980
|
+
private textDecoder;
|
|
981
|
+
constructor(data: Uint8Array);
|
|
982
|
+
/**
|
|
983
|
+
* Decode a Uint8Array back to an array of NodeState objects.
|
|
984
|
+
*/
|
|
985
|
+
decode(): NodeState[];
|
|
986
|
+
private readNodeState;
|
|
987
|
+
private readProperties;
|
|
988
|
+
private readTimestamps;
|
|
989
|
+
private readTimestamp;
|
|
990
|
+
private readValue;
|
|
991
|
+
private readByte;
|
|
992
|
+
private readUint32;
|
|
993
|
+
private readFloat64;
|
|
994
|
+
private readString;
|
|
995
|
+
private readUint8Array;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Encode an array of NodeState to binary.
|
|
999
|
+
*/
|
|
1000
|
+
declare function encodeNodeStates(states: NodeState[]): Uint8Array;
|
|
1001
|
+
/**
|
|
1002
|
+
* Decode binary back to NodeState array.
|
|
1003
|
+
*/
|
|
1004
|
+
declare function decodeNodeStates(data: Uint8Array): NodeState[];
|
|
1005
|
+
/**
|
|
1006
|
+
* Check if binary encoding would be beneficial for this payload size.
|
|
1007
|
+
* For small payloads, structured clone may be faster.
|
|
1008
|
+
*/
|
|
1009
|
+
declare function shouldUseBinaryEncoding(states: NodeState[]): boolean;
|
|
1010
|
+
|
|
1011
|
+
export { type AcquiredDoc, type CreateBridgeOptions, type CreateResult, type DataBridge, type DataBridgeConfig, type DataWorkerAPI, type DebounceOptions, type DebouncedFunction, type DeltaBatcher, type DeltaBatcherOptions, type QueryDelta as DeltaQueryDelta, type DocUpdateMessage, MainThreadBridge, NativeBridge, type NativeBridgeConfig, type NativeStorageAdapter, NodeStateDecoder, NodeStateEncoder, QueryCache, type QueryDelta$1 as QueryDelta, type QueryOptions, type QuerySubscription, type SerializedQueryOptions, type SortDirection, type SyncManagerLike, type SyncStatus, type SystemOrderField, type UpdateBatcher, type UpdateBatcherOptions, type UpdateResult, type WorkerAcquiredDoc, WorkerBridge, type WorkerConfig, createDataBridge, createDeltaBatcher, createMainThreadBridge, createMainThreadBridgeSync, createNativeBridge, createUpdateBatcher, createWorkerBridge, createWorkerBridgeSync, debounce, decodeNodeStates, encodeNodeStates, isExpo, isNodeEnvironment, isReactNative, isWorkerSupported, shouldUseBinaryEncoding };
|