@topgunbuild/client 0.2.0 → 0.2.1
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.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +116 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -232,6 +232,8 @@ var SyncEngine = class {
|
|
|
232
232
|
this.waitingForCapacity = [];
|
|
233
233
|
this.highWaterMarkEmitted = false;
|
|
234
234
|
this.backpressureListeners = /* @__PURE__ */ new Map();
|
|
235
|
+
// Write Concern state (Phase 5.01)
|
|
236
|
+
this.pendingWriteConcernPromises = /* @__PURE__ */ new Map();
|
|
235
237
|
this.nodeId = config.nodeId;
|
|
236
238
|
this.serverUrl = config.serverUrl;
|
|
237
239
|
this.storageAdapter = config.storageAdapter;
|
|
@@ -633,6 +635,22 @@ var SyncEngine = class {
|
|
|
633
635
|
}
|
|
634
636
|
async handleServerMessage(message) {
|
|
635
637
|
switch (message.type) {
|
|
638
|
+
case "BATCH": {
|
|
639
|
+
const batchData = message.data;
|
|
640
|
+
const view = new DataView(batchData.buffer, batchData.byteOffset, batchData.byteLength);
|
|
641
|
+
let offset = 0;
|
|
642
|
+
const count = view.getUint32(offset, true);
|
|
643
|
+
offset += 4;
|
|
644
|
+
for (let i = 0; i < count; i++) {
|
|
645
|
+
const msgLen = view.getUint32(offset, true);
|
|
646
|
+
offset += 4;
|
|
647
|
+
const msgData = batchData.slice(offset, offset + msgLen);
|
|
648
|
+
offset += msgLen;
|
|
649
|
+
const innerMsg = deserialize(msgData);
|
|
650
|
+
await this.handleServerMessage(innerMsg);
|
|
651
|
+
}
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
636
654
|
case "AUTH_REQUIRED":
|
|
637
655
|
this.sendAuth();
|
|
638
656
|
break;
|
|
@@ -664,8 +682,18 @@ var SyncEngine = class {
|
|
|
664
682
|
this.authToken = null;
|
|
665
683
|
break;
|
|
666
684
|
case "OP_ACK": {
|
|
667
|
-
const { lastId } = message.payload;
|
|
668
|
-
logger.info({ lastId }, "Received ACK for ops");
|
|
685
|
+
const { lastId, achievedLevel, results } = message.payload;
|
|
686
|
+
logger.info({ lastId, achievedLevel, hasResults: !!results }, "Received ACK for ops");
|
|
687
|
+
if (results && Array.isArray(results)) {
|
|
688
|
+
for (const result of results) {
|
|
689
|
+
const op = this.opLog.find((o) => o.id === result.opId);
|
|
690
|
+
if (op && !op.synced) {
|
|
691
|
+
op.synced = true;
|
|
692
|
+
logger.debug({ opId: result.opId, achievedLevel: result.achievedLevel, success: result.success }, "Op ACK with Write Concern");
|
|
693
|
+
}
|
|
694
|
+
this.resolveWriteConcernPromise(result.opId, result);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
669
697
|
let maxSyncedId = -1;
|
|
670
698
|
let ackedCount = 0;
|
|
671
699
|
this.opLog.forEach((op) => {
|
|
@@ -726,18 +754,20 @@ var SyncEngine = class {
|
|
|
726
754
|
}
|
|
727
755
|
case "SERVER_EVENT": {
|
|
728
756
|
const { mapName, eventType, key, record, orRecord, orTag } = message.payload;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
757
|
+
await this.applyServerEvent(mapName, eventType, key, record, orRecord, orTag);
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
case "SERVER_BATCH_EVENT": {
|
|
761
|
+
const { events } = message.payload;
|
|
762
|
+
for (const event of events) {
|
|
763
|
+
await this.applyServerEvent(
|
|
764
|
+
event.mapName,
|
|
765
|
+
event.eventType,
|
|
766
|
+
event.key,
|
|
767
|
+
event.record,
|
|
768
|
+
event.orRecord,
|
|
769
|
+
event.orTag
|
|
770
|
+
);
|
|
741
771
|
}
|
|
742
772
|
break;
|
|
743
773
|
}
|
|
@@ -940,6 +970,25 @@ var SyncEngine = class {
|
|
|
940
970
|
getHLC() {
|
|
941
971
|
return this.hlc;
|
|
942
972
|
}
|
|
973
|
+
/**
|
|
974
|
+
* Helper method to apply a single server event to the local map.
|
|
975
|
+
* Used by both SERVER_EVENT and SERVER_BATCH_EVENT handlers.
|
|
976
|
+
*/
|
|
977
|
+
async applyServerEvent(mapName, eventType, key, record, orRecord, orTag) {
|
|
978
|
+
const localMap = this.maps.get(mapName);
|
|
979
|
+
if (localMap) {
|
|
980
|
+
if (localMap instanceof LWWMap && record) {
|
|
981
|
+
localMap.merge(key, record);
|
|
982
|
+
await this.storageAdapter.put(`${mapName}:${key}`, record);
|
|
983
|
+
} else if (localMap instanceof ORMap) {
|
|
984
|
+
if (eventType === "OR_ADD" && orRecord) {
|
|
985
|
+
localMap.apply(key, orRecord);
|
|
986
|
+
} else if (eventType === "OR_REMOVE" && orTag) {
|
|
987
|
+
localMap.applyTombstone(orTag);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
943
992
|
/**
|
|
944
993
|
* Closes the WebSocket connection and cleans up resources.
|
|
945
994
|
*/
|
|
@@ -954,6 +1003,7 @@ var SyncEngine = class {
|
|
|
954
1003
|
this.websocket.close();
|
|
955
1004
|
this.websocket = null;
|
|
956
1005
|
}
|
|
1006
|
+
this.cancelAllWriteConcernPromises(new Error("SyncEngine closed"));
|
|
957
1007
|
this.stateMachine.transition("DISCONNECTED" /* DISCONNECTED */);
|
|
958
1008
|
logger.info("SyncEngine closed");
|
|
959
1009
|
}
|
|
@@ -1274,6 +1324,58 @@ var SyncEngine = class {
|
|
|
1274
1324
|
});
|
|
1275
1325
|
}
|
|
1276
1326
|
}
|
|
1327
|
+
// ============================================
|
|
1328
|
+
// Write Concern Methods (Phase 5.01)
|
|
1329
|
+
// ============================================
|
|
1330
|
+
/**
|
|
1331
|
+
* Register a pending Write Concern promise for an operation.
|
|
1332
|
+
* The promise will be resolved when the server sends an ACK with the operation result.
|
|
1333
|
+
*
|
|
1334
|
+
* @param opId - Operation ID
|
|
1335
|
+
* @param timeout - Timeout in ms (default: 5000)
|
|
1336
|
+
* @returns Promise that resolves with the Write Concern result
|
|
1337
|
+
*/
|
|
1338
|
+
registerWriteConcernPromise(opId, timeout = 5e3) {
|
|
1339
|
+
return new Promise((resolve, reject) => {
|
|
1340
|
+
const timeoutHandle = setTimeout(() => {
|
|
1341
|
+
this.pendingWriteConcernPromises.delete(opId);
|
|
1342
|
+
reject(new Error(`Write Concern timeout for operation ${opId}`));
|
|
1343
|
+
}, timeout);
|
|
1344
|
+
this.pendingWriteConcernPromises.set(opId, {
|
|
1345
|
+
resolve,
|
|
1346
|
+
reject,
|
|
1347
|
+
timeoutHandle
|
|
1348
|
+
});
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Resolve a pending Write Concern promise with the server result.
|
|
1353
|
+
*
|
|
1354
|
+
* @param opId - Operation ID
|
|
1355
|
+
* @param result - Result from server ACK
|
|
1356
|
+
*/
|
|
1357
|
+
resolveWriteConcernPromise(opId, result) {
|
|
1358
|
+
const pending = this.pendingWriteConcernPromises.get(opId);
|
|
1359
|
+
if (pending) {
|
|
1360
|
+
if (pending.timeoutHandle) {
|
|
1361
|
+
clearTimeout(pending.timeoutHandle);
|
|
1362
|
+
}
|
|
1363
|
+
pending.resolve(result);
|
|
1364
|
+
this.pendingWriteConcernPromises.delete(opId);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Cancel all pending Write Concern promises (e.g., on disconnect).
|
|
1369
|
+
*/
|
|
1370
|
+
cancelAllWriteConcernPromises(error) {
|
|
1371
|
+
for (const [opId, pending] of this.pendingWriteConcernPromises.entries()) {
|
|
1372
|
+
if (pending.timeoutHandle) {
|
|
1373
|
+
clearTimeout(pending.timeoutHandle);
|
|
1374
|
+
}
|
|
1375
|
+
pending.reject(error);
|
|
1376
|
+
}
|
|
1377
|
+
this.pendingWriteConcernPromises.clear();
|
|
1378
|
+
}
|
|
1277
1379
|
};
|
|
1278
1380
|
|
|
1279
1381
|
// src/TopGunClient.ts
|