@korajs/sync 0.4.0 → 0.6.0

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.
@@ -1,4 +1,4 @@
1
- import { c as SyncTransport, T as TransportOptions, a as SyncMessage, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, Q as QueueStorage } from './transport-B-BIguq9.cjs';
1
+ import { c as SyncTransport, T as TransportOptions, a as SyncMessage, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, Q as QueueStorage } from './transport-BrdBj7rH.cjs';
2
2
  import { Operation } from '@korajs/core';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { c as SyncTransport, T as TransportOptions, a as SyncMessage, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, Q as QueueStorage } from './transport-B-BIguq9.js';
1
+ import { c as SyncTransport, T as TransportOptions, a as SyncMessage, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, Q as QueueStorage } from './transport-BrdBj7rH.js';
2
2
  import { Operation } from '@korajs/core';
3
3
 
4
4
  /**
@@ -1,4 +1,5 @@
1
- import { KoraError, Operation, OperationType, HLCTimestamp, AtomicOp } from '@korajs/core';
1
+ import * as _korajs_core from '@korajs/core';
2
+ import { KoraError, Operation, VersionVector, OperationType, HLCTimestamp, AtomicOp } from '@korajs/core';
2
3
 
3
4
  /**
4
5
  * Encryption types for the Kora.js sync layer.
@@ -92,7 +93,7 @@ type SyncState = (typeof SYNC_STATES)[number];
92
93
  /**
93
94
  * Developer-facing sync status. Simplified view of the internal state.
94
95
  */
95
- declare const SYNC_STATUSES: readonly ["connected", "syncing", "synced", "offline", "error"];
96
+ declare const SYNC_STATUSES: readonly ["connected", "syncing", "synced", "offline", "error", "schema-mismatch"];
96
97
  type SyncStatus = (typeof SYNC_STATUSES)[number];
97
98
  /**
98
99
  * Sync status information exposed to developers.
@@ -144,6 +145,20 @@ interface SyncConfig {
144
145
  maxReconnectInterval?: number;
145
146
  /** Schema version of this client. */
146
147
  schemaVersion?: number;
148
+ /** Start sync automatically when the engine is created. Defaults to false. */
149
+ autoConnect?: boolean;
150
+ /**
151
+ * When true, wait for server ACKs on all outbound handshake delta batches before
152
+ * entering streaming. Improves backpressure for large initial syncs.
153
+ */
154
+ strictHandshake?: boolean;
155
+ /** Optional operation transforms for cross-schema-version sync. */
156
+ operationTransforms?: _korajs_core.OperationTransform[];
157
+ /**
158
+ * Richtext snapshot size (bytes) at which the optional Yjs doc channel is used.
159
+ * Defaults to 4096.
160
+ */
161
+ richtextDocChannelThreshold?: number;
147
162
  /**
148
163
  * End-to-end encryption configuration.
149
164
  * When enabled, `data` and `previousData` fields are encrypted before sending
@@ -158,6 +173,25 @@ interface SyncScopeContext {
158
173
  userId?: string;
159
174
  [key: string]: unknown;
160
175
  }
176
+ /**
177
+ * Persists last-acked server version vector and computes unsynced operations from the op log.
178
+ */
179
+ interface DeltaCursor {
180
+ /** ID of the last fully applied operation in the previous delta stream */
181
+ lastOperationId: string;
182
+ /** Zero-based batch index where the cursor was recorded */
183
+ batchIndex: number;
184
+ }
185
+ interface SyncStatePersistence {
186
+ loadLastAckedServerVector(): Promise<VersionVector>;
187
+ saveLastAckedServerVector(vector: VersionVector): Promise<void>;
188
+ mergeServerVectors(a: VersionVector, b: VersionVector): VersionVector;
189
+ countUnsyncedOperations(serverVector: VersionVector): Promise<number>;
190
+ getUnsyncedOperations(serverVector: VersionVector): Promise<Operation[]>;
191
+ /** Resume position for paginated initial sync (optional). */
192
+ loadDeltaCursor?(): Promise<DeltaCursor | null>;
193
+ saveDeltaCursor?(cursor: DeltaCursor | null): Promise<void>;
194
+ }
161
195
  /**
162
196
  * Interface for persisting the outbound operation queue.
163
197
  * Operations must survive page refreshes and be sent when connection is re-established.
@@ -191,6 +225,23 @@ declare class InvalidScopeError extends KoraError {
191
225
  constructor(message: string, context?: Record<string, unknown>);
192
226
  }
193
227
 
228
+ /**
229
+ * A live query filter that narrows which operations sync for a collection.
230
+ */
231
+ interface SyncQuerySubset {
232
+ collection: string;
233
+ where: Record<string, unknown>;
234
+ }
235
+ /**
236
+ * Returns true when an operation matches at least one active query subset
237
+ * for its collection. Collections without query subsets pass through.
238
+ */
239
+ declare function operationMatchesQuerySubsets(op: Operation, subsets: SyncQuerySubset[] | undefined, fullRecord?: Record<string, unknown> | null): boolean;
240
+ /**
241
+ * Deduplicate query subsets by collection + where JSON key.
242
+ */
243
+ declare function dedupeQuerySubsets(subsets: SyncQuerySubset[]): SyncQuerySubset[];
244
+
194
245
  type WireFormat = 'json' | 'protobuf';
