@korajs/sync 0.6.0 → 1.0.0-beta.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.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig, k as SyncStatePersistence, l as SyncQuerySubset } from './transport-BrdBj7rH.cjs';
2
- export { A as AcknowledgmentMessage, m as AwarenessStateWire, n as AwarenessUpdateMessage, E as EncryptedPayload, o as ErrorMessage, H as HandshakeMessage, p as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, q as SYNC_STATES, r as SYNC_STATUSES, s as ScopeViolationError, t as SyncEncryptionAlgorithm, u as SyncScopeContext, v as SyncStatus, w as dedupeQuerySubsets, x as isAcknowledgmentMessage, y as isAwarenessUpdateMessage, z as isErrorMessage, B as isHandshakeMessage, C as isHandshakeResponseMessage, F as isOperationBatchMessage, G as isSyncMessage, J as isYjsDocUpdateMessage, K as operationMatchesQuerySubsets } from './transport-BrdBj7rH.cjs';
1
+ import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig, k as SyncStatePersistence, l as SyncQuerySubset } from './transport-BMesMB6s.cjs';
2
+ export { A as AcknowledgmentMessage, m as AwarenessStateWire, n as AwarenessUpdateMessage, E as EncryptedPayload, o as ErrorMessage, H as HandshakeMessage, p as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, q as SYNC_STATES, r as SYNC_STATUSES, s as ScopeViolationError, t as SyncEncryptionAlgorithm, u as SyncScopeContext, v as SyncStatus, w as dedupeQuerySubsets, x as isAcknowledgmentMessage, y as isAwarenessUpdateMessage, z as isErrorMessage, B as isHandshakeMessage, C as isHandshakeResponseMessage, F as isOperationBatchMessage, G as isSyncMessage, J as isYjsDocUpdateMessage, K as operationMatchesQuerySubsets } from './transport-BMesMB6s.cjs';
3
3
  import * as _korajs_core from '@korajs/core';
4
4
  import { Operation, VersionVector, ApplyResult, KoraEventEmitter, TimeSource, SyncError, ConnectionQuality } from '@korajs/core';
5
5
  export { ApplyFailureReason, ApplyResult } from '@korajs/core';
@@ -81,6 +81,20 @@ interface SyncStore {
81
81
  * @param toSeq - End sequence number (inclusive)
82
82
  */
83
83
  getOperationRange(nodeId: string, fromSeq: number, toSeq: number): Promise<Operation[]>;
84
+ /**
85
+ * Optional: re-stamp never-acknowledged local operations after a fast device
86
+ * clock was corrected (timestamp rebase). Optional so hand-rolled SyncStore
87
+ * implementations keep working; when absent the engine simply skips the
88
+ * rebase and falls back to the clock-block behavior.
89
+ *
90
+ * @param ids - Operation ids that are candidates for re-stamping
91
+ * @param correctedNowMs - Trusted "now" (server time at handshake) in ms
92
+ */
93
+ rebaseUnsyncedOperations?(ids: string[], correctedNowMs: number): Promise<{
94
+ operations: Operation[];
95
+ idMapping: Record<string, string>;
96
+ rebasedCount: number;
97
+ }>;
84
98
  }
85
99
 
86
100
  /** Prefix for handshake rejection when client schema version is unsupported. */
