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

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-iXX0KA_W.cjs';
2
+ export { A as AcknowledgmentMessage, p as ActiveApplyFailure, q as AwarenessStateWire, r as AwarenessUpdateMessage, E as EncryptedPayload, s as ErrorMessage, H as HandshakeMessage, t as HandshakeResponseMessage, I as InvalidScopeError, M as MemoryRejectedOperationStorage, O as OperationBatchMessage, u as OperationRejectedMessage, v as SYNC_STATES, w as SYNC_STATUSES, x as ScopeViolationError, y as SyncEncryptionAlgorithm, z as SyncPhase, C as SyncScopeContext, F as SyncSettlementOptions, G as SyncSettlementResult, J as SyncStatus, K as dedupeQuerySubsets, L as isAcknowledgmentMessage, N as isAwarenessUpdateMessage, P as isBlobChunkPushMessage, U as isBlobChunkRequestMessage, X as isBlobChunkResponseMessage, Z as isErrorMessage, _ as isHandshakeMessage, $ as isHandshakeResponseMessage, a0 as isOperationBatchMessage, a1 as isOperationRejectedMessage, a2 as isSyncMessage, a3 as isYjsDocUpdateMessage, a4 as operationMatchesQuerySubsets, a5 as querySubsetContains } from './transport-iXX0KA_W.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,21 @@ 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 suspensionReason;
980
+ private authRejected;
981
+ private serverFrontier;
982
+ private hasInFlightDeliveryBatch;
983
+ private blockedFailure;
984
+ private outboundAckTimer;
985
+ private startPromise;
986
+ private stopPromise;
987
+ private reconnectPromise;
934
988
  private deltaBatchesReceived;
935
989
  private deltaReceiveComplete;
936
990
  private deltaSendComplete;