195
246
  /**
196
247
  * Wire-format operation. Plain object (no Map) for JSON serialization.
@@ -229,6 +280,10 @@ interface HandshakeMessage {
229
280
  supportedWireFormats?: WireFormat[];
230
281
  /** Per-collection sync scope filters. Limits which records are synced to this client. */
231
282
  syncScope?: Record<string, Record<string, unknown>>;
283
+ /** Base64url-encoded delta cursor for resuming paginated initial sync */
284
+ deltaCursor?: string;
285
+ /** Live query filters that further narrow synced data within auth/schema scope */
286
+ syncQueries?: SyncQuerySubset[];
232
287
  }
233
288
  /**
234
289
  * Server response to a handshake.
@@ -241,6 +296,10 @@ interface HandshakeResponseMessage {
241
296
  schemaVersion: number;
242
297
  accepted: boolean;
243
298
  rejectReason?: string;
299
+ /** Inclusive minimum schema version the server accepts (present when rejected for schema mismatch). */
300
+ supportedSchemaMin?: number;
301
+ /** Inclusive maximum schema version the server accepts (present when rejected for schema mismatch). */
302
+ supportedSchemaMax?: number;
244
303
  selectedWireFormat?: WireFormat;
245
304
  /** The server-accepted per-collection sync scope. Confirms what data will be synced. */
246
305
  acceptedScope?: Record<string, Record<string, unknown>>;
@@ -256,6 +315,10 @@ interface OperationBatchMessage {
256
315
  isFinal: boolean;
257
316
  /** Index of this batch (0-based) for ordering */
258
317
  batchIndex: number;
318
+ /** Base64url-encoded cursor marking the last operation in this batch */
319
+ cursor?: string;
320
+ /** Total batches in this delta exchange (for progress reporting) */
321
+ totalBatches?: number;
259
322
  }
260
323
  /**
261
324
  * Acknowledgment of a received message.
@@ -307,10 +370,23 @@ interface AwarenessUpdateMessage {
307
370
  /** Map of clientId -> state (null means removal) */
308
371
  states: Record<string, AwarenessStateWire | null>;
309
372
  }
373
+ /**
374
+ * Incremental Yjs update for a richtext field. Ephemeral side channel for large documents.
375
+ * JSON transport only; not persisted on the server operation log.
376
+ */
377
+ interface YjsDocUpdateMessage {
378
+ type: 'yjs-doc-update';
379
+ messageId: string;
380
+ collection: string;
381
+ recordId: string;
382
+ field: string;
383
+ /** Base64-encoded Yjs update binary */
384
+ update: string;
385
+ }
310
386
  /**
311
387
  * Union of all sync protocol messages.
312
388
  */
