@xnetjs/data-bridge 0.0.2 → 0.0.3

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/dist/index.d.ts CHANGED
@@ -1,320 +1,12 @@
1
- import * as _xnetjs_data from '@xnetjs/data';
2
- import { PropertyBuilder, DefinedSchema, InferCreateProps, NodeState, NodeChangeEvent, ListNodesOptions, NodeStore, SchemaIRI } from '@xnetjs/data';
1
+ import { D as DataBridge, R as RemoteNodeQueryClient, N as NodeQueryRouterThresholds, Q as QueryOptions, a as QuerySubscription, b as QueryDescriptor, B as BridgeTransactionResult, A as AcquiredDoc, c as DataBridgeConfig, S as SyncStatus, d as QueryMetadata, e as QuerySource, f as RemoteNodeQueryMode, g as RemoteNodeQuerySource, h as QueryRoutingMetadata, i as RemoteNodeQueryErrorResponse, j as RemoteNodeQuerySuccessResponse, k as QueryVerificationMetadata } from './types-BRvuTwEn.js';
2
+ export { K as CreateResult, l as QueryCompletenessMetadata, m as QueryExecutionMode, n as QueryMaterializedMetadata, o as QueryMaterializedViewOptions, q as QueryPageCountMode, p as QueryPageInfo, r as QueryPageOptions, s as QuerySearchField, t as QuerySearchFilter, u as QuerySourcePreference, v as QuerySpatialFilter, w as QuerySpatialPoint, x as QuerySpatialPointFields, y as QuerySpatialRadius, z as QuerySpatialRect, C as QuerySpatialRectFields, E as QuerySpatialWindow, H as QueryStalenessMetadata, ac as QueryStreamEvent, F as QueryStreamEventType, G as QueryStreamMetadata, ad as QueryStreamProgress, ae as QueryStreamProgressPhase, af as QueryStreamResetReason, ag as QueryStreamState, ah as QueryStreamStatus, L as REMOTE_NODE_QUERY_PROTOCOL, M as REMOTE_NODE_QUERY_PROTOCOL_VERSION, W as RemoteNodeQueryAuth, X as RemoteNodeQueryClientState, Y as RemoteNodeQueryInvalidation, Z as RemoteNodeQueryInvalidationController, _ as RemoteNodeQueryInvalidationObserver, $ as RemoteNodeQueryInvalidationReason, a0 as RemoteNodeQueryInvalidationSubscription, a1 as RemoteNodeQueryRequest, a2 as RemoteNodeQueryResponse, a3 as RemoteNodeQueryStreamController, a4 as RemoteNodeQueryStreamObserver, a5 as RemoteNodeQueryStreamSubscription, a6 as RemoteQueryCompleteness, a7 as RemoteQueryStaleness, a8 as RemoteQueryVerification, I as SortDirection, J as SystemOrderField, U as UpdateResult, a9 as createQueryStreamState, O as createRemoteNodeQueryRequest, P as isRemoteNodeQueryError, T as isRemoteNodeQuerySource, V as isRemoteNodeQuerySuccess, aa as reduceQueryStreamEvent, ab as reduceQueryStreamEvents } from './types-BRvuTwEn.js';
3
+ import { B as BoundedQueryWorkingSet, W as WorkerQuerySnapshot } from './query-descriptor-D0k2gUQ0.js';
4
+ export { r as BOUNDED_QUERY_OVERFETCH, u as BoundedQueryResultDelta, D as DataWorkerAPI, c as DocUpdateMessage, Q as QueryDelta, t as QueryResultDelta, S as SerializedQueryOptions, b as WorkerAcquiredDoc, a as WorkerConfig, l as applyNodeChangeToBoundedQueryResult, k as applyNodeChangeToQueryResult, i as applyQueryDescriptor, o as createBoundedWorkingSet, p as createBoundedWorkingSetDescriptor, d as createQueryDescriptor, f as decodeQueryCursor, e as encodeQueryCursor, g as filterQueryNodes, m as matchesQueryDescriptor, j as queryDescriptorNeedsBoundedReload, n as queryDescriptorSupportsBoundedDelta, q as queryDescriptorToOptions, s as serializeQueryDescriptor, h as sortQueryNodes } from './query-descriptor-D0k2gUQ0.js';
5
+ import { NodeStore, PropertyBuilder, DefinedSchema, InferCreateProps, NodeState, NodeBatchWriteInput, NodeBatchWriteResult, TransactionOperation, NodeChangeEvent, ListNodesOptions, SchemaIRI, NodeQueryResult } from '@xnetjs/data';
3
6
  import { Awareness } from 'y-protocols/awareness';
