@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.
@@ -0,0 +1,126 @@
1
+ import { D as DataBridge, Q as QueryOptions, a as QuerySubscription, b as QueryDescriptor, B as BridgeTransactionResult, A as AcquiredDoc, c as DataBridgeConfig, S as SyncStatus } from './types-BRvuTwEn.js';
2
+ import { NodeStore, PropertyBuilder, DefinedSchema, InferCreateProps, NodeState, NodeBatchWriteInput, NodeBatchWriteResult, TransactionOperation, NodeChangeEvent, ListNodesOptions } from '@xnetjs/data';
3
+ import 'y-protocols/awareness';
4
+ import 'yjs';
5
+
6
+ /**
7
+ * NativeBridge - DataBridge implementation for React Native/Expo
8
+ *
9
+ * Phase 5 implementation that provides the DataBridge interface for React Native.
10
+ *
11
+ * Architecture:
12
+ * - Uses expo-sqlite for storage (runs on native thread)
13
+ * - Uses NodeStore on JS thread (can be moved to JSI in future)
14
+ * - Provides same API as MainThreadBridge/WorkerBridge
15
+ *
16
+ * Future enhancements:
17
+ * - Turbo Module for heavy operations (crypto, queries)
18
+ * - JSI bindings for direct native access
19
+ * - Native WebSocket for sync
20
+ */
21
+
22
+ /**
23
+ * Interface for native storage adapters (expo-sqlite, etc.)
24
+ * This allows the NativeBridge to work with different storage backends.
25
+ */
26
+ interface NativeStorageAdapter {
27
+ /** Open the database */
28
+ open(): Promise<void>;
29
+ /** Close the database */
30
+ close(): Promise<void>;
31
+ /** Get a document by ID */
32
+ getDocument(id: string): Promise<{
33
+ content: Uint8Array;
34
+ } | null>;
35
+ /** Set a document */
36
+ setDocument(id: string, content: Uint8Array): Promise<void>;
37
+ /** Delete a document */
38
+ deleteDocument(id: string): Promise<void>;
39
+ /** List all document IDs */
40
+ listDocuments(): Promise<string[]>;
41
+ }
42
+ interface NativeBridgeConfig {
43
+ /** The NodeStore instance to use */
44
+ store: NodeStore;
45
+ /** Optional native storage adapter for Y.Doc persistence */
46
+ storageAdapter?: NativeStorageAdapter;
47
+ /** Signaling server URL for sync (optional, default: no sync) */
48
+ signalingUrl?: string;
49
+ }
50
+ /**
51
+ * DataBridge implementation for React Native/Expo.
52
+ *
53
+ * This implementation runs on the JS thread but is designed to work with
54
+ * React Native's architecture. Storage operations use expo-sqlite which
55
+ * runs on a native thread.
56
+ *
57
+ * For now, this is similar to MainThreadBridge but structured for RN.
58
+ * Future versions will add Turbo Module integration for off-thread operations.
59
+ */
60
+ declare class NativeBridge implements DataBridge {
61
+ private store;
62
+ private cache;
63
+ private statusListeners;
64
+ private storeUnsubscribe;
65
+ private storeBatchUnsubscribe;
66
+ private storageAdapter?;
67
+ private _status;
68
+ private destroyed;
69
+ constructor(config: NativeBridgeConfig);
70
+ query<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, options?: QueryOptions<P>): QuerySubscription<P>;
71
+ reloadQuery(descriptor: QueryDescriptor): Promise<void>;
72
+ /**
73
+ * Load query data from the store and update cache.
74
+ */
75
+ private loadQuery;
76
+ /**
77
+ * Handle store changes and invalidate affected caches.
78
+ */
79
+ private handleStoreChange;
80
+ private handleStoreBatchChange;
81
+ create<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string): Promise<NodeState>;
82
+ update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
83
+ delete(nodeId: string): Promise<void>;
84
+ restore(nodeId: string): Promise<NodeState>;
85
+ bulkWrite(input: NodeBatchWriteInput): Promise<NodeBatchWriteResult>;
86
+ transaction(operations: TransactionOperation[]): Promise<BridgeTransactionResult>;
87
+ /**
88
+ * Acquire a Y.Doc for editing.
89
+ *
90
+ * Note: Y.Doc management in React Native is limited compared to web.
91
+ * For now, this throws an error. Future versions will support Y.Doc
92
+ * via native WebSocket sync or JSI bindings.
93
+ */
94
+ acquireDoc(_nodeId: string): Promise<AcquiredDoc>;
95
+ /**
96
+ * Release a Y.Doc when no longer editing.
97
+ */
98
+ releaseDoc(_nodeId: string): void;
99
+ /**
100
+ * Initialize the bridge.
101
+ * Opens storage adapter if provided.
102
+ */
103
+ initialize(_config: DataBridgeConfig): Promise<void>;
104
+ destroy(): void;
105
+ get status(): SyncStatus;
106
+ on(event: 'status', handler: (status: SyncStatus) => void): () => void;
107
+ private setStatus;
108
+ get nodeStore(): NodeStore;
109
+ subscribeToChanges(listener: (event: NodeChangeEvent) => void): () => void;
110
+ get(nodeId: string): Promise<NodeState | null>;
111
+ list(options?: ListNodesOptions): Promise<NodeState[]>;
112
+ }
113
+ /**
114
+ * Create a NativeBridge from a NodeStore.
115
+ */
116
+ declare function createNativeBridge(config: NativeBridgeConfig): NativeBridge;
117
+ /**
118
+ * Check if we're running in a React Native environment.
119
+ */
120
+ declare function isReactNative(): boolean;
121
+ /**
122
+ * Check if we're running in Expo.
123
+ */
124
+ declare function isExpo(): boolean;
125
+
126
+ export { NativeBridge, type NativeBridgeConfig, type NativeStorageAdapter, createNativeBridge, isExpo, isReactNative };
@@ -0,0 +1,13 @@
1
+ import {
2
+ NativeBridge,
3
+ createNativeBridge,
4
+ isExpo,
5
+ isReactNative
6
+ } from "./chunk-25WNZV7W.js";
7
+ import "./chunk-5GTIP33X.js";
8
+ export {
9
+ NativeBridge,
10
+ createNativeBridge,
11
+ isExpo,
12
+ isReactNative
13
+ };
@@ -0,0 +1,298 @@
1
+ import { r as QueryPageOptions, b as QueryDescriptor, B as BridgeTransactionResult, S as SyncStatus, Q as QueryOptions } from './types-BRvuTwEn.js';
2
+ import { NodeState, NodeBatchWriteInput, NodeBatchWriteResult, TransactionOperation, NodeChangeEvent, SchemaIRI, PropertyBuilder, NodeQueryCursor } from '@xnetjs/data';
3
+
4
+ /**
5
+ * Types for worker-main thread communication
6
+ *
7
+ * These types define the contract between the DataWorker running in a
8
+ * Web Worker and the WorkerBridge on the main thread.
9
+ */
10
+
11
+ /**
12
+ * Result from acquiring a Y.Doc in the worker.
13
+ * The state is sent as a Uint8Array for efficient transfer.
14
+ */
15
+ interface WorkerAcquiredDoc {
16
+ /** The node ID this doc belongs to */
17
+ nodeId: string;
18
+ /** Encoded Y.Doc state (Y.encodeStateAsUpdate) */
19
+ state: Uint8Array;
20
+ /** Client ID for this connection */
21
+ clientId: number;
22
+ }
23
+ /**
24
+ * Document update message from worker to main thread
25
+ */
26
+ interface DocUpdateMessage {
27
+ type: 'doc-update';
28
+ nodeId: string;
29
+ /** Yjs update (Uint8Array) */
30
+ update: Uint8Array;
31
+ /** Origin of the update (e.g., 'remote', 'local') */
32
+ origin: string;
33
+ }
34
+ /**
35
+ * Configuration passed to the worker on initialization
36
+ */
37
+ interface WorkerConfig {
38
+ /** Database name for IndexedDB */
39
+ dbName: string;
40
+ /** Author's DID for signing changes */
41
+ authorDID: string;
42
+ /** Ed25519 signing key (serialized as array for transfer) */
43
+ signingKey: number[];
44
+ /**
45
+ * Optional transferred MessagePort connected to the SQLite worker
46
+ * (created via `WebSQLiteProxy.createMessagePort()`). When present the
47
+ * data worker persists through a `PortSQLiteAdapter` instead of
48
+ * in-memory storage.
49
+ */
50
+ storagePort?: MessagePort;
51
+ }
52
+ /**
53
+ * Serialized query options (sent to worker)
54
+ */
55
+ interface SerializedQueryOptions {
56
+ nodeId?: string;
57
+ where?: Record<string, unknown>;
58
+ includeDeleted?: boolean;
59
+ orderBy?: Record<string, 'asc' | 'desc'>;
60
+ limit?: number;
61
+ offset?: number;
62
+ page?: QueryPageOptions;
63
+ spatial?: QueryDescriptor['spatial'];
64
+ search?: string | QueryDescriptor['search'];
65
+ materializedView?: string | QueryDescriptor['materializedView'];
66
+ mode?: QueryDescriptor['mode'];
67
+ source?: QueryDescriptor['source'];
68
+ }
69
+ /**
70
+ * Wire format for query snapshots (initial loads and reloads).
71
+ *
72
+ * Large result sets are encoded with `binary-state.ts` so the backing
73
+ * ArrayBuffer can be transferred (zero-copy) instead of structured-cloned;
74
+ * small ones stay as plain structured-clone payloads, which is faster for
75
+ * few nodes.
76
+ */
77
+ type WorkerQuerySnapshot = {
78
+ encoding: 'json';
79
+ nodes: NodeState[];
80
+ } | {
81
+ encoding: 'binary';
82
+ data: Uint8Array;
83
+ };
84
+ /**
85
+ * Delta update types for incremental cache updates
86
+ */
87
+ type QueryDelta = {
88
+ type: 'add';
89
+ node: NodeState;
90
+ index: number;
91
+ } | {
92
+ type: 'remove';
93
+ nodeId: string;
94
+ } | {
95
+ type: 'update';
96
+ nodeId: string;
97
+ node: NodeState;
98
+ } | {
99
+ type: 'reload';
100
+ data: NodeState[];
101
+ };
102
+ /**
103
+ * Internal subscription tracking in the worker
104
+ */
105
+ interface WorkerSubscription {
106
+ schemaId: SchemaIRI;
107
+ descriptor: QueryDescriptor;
108
+ options: SerializedQueryOptions;
109
+ lastResult: NodeState[];
110
+ }
111
+ /**
112
+ * The API exposed by the DataWorker via Comlink.
113
+ * This is what WorkerBridge calls on the main thread.
114
+ */
115
+ interface DataWorkerAPI {
116
+ /**
117
+ * Initialize the worker with configuration.
118
+ * Must be called before any other method.
119
+ */
120
+ initialize(config: WorkerConfig): Promise<void>;
121
+ /**
122
+ * Subscribe to a query. Returns the initial snapshot (binary-encoded
123
+ * and transferred above the size threshold). Delta updates are sent via
124
+ * the callback.
125
+ */
126
+ subscribe(queryId: string, schemaId: string, options: SerializedQueryOptions, onDelta: (delta: QueryDelta) => void): Promise<WorkerQuerySnapshot>;
127
+ /**
128
+ * Unsubscribe from a query.
129
+ */
130
+ unsubscribe(queryId: string): Promise<void>;
131
+ /**
132
+ * Force a targeted reload for an existing subscription.
133
+ */
134
+ reloadQuery(queryId: string): Promise<WorkerQuerySnapshot>;
135
+ /**
136
+ * Create a new node.
137
+ */
138
+ create(schemaId: string, data: Record<string, unknown>, id?: string): Promise<NodeState>;
139
+ /**
140
+ * Update an existing node.
141
+ */
142
+ update(nodeId: string, changes: Record<string, unknown>): Promise<NodeState>;
143
+ /**
144
+ * Delete a node (soft delete).
145
+ */
146
+ delete(nodeId: string): Promise<void>;
147
+ /**
148
+ * Restore a deleted node.
149
+ */
150
+ restore(nodeId: string): Promise<NodeState>;
151
+ /**
152
+ * Execute a storage-owned batch write in the worker.
153
+ */
154
+ bulkWrite(input: NodeBatchWriteInput): Promise<NodeBatchWriteResult>;
155
+ /**
156
+ * Execute an atomic multi-operation transaction in the worker.
157
+ * Returns a structured-clone-safe result (no signed change list).
158
+ */
159
+ transaction(operations: TransactionOperation[]): Promise<BridgeTransactionResult>;
160
+ /**
161
+ * Get a single node by ID.
162
+ */
163
+ get(nodeId: string): Promise<NodeState | null>;
164
+ /**
165
+ * Acquire a Y.Doc for editing.
166
+ * Returns the current state which should be applied to the main-thread mirror doc.
167
+ *
168
+ * @param nodeId - The node ID to acquire
169
+ * @param onUpdate - Callback for receiving updates from the worker (remote changes)
170
+ * @returns The initial doc state and client ID
171
+ */
172
+ acquireDoc(nodeId: string, onUpdate: (update: Uint8Array, origin: string) => void): Promise<WorkerAcquiredDoc>;
173
+ /**
174
+ * Release a Y.Doc when no longer editing.
175
+ * The doc stays in the pool for background sync.
176
+ */
177
+ releaseDoc(nodeId: string): void;
178
+ /**
179
+ * Apply a local update from the main-thread mirror doc to the worker's source-of-truth doc.
180
+ * The worker will broadcast this to the network.
181
+ *
182
+ * @param nodeId - The node ID
183
+ * @param update - The Yjs update (from Y.encodeStateAsUpdate or update event)
184
+ */
185
+ applyLocalUpdate(nodeId: string, update: Uint8Array): void;
186
+ /**
187
+ * Get current sync status.
188
+ */
189
+ getStatus(): SyncStatus;
190
+ /**
191
+ * Subscribe to status changes.
192
+ */
193
+ onStatusChange(handler: (status: SyncStatus) => void): void;
194
+ /**
195
+ * Subscribe to the worker's raw store change feed (devtools and other
196
+ * instrumentation). The bridge registers one forwarder and fans out to
197
+ * local listeners.
198
+ */
199
+ subscribeToChanges(handler: (event: NodeChangeEvent) => void): void;
200
+ /**
201
+ * Clean up and close the worker.
202
+ */
203
+ destroy(): Promise<void>;
204
+ }
205
+
206
+ /**
207
+ * Shared query descriptor helpers for @xnetjs/data-bridge
208
+ */
209
+
210
+ type QueryResultDelta = {
211
+ kind: 'noop';
212
+ } | {
213
+ kind: 'reload';
214
+ } | {
215
+ kind: 'set';
216
+ data: NodeState[];
217
+ };
218
+ /**
219
+ * Extra rows fetched beyond a bounded query's visible window. The buffer
220
+ * lets node changes be applied in memory: removals are absorbed by spare
221
+ * rows instead of forcing a storage re-query, and inserts can be ranked
222
+ * against known rows.
223
+ */
224
+ declare const BOUNDED_QUERY_OVERFETCH = 25;
225
+ interface BoundedQueryWorkingSet {
226
+ /** Descriptor-ordered rows, at least covering the visible window. */
227
+ nodes: NodeState[];
228
+ /**
229
+ * True when the working-set fetch returned fewer rows than requested,
230
+ * meaning the buffer holds every matching node: deltas can never
231
+ * underflow into unknown rows and reloads are never required.
232
+ */
233
+ complete: boolean;
234
+ }
235
+ type BoundedQueryResultDelta = {
236
+ kind: 'noop';
237
+ } | {
238
+ kind: 'reload';
239
+ } | {
240
+ kind: 'set';
241
+ data: NodeState[];
242
+ workingSet: BoundedQueryWorkingSet;
243
+ };
244
+ declare function createQueryDescriptor<P extends Record<string, PropertyBuilder>>(schemaId: SchemaIRI, options?: QueryOptions<P>): QueryDescriptor;
245
+ declare function queryDescriptorToOptions<P extends Record<string, PropertyBuilder> = Record<string, PropertyBuilder>>(descriptor: QueryDescriptor): QueryOptions<P>;
246
+ declare function serializeQueryDescriptor(descriptor: QueryDescriptor): string;
247
+ declare function encodeQueryCursor(descriptor: QueryDescriptor, node: NodeState): string;
248
+ declare function decodeQueryCursor(cursor: string): NodeQueryCursor | null;
249
+ declare function matchesQueryDescriptor(descriptor: QueryDescriptor, node: NodeState | null | undefined): boolean;
250
+ declare function filterQueryNodes(nodes: NodeState[], descriptor: QueryDescriptor): NodeState[];
251
+ declare function sortQueryNodes(nodes: NodeState[], descriptor: QueryDescriptor): NodeState[];
252
+ declare function applyQueryDescriptor(nodes: NodeState[], descriptor: QueryDescriptor): NodeState[];
253
+ declare function queryDescriptorNeedsBoundedReload(descriptor: QueryDescriptor): boolean;
254
+ /**
255
+ * Whether a bounded (limited) descriptor can be maintained incrementally
256
+ * with an overfetch working set instead of re-executing on every change.
257
+ *
258
+ * Requirements:
259
+ * - `limit` without `offset`/`after`: window shifts under offset/cursor
260
+ * pages can't be resolved from a prefix buffer, so they keep reload
261
+ * semantics.
262
+ * - An explicit `orderBy`: the working set is a prefix of the descriptor
263
+ * order, and rows beyond the buffer are known to sort after it. Without
264
+ * `orderBy` the storage row order is not derivable in JS.
265
+ * - No materialized view: those windows are refreshed in storage.
266
+ */
267
+ declare function queryDescriptorSupportsBoundedDelta(descriptor: QueryDescriptor): boolean;
268
+ /**
269
+ * Descriptor used to fetch a bounded query's working set: the visible
270
+ * window plus {@link BOUNDED_QUERY_OVERFETCH} spare rows.
271
+ */
272
+ declare function createBoundedWorkingSetDescriptor(descriptor: QueryDescriptor): QueryDescriptor;
273
+ declare function createBoundedWorkingSet(descriptor: QueryDescriptor, nodes: NodeState[]): BoundedQueryWorkingSet;
274
+ /**
275
+ * Apply a single node change to a bounded query's working set without
276
+ * re-executing the query.
277
+ *
278
+ * Correctness rests on one invariant: the working set was fetched as a
279
+ * prefix of the descriptor order, so every row NOT in the buffer sorts
280
+ * after the row that was last in the buffer at fetch time. Changes that
281
+ * would force a decision about those unknown rows (buffer underflow below
282
+ * `limit`, or ranking a row that sorts past every known row while the
283
+ * window is short) return `reload`.
284
+ */
285
+ declare function applyNodeChangeToBoundedQueryResult(input: {
286
+ descriptor: QueryDescriptor;
287
+ workingSet: BoundedQueryWorkingSet;
288
+ nodeId: string;
289
+ nextNode: NodeState | null;
290
+ }): BoundedQueryResultDelta;
291
+ declare function applyNodeChangeToQueryResult(input: {
292
+ descriptor: QueryDescriptor;
293
+ currentData: NodeState[];
294
+ nodeId: string;
295
+ nextNode: NodeState | null;
296
+ }): QueryResultDelta;
297
+
298
+ export { type BoundedQueryWorkingSet as B, type DataWorkerAPI as D, type QueryDelta as Q, type SerializedQueryOptions as S, type WorkerQuerySnapshot as W, type WorkerConfig as a, type WorkerAcquiredDoc as b, type DocUpdateMessage as c, createQueryDescriptor as d, encodeQueryCursor as e, decodeQueryCursor as f, filterQueryNodes as g, sortQueryNodes as h, applyQueryDescriptor as i, queryDescriptorNeedsBoundedReload as j, applyNodeChangeToQueryResult as k, applyNodeChangeToBoundedQueryResult as l, matchesQueryDescriptor as m, queryDescriptorSupportsBoundedDelta as n, createBoundedWorkingSet as o, createBoundedWorkingSetDescriptor as p, queryDescriptorToOptions as q, BOUNDED_QUERY_OVERFETCH as r, serializeQueryDescriptor as s, type QueryResultDelta as t, type BoundedQueryResultDelta as u, type WorkerSubscription as v };