@@ -946,19 +1000,43 @@ declare class SyncEngine {
946
1000
  private activeScope;
947
1001
  /** Live query subsets registered from reactive subscriptions */
948
1002
  private querySubsets;
1003
+ private staticQuerySubsets;
949
1004
  private querySubsetReconnectTimer;
950
1005
  /** Resume cursor for paginated initial sync (persisted across reconnects) */
951
1006
  private resumeDeltaCursor;
952
1007
  private initialSyncTotalBatches;
1008
+ /**
1009
+ * The delivery watermark for the CURRENT sync view: the highest server delivery
1010
+ * sequence up to which every operation in the current view (scope + active query
1011
+ * subscriptions) has been contiguously applied. Advanced only through the server's
1012
+ * gap-free delivery stream (never through live relay), so it is a durable lower bound
1013
+ * on what this client holds for this view. Reported at handshake to resume the stream.
1014
+ *
1015
+ * The watermark is keyed by view: switching views (a scope or subscription change)
1016
+ * saves the current view's watermark and loads the target view's, so returning to a
1017
+ * previously-synced view resumes exactly where it left off instead of re-syncing. This
1018
+ * is correct because a view's watermark is a lower bound specific to that view's
1019
+ * filter; a widened view is simply a different (initially unsynced) view.
1020
+ */
1021
+ private deliveryWatermark;
1022
+ /**
1023
+ * In-memory cache of watermark per view signature, preloaded from persistence at start
1024
+ * so a view switch resolves synchronously (no race with the reconnect it triggers).
1025
+ */
1026
+ private readonly deliverySignatureWatermarks;
1027
+ private deliveryGapRepeatCount;
1028
+ private lastDeliveryGapKey;
953
1029
  constructor(options: SyncEngineOptions);
954
1030
  /**
955
1031
  * Start the sync engine: connect → handshake → delta exchange → streaming.
956
1032
  */
957
1033
  start(): Promise<void>;
1034
+ private startInternal;
958
1035
  /**
959
1036
  * Stop the sync engine. Disconnects the transport.
960
1037
  */
961
1038
  stop(): Promise<void>;
1039
+ private stopInternal;
962
1040
  private ensureDisconnected;
963
1041
  /**
964
1042
  * Push a local operation to the outbound queue.
@@ -979,6 +1057,7 @@ declare class SyncEngine {
979
1057
  * Get the current developer-facing sync status.
980
1058
  */
981
1059
  getStatus(): SyncStatusInfo;
1060
+ private resolvePhase;
982
1061
  /**
983
1062
  * True when the server rejected the client's schema version at handshake.
984
1063
  * Sync stays blocked until the app schema is upgraded or sync config changes.
@@ -1016,6 +1095,15 @@ declare class SyncEngine {
1016
1095
  * or in error state, restarts the sync. If already connected, no-op.
1017
1096
  */
1018
1097
  retryNow(): Promise<void>;
1098
+ /** Wake a permanent auth suspension after the binding reports a new auth state. */
1099
+ notifyAuthChanged(): void;
1100
+ /**
1101
+ * Atomically refresh the transport session. Overlapping callers share one
1102
+ * reconnect, in-flight outbound batches are returned to the queue by stop(), and the
1103
+ * next start handshakes with the latest auth token, sync scope, and query subsets.
1104
+ */
1105
+ reconnect(): Promise<void>;
1106
+ private reconnectInternal;
1019
1107
  /**
1020
1108
  * Export a diagnostics snapshot for debugging and support tickets.
1021
1109
  * Contains connection state, timing info, and queue metrics.
@@ -1042,6 +1130,41 @@ declare class SyncEngine {
1042
1130
  * @param scopeMap - New per-collection scope filters, or undefined to remove scope
1043
1131
  */
1044
1132
  updateScope(scopeMap: SyncScopeMap | undefined): void;
1133
+ /**
1134
+ * A stable identifier for the current sync view: the auth/tenant scope plus the set of
1135
+ * active query subscriptions, canonicalized so the same view always produces the same
1136
+ * string. The delivery watermark is keyed by this, so each distinct view resumes from
1137
+ * its own last position.
1138
+ */
1139
+ private deliverySignature;
1140
+ /**
1141
+ * Switch the active delivery watermark to the current view after a scope or subscription
1142
+ * change. The prior view's watermark is saved so returning to it resumes; the new view's
1143
+ * watermark is restored from the in-memory cache (0 for a never-synced view, which then
1144
+ * does a one-time resync under that view). No data is lost: a view's watermark is a lower
1145
+ * bound for that specific view's filter.
1146
+ */
1147
+ private switchDeliveryView;
1148
+ /**
1149
+ * Record a view's watermark in the in-memory cache as the most-recently-used entry (the
1150
+ * Map preserves insertion order, so re-inserting moves it to the tail), then evict cold
1151
+ * views past the retention cap. This is the single place the cache is written, so LRU
1152
+ * order and the size bound are always maintained together.
1153
+ */
1154
+ private setViewWatermark;
1155
+ /**
1156
+ * Trim the per-view watermark cache to `MAX_DELIVERY_VIEW_WATERMARKS`, evicting the
1157
+ * least-recently-used views. The default view ('') and the live view are never evicted
1158
+ * (evicting the live view would force an immediate re-scan of what we are actively
1159
+ * syncing). Each eviction also removes the persisted row, bounding storage. Safe by
1160
+ * construction: an evicted view simply back-fills from 0 (deduplicated) when next seen.
1161
+ */
1162
+ private evictColdViewWatermarks;
1163
+ /**
1164
+ * Remove a view's persisted watermark row (best-effort; a persistence layer that omits
1165
+ * the delete simply keeps the row, which is harmless).
1166
+ */
1167
+ private deleteDeliveryWatermark;
1045
1168
  /**
1046
1169
  * Get the currently active scope map. Returns undefined if no scope is configured.
1047
1170
  */
@@ -1051,6 +1174,9 @@ declare class SyncEngine {
1051
1174
  * Takes effect on the next connection; reconnects when already connected.
1052
1175
  */
1053
1176
  registerQuerySubset(subset: SyncQuerySubset): () => void;
1177
+ private switchDeliveryViewWithInheritance;
1178
+ /** Atomically replace the manifest used by static query-subset mode. */
1179
+ setQuerySubsets(subsets: SyncQuerySubset[]): void;
1054
1180
  /**
1055
1181
  * Returns deduplicated active query subsets from registered subscriptions.
1056
1182
  */
@@ -1105,8 +1231,29 @@ declare class SyncEngine {
1105
1231
  private markDeltaSendCompleteIfReady;
1106
1232
  private collectDelta;
1107
1233
  private handleOperationBatch;
1234
+ /** Acknowledge a delivery batch (used for duplicates) without re-applying it. */
1235
+ private sendDeliveryAck;
1108
1236
  private handleAcknowledgment;
1109
1237
  private handleError;
1238
+ /**
1239
+ * Handle a per-operation rejection: divert the op out of the pending outbound
1240
+ * queue (so it is never retried or resurrected on reconnect), record it in the
1241
+ * durable rejected store (so it is kept and explainable), and emit an event so
1242
+ * the app can reconcile. Unlike {@link handleError}, this is a normal per-op
1243
+ * signal, so the connection stays up.
1244
+ */
1245
+ private handleOperationRejected;
1246
+ /**
1247
+ * Every operation the server rejected that has not yet been reconciled. The app
1248
+ * uses this (alongside the `sync:operation-rejected` event) to surface failed
1249
+ * submissions and decide whether to roll back or resubmit.
1250
+ */
1251
+ getRejectedOperations(): Promise<RejectedOperation[]>;
1252
+ /**
1253
+ * Forget rejected operations by id once the app has reconciled them (rolled the
1254
+ * optimistic write back or resubmitted a corrected op).
1255
+ */
1256
+ clearRejectedOperations(operationIds: string[]): Promise<void>;
1110
1257
  private checkDeltaComplete;
1111
1258
  /**
1112
1259
  * Effective server vector: persisted last-ack merged with live handshake remote vector.
@@ -1127,13 +1274,20 @@ declare class SyncEngine {
1127
1274
  */
1128
1275
  private reconcileOutboundFromOpLog;
1129
1276
  private flushQueue;
1277
+ private startOutboundAckTimer;
1278
+ private clearOutboundAckTimer;
1279
+ private handleOutboundAckTimeout;
1130
1280
  private handleAwarenessUpdate;
1131
1281
  private handleTransportClose;
1132
1282
  private handleTransportError;
1133
1283
  private transitionTo;
1134
1284
  private setSerializerWireFormat;
1135
1285
  private operationAllowedForSync;
1286
+ private matchesScopeAndSubsets;
1287
+ private readRecordForBackfill;
1288
+ private filterAllowedForSync;
1136
1289
  private persistDeltaCursor;
1290
+ private persistDeliveryWatermark;
1137
1291
  private scheduleQuerySubsetReconnect;
1138
1292
  private reconnectForQuerySubsets;
1139
1293
  }
@@ -1375,4 +1529,4 @@ declare function getRemoteAwarenessStates(awareness: AwarenessManager): Awarenes
1375
1529
  */
1376
1530
  declare function subscribeRemoteAwarenessStates(awareness: AwarenessManager, listener: (states: AwarenessState[]) => void): () => void;
1377
1531
 
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 };
1532
+ 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-iXX0KA_W.js';
2
+ export { A as AcknowledgmentMessage, p as ActiveApplyFailure, q as AwarenessStateWire, r as AwarenessUpdateMessage, E as EncryptedPayload, s as ErrorMessage, H as HandshakeMessage, t as HandshakeResponseMessage, I as InvalidScopeError, M as MemoryRejectedOperationStorage, O as OperationBatchMessage, u as OperationRejectedMessage, v as SYNC_STATES, w as SYNC_STATUSES, x as ScopeViolationError, y as SyncEncryptionAlgorithm, z as SyncPhase, C as SyncScopeContext, F as SyncSettlementOptions, G as SyncSettlementResult, J as SyncStatus, K as dedupeQuerySubsets, L as isAcknowledgmentMessage, N as isAwarenessUpdateMessage, P as isBlobChunkPushMessage, U as isBlobChunkRequestMessage, X as isBlobChunkResponseMessage, Z as isErrorMessage, _ as isHandshakeMessage, $ as isHandshakeResponseMessage, a0 as isOperationBatchMessage, a1 as isOperationRejectedMessage, a2 as isSyncMessage, a3 as isYjsDocUpdateMessage, a4 as operationMatchesQuerySubsets, a5 as querySubsetContains } from './transport-iXX0KA_W.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,21 @@ 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 suspensionReason;
980
+ private authRejected;
981
+ private serverFrontier;
982
+ private hasInFlightDeliveryBatch;
983
+ private blockedFailure;
984
+ private outboundAckTimer;
985
+ private startPromise;
986
+ private stopPromise;
987
+ private reconnectPromise;
934
988
  private deltaBatchesReceived;
935
989
  private deltaReceiveComplete;
936
990
  private deltaSendComplete;
@@ -946,19 +1000,43 @@ declare class SyncEngine {
946
1000
  private activeScope;
947
1001
  /** Live query subsets registered from reactive subscriptions */
948
1002
  private querySubsets;
1003
+ private staticQuerySubsets;
949
1004
  private querySubsetReconnectTimer;
950
1005
  /** Resume cursor for paginated initial sync (persisted across reconnects) */
951
1006
  private resumeDeltaCursor;
952
1007
  private initialSyncTotalBatches;
1008
+ /**
1009
+ * The delivery watermark for the CURRENT sync view: the highest server delivery
1010
+ * sequence up to which every operation in the current view (scope + active query
1011
+ * subscriptions) has been contiguously applied. Advanced only through the server's
1012
+ * gap-free delivery stream (never through live relay), so it is a durable lower bound
1013
+ * on what this client holds for this view. Reported at handshake to resume the stream.
1014
+ *
1015
+ * The watermark is keyed by view: switching views (a scope or subscription change)
1016
+ * saves the current view's watermark and loads the target view's, so returning to a
1017
+ * previously-synced view resumes exactly where it left off instead of re-syncing. This
1018
+ * is correct because a view's watermark is a lower bound specific to that view's
1019
+ * filter; a widened view is simply a different (initially unsynced) view.
1020
+ */
1021
+ private deliveryWatermark;
1022
+ /**
1023
+ * In-memory cache of watermark per view signature, preloaded from persistence at start
1024
+ * so a view switch resolves synchronously (no race with the reconnect it triggers).
1025
+ */
1026
+ private readonly deliverySignatureWatermarks;
1027
+ private deliveryGapRepeatCount;
1028
+ private lastDeliveryGapKey;
953
1029
  constructor(options: SyncEngineOptions);
954
1030
  /**
955
1031
  * Start the sync engine: connect → handshake → delta exchange → streaming.
956
1032
  */
957
1033
  start(): Promise<void>;
1034
+ private startInternal;
958
1035
  /**
959
1036
  * Stop the sync engine. Disconnects the transport.
960
1037
  */
961
1038
  stop(): Promise<void>;
1039
+ private stopInternal;
962
1040
  private ensureDisconnected;
963
1041
  /**
964
1042
  * Push a local operation to the outbound queue.
@@ -979,6 +1057,7 @@ declare class SyncEngine {
979
1057
  * Get the current developer-facing sync status.
980
1058
  */
981
1059
  getStatus(): SyncStatusInfo;
1060
+ private resolvePhase;
982
1061
  /**
983
1062
  * True when the server rejected the client's schema version at handshake.
984
1063
  * Sync stays blocked until the app schema is upgraded or sync config changes.
@@ -1016,6 +1095,15 @@ declare class SyncEngine {
1016
1095
  * or in error state, restarts the sync. If already connected, no-op.
1017
1096
  */
1018
1097
  retryNow(): Promise<void>;
1098
+ /** Wake a permanent auth suspension after the binding reports a new auth state. */
1099
+ notifyAuthChanged(): void;
1100
+ /**
1101
+ * Atomically refresh the transport session. Overlapping callers share one
1102
+ * reconnect, in-flight outbound batches are returned to the queue by stop(), and the
1103
+ * next start handshakes with the latest auth token, sync scope, and query subsets.
1104
+ */
1105
+ reconnect(): Promise<void>;
1106
+ private reconnectInternal;
1019
1107
  /**
1020
1108
  * Export a diagnostics snapshot for debugging and support tickets.
1021
1109
  * Contains connection state, timing info, and queue metrics.
@@ -1042,6 +1130,41 @@ declare class SyncEngine {
1042
1130
  * @param scopeMap - New per-collection scope filters, or undefined to remove scope
1043
1131
  */
1044
1132
  updateScope(scopeMap: SyncScopeMap | undefined): void;
1133
+ /**
1134
+ * A stable identifier for the current sync view: the auth/tenant scope plus the set of
1135
+ * active query subscriptions, canonicalized so the same view always produces the same
1136
+ * string. The delivery watermark is keyed by this, so each distinct view resumes from
1137
+ * its own last position.
1138
+ */
1139
+ private deliverySignature;
1140
+ /**
1141
+ * Switch the active delivery watermark to the current view after a scope or subscription
1142
+ * change. The prior view's watermark is saved so returning to it resumes; the new view's
1143
+ * watermark is restored from the in-memory cache (0 for a never-synced view, which then
1144
+ * does a one-time resync under that view). No data is lost: a view's watermark is a lower
1145
+ * bound for that specific view's filter.
1146
+ */
1147
+ private switchDeliveryView;
1148
+ /**
1149
+ * Record a view's watermark in the in-memory cache as the most-recently-used entry (the
1150
+ * Map preserves insertion order, so re-inserting moves it to the tail), then evict cold
1151
+ * views past the retention cap. This is the single place the cache is written, so LRU
1152
+ * order and the size bound are always maintained together.
1153
+ */
1154
+ private setViewWatermark;
1155
+ /**
1156
+ * Trim the per-view watermark cache to `MAX_DELIVERY_VIEW_WATERMARKS`, evicting the
1157
+ * least-recently-used views. The default view ('') and the live view are never evicted
1158
+ * (evicting the live view would force an immediate re-scan of what we are actively
1159
+ * syncing). Each eviction also removes the persisted row, bounding storage. Safe by
1160
+ * construction: an evicted view simply back-fills from 0 (deduplicated) when next seen.
1161
+ */
1162
+ private evictColdViewWatermarks;
1163
+ /**
1164
+ * Remove a view's persisted watermark row (best-effort; a persistence layer that omits
1165
+ * the delete simply keeps the row, which is harmless).
1166
+ */
1167
+ private deleteDeliveryWatermark;
1045
1168
  /**
1046
1169
  * Get the currently active scope map. Returns undefined if no scope is configured.
1047
1170
  */
@@ -1051,6 +1174,9 @@ declare class SyncEngine {
1051
1174
  * Takes effect on the next connection; reconnects when already connected.
1052
1175
  */
1053
1176
  registerQuerySubset(subset: SyncQuerySubset): () => void;
1177
+ private switchDeliveryViewWithInheritance;
1178
+ /** Atomically replace the manifest used by static query-subset mode. */
1179
+ setQuerySubsets(subsets: SyncQuerySubset[]): void;
1054
1180
  /**
1055
1181
  * Returns deduplicated active query subsets from registered subscriptions.
1056
1182
  */
@@ -1105,8 +1231,29 @@ declare class SyncEngine {
1105
1231
  private markDeltaSendCompleteIfReady;
1106
1232
  private collectDelta;
1107
1233
  private handleOperationBatch;
1234
+ /** Acknowledge a delivery batch (used for duplicates) without re-applying it. */
1235
+ private sendDeliveryAck;
1108
1236
  private handleAcknowledgment;
1109
1237
  private handleError;
1238
+ /**
1239
+ * Handle a per-operation rejection: divert the op out of the pending outbound
1240
+ * queue (so it is never retried or resurrected on reconnect), record it in the
1241
+ * durable rejected store (so it is kept and explainable), and emit an event so
1242
+ * the app can reconcile. Unlike {@link handleError}, this is a normal per-op
1243
+ * signal, so the connection stays up.
1244
+ */
1245
+ private handleOperationRejected;
1246
+ /**
1247
+ * Every operation the server rejected that has not yet been reconciled. The app
1248
+ * uses this (alongside the `sync:operation-rejected` event) to surface failed
1249
+ * submissions and decide whether to roll back or resubmit.
1250
+ */
1251
+ getRejectedOperations(): Promise<RejectedOperation[]>;
1252
+ /**
1253
+ * Forget rejected operations by id once the app has reconciled them (rolled the
1254
+ * optimistic write back or resubmitted a corrected op).
1255
+ */
1256
+ clearRejectedOperations(operationIds: string[]): Promise<void>;
1110
1257
  private checkDeltaComplete;
1111
1258
  /**
1112
1259
  * Effective server vector: persisted last-ack merged with live handshake remote vector.
@@ -1127,13 +1274,20 @@ declare class SyncEngine {
1127
1274
  */
1128
1275
  private reconcileOutboundFromOpLog;
1129
1276
  private flushQueue;
1277
+ private startOutboundAckTimer;
1278
+ private clearOutboundAckTimer;
1279
+ private handleOutboundAckTimeout;
1130
1280
  private handleAwarenessUpdate;
1131
1281
  private handleTransportClose;
1132
1282
  private handleTransportError;
1133
1283
  private transitionTo;
1134
1284
  private setSerializerWireFormat;
1135
1285
  private operationAllowedForSync;
1286
+ private matchesScopeAndSubsets;
1287
+ private readRecordForBackfill;
1288
+ private filterAllowedForSync;
1136
1289
  private persistDeltaCursor;
1290
+ private persistDeliveryWatermark;
1137
1291
  private scheduleQuerySubsetReconnect;
1138
1292
  private reconnectForQuerySubsets;
1139
1293
  }
@@ -1375,4 +1529,4 @@ declare function getRemoteAwarenessStates(awareness: AwarenessManager): Awarenes
1375
1529
  */
1376
1530
  declare function subscribeRemoteAwarenessStates(awareness: AwarenessManager, listener: (states: AwarenessState[]) => void): () => void;
1377
1531
 
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 };
1532
+ 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 };