@korajs/sync 1.0.0-beta.1 → 1.0.0-beta.10

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, B as BlobChunkRequestMessage, g as BlobChunkResponseMessage, h as BlobChunkPushMessage, i as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, j as SyncState, k as SyncStatusInfo, l as SyncConfig, m as SyncStatePersistence, n as SyncQuerySubset } from './transport-CZLMdZ0k.cjs';
2
- export { A as AcknowledgmentMessage, o as AwarenessStateWire, p as AwarenessUpdateMessage, E as EncryptedPayload, q as ErrorMessage, H as HandshakeMessage, r as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, s as SYNC_STATES, t as SYNC_STATUSES, u as ScopeViolationError, v as SyncEncryptionAlgorithm, w as SyncScopeContext, x as SyncStatus, y as dedupeQuerySubsets, z as isAcknowledgmentMessage, C as isAwarenessUpdateMessage, F as isBlobChunkPushMessage, G as isBlobChunkRequestMessage, J as isBlobChunkResponseMessage, K as isErrorMessage, L as isHandshakeMessage, M as isHandshakeResponseMessage, N as isOperationBatchMessage, P as isSyncMessage, R as isYjsDocUpdateMessage, U as operationMatchesQuerySubsets } from './transport-CZLMdZ0k.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, B as BlobChunkRequestMessage, g as BlobChunkResponseMessage, h as BlobChunkPushMessage, i as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, j as SyncState, k as SyncStatusInfo, l as SyncConfig, R as RejectedOperationStorage, m as SyncStatePersistence, n as SyncQuerySubset, o as RejectedOperation } from './transport-B2GQerqJ.cjs';
2
+ export { A as AcknowledgmentMessage, p as AwarenessStateWire, q as AwarenessUpdateMessage, E as EncryptedPayload, r as ErrorMessage, H as HandshakeMessage, s as HandshakeResponseMessage, I as InvalidScopeError, M as MemoryRejectedOperationStorage, O as OperationBatchMessage, t as OperationRejectedMessage, u as SYNC_STATES, v as SYNC_STATUSES, w as ScopeViolationError, x as SyncEncryptionAlgorithm, y as SyncScopeContext, z as SyncStatus, C as dedupeQuerySubsets, F as isAcknowledgmentMessage, G as isAwarenessUpdateMessage, J as isBlobChunkPushMessage, K as isBlobChunkRequestMessage, L as isBlobChunkResponseMessage, N as isErrorMessage, P as isHandshakeMessage, U as isHandshakeResponseMessage, X as isOperationBatchMessage, Z as isOperationRejectedMessage, _ as isSyncMessage, $ as isYjsDocUpdateMessage, a0 as operationMatchesQuerySubsets } from './transport-B2GQerqJ.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';
@@ -95,6 +95,17 @@ interface SyncStore {
95
95
  idMapping: Record<string, string>;
96
96
  rebasedCount: number;
97
97
  }>;
98
+ /**
99
+ * Optional: read a record's current materialized field values, used to backfill
100
+ * scope / query-subset fields that a partial update (or a delete) does not carry
101
+ * in its own data. Without it, such an operation would be judged out of scope by
102
+ * its changed fields alone and wrongly dropped from sync. Optional so hand-rolled
103
+ * SyncStore implementations keep working; when absent, the engine falls back to
104
+ * judging visibility from the operation's own data.
105
+ *
106
+ * @returns the record's fields, or null when it cannot be read.
107
+ */
108
+ readRecordFields?(collection: string, recordId: string): Promise<Record<string, unknown> | null>;
98
109
  }
99
110
 
100
111
  /** Prefix for handshake rejection when client schema version is unsupported. */
@@ -272,6 +283,14 @@ interface ChaosConfig {
272
283
  maxLatency?: number;
273
284
  /** Injectable random source for deterministic testing. Returns value in [0, 1). */
274
285
  randomSource?: () => number;
286
+ /**
287
+ * Deterministic targeted drop. When it returns true for a message and direction,
288
+ * that message is dropped unconditionally (independent of `dropRate`). Enables
289
+ * reproducing an exact gap (e.g. drop one specific operation) instead of relying
290
+ * on probabilistic drops. `direction` is 'outgoing' for `send` and 'incoming' for
291
+ * messages arriving from the inner transport.
292
+ */
293
+ dropPredicate?: (message: SyncMessage, direction: 'outgoing' | 'incoming') => boolean;
275
294
  }