313
- type SyncMessage = HandshakeMessage | HandshakeResponseMessage | OperationBatchMessage | AcknowledgmentMessage | ErrorMessage | AwarenessUpdateMessage;
389
+ type SyncMessage = HandshakeMessage | HandshakeResponseMessage | OperationBatchMessage | AcknowledgmentMessage | ErrorMessage | AwarenessUpdateMessage | YjsDocUpdateMessage;
314
390
  /**
315
391
  * Check if an unknown value is a valid SyncMessage.
316
392
  */
@@ -339,6 +415,10 @@ declare function isErrorMessage(value: unknown): value is ErrorMessage;
339
415
  * Check if a value is an AwarenessUpdateMessage.
340
416
  */
341
417
  declare function isAwarenessUpdateMessage(value: unknown): value is AwarenessUpdateMessage;
418
+ /**
419
+ * Check if a value is a YjsDocUpdateMessage.
420
+ */
421
+ declare function isYjsDocUpdateMessage(value: unknown): value is YjsDocUpdateMessage;
342
422
 
343
423
  /**
344
424
  * Options for transport connection.
@@ -382,4 +462,4 @@ interface SyncTransport {
382
462
  isConnected(): boolean;
383
463
  }
384
464
 
385
- export { type AcknowledgmentMessage as A, isSyncMessage as B, type EncryptedPayload as E, type HandshakeMessage as H, InvalidScopeError as I, type OperationBatchMessage as O, type QueueStorage as Q, type SyncScopeMap as S, type TransportOptions as T, type VersionedKey as V, type WireFormat as W, type SyncMessage as a, type SerializedOperation as b, type SyncTransport as c, type TransportMessageHandler as d, type TransportCloseHandler as e, type TransportErrorHandler as f, type SyncEncryptionConfig as g, type SyncState as h, type SyncStatusInfo as i, type SyncConfig as j, type AwarenessStateWire as k, type AwarenessUpdateMessage as l, type ErrorMessage as m, type HandshakeResponseMessage as n, SYNC_STATES as o, SYNC_STATUSES as p, ScopeViolationError as q, type SyncEncryptionAlgorithm as r, type SyncScopeContext as s, type SyncStatus as t, isAcknowledgmentMessage as u, isAwarenessUpdateMessage as v, isErrorMessage as w, isHandshakeMessage as x, isHandshakeResponseMessage as y, isOperationBatchMessage as z };
465
+ export { type AcknowledgmentMessage as A, isHandshakeMessage as B, isHandshakeResponseMessage as C, type DeltaCursor as D, type EncryptedPayload as E, isOperationBatchMessage as F, isSyncMessage as G, type HandshakeMessage as H, InvalidScopeError as I, isYjsDocUpdateMessage as J, operationMatchesQuerySubsets as K, type OperationBatchMessage as O, type QueueStorage as Q, type SyncScopeMap as S, type TransportOptions as T, type VersionedKey as V, type WireFormat as W, type YjsDocUpdateMessage as Y, type SyncMessage as a, type SerializedOperation as b, type SyncTransport as c, type TransportMessageHandler as d, type TransportCloseHandler as e, type TransportErrorHandler as f, type SyncEncryptionConfig as g, type SyncState as h, type SyncStatusInfo as i, type SyncConfig as j, type SyncStatePersistence as k, type SyncQuerySubset as l, type AwarenessStateWire as m, type AwarenessUpdateMessage as n, type ErrorMessage as o, type HandshakeResponseMessage as p, SYNC_STATES as q, SYNC_STATUSES as r, ScopeViolationError as s, type SyncEncryptionAlgorithm as t, type SyncScopeContext as u, type SyncStatus as v, dedupeQuerySubsets as w, isAcknowledgmentMessage as x, isAwarenessUpdateMessage as y, isErrorMessage as z };
@@ -1,4 +1,5 @@
1
- import { KoraError, Operation, OperationType, HLCTimestamp, AtomicOp } from '@korajs/core';
1
+ import * as _korajs_core from '@korajs/core';
2
+ import { KoraError, Operation, VersionVector, OperationType, HLCTimestamp, AtomicOp } from '@korajs/core';
2
3
 
3
4
  /**
4
5
  * Encryption types for the Kora.js sync layer.
@@ -92,7 +93,7 @@ type SyncState = (typeof SYNC_STATES)[number];
92
93
  /**
93
94
  * Developer-facing sync status. Simplified view of the internal state.
94
95
  */