4
7
  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
- }
8
+ import { SQLiteAdapter, SQLiteConfig, SQLRow, SQLValue, RunResult, SQLiteNodeBatchApplyInput, SQLiteNodeBatchApplyResult, PreparedStatement } from '@xnetjs/sqlite';
9
+ export { NativeBridge, NativeBridgeConfig, NativeStorageAdapter, createNativeBridge, isExpo, isReactNative } from './native-bridge.js';
318
10
 
319
11
  /**
320
12
  * MainThreadBridge - Direct NodeStore access implementation
@@ -337,6 +29,10 @@ interface SyncManagerLike {
337
29
  release(nodeId: string): void;
338
30
  getAwareness(nodeId: string): Awareness | null;
339
31
  }
32
+ interface MainThreadBridgeOptions {
33
+ remoteNodeQueryClient?: RemoteNodeQueryClient;
34
+ remoteNodeQueryRouting?: Partial<NodeQueryRouterThresholds>;
35
+ }
340
36
  /**
341
37
  * DataBridge implementation that accesses NodeStore directly on the main thread.
342
38
  *
@@ -349,26 +45,92 @@ declare class MainThreadBridge implements DataBridge {
349
45
  private cache;
350
46
  private statusListeners;
351
47
  private storeUnsubscribe;
48
+ private storeBatchUnsubscribe;
352
49
  private _syncManager;
353
- constructor(store: NodeStore);
50
+ private remoteNodeQueryClient;
51
+ private remoteNodeQueryRouting;
52
+ private remoteLoads;
53
+ private remoteStreams;
54
+ private remoteInvalidations;
55
+ private pendingStoreChanges;
56
+ private storeChangeFlushQueued;
57
+ constructor(store: NodeStore, options?: MainThreadBridgeOptions);
354
58
  /**
355
59
  * Set the SyncManager for Y.Doc acquisition.
356
60
  * This is called by XNetProvider after the SyncManager is created.
357
61
  */
358
62
  setSyncManager(syncManager: SyncManagerLike | null): void;
359
63
  query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
64
+ private subscribeToQuery;
65
+ reloadQuery(descriptor: QueryDescriptor): Promise<void>;
66
+ /**
67
+ * Load query data according to the descriptor's local/remote execution mode.
68
+ */
69
+ private loadInitialQuery;
70
+ private getQueryRouteRowCount;
71
+ /**
72
+ * Load query data from the local store and update cache.
73
+ */
74
+ private loadLocalQuery;
75
+ /**
76
+ * Load query data from the configured remote query client.
77
+ */
78
+ private loadRemoteQuery;
79
+ private shouldUseRemoteStream;
80
+ private startRemoteQueryStream;
81
+ private stopRemoteQueryStream;
82
+ private normalizeRemoteStreamSubscription;
83
+ private normalizeRemoteInvalidationSubscription;
84
+ private normalizeRemoteSubscription;
85
+ private startRemoteInvalidationSubscription;
86
+ private stopRemoteInvalidationSubscription;
87
+ private handleRemoteQueryInvalidation;
88
+ private getRemoteInvalidationRouteRowCount;
89
+ private getRemoteInvalidationEntries;
90
+ private applyRemoteStreamEvent;
91
+ private normalizeRemoteStreamEvent;
92
+ private withStreamMetadata;
93
+ private createStreamErrorMetadata;
94
+ private executeRemoteQuery;
95
+ private handleRemoteQueryError;
96
+ private debugQueryPlan;
360
97
  /**
361
- * Load query data from the store and update cache.
98
+ * Handle store changes and invalidate affected caches.
362
99
  */