276
295
  /**
277
296
  * Chaos transport that wraps another transport and injects faults.
@@ -287,6 +306,7 @@ declare class ChaosTransport implements SyncTransport {
287
306
  private readonly reorderRate;
288
307
  private readonly maxLatency;
289
308
  private readonly random;
309
+ private readonly dropPredicate;
290
310
  private messageHandler;
291
311
  private reorderBuffer;
292
312
  private pending;
@@ -805,6 +825,13 @@ declare class OutboundQueue {
805
825
  * Acknowledge a batch, removing its operations permanently.
806
826
  */
807
827
  acknowledge(batchId: string): Promise<void>;
828
+ /**
829
+ * Acknowledge only operations in an in-flight batch whose local sequence number is
830
+ * covered by the server's ack. Any later operations return to the front of the
831
+ * queue for retry. This is required when the server accepts a prefix but rejects a
832
+ * later operation retriably (for example a stale-scope push after auth changed).
833
+ */
834
+ acknowledgeThrough(batchId: string, lastSequenceNumber: number): Promise<void>;
808
835
  /**
809
836
  * Return a failed batch to the front of the queue for retry.
810
837
  * Prepends the operations to maintain priority.
@@ -844,6 +871,14 @@ declare class OutboundQueue {
844
871
  * Whether initialize() has been called.
845
872
  */
846
873
  get isInitialized(): boolean;
874
+ /**
875
+ * Remove a single operation the server permanently rejected — from the queue,
876
+ * from any in-flight batch, and from durable storage — so a later batch ack or
877
+ * a reconnect `returnBatch` can never resend or resurrect it. Returns the
878
+ * removed operation so the caller can record it in a durable rejected store,
879
+ * or null if it was not present (already acknowledged and removed).
880
+ */
881
+ reject(opId: string): Promise<Operation | null>;
847
882
  /**
848
883
  * Remove operations by id from queue and persistent storage.
849
884
  * Used when ops were already sent during handshake delta exchange.
@@ -867,6 +902,11 @@ interface SyncEngineOptions {
867
902
  emitter?: KoraEventEmitter;
868
903
  /** Queue storage for persistent outbound queue. Defaults to in-memory. */
869
904
  queueStorage?: QueueStorage;
905
+ /**
906
+ * Durable storage for operations the server rejected. Defaults to in-memory.
907
+ * Provide a store-backed implementation so rejections survive a page refresh.
908
+ */
909
+ rejectedStorage?: RejectedOperationStorage;
870
910
  /**
871
911
  * Optional encryptor for end-to-end encryption.
872
912
  * When provided, `data` and `previousData` fields of operations are encrypted
@@ -894,6 +934,8 @@ interface SyncDiagnostics {
894
934
  pendingOperations: number;
895
935
  hasInFlightBatch: boolean;
896
936
  reconnecting: boolean;
937
+ deliveryWatermark: number;
938
+ deliveryGapRepeatCount: number;
897
939
  timestamp: number;
898
940
  }
899
941
  /**
@@ -911,7 +953,9 @@ declare class SyncEngine {
911
953
  private readonly serializer;
912
954
  private readonly emitter;
913
955
  private readonly outboundQueue;
956
+ private readonly rejectedStorage;
914
957
  private readonly batchSize;
958
+ private readonly outboundAckTimeoutMs;
915
959
  private readonly encryptor;
916
960
  private readonly awarenessManager;
917
961
  private readonly richtextDocChannel;
@@ -926,11 +970,16 @@ declare class SyncEngine {
926
970
  private lastSuccessfulPull;
927
971
  private conflictCount;
928
972
  private currentBatch;
973
+ private currentBatchRequiresPartialAck;
929
974
  private reconnecting;
930
975
  private schemaBlocked;
931
976
  private clockBlocked;
932
977
  private clockSkewMs;
933
978
  private blobStorageEnabled;
979
+ private outboundAckTimer;
980
+ private startPromise;
981
+ private stopPromise;
982
+ private reconnectPromise;
934
983
  private deltaBatchesReceived;
935
984
  private deltaReceiveComplete;
936
985
  private deltaSendComplete;
@@ -950,15 +999,38 @@ declare class SyncEngine {
950
999
  /** Resume cursor for paginated initial sync (persisted across reconnects) */
951
1000
  private resumeDeltaCursor;
952
1001
  private initialSyncTotalBatches;