95
- declare const SYNC_STATUSES: readonly ["connected", "syncing", "synced", "offline", "error"];
96
+ declare const SYNC_STATUSES: readonly ["connected", "syncing", "synced", "offline", "error", "schema-mismatch"];
96
97
  type SyncStatus = (typeof SYNC_STATUSES)[number];
97
98
  /**
98
99
  * Sync status information exposed to developers.
@@ -144,6 +145,20 @@ interface SyncConfig {
144
145
  maxReconnectInterval?: number;
145
146
  /** Schema version of this client. */
146
147
  schemaVersion?: number;
148
+ /** Start sync automatically when the engine is created. Defaults to false. */
149
+ autoConnect?: boolean;
150
+ /**
151
+ * When true, wait for server ACKs on all outbound handshake delta batches before
152
+ * entering streaming. Improves backpressure for large initial syncs.
153
+ */
154
+ strictHandshake?: boolean;
155
+ /** Optional operation transforms for cross-schema-version sync. */
156
+ operationTransforms?: _korajs_core.OperationTransform[];
157
+ /**
158
+ * Richtext snapshot size (bytes) at which the optional Yjs doc channel is used.
159
+ * Defaults to 4096.
160
+ */
161
+ richtextDocChannelThreshold?: number;
147
162
  /**
148
163
  * End-to-end encryption configuration.
149
164
  * When enabled, `data` and `previousData` fields are encrypted before sending
@@ -158,6 +173,25 @@ interface SyncScopeContext {
158
173
  userId?: string;
159
174
  [key: string]: unknown;
160
175
  }
176
+ /**
177
+ * Persists last-acked server version vector and computes unsynced operations from the op log.
178
+ */
179
+ interface DeltaCursor {
180
+ /** ID of the last fully applied operation in the previous delta stream */
181
+ lastOperationId: string;
182
+ /** Zero-based batch index where the cursor was recorded */
183
+ batchIndex: number;
184
+ }
185
+ interface SyncStatePersistence {
186
+ loadLastAckedServerVector(): Promise<VersionVector>;
187
+ saveLastAckedServerVector(vector: VersionVector): Promise<void>;
188
+ mergeServerVectors(a: VersionVector, b: VersionVector): VersionVector;
189
+ countUnsyncedOperations(serverVector: VersionVector): Promise<number>;
190
+ getUnsyncedOperations(serverVector: VersionVector): Promise<Operation[]>;
191
+ /** Resume position for paginated initial sync (optional). */
192
+ loadDeltaCursor?(): Promise<DeltaCursor | null>;
193
+ saveDeltaCursor?(cursor: DeltaCursor | null): Promise<void>;
194
+ }
161
195
  /**
162
196
  * Interface for persisting the outbound operation queue.
163
197
  * Operations must survive page refreshes and be sent when connection is re-established.
@@ -191,6 +225,23 @@ declare class InvalidScopeError extends KoraError {
191
225
  constructor(message: string, context?: Record<string, unknown>);
192
226
  }
193
227
 
228
+ /**
229
+ * A live query filter that narrows which operations sync for a collection.
230
+ */
231
+ interface SyncQuerySubset {
232
+ collection: string;
233
+ where: Record<string, unknown>;
234
+ }
235
+ /**
236
+ * Returns true when an operation matches at least one active query subset
237
+ * for its collection. Collections without query subsets pass through.
238
+ */
239
+ declare function operationMatchesQuerySubsets(op: Operation, subsets: SyncQuerySubset[] | undefined, fullRecord?: Record<string, unknown> | null): boolean;
240
+ /**
241
+ * Deduplicate query subsets by collection + where JSON key.
242
+ */
243
+ declare function dedupeQuerySubsets(subsets: SyncQuerySubset[]): SyncQuerySubset[];
244
+
194
245
  type WireFormat = 'json' | 'protobuf';