363
- private loadQuery;
100
+ private enqueueStoreChange;
101
+ private flushStoreChanges;
102
+ private isBulkStoreChangeSet;
103
+ private handleStoreChangeSet;
104
+ private handleStoreBatchChange;
105
+ private reloadEntriesForSchemas;
106
+ private applyStoreBatchChangeDeltas;
364
107
  /**
365
- * Handle store changes and invalidate affected caches.
108
+ * Apply a list of node changes to a single cache entry, falling back to a
109
+ * storage re-query only when a delta is ambiguous. Optimistic
110
+ * (pre-persistence) applies skip ambiguous entries instead of reloading —
111
+ * storage still holds the OLD state, and the durable change event that
112
+ * follows will reconcile them.
366
113
  */
114
+ private applyChangesToEntry;
115
+ /**
116
+ * Find the freshest cached snapshot of a node across all cache entries.
117
+ */
118
+ private findCachedNode;
119
+ /**
120
+ * Synchronously apply an optimistic node mutation to every affected cache
121
+ * entry before persistence, so subscribers see the edit immediately.
122
+ * Returns a revert function that restores authoritative state by
123
+ * re-querying storage (used when persistence fails).
124
+ */
125
+ private applyOptimisticNodeChange;
126
+ private applyChangeToEntryState;
367
127
  private handleStoreChange;
368
128
  create<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string): Promise<NodeState>;
369
129
  update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
370
130
  delete(nodeId: string): Promise<void>;
371
131
  restore(nodeId: string): Promise<NodeState>;
132
+ bulkWrite(input: NodeBatchWriteInput): Promise<NodeBatchWriteResult>;
133
+ transaction(operations: TransactionOperation[]): Promise<BridgeTransactionResult>;
372
134
  /**
373
135
  * Acquire a Y.Doc for editing.
374
136
  * Delegates to SyncManager if available, otherwise throws.
@@ -380,6 +142,7 @@ declare class MainThreadBridge implements DataBridge {
380
142
  * Release a Y.Doc when no longer editing.
381
143
  */
382
144
  releaseDoc(nodeId: string): void;
145
+ initialize(config: DataBridgeConfig): Promise<void>;
383
146
  destroy(): void;
384
147
  get status(): SyncStatus;
385
148
  on(event: 'status', handler: (status: SyncStatus) => void): () => void;