1002
+ /**
1003
+ * The delivery watermark for the CURRENT sync view: the highest server delivery
1004
+ * sequence up to which every operation in the current view (scope + active query
1005
+ * subscriptions) has been contiguously applied. Advanced only through the server's
1006
+ * gap-free delivery stream (never through live relay), so it is a durable lower bound
1007
+ * on what this client holds for this view. Reported at handshake to resume the stream.
1008
+ *
1009
+ * The watermark is keyed by view: switching views (a scope or subscription change)
1010
+ * saves the current view's watermark and loads the target view's, so returning to a
1011
+ * previously-synced view resumes exactly where it left off instead of re-syncing. This
1012
+ * is correct because a view's watermark is a lower bound specific to that view's
1013
+ * filter; a widened view is simply a different (initially unsynced) view.
1014
+ */
1015
+ private deliveryWatermark;
1016
+ /**
1017
+ * In-memory cache of watermark per view signature, preloaded from persistence at start
1018
+ * so a view switch resolves synchronously (no race with the reconnect it triggers).
1019
+ */
1020
+ private readonly deliverySignatureWatermarks;
1021
+ private deliveryGapRepeatCount;
1022
+ private lastDeliveryGapKey;
953
1023
  constructor(options: SyncEngineOptions);
954
1024
  /**
955
1025
  * Start the sync engine: connect → handshake → delta exchange → streaming.
956
1026
  */
957
1027
  start(): Promise<void>;
1028
+ private startInternal;
958
1029
  /**
959
1030
  * Stop the sync engine. Disconnects the transport.
960
1031
  */
961
1032
  stop(): Promise<void>;
1033
+ private stopInternal;
962
1034
  private ensureDisconnected;
963
1035
  /**
964
1036
  * Push a local operation to the outbound queue.
@@ -1016,6 +1088,13 @@ declare class SyncEngine {
1016
1088
  * or in error state, restarts the sync. If already connected, no-op.
1017
1089
  */
1018
1090
  retryNow(): Promise<void>;
1091
+ /**
1092
+ * Atomically refresh the transport session. Overlapping callers share one
1093
+ * reconnect, in-flight outbound batches are returned to the queue by stop(), and the
1094
+ * next start handshakes with the latest auth token, sync scope, and query subsets.
1095
+ */
1096
+ reconnect(): Promise<void>;
1097
+ private reconnectInternal;
1019
1098
  /**
1020
1099
  * Export a diagnostics snapshot for debugging and support tickets.
1021
1100
  * Contains connection state, timing info, and queue metrics.
@@ -1042,6 +1121,41 @@ declare class SyncEngine {
1042
1121
  * @param scopeMap - New per-collection scope filters, or undefined to remove scope
1043
1122
  */
1044
1123
  updateScope(scopeMap: SyncScopeMap | undefined): void;
1124
+ /**
1125
+ * A stable identifier for the current sync view: the auth/tenant scope plus the set of
1126
+ * active query subscriptions, canonicalized so the same view always produces the same
1127
+ * string. The delivery watermark is keyed by this, so each distinct view resumes from
1128
+ * its own last position.
1129
+ */
1130
+ private deliverySignature;
1131
+ /**
1132
+ * Switch the active delivery watermark to the current view after a scope or subscription
1133
+ * change. The prior view's watermark is saved so returning to it resumes; the new view's
1134
+ * watermark is restored from the in-memory cache (0 for a never-synced view, which then
1135
+ * does a one-time resync under that view). No data is lost: a view's watermark is a lower
1136
+ * bound for that specific view's filter.
1137
+ */
1138
+ private switchDeliveryView;
1139
+ /**
1140
+ * Record a view's watermark in the in-memory cache as the most-recently-used entry (the
1141
+ * Map preserves insertion order, so re-inserting moves it to the tail), then evict cold
1142
+ * views past the retention cap. This is the single place the cache is written, so LRU
1143
+ * order and the size bound are always maintained together.
1144
+ */
1145
+ private setViewWatermark;
1146
+ /**
1147
+ * Trim the per-view watermark cache to `MAX_DELIVERY_VIEW_WATERMARKS`, evicting the
1148
+ * least-recently-used views. The default view ('') and the live view are never evicted
1149
+ * (evicting the live view would force an immediate re-scan of what we are actively
1150
+ * syncing). Each eviction also removes the persisted row, bounding storage. Safe by
1151
+ * construction: an evicted view simply back-fills from 0 (deduplicated) when next seen.
1152
+ */
1153
+ private evictColdViewWatermarks;
1154
+ /**
1155
+ * Remove a view's persisted watermark row (best-effort; a persistence layer that omits
1156
+ * the delete simply keeps the row, which is harmless).
1157
+ */
1158
+ private deleteDeliveryWatermark;
1045
1159
  /**
1046
1160
  * Get the currently active scope map. Returns undefined if no scope is configured.
1047
1161
  */