195
246
  /**
196
247
  * Wire-format operation. Plain object (no Map) for JSON serialization.
@@ -229,6 +280,10 @@ interface HandshakeMessage {
229
280
  supportedWireFormats?: WireFormat[];
230
281
  /** Per-collection sync scope filters. Limits which records are synced to this client. */
231
282
  syncScope?: Record<string, Record<string, unknown>>;
283
+ /** Base64url-encoded delta cursor for resuming paginated initial sync */
284
+ deltaCursor?: string;
285
+ /** Live query filters that further narrow synced data within auth/schema scope */
286
+ syncQueries?: SyncQuerySubset[];
232
287
  }
233
288
  /**
234
289
  * Server response to a handshake.
@@ -241,6 +296,10 @@ interface HandshakeResponseMessage {
241
296
  schemaVersion: number;
242
297
  accepted: boolean;
243
298
  rejectReason?: string;
299
+ /** Inclusive minimum schema version the server accepts (present when rejected for schema mismatch). */
300
+ supportedSchemaMin?: number;
301
+ /** Inclusive maximum schema version the server accepts (present when rejected for schema mismatch). */
302
+ supportedSchemaMax?: number;
244
303
  selectedWireFormat?: WireFormat;
245
304
  /** The server-accepted per-collection sync scope. Confirms what data will be synced. */
246
305
  acceptedScope?: Record<string, Record<string, unknown>>;
@@ -256,6 +315,10 @@ interface OperationBatchMessage {
256
315
  isFinal: boolean;
257
316
  /** Index of this batch (0-based) for ordering */
258
317
  batchIndex: number;
318
+ /** Base64url-encoded cursor marking the last operation in this batch */
319
+ cursor?: string;
320
+ /** Total batches in this delta exchange (for progress reporting) */
321
+ totalBatches?: number;
259
322
  }
260
323
  /**
261
324
  * Acknowledgment of a received message.
@@ -307,10 +370,23 @@ interface AwarenessUpdateMessage {
307
370
  /** Map of clientId -> state (null means removal) */
308
371
  states: Record<string, AwarenessStateWire | null>;
309
372
  }
373
+ /**
374
+ * Incremental Yjs update for a richtext field. Ephemeral side channel for large documents.
375
+ * JSON transport only; not persisted on the server operation log.
376
+ */
377
+ interface YjsDocUpdateMessage {
378
+ type: 'yjs-doc-update';
379
+ messageId: string;
380
+ collection: string;
381
+ recordId: string;
382
+ field: string;
383
+ /** Base64-encoded Yjs update binary */
384
+ update: string;
385
+ }
310
386
  /**
311
387
  * Union of all sync protocol messages.
312
388
  */
313
- type SyncMessage = HandshakeMessage | HandshakeResponseMessage | OperationBatchMessage | AcknowledgmentMessage | ErrorMessage | AwarenessUpdateMessage;
389
+ type SyncMessage = HandshakeMessage | HandshakeResponseMessage | OperationBatchMessage | AcknowledgmentMessage | ErrorMessage | AwarenessUpdateMessage | YjsDocUpdateMessage;
314
390
  /**
315
391
  * Check if an unknown value is a valid SyncMessage.
316
392
  */
@@ -339,6 +415,10 @@ declare function isErrorMessage(value: unknown): value is ErrorMessage;
339
415
  * Check if a value is an AwarenessUpdateMessage.
340
416
  */
341
417
  declare function isAwarenessUpdateMessage(value: unknown): value is AwarenessUpdateMessage;