@@ -391,7 +154,7 @@ declare class MainThreadBridge implements DataBridge {
391
154
  /**
392
155
  * Create a MainThreadBridge from a NodeStore.
393
156
  */
394
- declare function createMainThreadBridge(store: NodeStore): MainThreadBridge;
157
+ declare function createMainThreadBridge(store: NodeStore, options?: MainThreadBridgeOptions): MainThreadBridge;
395
158
 
396
159
  /**
397
160
  * WorkerBridge - Main thread bridge to DataWorker
@@ -417,17 +180,24 @@ declare class WorkerBridge implements DataBridge {
417
180
  private remote;
418
181
  private cache;
419
182
  private subscriptions;
420
- private queryCounter;
183
+ private activeRemoteSubscriptions;
421
184
  private statusListeners;
185
+ private changeListeners;
186
+ private changeFeedStarted;
422
187
  private _status;
423
188
  private initialized;
424
189
  private mirrorDocs;
425
190
  constructor(workerUrl: string | URL);
426
191
  /**
427
192
  * Initialize the bridge and underlying worker.
193
+ *
194
+ * When `config.storagePort` is provided (a MessagePort connected to the
195
+ * SQLite worker), it is transferred to the data worker so storage calls
196
+ * run worker-to-worker without a main-thread hop.
428
197
  */
429
198
  initialize(config: DataBridgeConfig): Promise<void>;
430
199
  query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
200
+ reloadQuery(descriptor: QueryDescriptor): Promise<void>;
431
201
  private startWorkerSubscription;
432
202
  private applyDelta;
433
203
  private serializeOptions;
@@ -435,6 +205,14 @@ declare class WorkerBridge implements DataBridge {
435
205
  update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
436
206
  delete(nodeId: string): Promise<void>;
437
207
  restore(nodeId: string): Promise<NodeState>;
208
+ bulkWrite(input: NodeBatchWriteInput): Promise<NodeBatchWriteResult>;
209
+ transaction(operations: TransactionOperation[]): Promise<BridgeTransactionResult>;
210
+ /**
211
+ * Subscribe to the worker's store change feed (devtools and other
212
+ * instrumentation). A single proxied forwarder is registered with the
213
+ * worker on first use; events fan out to local listeners from there.
214
+ */
215
+ subscribeToChanges(listener: (event: NodeChangeEvent) => void): () => void;
438
216
  /**
439
217
  * Acquire a Y.Doc for editing.
440
218
  *
@@ -464,119 +242,52 @@ declare class WorkerBridge implements DataBridge {
464
242
  declare function createWorkerBridge(workerUrl: string | URL): WorkerBridge;
465
243
 
466
244
  /**
467
- * NativeBridge - DataBridge implementation for React Native/Expo
245
+ * PortSQLiteAdapter - SQLiteAdapter over a forwarded MessagePort
468
246
  *
469
- * Phase 5 implementation that provides the DataBridge interface for React Native.
247
+ * Runs inside the data worker and speaks the same Comlink
248
+ * `SQLiteWorkerHandler` protocol as `WebSQLiteProxy`, but over a
249
+ * MessagePort transferred from the main thread instead of a Worker it
250
+ * owns. This is option A of exploration 0164: the data worker hosts
251
+ * NodeStore + invalidation while storage stays in the existing SQLite
252
+ * worker — storage calls hop data worker → SQLite worker without
253
+ * touching the main thread.
470
254
  *
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
255
+ * The database lifecycle is owned by the main thread (which opened it
256
+ * and applied the schema before forwarding the port), so `open()` only
257
+ * verifies connectivity and `close()` only closes this port.
480
258
  */
481
259
 
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 */
260
+ declare class PortSQLiteAdapter implements SQLiteAdapter {
261
+ private port;
262
+ private proxy;
263
+ private inTransaction;
264
+ constructor(port: MessagePort);
265
+ open(_config?: SQLiteConfig): Promise<void>;
490
266
  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;
267
+ isOpen(): boolean;
268
+ private requireProxy;
269
+ query<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T[]>;
270
+ queryOne<T extends SQLRow = SQLRow>(sql: string, params?: SQLValue[]): Promise<T | null>;
271
+ run(sql: string, params?: SQLValue[]): Promise<RunResult>;
272
+ exec(sql: string): Promise<void>;
273
+ transaction<T>(_fn: () => Promise<T>): Promise<T>;
274
+ transactionBatch(operations: Array<{
275
+ sql: string;
276
+ params?: SQLValue[];
277
+ }>): Promise<void>;
278
+ applyNodeBatch(input: SQLiteNodeBatchApplyInput): Promise<SQLiteNodeBatchApplyResult>;
279
+ beginTransaction(): Promise<void>;
280
+ commit(): Promise<void>;
281
+ rollback(): Promise<void>;
282
+ prepare(_sql: string): Promise<PreparedStatement>;
283
+ getSchemaVersion(): Promise<number>;
284
+ setSchemaVersion(version: number): Promise<void>;
285
+ applySchema(version: number, sql: string): Promise<boolean>;
286
+ getDatabaseSize(): Promise<number>;
287
+ vacuum(): Promise<void>;
288
+ checkpoint(): Promise<number>;
289
+ getStorageMode(): Promise<'opfs' | 'memory'>;
509
290
  }
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
291
 
581
292
  /**
582
293
  * Factory functions for creating DataBridge instances
@@ -627,6 +338,12 @@ declare function isWorkerSupported(): boolean;
627
338
  * Check if we're in a Node.js environment (e.g., SSR, tests).
628
339
  */
629
340
  declare function isNodeEnvironment(): boolean;
341
+ /**
342
+ * URL of the package's built data worker, resolved relative to this
343
+ * module (mirrors how @xnetjs/sqlite locates its web worker). Pass to
344
+ * `WorkerBridge`/`createDataBridge` or `runtime.worker.url`.
345
+ */
346
+ declare function getDefaultDataWorkerUrl(): URL;
630
347
  /**
631
348
  * Create a DataBridge with automatic platform detection.
632
349
  *
@@ -652,7 +369,7 @@ declare function createDataBridge(options: CreateBridgeOptions): Promise<DataBri
652
369
  * Create a MainThreadBridge directly.
653
370
  * Use this when you don't need off-main-thread support.
654
371
  */
655
- declare function createMainThreadBridgeSync(nodeStore: NodeStore): MainThreadBridge;
372
+ declare function createMainThreadBridgeSync(nodeStore: NodeStore, options?: ConstructorParameters<typeof MainThreadBridge>[1]): MainThreadBridge;
656
373
  /**
657
374
  * Create a WorkerBridge directly.
658
375
  * The bridge must be initialized before use.
@@ -707,7 +424,7 @@ declare class QueryCache {
707
424
  * Compute a stable query ID from schema and options.
708
425
  * Same query params should produce the same ID for deduplication.
709
426
  */
710
- computeQueryId<P extends Record<string, PropertyBuilder>>(schemaId: string, options?: QueryOptions<P>): string;
427
+ computeQueryId<P extends Record<string, PropertyBuilder>>(schemaId: string | QueryDescriptor, options?: QueryOptions<P>): string;
711
428
  /**
712
429
  * Get cached data for a query (synchronous for useSyncExternalStore).
713
430
  * Updates lastAccessed for LRU tracking.
@@ -721,11 +438,11 @@ declare class QueryCache {
721
438
  * Set cached data for a query and notify subscribers.
722
439
  * Triggers LRU eviction if cache exceeds maxSize.
723
440
  */
724
- set(queryId: string, data: NodeState[], schemaId: SchemaIRI, options: QueryOptions): void;
441
+ set(queryId: string, data: NodeState[] | null, schemaIdOrDescriptor?: SchemaIRI | QueryDescriptor, options?: QueryOptions, metadata?: QueryMetadata | null, workingSet?: BoundedQueryWorkingSet | null): void;
725
442
  /**
726
443
  * Initialize a cache entry (called when starting a subscription).
727
444
  */
728
- initEntry(queryId: string, schemaId: SchemaIRI, options: QueryOptions): void;
445
+ initEntry(queryId: string, schemaIdOrDescriptor: SchemaIRI | QueryDescriptor, options?: QueryOptions): void;
729
446
  /**
730
447
  * Subscribe to cache updates for a query.
731
448
  * Uses strong references - callback will not be garbage collected until unsubscribed.
@@ -768,14 +485,48 @@ declare class QueryCache {
768
485
  * Get all query IDs that match a schema.
769
486
  */
770
487
  getQueriesForSchema(schemaId: SchemaIRI): string[];
488
+ /**
489
+ * Get all cached entries for a schema.
490
+ */
491
+ getEntriesForSchema(schemaId: SchemaIRI): Array<{
492
+ queryId: string;
493
+ descriptor: QueryDescriptor;
494
+ data: NodeState[] | null;
495
+ workingSet: BoundedQueryWorkingSet | null;
496
+ }>;
497
+ /**
498
+ * Get all cached entries.
499
+ */
500
+ getEntries(): Array<{
501
+ queryId: string;
502
+ descriptor: QueryDescriptor;
503
+ data: NodeState[] | null;
504
+ workingSet: BoundedQueryWorkingSet | null;
505
+ }>;
506
+ /**
507
+ * Get the bounded-query working set for a cached query, if any.
508
+ */
509
+ getWorkingSet(queryId: string): BoundedQueryWorkingSet | null;
771
510
  /**
772
511
  * Get the schema IRI for a cached query.
773
512
  */
774
513
  getSchemaId(queryId: string): SchemaIRI | undefined;
514
+ /**
515
+ * Get the descriptor for a cached query.
516
+ */
517
+ getDescriptor(queryId: string): QueryDescriptor | undefined;
775
518
  /**
776
519
  * Get the options for a cached query.
777
520
  */
778
521
  getOptions(queryId: string): QueryOptions | undefined;
522
+ /**
523
+ * Get the latest metadata for a cached query.
524
+ */
525
+ getMetadata(queryId: string): QueryMetadata | null;
526
+ /**
527
+ * Set metadata without replacing query data.
528
+ */
529
+ setMetadata(queryId: string, metadata: QueryMetadata | null): void;
779
530
  /**
780
531
  * Clear the entire cache and stop cleanup interval.
781
532
  */
@@ -819,6 +570,71 @@ declare class QueryCache {
819
570
  paginateNodes(nodes: NodeState[], options?: QueryOptions): NodeState[];
820
571
  }
821
572
 
573
+ /**
574
+ * Shared query metadata helpers for bridge implementations.
575
+ */
576
+
577
+ declare function createQueryMetadata(input: {
578
+ descriptor: QueryDescriptor;
579
+ result: NodeQueryResult;
580
+ source: QuerySource;
581
+ }): QueryMetadata;
582
+ declare function createQueryErrorMetadata(input: {
583
+ descriptor: QueryDescriptor;
584
+ source: QuerySource;
585
+ error: Error;
586
+ }): QueryMetadata;
587
+ declare function createQuerySnapshotMetadata(input: {
588
+ descriptor: QueryDescriptor;
589
+ nodes: NodeState[];
590
+ source: QuerySource;
591
+ }): QueryMetadata;
592
+
593
+ /**
594
+ * Helpers for progressive remote Node query execution.
595
+ */
596
+
597
+ declare const DEFAULT_NODE_QUERY_ROUTER_THRESHOLDS: NodeQueryRouterThresholds;
598
+ type RemoteNodeQueryRouteDecision = {
599
+ shouldRunRemote: false;
600
+ source: 'local';
601
+ reason: string;
602
+ localRowCount?: number;
603
+ thresholds: NodeQueryRouterThresholds;
604
+ } | {
605
+ shouldRunRemote: true;
606
+ mode: RemoteNodeQueryMode;
607
+ source: RemoteNodeQuerySource;
608
+ reason: string;
609
+ localRowCount?: number;
610
+ thresholds: NodeQueryRouterThresholds;
611
+ };
612
+ declare function getRemoteQueryMode(descriptor: QueryDescriptor): RemoteNodeQueryMode | null;
613
+ declare function getRemoteQuerySource(descriptor: QueryDescriptor): RemoteNodeQuerySource;
614
+ declare function shouldRunRemoteQuery(descriptor: QueryDescriptor): boolean;
615
+ declare function shouldUseRemoteOnlyQuery(descriptor: QueryDescriptor): boolean;
616
+ declare function normalizeNodeQueryRouterThresholds(thresholds?: Partial<NodeQueryRouterThresholds>): NodeQueryRouterThresholds;
617
+ declare function routeRemoteNodeQuery(input: {
618
+ descriptor: QueryDescriptor;
619
+ localRowCount?: number;
620
+ hasRemoteClient: boolean;
621
+ thresholds?: Partial<NodeQueryRouterThresholds>;
622
+ }): RemoteNodeQueryRouteDecision;
623
+ declare function createQueryRoutingMetadata(route: RemoteNodeQueryRouteDecision): QueryRoutingMetadata;
624
+ declare function mergeRemoteNodeSnapshots(localNodes: readonly NodeState[], remoteNodes: readonly NodeState[]): NodeState[];
625
+ declare function isRemoteVerificationFailed(verification: QueryVerificationMetadata | undefined): boolean;
626
+ declare function filterRemoteNodesByVerification(nodes: readonly NodeState[], verification: QueryVerificationMetadata | undefined): NodeState[];
627
+ declare function createRemoteSuccessMetadata(input: {
628
+ response: RemoteNodeQuerySuccessResponse;
629
+ source: QuerySource;
630
+ loadedCount: number;
631
+ }): QueryMetadata;
632
+ declare function createRemoteFallbackMetadata(input: {
633
+ localMetadata: QueryMetadata;
634
+ error: RemoteNodeQueryErrorResponse | Error;
635
+ }): QueryMetadata;
636
+ declare function withRemoteErrorVerificationMetadata(metadata: QueryMetadata, error: RemoteNodeQueryErrorResponse | Error): QueryMetadata;
637
+
822
638
  /**
823
639
  * Debounce utilities for DataBridge
824
640
  *
@@ -929,22 +745,6 @@ interface DeltaBatcher {
929
745
  */
930
746
  declare function createDeltaBatcher(options: DeltaBatcherOptions): DeltaBatcher;
931
747
 
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
748
  /**
949
749
  * Binary encoder for NodeState arrays.
950
750
  *
@@ -1007,5 +807,15 @@ declare function decodeNodeStates(data: Uint8Array): NodeState[];
1007
807
  * For small payloads, structured clone may be faster.
1008
808
  */
1009
809
  declare function shouldUseBinaryEncoding(states: NodeState[]): boolean;
810
+ /**
811
+ * Encode a query snapshot for the worker → main-thread wire: binary above
812
+ * the size threshold (so the caller can transfer the buffer), structured
813
+ * clone below it.
814
+ */
815
+ declare function encodeWorkerQuerySnapshot(nodes: NodeState[]): WorkerQuerySnapshot;
816
+ /**
817
+ * Decode a wire snapshot back to NodeState[] on the main thread.
818
+ */
819
+ declare function decodeWorkerQuerySnapshot(snapshot: WorkerQuerySnapshot): NodeState[];
1010
820
 
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 };
821
+ export { AcquiredDoc, BoundedQueryWorkingSet, BridgeTransactionResult, type CreateBridgeOptions, DEFAULT_NODE_QUERY_ROUTER_THRESHOLDS, DataBridge, DataBridgeConfig, type DebounceOptions, type DebouncedFunction, type DeltaBatcher, type DeltaBatcherOptions, type QueryDelta as DeltaQueryDelta, MainThreadBridge, type MainThreadBridgeOptions, NodeQueryRouterThresholds, NodeStateDecoder, NodeStateEncoder, PortSQLiteAdapter, QueryCache, QueryDescriptor, QueryMetadata, QueryOptions, QueryRoutingMetadata, QuerySource, QuerySubscription, QueryVerificationMetadata, RemoteNodeQueryClient, RemoteNodeQueryErrorResponse, RemoteNodeQueryMode, type RemoteNodeQueryRouteDecision, RemoteNodeQuerySource, RemoteNodeQuerySuccessResponse, type SyncManagerLike, SyncStatus, type UpdateBatcher, type UpdateBatcherOptions, WorkerBridge, WorkerQuerySnapshot, createDataBridge, createDeltaBatcher, createMainThreadBridge, createMainThreadBridgeSync, createQueryErrorMetadata, createQueryMetadata, createQueryRoutingMetadata, createQuerySnapshotMetadata, createRemoteFallbackMetadata, createRemoteSuccessMetadata, createUpdateBatcher, createWorkerBridge, createWorkerBridgeSync, debounce, decodeNodeStates, decodeWorkerQuerySnapshot, encodeNodeStates, encodeWorkerQuerySnapshot, filterRemoteNodesByVerification, getDefaultDataWorkerUrl, getRemoteQueryMode, getRemoteQuerySource, isNodeEnvironment, isRemoteVerificationFailed, isWorkerSupported, mergeRemoteNodeSnapshots, normalizeNodeQueryRouterThresholds, routeRemoteNodeQuery, shouldRunRemoteQuery, shouldUseBinaryEncoding, shouldUseRemoteOnlyQuery, withRemoteErrorVerificationMetadata };