@@ -275,7 +289,7 @@ declare class ChaosTransport implements SyncTransport {
275
289
  private readonly random;
276
290
  private messageHandler;
277
291
  private reorderBuffer;
278
- private timers;
292
+ private pending;
279
293
  constructor(inner: SyncTransport, config?: ChaosConfig);
280
294
  connect(url: string, options?: TransportOptions): Promise<void>;
281
295
  disconnect(): Promise<void>;
@@ -529,11 +543,13 @@ declare class SyncEncryptor {
529
543
  * @param config - Encryption configuration with passphrase
530
544
  * @param salt - Optional salt for deterministic key derivation (mainly for testing).
531
545
  * If omitted, a random salt is generated.
546
+ * @param iterations - Optional PBKDF2 iteration count. Defaults to the
547
+ * production-strength value. Lower it only in tests.
532
548
  * @returns A configured SyncEncryptor instance
533
549
  * @throws {EncryptionError} If configuration is invalid
534
550
  * @throws {KeyDerivationError} If key derivation fails
535
551
  */
536
- static create(config: SyncEncryptionConfig, salt?: Uint8Array): Promise<SyncEncryptor>;
552
+ static create(config: SyncEncryptionConfig, salt?: Uint8Array, iterations?: number): Promise<SyncEncryptor>;
537
553
  /**
538
554
  * Creates a SyncEncryptor from pre-derived versioned keys.
539
555
  *
@@ -745,6 +761,20 @@ declare class OutboundQueue {
745
761
  * Peek at the first `count` operations without removing them.
746
762
  */
747
763
  peek(count: number): Operation[];
764
+ /**
765
+ * All queued operations (not counting in-flight), in causal order.
766
+ */
767
+ getAll(): Operation[];
768
+ /**
769
+ * Atomically replace the entire queue with a new set of operations.
770
+ *
771
+ * Used after a timestamp rebase rewrote the queued operations under new
772
+ * content-addressed ids: the old entries must vanish from memory AND from
773
+ * persistent storage in one step, or a page refresh could resurrect
774
+ * stale-stamped ops the server would reject. Resets the seen set to exactly
775
+ * the new ids so the rewritten ops are not treated as duplicates.
776
+ */
777
+ replaceAll(ops: Operation[]): Promise<void>;
748
778
  /**
749
779
  * Whether initialize() has been called.
750
780
  */
@@ -832,6 +862,8 @@ declare class SyncEngine {
832
862
  private currentBatch;
833
863
  private reconnecting;
834
864
  private schemaBlocked;
865
+ private clockBlocked;
866
+ private clockSkewMs;
835
867
  private deltaBatchesReceived;
836
868
  private deltaReceiveComplete;
837
869
  private deltaSendComplete;
@@ -885,6 +917,18 @@ declare class SyncEngine {
885
917
  * Sync stays blocked until the app schema is upgraded or sync config changes.
886
918
  */
887
919
  isSchemaBlocked(): boolean;
920
+ /**
921
+ * True when sync is blocked because this device's clock is too far ahead.
922
+ * Local writes continue to work and queue; only sync is paused.
923
+ */
924
+ isClockBlocked(): boolean;
925
+ /** serverTime - localTime measured at the last handshake, or null before first connect. */
926
+ getClockSkewMs(): number | null;
927
+ /**
928
+ * Clears the clock block after the user corrects the device clock.
929
+ * Moves the engine back to `disconnected` so `start()` can run again.
930
+ */
931
+ clearClockBlock(): void;
888
932
  /**
889
933
  * Clears schema-mismatch block after upgrading the local schema / sync config.
890
934
  * Moves the engine back to `disconnected` so `start()` can run again.
@@ -958,7 +1002,22 @@ declare class SyncEngine {
958
1002
  private enqueueMessage;
959
1003
  private handleMessageAsync;
960
1004
  private handleMessageFailure;
1005
+ /**
1006
+ * Compares server wall-clock time from the handshake with local time.
1007
+ * Negative skew = this device's clock is fast (dangerous for LWW and rejected
1008
+ * by server ingest beyond 60s). Positive skew = slow (accepted but surfaced).
1009
+ * Zero developer work required: the result flows into sync status and events.
1010
+ */
1011
+ private evaluateClockSkew;
961
1012
  private handleHandshakeResponse;
1013
+ /**
1014
+ * Re-stamps queued (never-acknowledged) operations whose timestamps are far
1015
+ * enough in the future that the server would reject them, using the server's
1016
+ * own handshake time as the trusted "now". This is the automatic recovery
1017
+ * path after a user corrects a fast device clock: the queue drains
1018
+ * immediately instead of waiting for real time to catch up.
1019
+ */
1020
+ private maybeRebaseQueuedOperations;
962
1021
  private sendDelta;
963
1022
  private markDeltaSendCompleteIfReady;
964
1023
  private collectDelta;
@@ -1164,6 +1223,9 @@ declare function generateSalt(): Uint8Array;
1164
1223
  *
1165
1224
  * @param passphrase - The user's passphrase (must be non-empty)
1166
1225
  * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.
1226
+ * @param iterations - Optional PBKDF2 iteration count. Defaults to
1227
+ * {@link DEFAULT_PBKDF2_ITERATIONS}. Lower it only in tests; production must
1228
+ * keep the default so derived keys stay brute-force resistant.
1167
1229
  * @returns The derived CryptoKey and the salt used
1168
1230
  * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails
1169
1231
  *
@@ -1176,7 +1238,7 @@ declare function generateSalt(): Uint8Array;
1176
1238
  * const { key: sameKey } = await deriveKey('my-secure-passphrase', salt)
1177
1239
  * ```
1178
1240
  */
1179
- declare function deriveKey(passphrase: string, salt?: Uint8Array): Promise<{
1241
+ declare function deriveKey(passphrase: string, salt?: Uint8Array, iterations?: number): Promise<{
1180
1242
  key: CryptoKey;
1181
1243
  salt: Uint8Array;
1182
1244
  }>;
@@ -1190,10 +1252,12 @@ declare function deriveKey(passphrase: string, salt?: Uint8Array): Promise<{
1190
1252
  * @param passphrase - The user's passphrase
1191
1253
  * @param version - Key version number (must be a positive integer)
1192
1254
  * @param salt - Optional salt bytes. If omitted, a random salt is generated.
1255
+ * @param iterations - Optional PBKDF2 iteration count. Defaults to
1256
+ * {@link DEFAULT_PBKDF2_ITERATIONS}. Lower it only in tests.
1193
1257
  * @returns A {@link VersionedKey} containing the key, version, and salt
1194
1258
  * @throws {KeyDerivationError} If parameters are invalid or derivation fails
1195
1259
  */
1196
- declare function deriveVersionedKey(passphrase: string, version: number, salt?: Uint8Array): Promise<VersionedKey>;
1260
+ declare function deriveVersionedKey(passphrase: string, version: number, salt?: Uint8Array, iterations?: number): Promise<VersionedKey>;
1197
1261
 
1198
1262
  /** Default status when sync is not configured or the engine is unavailable. */
1199
1263
  declare const OFFLINE_SYNC_STATUS: SyncStatusInfo;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig, k as SyncStatePersistence, l as SyncQuerySubset } from './transport-BrdBj7rH.js';
2
- export { A as AcknowledgmentMessage, m as AwarenessStateWire, n as AwarenessUpdateMessage, E as EncryptedPayload, o as ErrorMessage, H as HandshakeMessage, p as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, q as SYNC_STATES, r as SYNC_STATUSES, s as ScopeViolationError, t as SyncEncryptionAlgorithm, u as SyncScopeContext, v as SyncStatus, w as dedupeQuerySubsets, x as isAcknowledgmentMessage, y as isAwarenessUpdateMessage, z as isErrorMessage, B as isHandshakeMessage, C as isHandshakeResponseMessage, F as isOperationBatchMessage, G as isSyncMessage, J as isYjsDocUpdateMessage, K as operationMatchesQuerySubsets } from './transport-BrdBj7rH.js';
1
+ import { S as SyncScopeMap, D as DeltaCursor, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig, k as SyncStatePersistence, l as SyncQuerySubset } from './transport-BMesMB6s.js';
2
+ export { A as AcknowledgmentMessage, m as AwarenessStateWire, n as AwarenessUpdateMessage, E as EncryptedPayload, o as ErrorMessage, H as HandshakeMessage, p as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, q as SYNC_STATES, r as SYNC_STATUSES, s as ScopeViolationError, t as SyncEncryptionAlgorithm, u as SyncScopeContext, v as SyncStatus, w as dedupeQuerySubsets, x as isAcknowledgmentMessage, y as isAwarenessUpdateMessage, z as isErrorMessage, B as isHandshakeMessage, C as isHandshakeResponseMessage, F as isOperationBatchMessage, G as isSyncMessage, J as isYjsDocUpdateMessage, K as operationMatchesQuerySubsets } from './transport-BMesMB6s.js';
3
3
  import * as _korajs_core from '@korajs/core';
4
4
  import { Operation, VersionVector, ApplyResult, KoraEventEmitter, TimeSource, SyncError, ConnectionQuality } from '@korajs/core';
5
5
  export { ApplyFailureReason, ApplyResult } from '@korajs/core';
@@ -81,6 +81,20 @@ interface SyncStore {
81
81
  * @param toSeq - End sequence number (inclusive)
82
82
  */
83
83
  getOperationRange(nodeId: string, fromSeq: number, toSeq: number): Promise<Operation[]>;
84
+ /**
85
+ * Optional: re-stamp never-acknowledged local operations after a fast device
86
+ * clock was corrected (timestamp rebase). Optional so hand-rolled SyncStore
87
+ * implementations keep working; when absent the engine simply skips the
88
+ * rebase and falls back to the clock-block behavior.
89
+ *
90
+ * @param ids - Operation ids that are candidates for re-stamping
91
+ * @param correctedNowMs - Trusted "now" (server time at handshake) in ms
92
+ */
93
+ rebaseUnsyncedOperations?(ids: string[], correctedNowMs: number): Promise<{
94
+ operations: Operation[];
95
+ idMapping: Record<string, string>;
96
+ rebasedCount: number;
97
+ }>;
84
98
  }
85
99
 
86
100
  /** Prefix for handshake rejection when client schema version is unsupported. */
@@ -275,7 +289,7 @@ declare class ChaosTransport implements SyncTransport {
275
289
  private readonly random;
276
290
  private messageHandler;
277
291
  private reorderBuffer;
278
- private timers;
292
+ private pending;
279
293
  constructor(inner: SyncTransport, config?: ChaosConfig);
280
294
  connect(url: string, options?: TransportOptions): Promise<void>;
281
295
  disconnect(): Promise<void>;
@@ -529,11 +543,13 @@ declare class SyncEncryptor {
529
543
  * @param config - Encryption configuration with passphrase
530
544
  * @param salt - Optional salt for deterministic key derivation (mainly for testing).
531
545
  * If omitted, a random salt is generated.
546
+ * @param iterations - Optional PBKDF2 iteration count. Defaults to the
547
+ * production-strength value. Lower it only in tests.
532
548
  * @returns A configured SyncEncryptor instance
533
549
  * @throws {EncryptionError} If configuration is invalid
534
550
  * @throws {KeyDerivationError} If key derivation fails
535
551
  */
536
- static create(config: SyncEncryptionConfig, salt?: Uint8Array): Promise<SyncEncryptor>;
552
+ static create(config: SyncEncryptionConfig, salt?: Uint8Array, iterations?: number): Promise<SyncEncryptor>;
537
553
  /**
538
554
  * Creates a SyncEncryptor from pre-derived versioned keys.
539
555
  *
@@ -745,6 +761,20 @@ declare class OutboundQueue {
745
761
  * Peek at the first `count` operations without removing them.
746
762
  */
747
763
  peek(count: number): Operation[];
764
+ /**
765
+ * All queued operations (not counting in-flight), in causal order.
766
+ */
767
+ getAll(): Operation[];
768
+ /**
769
+ * Atomically replace the entire queue with a new set of operations.
770
+ *
771
+ * Used after a timestamp rebase rewrote the queued operations under new
772
+ * content-addressed ids: the old entries must vanish from memory AND from
773
+ * persistent storage in one step, or a page refresh could resurrect
774
+ * stale-stamped ops the server would reject. Resets the seen set to exactly
775
+ * the new ids so the rewritten ops are not treated as duplicates.
776
+ */
777
+ replaceAll(ops: Operation[]): Promise<void>;
748
778
  /**
749
779
  * Whether initialize() has been called.
750
780
  */
@@ -832,6 +862,8 @@ declare class SyncEngine {
832
862
  private currentBatch;
833
863
  private reconnecting;
834
864
  private schemaBlocked;
865
+ private clockBlocked;
866
+ private clockSkewMs;
835
867
  private deltaBatchesReceived;
836
868
  private deltaReceiveComplete;
837
869
  private deltaSendComplete;
@@ -885,6 +917,18 @@ declare class SyncEngine {
885
917
  * Sync stays blocked until the app schema is upgraded or sync config changes.
886
918
  */
887
919
  isSchemaBlocked(): boolean;
920
+ /**
921
+ * True when sync is blocked because this device's clock is too far ahead.
922
+ * Local writes continue to work and queue; only sync is paused.
923
+ */
924
+ isClockBlocked(): boolean;
925
+ /** serverTime - localTime measured at the last handshake, or null before first connect. */
926
+ getClockSkewMs(): number | null;
927
+ /**
928
+ * Clears the clock block after the user corrects the device clock.
929
+ * Moves the engine back to `disconnected` so `start()` can run again.
930
+ */
931
+ clearClockBlock(): void;
888
932
  /**
889
933
  * Clears schema-mismatch block after upgrading the local schema / sync config.
890
934
  * Moves the engine back to `disconnected` so `start()` can run again.
@@ -958,7 +1002,22 @@ declare class SyncEngine {
958
1002
  private enqueueMessage;
959
1003
  private handleMessageAsync;
960
1004
  private handleMessageFailure;
1005
+ /**
1006
+ * Compares server wall-clock time from the handshake with local time.
1007
+ * Negative skew = this device's clock is fast (dangerous for LWW and rejected
1008
+ * by server ingest beyond 60s). Positive skew = slow (accepted but surfaced).
1009
+ * Zero developer work required: the result flows into sync status and events.
1010
+ */
1011
+ private evaluateClockSkew;
961
1012
  private handleHandshakeResponse;
1013
+ /**
1014
+ * Re-stamps queued (never-acknowledged) operations whose timestamps are far
1015
+ * enough in the future that the server would reject them, using the server's
1016
+ * own handshake time as the trusted "now". This is the automatic recovery
1017
+ * path after a user corrects a fast device clock: the queue drains
1018
+ * immediately instead of waiting for real time to catch up.
1019
+ */
1020
+ private maybeRebaseQueuedOperations;
962
1021
  private sendDelta;
963
1022
  private markDeltaSendCompleteIfReady;
964
1023
  private collectDelta;
@@ -1164,6 +1223,9 @@ declare function generateSalt(): Uint8Array;
1164
1223
  *
1165
1224
  * @param passphrase - The user's passphrase (must be non-empty)
1166
1225
  * @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.
1226
+ * @param iterations - Optional PBKDF2 iteration count. Defaults to
1227
+ * {@link DEFAULT_PBKDF2_ITERATIONS}. Lower it only in tests; production must
1228
+ * keep the default so derived keys stay brute-force resistant.
1167
1229
  * @returns The derived CryptoKey and the salt used
1168
1230
  * @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails
1169
1231
  *
@@ -1176,7 +1238,7 @@ declare function generateSalt(): Uint8Array;
1176
1238
  * const { key: sameKey } = await deriveKey('my-secure-passphrase', salt)
1177
1239
  * ```
1178
1240
  */
1179
- declare function deriveKey(passphrase: string, salt?: Uint8Array): Promise<{
1241
+ declare function deriveKey(passphrase: string, salt?: Uint8Array, iterations?: number): Promise<{
1180
1242
  key: CryptoKey;
1181
1243
  salt: Uint8Array;
1182
1244
  }>;
@@ -1190,10 +1252,12 @@ declare function deriveKey(passphrase: string, salt?: Uint8Array): Promise<{
1190
1252
  * @param passphrase - The user's passphrase
1191
1253
  * @param version - Key version number (must be a positive integer)
1192
1254
  * @param salt - Optional salt bytes. If omitted, a random salt is generated.
1255
+ * @param iterations - Optional PBKDF2 iteration count. Defaults to
1256
+ * {@link DEFAULT_PBKDF2_ITERATIONS}. Lower it only in tests.
1193
1257
  * @returns A {@link VersionedKey} containing the key, version, and salt
1194
1258
  * @throws {KeyDerivationError} If parameters are invalid or derivation fails
1195
1259
  */
1196
- declare function deriveVersionedKey(passphrase: string, version: number, salt?: Uint8Array): Promise<VersionedKey>;
1260
+ declare function deriveVersionedKey(passphrase: string, version: number, salt?: Uint8Array, iterations?: number): Promise<VersionedKey>;
1197
1261
 
1198
1262
  /** Default status when sync is not configured or the engine is unavailable. */
1199
1263
  declare const OFFLINE_SYNC_STATUS: SyncStatusInfo;