@korajs/sync 1.0.0-beta.1 → 1.0.0-beta.4
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 +142 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +140 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/{transport-CZLMdZ0k.d.cts → transport-BGr-_eH8.d.cts} +75 -2
- package/dist/{transport-CZLMdZ0k.d.ts → transport-BGr-_eH8.d.ts} +75 -2
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
InvalidScopeError: () => InvalidScopeError,
|
|
42
42
|
JsonMessageSerializer: () => JsonMessageSerializer,
|
|
43
43
|
KeyDerivationError: () => KeyDerivationError,
|
|
44
|
+
MemoryRejectedOperationStorage: () => MemoryRejectedOperationStorage,
|
|
44
45
|
NegotiatedMessageSerializer: () => NegotiatedMessageSerializer,
|
|
45
46
|
OFFLINE_SYNC_STATUS: () => OFFLINE_SYNC_STATUS,
|
|
46
47
|
OutboundQueue: () => OutboundQueue,
|
|
@@ -79,6 +80,7 @@ __export(index_exports, {
|
|
|
79
80
|
isHandshakeMessage: () => isHandshakeMessage,
|
|
80
81
|
isHandshakeResponseMessage: () => isHandshakeResponseMessage,
|
|
81
82
|
isOperationBatchMessage: () => isOperationBatchMessage,
|
|
83
|
+
isOperationRejectedMessage: () => isOperationRejectedMessage,
|
|
82
84
|
isSchemaMismatchReject: () => isSchemaMismatchReject,
|
|
83
85
|
isSyncMessage: () => isSyncMessage,
|
|
84
86
|
isYjsDocUpdateMessage: () => isYjsDocUpdateMessage,
|
|
@@ -111,6 +113,20 @@ var SYNC_STATUSES = [
|
|
|
111
113
|
"error",
|
|
112
114
|
"schema-mismatch"
|
|
113
115
|
];
|
|
116
|
+
var MemoryRejectedOperationStorage = class {
|
|
117
|
+
rejected = /* @__PURE__ */ new Map();
|
|
118
|
+
async record(rejected) {
|
|
119
|
+
this.rejected.set(rejected.operationId, rejected);
|
|
120
|
+
}
|
|
121
|
+
async list() {
|
|
122
|
+
return [...this.rejected.values()];
|
|
123
|
+
}
|
|
124
|
+
async remove(operationIds) {
|
|
125
|
+
for (const id of operationIds) {
|
|
126
|
+
this.rejected.delete(id);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
114
130
|
var ScopeViolationError = class extends import_core.KoraError {
|
|
115
131
|
constructor(operationId, collection, scope, message) {
|
|
116
132
|
super(
|
|
@@ -308,6 +324,8 @@ function isSyncMessage(value) {
|
|
|
308
324
|
return isAcknowledgmentMessage(value);
|
|
309
325
|
case "error":
|
|
310
326
|
return isErrorMessage(value);
|
|
327
|
+
case "operation-rejected":
|
|
328
|
+
return isOperationRejectedMessage(value);
|
|
311
329
|
case "awareness-update":
|
|
312
330
|
return isAwarenessUpdateMessage(value);
|
|
313
331
|
case "yjs-doc-update":
|
|
@@ -347,6 +365,11 @@ function isErrorMessage(value) {
|
|
|
347
365
|
const msg = value;
|
|
348
366
|
return msg.type === "error" && typeof msg.messageId === "string" && typeof msg.code === "string" && typeof msg.message === "string" && typeof msg.retriable === "boolean";
|
|
349
367
|
}
|
|
368
|
+
function isOperationRejectedMessage(value) {
|
|
369
|
+
if (typeof value !== "object" || value === null) return false;
|
|
370
|
+
const msg = value;
|
|
371
|
+
return msg.type === "operation-rejected" && typeof msg.messageId === "string" && typeof msg.operationId === "string" && typeof msg.collection === "string" && typeof msg.recordId === "string" && typeof msg.code === "string" && typeof msg.message === "string" && typeof msg.retriable === "boolean";
|
|
372
|
+
}
|
|
350
373
|
function isAwarenessUpdateMessage(value) {
|
|
351
374
|
if (typeof value !== "object" || value === null) return false;
|
|
352
375
|
const msg = value;
|
|
@@ -638,6 +661,17 @@ function toProtoEnvelope(message) {
|
|
|
638
661
|
errorMessage: message.message,
|
|
639
662
|
retriable: message.retriable
|
|
640
663
|
};
|
|
664
|
+
case "operation-rejected":
|
|
665
|
+
return {
|
|
666
|
+
type: message.type,
|
|
667
|
+
messageId: message.messageId,
|
|
668
|
+
operationId: message.operationId,
|
|
669
|
+
collection: message.collection,
|
|
670
|
+
recordId: message.recordId,
|
|
671
|
+
errorCode: message.code,
|
|
672
|
+
errorMessage: message.message,
|
|
673
|
+
retriable: message.retriable
|
|
674
|
+
};
|
|
641
675
|
case "awareness-update":
|
|
642
676
|
case "yjs-doc-update":
|
|
643
677
|
return {
|
|
@@ -724,6 +758,17 @@ function fromProtoEnvelope(envelope) {
|
|
|
724
758
|
message: envelope.errorMessage ?? "Unknown error",
|
|
725
759
|
retriable: envelope.retriable ?? false
|
|
726
760
|
};
|
|
761
|
+
case "operation-rejected":
|
|
762
|
+
return {
|
|
763
|
+
type: "operation-rejected",
|
|
764
|
+
messageId: envelope.messageId,
|
|
765
|
+
operationId: envelope.operationId ?? "",
|
|
766
|
+
collection: envelope.collection ?? "",
|
|
767
|
+
recordId: envelope.recordId ?? "",
|
|
768
|
+
code: envelope.errorCode ?? "REJECTED",
|
|
769
|
+
message: envelope.errorMessage ?? "",
|
|
770
|
+
retriable: envelope.retriable ?? false
|
|
771
|
+
};
|
|
727
772
|
case "blob-chunk-request":
|
|
728
773
|
return {
|
|
729
774
|
type: "blob-chunk-request",
|
|
@@ -898,6 +943,12 @@ function encodeEnvelope(envelope) {
|
|
|
898
943
|
if (envelope.hasBytes !== void 0) writer.uint32(184).bool(envelope.hasBytes);
|
|
899
944
|
if (envelope.blobStorageEnabled !== void 0)
|
|
900
945
|
writer.uint32(200).bool(envelope.blobStorageEnabled);
|
|
946
|
+
if (envelope.operationId && envelope.operationId.length > 0)
|
|
947
|
+
writer.uint32(210).string(envelope.operationId);
|
|
948
|
+
if (envelope.collection && envelope.collection.length > 0)
|
|
949
|
+
writer.uint32(218).string(envelope.collection);
|
|
950
|
+
if (envelope.recordId && envelope.recordId.length > 0)
|
|
951
|
+
writer.uint32(226).string(envelope.recordId);
|
|
901
952
|
return writer.finish();
|
|
902
953
|
}
|
|
903
954
|
function decodeEnvelope(bytes) {
|
|
@@ -984,6 +1035,15 @@ function decodeEnvelope(bytes) {
|
|
|
984
1035
|
case 25:
|
|
985
1036
|
envelope.blobStorageEnabled = reader.bool();
|
|
986
1037
|
break;
|
|
1038
|
+
case 26:
|
|
1039
|
+
envelope.operationId = reader.string();
|
|
1040
|
+
break;
|
|
1041
|
+
case 27:
|
|
1042
|
+
envelope.collection = reader.string();
|
|
1043
|
+
break;
|
|
1044
|
+
case 28:
|
|
1045
|
+
envelope.recordId = reader.string();
|
|
1046
|
+
break;
|
|
987
1047
|
default:
|
|
988
1048
|
reader.skipType(tag & 7);
|
|
989
1049
|
}
|
|
@@ -2584,6 +2644,37 @@ var OutboundQueue = class {
|
|
|
2584
2644
|
get isInitialized() {
|
|
2585
2645
|
return this.initialized;
|
|
2586
2646
|
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Remove a single operation the server permanently rejected — from the queue,
|
|
2649
|
+
* from any in-flight batch, and from durable storage — so a later batch ack or
|
|
2650
|
+
* a reconnect `returnBatch` can never resend or resurrect it. Returns the
|
|
2651
|
+
* removed operation so the caller can record it in a durable rejected store,
|
|
2652
|
+
* or null if it was not present (already acknowledged and removed).
|
|
2653
|
+
*/
|
|
2654
|
+
async reject(opId) {
|
|
2655
|
+
let removed = null;
|
|
2656
|
+
for (const [batchId, ops] of this.inFlight) {
|
|
2657
|
+
const idx = ops.findIndex((op) => op.id === opId);
|
|
2658
|
+
if (idx !== -1) {
|
|
2659
|
+
removed = ops[idx] ?? null;
|
|
2660
|
+
ops.splice(idx, 1);
|
|
2661
|
+
if (ops.length === 0) {
|
|
2662
|
+
this.inFlight.delete(batchId);
|
|
2663
|
+
}
|
|
2664
|
+
break;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
if (!removed) {
|
|
2668
|
+
const idx = this.queue.findIndex((op) => op.id === opId);
|
|
2669
|
+
if (idx !== -1) {
|
|
2670
|
+
removed = this.queue[idx] ?? null;
|
|
2671
|
+
this.queue.splice(idx, 1);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
this.seen.delete(opId);
|
|
2675
|
+
await this.storage.dequeue([opId]);
|
|
2676
|
+
return removed;
|
|
2677
|
+
}
|
|
2587
2678
|
/**
|
|
2588
2679
|
* Remove operations by id from queue and persistent storage.
|
|
2589
2680
|
* Used when ops were already sent during handshake delta exchange.
|
|
@@ -2623,6 +2714,7 @@ var SyncEngine = class {
|
|
|
2623
2714
|
serializer;
|
|
2624
2715
|
emitter;
|
|
2625
2716
|
outboundQueue;
|
|
2717
|
+
rejectedStorage;
|
|
2626
2718
|
batchSize;
|
|
2627
2719
|
encryptor;
|
|
2628
2720
|
awarenessManager;
|
|
@@ -2675,6 +2767,7 @@ var SyncEngine = class {
|
|
|
2675
2767
|
this.activeScope = options.config.scopeMap;
|
|
2676
2768
|
const queueStorage = options.queueStorage ?? new MemoryQueueStorage();
|
|
2677
2769
|
this.outboundQueue = new OutboundQueue(queueStorage);
|
|
2770
|
+
this.rejectedStorage = options.rejectedStorage ?? new MemoryRejectedOperationStorage();
|
|
2678
2771
|
this.metricsCollector = new SyncMetricsCollector(options.metricsConfig);
|
|
2679
2772
|
if (this.emitter) {
|
|
2680
2773
|
this.metricsCollector.attachEmitter(this.emitter);
|
|
@@ -3056,6 +3149,9 @@ var SyncEngine = class {
|
|
|
3056
3149
|
case "error":
|
|
3057
3150
|
this.handleError(message);
|
|
3058
3151
|
break;
|
|
3152
|
+
case "operation-rejected":
|
|
3153
|
+
await this.handleOperationRejected(message);
|
|
3154
|
+
break;
|
|
3059
3155
|
case "awareness-update":
|
|
3060
3156
|
this.handleAwarenessUpdate(message);
|
|
3061
3157
|
break;
|
|
@@ -3393,6 +3489,50 @@ var SyncEngine = class {
|
|
|
3393
3489
|
this.emitter?.emit({ type: "sync:disconnected", reason: msg.message });
|
|
3394
3490
|
this.transitionTo("disconnected");
|
|
3395
3491
|
}
|
|
3492
|
+
/**
|
|
3493
|
+
* Handle a per-operation rejection: divert the op out of the pending outbound
|
|
3494
|
+
* queue (so it is never retried or resurrected on reconnect), record it in the
|
|
3495
|
+
* durable rejected store (so it is kept and explainable), and emit an event so
|
|
3496
|
+
* the app can reconcile. Unlike {@link handleError}, this is a normal per-op
|
|
3497
|
+
* signal, so the connection stays up.
|
|
3498
|
+
*/
|
|
3499
|
+
async handleOperationRejected(msg) {
|
|
3500
|
+
await this.outboundQueue.reject(msg.operationId);
|
|
3501
|
+
await this.rejectedStorage.record({
|
|
3502
|
+
operationId: msg.operationId,
|
|
3503
|
+
collection: msg.collection,
|
|
3504
|
+
recordId: msg.recordId,
|
|
3505
|
+
code: msg.code,
|
|
3506
|
+
message: msg.message,
|
|
3507
|
+
retriable: msg.retriable,
|
|
3508
|
+
rejectedAt: Date.now()
|
|
3509
|
+
});
|
|
3510
|
+
await this.refreshPendingCount();
|
|
3511
|
+
this.emitter?.emit({
|
|
3512
|
+
type: "sync:operation-rejected",
|
|
3513
|
+
operationId: msg.operationId,
|
|
3514
|
+
collection: msg.collection,
|
|
3515
|
+
recordId: msg.recordId,
|
|
3516
|
+
code: msg.code,
|
|
3517
|
+
message: msg.message,
|
|
3518
|
+
retriable: msg.retriable
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
/**
|
|
3522
|
+
* Every operation the server rejected that has not yet been reconciled. The app
|
|
3523
|
+
* uses this (alongside the `sync:operation-rejected` event) to surface failed
|
|
3524
|
+
* submissions and decide whether to roll back or resubmit.
|
|
3525
|
+
*/
|
|
3526
|
+
getRejectedOperations() {
|
|
3527
|
+
return this.rejectedStorage.list();
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Forget rejected operations by id once the app has reconciled them (rolled the
|
|
3531
|
+
* optimistic write back or resubmitted a corrected op).
|
|
3532
|
+
*/
|
|
3533
|
+
clearRejectedOperations(operationIds) {
|
|
3534
|
+
return this.rejectedStorage.remove(operationIds);
|
|
3535
|
+
}
|
|
3396
3536
|
async checkDeltaComplete() {
|
|
3397
3537
|
if (!this.deltaSendComplete || !this.deltaReceiveComplete) {
|
|
3398
3538
|
return;
|
|
@@ -4458,6 +4598,7 @@ function subscribeRemoteAwarenessStates(awareness, listener) {
|
|
|
4458
4598
|
InvalidScopeError,
|
|
4459
4599
|
JsonMessageSerializer,
|
|
4460
4600
|
KeyDerivationError,
|
|
4601
|
+
MemoryRejectedOperationStorage,
|
|
4461
4602
|
NegotiatedMessageSerializer,
|
|
4462
4603
|
OFFLINE_SYNC_STATUS,
|
|
4463
4604
|
OutboundQueue,
|
|
@@ -4496,6 +4637,7 @@ function subscribeRemoteAwarenessStates(awareness, listener) {
|
|
|
4496
4637
|
isHandshakeMessage,
|
|
4497
4638
|
isHandshakeResponseMessage,
|
|
4498
4639
|
isOperationBatchMessage,
|
|
4640
|
+
isOperationRejectedMessage,
|
|
4499
4641
|
isSchemaMismatchReject,
|
|
4500
4642
|
isSyncMessage,
|
|
4501
4643
|
isYjsDocUpdateMessage,
|