@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.
@@ -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 };