@@ -1105,8 +1219,29 @@ declare class SyncEngine {
1105
1219
  private markDeltaSendCompleteIfReady;
1106
1220
  private collectDelta;
1107
1221
  private handleOperationBatch;
1222
+ /** Acknowledge a delivery batch (used for duplicates) without re-applying it. */
1223
+ private sendDeliveryAck;
1108
1224
  private handleAcknowledgment;
1109
1225
  private handleError;
1226
+ /**
1227
+ * Handle a per-operation rejection: divert the op out of the pending outbound
1228
+ * queue (so it is never retried or resurrected on reconnect), record it in the
1229
+ * durable rejected store (so it is kept and explainable), and emit an event so
1230
+ * the app can reconcile. Unlike {@link handleError}, this is a normal per-op
1231
+ * signal, so the connection stays up.
1232
+ */
1233
+ private handleOperationRejected;
1234
+ /**
1235
+ * Every operation the server rejected that has not yet been reconciled. The app
1236
+ * uses this (alongside the `sync:operation-rejected` event) to surface failed
1237
+ * submissions and decide whether to roll back or resubmit.
1238
+ */
1239
+ getRejectedOperations(): Promise<RejectedOperation[]>;
1240
+ /**
1241
+ * Forget rejected operations by id once the app has reconciled them (rolled the
1242
+ * optimistic write back or resubmitted a corrected op).
1243
+ */
1244
+ clearRejectedOperations(operationIds: string[]): Promise<void>;
1110
1245
  private checkDeltaComplete;
1111
1246
  /**
1112
1247
  * Effective server vector: persisted last-ack merged with live handshake remote vector.
@@ -1127,13 +1262,20 @@ declare class SyncEngine {
1127
1262
  */
1128
1263
  private reconcileOutboundFromOpLog;
1129
1264
  private flushQueue;
1265
+ private startOutboundAckTimer;
1266
+ private clearOutboundAckTimer;
1267
+ private handleOutboundAckTimeout;
1130
1268
  private handleAwarenessUpdate;
1131
1269
  private handleTransportClose;
1132
1270
  private handleTransportError;
1133
1271
  private transitionTo;
1134
1272
  private setSerializerWireFormat;
1135
1273
  private operationAllowedForSync;
1274
+ private matchesScopeAndSubsets;
1275
+ private readRecordForBackfill;
1276
+ private filterAllowedForSync;
1136
1277
  private persistDeltaCursor;
1278
+ private persistDeliveryWatermark;
1137
1279
  private scheduleQuerySubsetReconnect;
1138
1280
  private reconnectForQuerySubsets;
1139
1281
  }
@@ -1375,4 +1517,4 @@ declare function getRemoteAwarenessStates(awareness: AwarenessManager): Awarenes
1375
1517
  */
1376
1518
  declare function subscribeRemoteAwarenessStates(awareness: AwarenessManager, listener: (states: AwarenessState[]) => void): () => void;
1377
1519
 
