@korajs/sync 1.0.0-beta.7 → 1.0.0-beta.9
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.cjs +102 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +102 -9
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/{transport-ByWfWBbB.d.cts → transport-B2GQerqJ.d.cts} +3 -1
- package/dist/{transport-ByWfWBbB.d.ts → transport-B2GQerqJ.d.ts} +3 -1
- package/package.json +3 -3
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, R as RejectedOperationStorage, m as SyncStatePersistence, n as SyncQuerySubset, o as RejectedOperation } from './transport-
|
|
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-
|
|
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';
|
|
@@ -825,6 +825,13 @@ declare class OutboundQueue {
|
|
|
825
825
|
* Acknowledge a batch, removing its operations permanently.
|
|
826
826
|
*/
|
|
827
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>;
|
|
828
835
|
/**
|
|
829
836
|
* Return a failed batch to the front of the queue for retry.
|
|
830
837
|
* Prepends the operations to maintain priority.
|
|
@@ -927,6 +934,8 @@ interface SyncDiagnostics {
|
|
|
927
934
|
pendingOperations: number;
|
|
928
935
|
hasInFlightBatch: boolean;
|
|
929
936
|
reconnecting: boolean;
|
|
937
|
+
deliveryWatermark: number;
|
|
938
|
+
deliveryGapRepeatCount: number;
|
|
930
939
|
timestamp: number;
|
|
931
940
|
}
|
|
932
941
|
/**
|
|
@@ -961,6 +970,7 @@ declare class SyncEngine {
|
|
|
961
970
|
private lastSuccessfulPull;
|
|
962
971
|
private conflictCount;
|
|
963
972
|
private currentBatch;
|
|
973
|
+
private currentBatchRequiresPartialAck;
|
|
964
974
|
private reconnecting;
|
|
965
975
|
private schemaBlocked;
|
|
966
976
|
private clockBlocked;
|
|
@@ -969,6 +979,7 @@ declare class SyncEngine {
|
|
|
969
979
|
private outboundAckTimer;
|
|
970
980
|
private startPromise;
|
|
971
981
|
private stopPromise;
|
|
982
|
+
private reconnectPromise;
|
|
972
983
|
private deltaBatchesReceived;
|
|
973
984
|
private deltaReceiveComplete;
|
|
974
985
|
private deltaSendComplete;
|
|
@@ -1007,6 +1018,8 @@ declare class SyncEngine {
|
|
|
1007
1018
|
* so a view switch resolves synchronously (no race with the reconnect it triggers).
|
|
1008
1019
|
*/
|
|
1009
1020
|
private readonly deliverySignatureWatermarks;
|
|
1021
|
+
private deliveryGapRepeatCount;
|
|
1022
|
+
private lastDeliveryGapKey;
|
|
1010
1023
|
constructor(options: SyncEngineOptions);
|
|
1011
1024
|
/**
|
|
1012
1025
|
* Start the sync engine: connect → handshake → delta exchange → streaming.
|
|
@@ -1075,6 +1088,13 @@ declare class SyncEngine {
|
|
|
1075
1088
|
* or in error state, restarts the sync. If already connected, no-op.
|
|
1076
1089
|
*/
|
|
1077
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;
|
|
1078
1098
|
/**
|
|
1079
1099
|
* Export a diagnostics snapshot for debugging and support tickets.
|
|
1080
1100
|
* Contains connection state, timing info, and queue metrics.
|
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, R as RejectedOperationStorage, m as SyncStatePersistence, n as SyncQuerySubset, o as RejectedOperation } from './transport-
|
|
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-
|
|
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';
|
|
@@ -825,6 +825,13 @@ declare class OutboundQueue {
|
|
|
825
825
|
* Acknowledge a batch, removing its operations permanently.
|
|
826
826
|
*/
|
|
827
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>;
|
|
828
835
|
/**
|
|
829
836
|
* Return a failed batch to the front of the queue for retry.
|
|
830
837
|
* Prepends the operations to maintain priority.
|
|
@@ -927,6 +934,8 @@ interface SyncDiagnostics {
|
|
|
927
934
|
pendingOperations: number;
|
|
928
935
|
hasInFlightBatch: boolean;
|
|
929
936
|
reconnecting: boolean;
|
|
937
|
+
deliveryWatermark: number;
|
|
938
|
+
deliveryGapRepeatCount: number;
|
|
930
939
|
timestamp: number;
|
|
931
940
|
}
|
|
932
941
|
/**
|
|
@@ -961,6 +970,7 @@ declare class SyncEngine {
|
|
|
961
970
|
private lastSuccessfulPull;
|
|
962
971
|
private conflictCount;
|
|
963
972
|
private currentBatch;
|
|
973
|
+
private currentBatchRequiresPartialAck;
|
|
964
974
|
private reconnecting;
|
|
965
975
|
private schemaBlocked;
|
|
966
976
|
private clockBlocked;
|
|
@@ -969,6 +979,7 @@ declare class SyncEngine {
|
|
|
969
979
|
private outboundAckTimer;
|
|
970
980
|
private startPromise;
|
|
971
981
|
private stopPromise;
|
|
982
|
+
private reconnectPromise;
|
|
972
983
|
private deltaBatchesReceived;
|
|
973
984
|
private deltaReceiveComplete;
|
|
974
985
|
private deltaSendComplete;
|
|
@@ -1007,6 +1018,8 @@ declare class SyncEngine {
|
|
|
1007
1018
|
* so a view switch resolves synchronously (no race with the reconnect it triggers).
|
|
1008
1019
|
*/
|
|
1009
1020
|
private readonly deliverySignatureWatermarks;
|
|
1021
|
+
private deliveryGapRepeatCount;
|
|
1022
|
+
private lastDeliveryGapKey;
|
|
1010
1023
|
constructor(options: SyncEngineOptions);
|
|
1011
1024
|
/**
|
|
1012
1025
|
* Start the sync engine: connect → handshake → delta exchange → streaming.
|
|
@@ -1075,6 +1088,13 @@ declare class SyncEngine {
|
|
|
1075
1088
|
* or in error state, restarts the sync. If already connected, no-op.
|
|
1076
1089
|
*/
|
|
1077
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;
|
|
1078
1098
|
/**
|
|
1079
1099
|
* Export a diagnostics snapshot for debugging and support tickets.
|
|
1080
1100
|
* Contains connection state, timing info, and queue metrics.
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ var SYNC_STATES = [
|
|
|
14
14
|
];
|
|
15
15
|
var SYNC_STATUSES = [
|
|
16
16
|
"connected",
|
|
17
|
+
"reconnecting",
|
|
17
18
|
"syncing",
|
|
18
19
|
"synced",
|
|
19
20
|
"offline",
|
|
@@ -2500,6 +2501,32 @@ var OutboundQueue = class {
|
|
|
2500
2501
|
const ids = ops.map((op) => op.id);
|
|
2501
2502
|
await this.storage.dequeue(ids);
|
|
2502
2503
|
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Acknowledge only operations in an in-flight batch whose local sequence number is
|
|
2506
|
+
* covered by the server's ack. Any later operations return to the front of the
|
|
2507
|
+
* queue for retry. This is required when the server accepts a prefix but rejects a
|
|
2508
|
+
* later operation retriably (for example a stale-scope push after auth changed).
|
|
2509
|
+
*/
|
|
2510
|
+
async acknowledgeThrough(batchId, lastSequenceNumber) {
|
|
2511
|
+
const ops = this.inFlight.get(batchId);
|
|
2512
|
+
if (!ops) return;
|
|
2513
|
+
this.inFlight.delete(batchId);
|
|
2514
|
+
const acknowledged = ops.filter((op) => op.sequenceNumber <= lastSequenceNumber);
|
|
2515
|
+
const retry = ops.filter((op) => op.sequenceNumber > lastSequenceNumber);
|
|
2516
|
+
if (retry.length > 0) {
|
|
2517
|
+
this.queue.unshift(...retry);
|
|
2518
|
+
if (this.queue.length > 1) {
|
|
2519
|
+
this.queue = topologicalSort(this.queue);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
if (acknowledged.length > 0) {
|
|
2523
|
+
const ids = acknowledged.map((op) => op.id);
|
|
2524
|
+
for (const id of ids) {
|
|
2525
|
+
this.seen.delete(id);
|
|
2526
|
+
}
|
|
2527
|
+
await this.storage.dequeue(ids);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2503
2530
|
/**
|
|
2504
2531
|
* Return a failed batch to the front of the queue for retry.
|
|
2505
2532
|
* Prepends the operations to maintain priority.
|
|
@@ -2681,6 +2708,7 @@ var SyncEngine = class {
|
|
|
2681
2708
|
lastSuccessfulPull = null;
|
|
2682
2709
|
conflictCount = 0;
|
|
2683
2710
|
currentBatch = null;
|
|
2711
|
+
currentBatchRequiresPartialAck = false;
|
|
2684
2712
|
reconnecting = false;
|
|
2685
2713
|
schemaBlocked = false;
|
|
2686
2714
|
clockBlocked = false;
|
|
@@ -2689,6 +2717,7 @@ var SyncEngine = class {
|
|
|
2689
2717
|
outboundAckTimer = null;
|
|
2690
2718
|
startPromise = null;
|
|
2691
2719
|
stopPromise = null;
|
|
2720
|
+
reconnectPromise = null;
|
|
2692
2721
|
// Track delta exchange state
|
|
2693
2722
|
deltaBatchesReceived = 0;
|
|
2694
2723
|
deltaReceiveComplete = false;
|
|
@@ -2728,6 +2757,8 @@ var SyncEngine = class {
|
|
|
2728
2757
|
* so a view switch resolves synchronously (no race with the reconnect it triggers).
|
|
2729
2758
|
*/
|
|
2730
2759
|
deliverySignatureWatermarks = /* @__PURE__ */ new Map();
|
|
2760
|
+
deliveryGapRepeatCount = 0;
|
|
2761
|
+
lastDeliveryGapKey = null;
|
|
2731
2762
|
constructor(options) {
|
|
2732
2763
|
this.transport = options.transport;
|
|
2733
2764
|
this.store = options.store;
|
|
@@ -2875,6 +2906,7 @@ var SyncEngine = class {
|
|
|
2875
2906
|
if (this.currentBatch) {
|
|
2876
2907
|
this.outboundQueue.returnBatch(this.currentBatch.batchId);
|
|
2877
2908
|
this.currentBatch = null;
|
|
2909
|
+
this.currentBatchRequiresPartialAck = false;
|
|
2878
2910
|
}
|
|
2879
2911
|
try {
|
|
2880
2912
|
await this.transport.disconnect();
|
|
@@ -2924,6 +2956,7 @@ var SyncEngine = class {
|
|
|
2924
2956
|
getStatus() {
|
|
2925
2957
|
const pendingOperations = this.syncState ? this.cachedUnsyncedCount : this.outboundQueue.totalPending;
|
|
2926
2958
|
const base = {
|
|
2959
|
+
reconnecting: this.reconnecting,
|
|
2927
2960
|
pendingOperations,
|
|
2928
2961
|
lastSyncedAt: this.lastSyncedAt,
|
|
2929
2962
|
lastSuccessfulPush: this.lastSuccessfulPush,
|
|
@@ -2943,7 +2976,7 @@ var SyncEngine = class {
|
|
|
2943
2976
|
case "connecting":
|
|
2944
2977
|
case "handshaking":
|
|
2945
2978
|
case "syncing":
|
|
2946
|
-
return { ...base, status: this.reconnecting ? "
|
|
2979
|
+
return { ...base, status: this.reconnecting ? "reconnecting" : "syncing" };
|
|
2947
2980
|
case "streaming":
|
|
2948
2981
|
return { ...base, status: pendingOperations > 0 ? "syncing" : "synced" };
|
|
2949
2982
|
case "error":
|
|
@@ -3028,6 +3061,24 @@ var SyncEngine = class {
|
|
|
3028
3061
|
await this.start();
|
|
3029
3062
|
}
|
|
3030
3063
|
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Atomically refresh the transport session. Overlapping callers share one
|
|
3066
|
+
* reconnect, in-flight outbound batches are returned to the queue by stop(), and the
|
|
3067
|
+
* next start handshakes with the latest auth token, sync scope, and query subsets.
|
|
3068
|
+
*/
|
|
3069
|
+
async reconnect() {
|
|
3070
|
+
if (this.schemaBlocked) return;
|
|
3071
|
+
if (this.reconnectPromise) return this.reconnectPromise;
|
|
3072
|
+
this.reconnecting = false;
|
|
3073
|
+
this.reconnectPromise = this.reconnectInternal().finally(() => {
|
|
3074
|
+
this.reconnectPromise = null;
|
|
3075
|
+
});
|
|
3076
|
+
return this.reconnectPromise;
|
|
3077
|
+
}
|
|
3078
|
+
async reconnectInternal() {
|
|
3079
|
+
await this.stop();
|
|
3080
|
+
await this.start();
|
|
3081
|
+
}
|
|
3031
3082
|
/**
|
|
3032
3083
|
* Export a diagnostics snapshot for debugging and support tickets.
|
|
3033
3084
|
* Contains connection state, timing info, and queue metrics.
|
|
@@ -3046,6 +3097,8 @@ var SyncEngine = class {
|
|
|
3046
3097
|
pendingOperations: this.outboundQueue.totalPending,
|
|
3047
3098
|
hasInFlightBatch: this.currentBatch !== null,
|
|
3048
3099
|
reconnecting: this.reconnecting,
|
|
3100
|
+
deliveryWatermark: this.deliveryWatermark,
|
|
3101
|
+
deliveryGapRepeatCount: this.deliveryGapRepeatCount,
|
|
3049
3102
|
timestamp: Date.now()
|
|
3050
3103
|
};
|
|
3051
3104
|
}
|
|
@@ -3235,7 +3288,7 @@ var SyncEngine = class {
|
|
|
3235
3288
|
await this.handleOperationBatch(message);
|
|
3236
3289
|
break;
|
|
3237
3290
|
case "acknowledgment":
|
|
3238
|
-
this.handleAcknowledgment(message);
|
|
3291
|
+
await this.handleAcknowledgment(message);
|
|
3239
3292
|
break;
|
|
3240
3293
|
case "error":
|
|
3241
3294
|
this.handleError(message);
|
|
@@ -3474,6 +3527,17 @@ var SyncEngine = class {
|
|
|
3474
3527
|
if (isDeliveryBatch) {
|
|
3475
3528
|
const base = msg.baseDeliverySequence ?? 0;
|
|
3476
3529
|
if (base > this.deliveryWatermark) {
|
|
3530
|
+
const gapKey = `${this.deliveryWatermark}:${base}`;
|
|
3531
|
+
this.deliveryGapRepeatCount = this.lastDeliveryGapKey === gapKey ? this.deliveryGapRepeatCount + 1 : 1;
|
|
3532
|
+
this.lastDeliveryGapKey = gapKey;
|
|
3533
|
+
this.emitter?.emit({
|
|
3534
|
+
type: "sync:delivery-gap",
|
|
3535
|
+
expectedBase: this.deliveryWatermark,
|
|
3536
|
+
receivedBase: base,
|
|
3537
|
+
currentWatermark: this.deliveryWatermark,
|
|
3538
|
+
messageId: msg.messageId,
|
|
3539
|
+
repeatCount: this.deliveryGapRepeatCount
|
|
3540
|
+
});
|
|
3477
3541
|
return;
|
|
3478
3542
|
}
|
|
3479
3543
|
if (base < this.deliveryWatermark) {
|
|
@@ -3538,6 +3602,8 @@ var SyncEngine = class {
|
|
|
3538
3602
|
});
|
|
3539
3603
|
}
|
|
3540
3604
|
if (isDeliveryBatch && fullyApplied && msg.maxDeliverySequence !== void 0) {
|
|
3605
|
+
this.deliveryGapRepeatCount = 0;
|
|
3606
|
+
this.lastDeliveryGapKey = null;
|
|
3541
3607
|
this.deliveryWatermark = msg.maxDeliverySequence;
|
|
3542
3608
|
this.setViewWatermark(this.deliverySignature(), this.deliveryWatermark);
|
|
3543
3609
|
await this.persistDeliveryWatermark(this.deliveryWatermark);
|
|
@@ -3579,22 +3645,29 @@ var SyncEngine = class {
|
|
|
3579
3645
|
};
|
|
3580
3646
|
this.transport.send(ack);
|
|
3581
3647
|
}
|
|
3582
|
-
handleAcknowledgment(msg) {
|
|
3648
|
+
async handleAcknowledgment(msg) {
|
|
3583
3649
|
this.clearOutboundAckTimer();
|
|
3584
3650
|
if (this.state === "syncing" && this.config.strictHandshake) {
|
|
3585
3651
|
this.pendingDeltaBatchAcks.delete(msg.acknowledgedMessageId);
|
|
3586
3652
|
this.markDeltaSendCompleteIfReady();
|
|
3587
3653
|
}
|
|
3588
3654
|
if (this.currentBatch) {
|
|
3589
|
-
|
|
3655
|
+
if (this.currentBatchRequiresPartialAck) {
|
|
3656
|
+
await this.outboundQueue.acknowledgeThrough(
|
|
3657
|
+
this.currentBatch.batchId,
|
|
3658
|
+
msg.lastSequenceNumber
|
|
3659
|
+
);
|
|
3660
|
+
} else {
|
|
3661
|
+
await this.outboundQueue.acknowledge(this.currentBatch.batchId);
|
|
3662
|
+
}
|
|
3590
3663
|
this.currentBatch = null;
|
|
3664
|
+
this.currentBatchRequiresPartialAck = false;
|
|
3591
3665
|
const now = Date.now();
|
|
3592
3666
|
this.lastSyncedAt = now;
|
|
3593
3667
|
this.lastSuccessfulPush = now;
|
|
3594
3668
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
);
|
|
3669
|
+
await this.advanceLastAckedForLocalNode(msg.lastSequenceNumber);
|
|
3670
|
+
await this.refreshPendingCount();
|
|
3598
3671
|
if (this.state === "streaming" && this.outboundQueue.hasOperations) {
|
|
3599
3672
|
this.flushQueue();
|
|
3600
3673
|
}
|
|
@@ -3604,6 +3677,7 @@ var SyncEngine = class {
|
|
|
3604
3677
|
if (this.currentBatch) {
|
|
3605
3678
|
this.outboundQueue.returnBatch(this.currentBatch.batchId);
|
|
3606
3679
|
this.currentBatch = null;
|
|
3680
|
+
this.currentBatchRequiresPartialAck = false;
|
|
3607
3681
|
}
|
|
3608
3682
|
this.transitionTo("error");
|
|
3609
3683
|
if (msg.code === "AUTH_FAILED") {
|
|
@@ -3631,6 +3705,20 @@ var SyncEngine = class {
|
|
|
3631
3705
|
* signal, so the connection stays up.
|
|
3632
3706
|
*/
|
|
3633
3707
|
async handleOperationRejected(msg) {
|
|
3708
|
+
if (msg.retriable) {
|
|
3709
|
+
this.currentBatchRequiresPartialAck = true;
|
|
3710
|
+
await this.refreshPendingCount();
|
|
3711
|
+
this.emitter?.emit({
|
|
3712
|
+
type: "sync:operation-rejected",
|
|
3713
|
+
operationId: msg.operationId,
|
|
3714
|
+
collection: msg.collection,
|
|
3715
|
+
recordId: msg.recordId,
|
|
3716
|
+
code: msg.code,
|
|
3717
|
+
message: msg.message,
|
|
3718
|
+
retriable: true
|
|
3719
|
+
});
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3634
3722
|
await this.outboundQueue.reject(msg.operationId);
|
|
3635
3723
|
await this.rejectedStorage.record({
|
|
3636
3724
|
operationId: msg.operationId,
|
|
@@ -3776,6 +3864,7 @@ var SyncEngine = class {
|
|
|
3776
3864
|
const batch = this.outboundQueue.takeBatch(this.batchSize);
|
|
3777
3865
|
if (!batch) return;
|
|
3778
3866
|
this.currentBatch = batch;
|
|
3867
|
+
this.currentBatchRequiresPartialAck = false;
|
|
3779
3868
|
if (this.encryptor) {
|
|
3780
3869
|
this.encryptor.encryptBatch(batch.operations).then(
|
|
3781
3870
|
(encrypted) => {
|
|
@@ -3798,6 +3887,7 @@ var SyncEngine = class {
|
|
|
3798
3887
|
(err) => {
|
|
3799
3888
|
this.outboundQueue.returnBatch(batch.batchId);
|
|
3800
3889
|
this.currentBatch = null;
|
|
3890
|
+
this.currentBatchRequiresPartialAck = false;
|
|
3801
3891
|
this.emitter?.emit({
|
|
3802
3892
|
type: "sync:disconnected",
|
|
3803
3893
|
reason: err instanceof Error ? err.message : "Encryption failed"
|
|
@@ -3838,6 +3928,7 @@ var SyncEngine = class {
|
|
|
3838
3928
|
if (!this.currentBatch || this.currentBatch.batchId !== batchId) return;
|
|
3839
3929
|
this.outboundQueue.returnBatch(batchId);
|
|
3840
3930
|
this.currentBatch = null;
|
|
3931
|
+
this.currentBatchRequiresPartialAck = false;
|
|
3841
3932
|
this.clearOutboundAckTimer();
|
|
3842
3933
|
await this.refreshPendingCount();
|
|
3843
3934
|
if (this.state === "disconnected") return;
|
|
@@ -3862,6 +3953,7 @@ var SyncEngine = class {
|
|
|
3862
3953
|
if (this.currentBatch) {
|
|
3863
3954
|
this.outboundQueue.returnBatch(this.currentBatch.batchId);
|
|
3864
3955
|
this.currentBatch = null;
|
|
3956
|
+
this.currentBatchRequiresPartialAck = false;
|
|
3865
3957
|
}
|
|
3866
3958
|
if (this.schemaBlocked) {
|
|
3867
3959
|
return;
|
|
@@ -3952,8 +4044,7 @@ var SyncEngine = class {
|
|
|
3952
4044
|
}, 500);
|
|
3953
4045
|
}
|
|
3954
4046
|
async reconnectForQuerySubsets() {
|
|
3955
|
-
await this.
|
|
3956
|
-
await this.start();
|
|
4047
|
+
await this.reconnect();
|
|
3957
4048
|
}
|
|
3958
4049
|
};
|
|
3959
4050
|
function awarenessStatesToWire(states) {
|
|
@@ -4634,6 +4725,7 @@ function fromBase642(str) {
|
|
|
4634
4725
|
// src/reactivity/sync-status-controller.ts
|
|
4635
4726
|
var OFFLINE_SYNC_STATUS = Object.freeze({
|
|
4636
4727
|
status: "offline",
|
|
4728
|
+
reconnecting: false,
|
|
4637
4729
|
pendingOperations: 0,
|
|
4638
4730
|
lastSyncedAt: null,
|
|
4639
4731
|
lastSuccessfulPush: null,
|
|
@@ -4651,6 +4743,7 @@ var SYNC_STATUS_EVENT_TYPES = [
|
|
|
4651
4743
|
"sync:received",
|
|
4652
4744
|
"sync:acknowledged",
|
|
4653
4745
|
"sync:apply-failed",
|
|
4746
|
+
"sync:delivery-gap",
|
|
4654
4747
|
"sync:diagnostics",
|
|
4655
4748
|
"sync:initial-sync-progress"
|
|
4656
4749
|
];
|