@korajs/sync 1.0.0-beta.5 → 1.0.0-beta.6

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 CHANGED
@@ -2735,6 +2735,7 @@ var OutboundQueue = class {
2735
2735
  // src/engine/sync-engine.ts
2736
2736
  var DEFAULT_BATCH_SIZE = 100;
2737
2737
  var DEFAULT_SCHEMA_VERSION = 1;
2738
+ var DEFAULT_OUTBOUND_ACK_TIMEOUT_MS = 3e4;
2738
2739
  var VALID_TRANSITIONS = {
2739
2740
  disconnected: ["connecting"],
2740
2741
  connecting: ["handshaking", "error", "disconnected"],
@@ -2769,6 +2770,7 @@ var SyncEngine = class {
2769
2770
  outboundQueue;
2770
2771
  rejectedStorage;
2771
2772
  batchSize;
2773
+ outboundAckTimeoutMs;
2772
2774
  encryptor;
2773
2775
  awarenessManager;
2774
2776
  richtextDocChannel;
@@ -2788,6 +2790,9 @@ var SyncEngine = class {
2788
2790
  clockBlocked = false;
2789
2791
  clockSkewMs = null;
2790
2792
  blobStorageEnabled = false;
2793
+ outboundAckTimer = null;
2794
+ startPromise = null;
2795
+ stopPromise = null;
2791
2796
  // Track delta exchange state
2792
2797
  deltaBatchesReceived = 0;
2793
2798
  deltaReceiveComplete = false;
@@ -2834,6 +2839,7 @@ var SyncEngine = class {
2834
2839
  this.serializer = options.serializer ?? new NegotiatedMessageSerializer("json");
2835
2840
  this.emitter = options.emitter ?? null;
2836
2841
  this.batchSize = options.config.batchSize ?? DEFAULT_BATCH_SIZE;
2842
+ this.outboundAckTimeoutMs = options.config.outboundAckTimeoutMs ?? DEFAULT_OUTBOUND_ACK_TIMEOUT_MS;
2837
2843
  this.encryptor = options.encryptor ?? null;
2838
2844
  this.syncState = options.syncState ?? null;
2839
2845
  this.activeScope = options.config.scopeMap;
@@ -2879,10 +2885,20 @@ var SyncEngine = class {
2879
2885
  * Start the sync engine: connect → handshake → delta exchange → streaming.
2880
2886
  */
2881
2887
  async start() {
2888
+ if (this.state === "streaming") return;
2889
+ if (this.startPromise) return this.startPromise;
2890
+ this.startPromise = this.startInternal().finally(() => {
2891
+ this.startPromise = null;
2892
+ });
2893
+ return this.startPromise;
2894
+ }
2895
+ async startInternal() {
2896
+ if (this.stopPromise) {
2897
+ await this.stopPromise;
2898
+ }
2899
+ if (this.state === "streaming") return;
2882
2900
  if (this.state !== "disconnected") {
2883
- throw new import_core7.SyncError("Cannot start sync engine: not in disconnected state", {
2884
- currentState: this.state
2885
- });
2901
+ await this.stop();
2886
2902
  }
2887
2903
  await this.outboundQueue.initialize();
2888
2904
  if (this.syncState) {
@@ -2915,7 +2931,12 @@ var SyncEngine = class {
2915
2931
  this.transitionTo("connecting");
2916
2932
  try {
2917
2933
  const authToken = this.config.auth ? (await this.config.auth()).token : void 0;
2934
+ if (this.state !== "connecting") return;
2918
2935
  await this.transport.connect(this.config.url, { authToken });
2936
+ if (this.state !== "connecting") {
2937
+ await this.transport.disconnect();
2938
+ return;
2939
+ }
2919
2940
  this.transitionTo("handshaking");
2920
2941
  const localVector = this.store.getVersionVector();
2921
2942
  const activeQuerySubsets = this.getActiveQuerySubsets();
@@ -2945,7 +2966,15 @@ var SyncEngine = class {
2945
2966
  * Stop the sync engine. Disconnects the transport.
2946
2967
  */
2947
2968
  async stop() {
2969
+ if (this.stopPromise) return this.stopPromise;
2970
+ this.stopPromise = this.stopInternal().finally(() => {
2971
+ this.stopPromise = null;
2972
+ });
2973
+ return this.stopPromise;
2974
+ }
2975
+ async stopInternal() {
2948
2976
  if (this.state === "disconnected") return;
2977
+ this.clearOutboundAckTimer();
2949
2978
  this.awarenessManager.stopCleanupTimer();
2950
2979
  if (this.currentBatch) {
2951
2980
  this.outboundQueue.returnBatch(this.currentBatch.batchId);
@@ -3655,6 +3684,7 @@ var SyncEngine = class {
3655
3684
  this.transport.send(ack);
3656
3685
  }
3657
3686
  handleAcknowledgment(msg) {
3687
+ this.clearOutboundAckTimer();
3658
3688
  if (this.state === "syncing" && this.config.strictHandshake) {
3659
3689
  this.pendingDeltaBatchAcks.delete(msg.acknowledgedMessageId);
3660
3690
  this.markDeltaSendCompleteIfReady();
@@ -3674,6 +3704,7 @@ var SyncEngine = class {
3674
3704
  }
3675
3705
  }
3676
3706
  handleError(msg) {
3707
+ this.clearOutboundAckTimer();
3677
3708
  if (this.currentBatch) {
3678
3709
  this.outboundQueue.returnBatch(this.currentBatch.batchId);
3679
3710
  this.currentBatch = null;
@@ -3861,6 +3892,7 @@ var SyncEngine = class {
3861
3892
  batchIndex: 0
3862
3893
  };
3863
3894
  this.transport.send(batchMsg);
3895
+ this.startOutboundAckTimer(batch.batchId);
3864
3896
  this.emitter?.emit({
3865
3897
  type: "sync:sent",
3866
3898
  operations: batch.operations,
@@ -3886,6 +3918,7 @@ var SyncEngine = class {
3886
3918
  batchIndex: 0
3887
3919
  };
3888
3920
  this.transport.send(batchMsg);
3921
+ this.startOutboundAckTimer(batch.batchId);
3889
3922
  this.emitter?.emit({
3890
3923
  type: "sync:sent",
3891
3924
  operations: batch.operations,
@@ -3893,6 +3926,33 @@ var SyncEngine = class {
3893
3926
  });
3894
3927
  }
3895
3928
  }
3929
+ startOutboundAckTimer(batchId) {
3930
+ this.clearOutboundAckTimer();
3931
+ if (this.outboundAckTimeoutMs <= 0) return;
3932
+ this.outboundAckTimer = setTimeout(() => {
3933
+ void this.handleOutboundAckTimeout(batchId);
3934
+ }, this.outboundAckTimeoutMs);
3935
+ }
3936
+ clearOutboundAckTimer() {
3937
+ if (!this.outboundAckTimer) return;
3938
+ clearTimeout(this.outboundAckTimer);
3939
+ this.outboundAckTimer = null;
3940
+ }
3941
+ async handleOutboundAckTimeout(batchId) {
3942
+ if (!this.currentBatch || this.currentBatch.batchId !== batchId) return;
3943
+ this.outboundQueue.returnBatch(batchId);
3944
+ this.currentBatch = null;
3945
+ this.clearOutboundAckTimer();
3946
+ await this.refreshPendingCount();
3947
+ if (this.state === "disconnected") return;
3948
+ const reason = `Timed out waiting for acknowledgment of outbound batch ${batchId}`;
3949
+ try {
3950
+ await this.transport.disconnect();
3951
+ } finally {
3952
+ this.ensureDisconnected();
3953
+ this.emitter?.emit({ type: "sync:disconnected", reason });
3954
+ }
3955
+ }
3896
3956
  handleAwarenessUpdate(msg) {
3897
3957
  const awarenessMessage = {
3898
3958
  type: "awareness",
@@ -3902,6 +3962,7 @@ var SyncEngine = class {
3902
3962
  this.awarenessManager.handleRemoteMessage(awarenessMessage);
3903
3963
  }
3904
3964
  handleTransportClose(reason) {
3965
+ this.clearOutboundAckTimer();
3905
3966
  if (this.currentBatch) {
3906
3967
  this.outboundQueue.returnBatch(this.currentBatch.batchId);
3907
3968
  this.currentBatch = null;