1378
- export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, BlobChunkChannel, type BlobChunkChannelMessage, type BlobChunkChannelOptions, BlobChunkPushMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeBlobChunkBytes, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeBlobChunkBytes, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
1520
+ export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, BlobChunkChannel, type BlobChunkChannelMessage, type BlobChunkChannelOptions, BlobChunkPushMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RejectedOperation, RejectedOperationStorage, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeBlobChunkBytes, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeBlobChunkBytes, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
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, B as BlobChunkRequestMessage, g as BlobChunkResponseMessage, h as BlobChunkPushMessage, i as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, j as SyncState, k as SyncStatusInfo, l as SyncConfig, m as SyncStatePersistence, n as SyncQuerySubset } from './transport-CZLMdZ0k.js';
2
- export { A as AcknowledgmentMessage, o as AwarenessStateWire, p as AwarenessUpdateMessage, E as EncryptedPayload, q as ErrorMessage, H as HandshakeMessage, r as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, s as SYNC_STATES, t as SYNC_STATUSES, u as ScopeViolationError, v as SyncEncryptionAlgorithm, w as SyncScopeContext, x as SyncStatus, y as dedupeQuerySubsets, z as isAcknowledgmentMessage, C as isAwarenessUpdateMessage, F as isBlobChunkPushMessage, G as isBlobChunkRequestMessage, J as isBlobChunkResponseMessage, K as isErrorMessage, L as isHandshakeMessage, M as isHandshakeResponseMessage, N as isOperationBatchMessage, P as isSyncMessage, R as isYjsDocUpdateMessage, U as operationMatchesQuerySubsets } from './transport-CZLMdZ0k.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, B as BlobChunkRequestMessage, g as BlobChunkResponseMessage, h as BlobChunkPushMessage, i as SyncEncryptionConfig, V as VersionedKey, Y as YjsDocUpdateMessage, Q as QueueStorage, j as SyncState, k as SyncStatusInfo, l as SyncConfig, R as RejectedOperationStorage, m as SyncStatePersistence, n as SyncQuerySubset, o as RejectedOperation } from './transport-B2GQerqJ.js';
2
+ export { A as AcknowledgmentMessage, p as AwarenessStateWire, q as AwarenessUpdateMessage, E as EncryptedPayload, r as ErrorMessage, H as HandshakeMessage, s as HandshakeResponseMessage, I as InvalidScopeError, M as MemoryRejectedOperationStorage, O as OperationBatchMessage, t as OperationRejectedMessage, u as SYNC_STATES, v as SYNC_STATUSES, w as ScopeViolationError, x as SyncEncryptionAlgorithm, y as SyncScopeContext, z as SyncStatus, C as dedupeQuerySubsets, F as isAcknowledgmentMessage, G as isAwarenessUpdateMessage, J as isBlobChunkPushMessage, K as isBlobChunkRequestMessage, L as isBlobChunkResponseMessage, N as isErrorMessage, P as isHandshakeMessage, U as isHandshakeResponseMessage, X as isOperationBatchMessage, Z as isOperationRejectedMessage, _ as isSyncMessage, $ as isYjsDocUpdateMessage, a0 as operationMatchesQuerySubsets } from './transport-B2GQerqJ.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';
@@ -95,6 +95,17 @@ interface SyncStore {
95
95
  idMapping: Record<string, string>;
96
96
  rebasedCount: number;
97
97
  }>;
98
+ /**
99
+ * Optional: read a record's current materialized field values, used to backfill
100
+ * scope / query-subset fields that a partial update (or a delete) does not carry
101
+ * in its own data. Without it, such an operation would be judged out of scope by
102
+ * its changed fields alone and wrongly dropped from sync. Optional so hand-rolled
103
+ * SyncStore implementations keep working; when absent, the engine falls back to
104
+ * judging visibility from the operation's own data.
105
+ *
106
+ * @returns the record's fields, or null when it cannot be read.
107
+ */
108
+ readRecordFields?(collection: string, recordId: string): Promise<Record<string, unknown> | null>;
98
109
  }
99
110
 
100
111
  /** Prefix for handshake rejection when client schema version is unsupported. */
@@ -272,6 +283,14 @@ interface ChaosConfig {
272
283
  maxLatency?: number;
273
284
  /** Injectable random source for deterministic testing. Returns value in [0, 1). */
274
285
  randomSource?: () => number;
286
+ /**
287
+ * Deterministic targeted drop. When it returns true for a message and direction,
288
+ * that message is dropped unconditionally (independent of `dropRate`). Enables
289
+ * reproducing an exact gap (e.g. drop one specific operation) instead of relying
290
+ * on probabilistic drops. `direction` is 'outgoing' for `send` and 'incoming' for
291
+ * messages arriving from the inner transport.
292
+ */
293
+ dropPredicate?: (message: SyncMessage, direction: 'outgoing' | 'incoming') => boolean;
275
294
  }
276
295
  /**
277
296
  * Chaos transport that wraps another transport and injects faults.
@@ -287,6 +306,7 @@ declare class ChaosTransport implements SyncTransport {
287
306
  private readonly reorderRate;
288
307
  private readonly maxLatency;
289
308
  private readonly random;
309
+ private readonly dropPredicate;
290
310
  private messageHandler;
291
311
  private reorderBuffer;
292
312
  private pending;
@@ -805,6 +825,13 @@ declare class OutboundQueue {
805
825
  * Acknowledge a batch, removing its operations permanently.
806
826
  */