418
+ /**
419
+ * Check if a value is a YjsDocUpdateMessage.
420
+ */
421
+ declare function isYjsDocUpdateMessage(value: unknown): value is YjsDocUpdateMessage;
342
422
 
343
423
  /**
344
424
  * Options for transport connection.
@@ -382,4 +462,4 @@ interface SyncTransport {
382
462
  isConnected(): boolean;
383
463
  }
384
464
 
385
- export { type AcknowledgmentMessage as A, isSyncMessage as B, type EncryptedPayload as E, type HandshakeMessage as H, InvalidScopeError as I, type OperationBatchMessage as O, type QueueStorage as Q, type SyncScopeMap as S, type TransportOptions as T, type VersionedKey as V, type WireFormat as W, type SyncMessage as a, type SerializedOperation as b, type SyncTransport as c, type TransportMessageHandler as d, type TransportCloseHandler as e, type TransportErrorHandler as f, type SyncEncryptionConfig as g, type SyncState as h, type SyncStatusInfo as i, type SyncConfig as j, type AwarenessStateWire as k, type AwarenessUpdateMessage as l, type ErrorMessage as m, type HandshakeResponseMessage as n, SYNC_STATES as o, SYNC_STATUSES as p, ScopeViolationError as q, type SyncEncryptionAlgorithm as r, type SyncScopeContext as s, type SyncStatus as t, isAcknowledgmentMessage as u, isAwarenessUpdateMessage as v, isErrorMessage as w, isHandshakeMessage as x, isHandshakeResponseMessage as y, isOperationBatchMessage as z };
465
+ export { type AcknowledgmentMessage as A, isHandshakeMessage as B, isHandshakeResponseMessage as C, type DeltaCursor as D, type EncryptedPayload as E, isOperationBatchMessage as F, isSyncMessage as G, type HandshakeMessage as H, InvalidScopeError as I, isYjsDocUpdateMessage as J, operationMatchesQuerySubsets as K, type OperationBatchMessage as O, type QueueStorage as Q, type SyncScopeMap as S, type TransportOptions as T, type VersionedKey as V, type WireFormat as W, type YjsDocUpdateMessage as Y, type SyncMessage as a, type SerializedOperation as b, type SyncTransport as c, type TransportMessageHandler as d, type TransportCloseHandler as e, type TransportErrorHandler as f, type SyncEncryptionConfig as g, type SyncState as h, type SyncStatusInfo as i, type SyncConfig as j, type SyncStatePersistence as k, type SyncQuerySubset as l, type AwarenessStateWire as m, type AwarenessUpdateMessage as n, type ErrorMessage as o, type HandshakeResponseMessage as p, SYNC_STATES as q, SYNC_STATUSES as r, ScopeViolationError as s, type SyncEncryptionAlgorithm as t, type SyncScopeContext as u, type SyncStatus as v, dedupeQuerySubsets as w, isAcknowledgmentMessage as x, isAwarenessUpdateMessage as y, isErrorMessage as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/sync",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Sync protocol and transports for Kora.js with delta sync, causal ordering, and automatic reconnection",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -33,8 +33,8 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "protobufjs": "^8.0.1",
36
- "@korajs/core": "0.4.0",
37
- "@korajs/merge": "0.4.0"
36
+ "@korajs/core": "0.6.0",
37
+ "@korajs/merge": "0.6.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@fast-check/vitest": "0.2.0",
@@ -47,7 +47,8 @@
47
47
  "build": "tsup",
48
48
  "dev": "tsup --watch",
49
49
  "test": "vitest run",
50
- "test:chaos": "vitest run tests/integration/chaos-nightly.test.ts",
50
+ "test:benchmarks": "vitest run --config vitest.benchmark.config.ts",
51
+ "test:chaos": "NIGHTLY=true vitest run tests/integration/chaos-nightly.test.ts",
51
52
  "test:watch": "vitest",
52
53
  "typecheck": "tsc --noEmit",
53
54
  "lint": "biome check .",