807
827
  acknowledge(batchId: string): Promise<void>;
828
+ /**
829
+ * Acknowledge only operations in an in-flight batch whose local sequence number is
830
+ * covered by the server's ack. Any later operations return to the front of the
831
+ * queue for retry. This is required when the server accepts a prefix but rejects a
832
+ * later operation retriably (for example a stale-scope push after auth changed).
833
+ */
834
+ acknowledgeThrough(batchId: string, lastSequenceNumber: number): Promise<void>;
808
835
  /**
809
836
  * Return a failed batch to the front of the queue for retry.
810
837
  * Prepends the operations to maintain priority.
@@ -844,6 +871,14 @@ declare class OutboundQueue {
844
871
  * Whether initialize() has been called.
845
872
  */
846
873
  get isInitialized(): boolean;
874
+ /**
875
+ * Remove a single operation the server permanently rejected — from the queue,
876
+ * from any in-flight batch, and from durable storage — so a later batch ack or
877
+ * a reconnect `returnBatch` can never resend or resurrect it. Returns the
878
+ * removed operation so the caller can record it in a durable rejected store,
879
+ * or null if it was not present (already acknowledged and removed).
880
+ */
881
+ reject(opId: string): Promise<Operation | null>;
847
882
  /**
848
883
  * Remove operations by id from queue and persistent storage.
849
884
  * Used when ops were already sent during handshake delta exchange.
@@ -867,6 +902,11 @@ interface SyncEngineOptions {
867
902
  emitter?: KoraEventEmitter;
868
903
  /** Queue storage for persistent outbound queue. Defaults to in-memory. */
869
904
  queueStorage?: QueueStorage;
905
+ /**
906
+ * Durable storage for operations the server rejected. Defaults to in-memory.
907
+ * Provide a store-backed implementation so rejections survive a page refresh.
908
+ */
909
+ rejectedStorage?: RejectedOperationStorage;
870
910
  /**
871
911
  * Optional encryptor for end-to-end encryption.
872
912
  * When provided, `data` and `previousData` fields of operations are encrypted
@@ -894,6 +934,8 @@ interface SyncDiagnostics {
894
934
  pendingOperations: number;
895
935
  hasInFlightBatch: boolean;
896
936
  reconnecting: boolean;
937
+ deliveryWatermark: number;
938
+ deliveryGapRepeatCount: number;
897
939
  timestamp: number;
898
940
  }
899
941
  /**
@@ -911,7 +953,9 @@ declare class SyncEngine {
911
953
  private readonly serializer;
912
954
  private readonly emitter;
913
955
  private readonly outboundQueue;
956
+ private readonly rejectedStorage;
914
957
  private readonly batchSize;
958
+ private readonly outboundAckTimeoutMs;
915
959
  private readonly encryptor;
916
960
  private readonly awarenessManager;
917
961
  private readonly richtextDocChannel;
@@ -926,11 +970,16 @@ declare class SyncEngine {
926
970
  private lastSuccessfulPull;
927
971
  private conflictCount;
928
972
  private currentBatch;
973
+ private currentBatchRequiresPartialAck;
929
974
  private reconnecting;
930
975
  private schemaBlocked;
931
976
  private clockBlocked;
932
977
  private clockSkewMs;
933
978
  private blobStorageEnabled;
979
+ private outboundAckTimer;
980
+ private startPromise;
981
+ private stopPromise;
982
+ private reconnectPromise;
934
983
  private deltaBatchesReceived;
935
984
  private deltaReceiveComplete;
936
985
  private deltaSendComplete;
@@ -950,15 +999,38 @@ declare class SyncEngine {
950
999
  /** Resume cursor for paginated initial sync (persisted across reconnects) */
951
1000
  private resumeDeltaCursor;
952
1001
  private initialSyncTotalBatches;
1002
+ /**
1003
+ * The delivery watermark for the CURRENT sync view: the highest server delivery
1004
+ * sequence up to which every operation in the current view (scope + active query
1005
+ * subscriptions) has been contiguously applied. Advanced only through the server's
1006
+ * gap-free delivery stream (never through live relay), so it is a durable lower bound
1007
+ * on what this client holds for this view. Reported at handshake to resume the stream.
1008
+ *
1009
+ * The watermark is keyed by view: switching views (a scope or subscription change)
1010
+ * saves the current view's watermark and loads the target view's, so returning to a
1011
+ * previously-synced view resumes exactly where it left off instead of re-syncing. This
1012
+ * is correct because a view's watermark is a lower bound specific to that view's
1013
+ * filter; a widened view is simply a different (initially unsynced) view.
1014
+ */
1015
+ private deliveryWatermark;
1016
+ /**
1017
+ * In-memory cache of watermark per view signature, preloaded from persistence at start
1018
+ * so a view switch resolves synchronously (no race with the reconnect it triggers).
1019
+ */
1020
+ private readonly deliverySignatureWatermarks;
1021
+ private deliveryGapRepeatCount;
1022
+ private lastDeliveryGapKey;
953
1023
  constructor(options: SyncEngineOptions);
954
1024
  /**
955
1025
  * Start the sync engine: connect → handshake → delta exchange → streaming.
956
1026
  */
957
1027
  start(): Promise<void>;
1028
+ private startInternal;
958
1029
  /**
959
1030
  * Stop the sync engine. Disconnects the transport.
960
1031
  */
961
1032
  stop(): Promise<void>;
1033
+ private stopInternal;
962
1034
  private ensureDisconnected;
963
1035
  /**
964
1036
  * Push a local operation to the outbound queue.
@@ -1016,6 +1088,13 @@ declare class SyncEngine {
1016
1088
  * or in error state, restarts the sync. If already connected, no-op.
1017
1089
  */
1018
1090
  retryNow(): Promise<void>;
1091
+ /**
1092
+ * Atomically refresh the transport session. Overlapping callers share one
1093
+ * reconnect, in-flight outbound batches are returned to the queue by stop(), and the
1094
+ * next start handshakes with the latest auth token, sync scope, and query subsets.
1095
+ */
1096
+ reconnect(): Promise<void>;
1097
+ private reconnectInternal;
1019
1098
  /**
1020
1099
  * Export a diagnostics snapshot for debugging and support tickets.
1021
1100
  * Contains connection state, timing info, and queue metrics.
@@ -1042,6 +1121,41 @@ declare class SyncEngine {
1042
1121
  * @param scopeMap - New per-collection scope filters, or undefined to remove scope
1043
1122
  */
1044
1123
  updateScope(scopeMap: SyncScopeMap | undefined): void;
1124
+ /**
1125
+ * A stable identifier for the current sync view: the auth/tenant scope plus the set of
1126
+ * active query subscriptions, canonicalized so the same view always produces the same
1127
+ * string. The delivery watermark is keyed by this, so each distinct view resumes from
1128
+ * its own last position.
1129
+ */
1130
+ private deliverySignature;
1131
+ /**
1132
+ * Switch the active delivery watermark to the current view after a scope or subscription
1133
+ * change. The prior view's watermark is saved so returning to it resumes; the new view's
1134
+ * watermark is restored from the in-memory cache (0 for a never-synced view, which then
1135
+ * does a one-time resync under that view). No data is lost: a view's watermark is a lower
1136
+ * bound for that specific view's filter.
1137
+ */
1138
+ private switchDeliveryView;
1139
+ /**
1140
+ * Record a view's watermark in the in-memory cache as the most-recently-used entry (the
1141
+ * Map preserves insertion order, so re-inserting moves it to the tail), then evict cold
1142
+ * views past the retention cap. This is the single place the cache is written, so LRU
1143
+ * order and the size bound are always maintained together.
1144
+ */
1145
+ private setViewWatermark;
1146
+ /**
1147
+ * Trim the per-view watermark cache to `MAX_DELIVERY_VIEW_WATERMARKS`, evicting the
1148
+ * least-recently-used views. The default view ('') and the live view are never evicted
1149
+ * (evicting the live view would force an immediate re-scan of what we are actively
1150
+ * syncing). Each eviction also removes the persisted row, bounding storage. Safe by
1151
+ * construction: an evicted view simply back-fills from 0 (deduplicated) when next seen.
1152
+ */
1153
+ private evictColdViewWatermarks;
1154
+ /**
1155
+ * Remove a view's persisted watermark row (best-effort; a persistence layer that omits
1156
+ * the delete simply keeps the row, which is harmless).
1157
+ */
1158
+ private deleteDeliveryWatermark;
1045
1159
  /**
1046
1160
  * Get the currently active scope map. Returns undefined if no scope is configured.
1047
1161
  */
@@ -1105,8 +1219,29 @@ declare class SyncEngine {
1105
1219
  private markDeltaSendCompleteIfReady;
1106
1220
  private collectDelta;
1107
1221
  private handleOperationBatch;
1222
+ /** Acknowledge a delivery batch (used for duplicates) without re-applying it. */
1223
+ private sendDeliveryAck;
1108
1224
  private handleAcknowledgment;
1109
1225
  private handleError;
1226
+ /**
1227
+ * Handle a per-operation rejection: divert the op out of the pending outbound
1228
+ * queue (so it is never retried or resurrected on reconnect), record it in the
1229
+ * durable rejected store (so it is kept and explainable), and emit an event so
1230
+ * the app can reconcile. Unlike {@link handleError}, this is a normal per-op
1231
+ * signal, so the connection stays up.
1232
+ */
1233
+ private handleOperationRejected;
1234
+ /**
1235
+ * Every operation the server rejected that has not yet been reconciled. The app
1236
+ * uses this (alongside the `sync:operation-rejected` event) to surface failed
1237
+ * submissions and decide whether to roll back or resubmit.
1238
+ */
1239
+ getRejectedOperations(): Promise<RejectedOperation[]>;
1240
+ /**
1241
+ * Forget rejected operations by id once the app has reconciled them (rolled the
1242
+ * optimistic write back or resubmitted a corrected op).
1243
+ */
1244
+ clearRejectedOperations(operationIds: string[]): Promise<void>;
1110
1245
  private checkDeltaComplete;
1111
1246
  /**
1112
1247
  * Effective server vector: persisted last-ack merged with live handshake remote vector.
@@ -1127,13 +1262,20 @@ declare class SyncEngine {
1127
1262
  */
1128
1263
  private reconcileOutboundFromOpLog;
1129
1264
  private flushQueue;
1265
+ private startOutboundAckTimer;
1266
+ private clearOutboundAckTimer;
1267
+ private handleOutboundAckTimeout;
1130
1268
  private handleAwarenessUpdate;
1131
1269
  private handleTransportClose;
1132
1270
  private handleTransportError;
1133
1271
  private transitionTo;
1134
1272
  private setSerializerWireFormat;
1135
1273
  private operationAllowedForSync;
1274
+ private matchesScopeAndSubsets;
1275
+ private readRecordForBackfill;
1276
+ private filterAllowedForSync;
1136
1277
  private persistDeltaCursor;
1278
+ private persistDeliveryWatermark;
1137
1279
  private scheduleQuerySubsetReconnect;
1138
1280
  private reconnectForQuerySubsets;
1139
1281
  }
@@ -1375,4 +1517,4 @@ declare function getRemoteAwarenessStates(awareness: AwarenessManager): Awarenes
1375
1517
  */
1376
1518
  declare function subscribeRemoteAwarenessStates(awareness: AwarenessManager, listener: (states: AwarenessState[]) => void): () => void;
1377
1519
 
1378
- export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, BlobChunkChannel, type BlobChunkChannelMessage, type BlobChunkChannelOptions, BlobChunkPushMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeBlobChunkBytes, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeBlobChunkBytes, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };
1520
+ export { type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, BlobChunkChannel, type BlobChunkChannelMessage, type BlobChunkChannelOptions, BlobChunkPushMessage, BlobChunkRequestMessage, BlobChunkResponseMessage, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DEFAULT_RICHTEXT_DOC_CHANNEL_THRESHOLD, DecryptionError, DeltaCursor, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, OFFLINE_SYNC_STATUS, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, RejectedOperation, RejectedOperationStorage, RichtextDocChannel, type RichtextDocChannelOptions, type RichtextDocListener, SCHEMA_MISMATCH_PREFIX, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncQuerySubset, SyncScopeMap, SyncState, SyncStatePersistence, type SyncStatusController, type SyncStatusControllerOptions, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, YjsDocUpdateMessage, createDeltaCursorFromBatch, createSyncStatusController, decodeBlobChunkBytes, decodeDeltaCursor, decodeYjsUpdate, deriveKey, deriveVersionedKey, encodeBlobChunkBytes, encodeDeltaCursor, encodeYjsUpdate, filterOperationsByScope, generateSalt, getRemoteAwarenessStates, isClientSchemaVersionSupported, isEncryptedPayload, isSchemaMismatchReject, operationMatchesScope, richtextDocKey, sliceOperationsAfterCursor, subscribeRemoteAwarenessStates, versionVectorToWire, wireToVersionVector };