UNPKG

@x5e/gink

Version:

an eventually consistent database

181 lines 6.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstractConnection = void 0; const builders_1 = require("./builders"); class AbstractConnection { constructor() { this.listeners = []; this.unacked = new Map(); this.unackedChains = 0; this.hasSentInitialSyncState = false; this.hasRecvInitialSyncState = false; this.hasSentGreetingState = false; this.isReadOnlyState = false; this.resetAbstractConnection(); } get ready() { return this._ready; } resetAbstractConnection() { this.unacked = new Map(); this.unackedChains = 0; this.peerHasMap = undefined; this.hasSentInitialSyncState = false; this.hasRecvInitialSyncState = false; this.hasSentGreetingState = false; this.peerHasMap = undefined; this._ready = new Promise((resolve) => { this.onReady = resolve; }); } get synced() { return (this.hasSentGreeting && (this.hasSentInitialSync || this.isReadOnly) && this.hasRecvInitialSync && this.connected && !this.hasSentUnackedData); } get connected() { throw new Error("Not implemented"); } get isReadOnly() { return this.isReadOnlyState; } set isReadOnly(value) { this.isReadOnlyState = value; } get hasSentGreeting() { return this.hasSentGreetingState; } get hasSentInitialSync() { return this.hasSentInitialSyncState; } get hasRecvInitialSync() { return this.hasRecvInitialSyncState; } markHasSentGreeting() { this.hasSentGreetingState = true; this.notify(); } markHasSentInitialSync() { this.hasSentInitialSyncState = true; this.notify(); } markHasRecvInitialSync() { this.hasRecvInitialSyncState = true; this.notify(); } get hasSentUnackedData() { return this.unackedChains > 0; } onAck(bundleInfo) { const innerMap = this.unacked.get(bundleInfo.medallion); if (!innerMap) { console.error("Received an ack for a medallion we don't have?", bundleInfo); return; } const lastSentForThisChain = innerMap.get(bundleInfo.chainStart); if (!lastSentForThisChain) { console.error("received an ack for a chain we didn't send?", bundleInfo); return; } if (bundleInfo.timestamp === lastSentForThisChain) { innerMap.delete(bundleInfo.chainStart); if (this.unackedChains > 0) { this.unackedChains--; if (this.unackedChains === 0) { this.notify(); } } else { console.error("expected unacked chains to be > 0"); } } } notify() { this.listeners.forEach((listener) => listener()); if (this.synced) { this.onReady?.(); } } subscribe(callback) { this.listeners.push(callback); return () => { this.listeners = this.listeners.filter((listener) => listener !== callback); }; } send(_) { throw new Error("Not implemented"); } sendInitialBundlesSent() { const message = new builders_1.SyncMessageBuilder(); const signal = builders_1.Signal.INITIAL_BUNDLES_SENT; message.setSignal(signal); const bundleBytes = message.serializeBinary(); this.send(bundleBytes); } close() { throw new Error("Not implemented"); } setPeerHasMap(hasMap) { if (this.peerHasMap && hasMap) { throw new Error("Already received a HasMap/Greeting from this Peer?"); } this.peerHasMap = hasMap; } /** * The Message proto contains an embedded one-of. Essentially this will wrap * the bundle bytes payload in a wrapper by prefixing a few bytes to it. * In theory the "Message" proto could be expanded with some extra meta * (e.g. send time) in the future. * Note that the bundle is always passed around as bytes and then * parsed as needed to avoid losing unknown fields. * @param bundleBytes: the bytes corresponding to a bundle * @returns a serialized "Message" proto */ static makeBundleMessage(bundleBytes) { const message = new builders_1.SyncMessageBuilder(); message.setBundle(bundleBytes); return message.serializeBinary(); } /** * Sends a bundle if we've received a greeting and our internal recordkeeping indicates * that the peer could use this particular bundle (but ensures that we're not sending * bundles that would cause gaps in the peer's chain.) * @param bundleBytes The bundle to be sent. * @param bundleInfo Meta about the bundle. */ sendIfNeeded(bundle) { if (this.peerHasMap?.markAsHaving(bundle.info, true)) { this.send(AbstractConnection.makeBundleMessage(bundle.bytes)); if (!this.unacked.has(bundle.info.medallion)) { this.unacked.set(bundle.info.medallion, new Map()); } const innerMap = this.unacked.get(bundle.info.medallion); const hadUnacked = this.unackedChains > 0; if (!innerMap.has(bundle.info.chainStart)) { this.unackedChains++; } innerMap.set(bundle.info.chainStart, bundle.info.timestamp); if (!hadUnacked) { this.notify(); } } } onReceivedBundle(bundleInfo) { this.peerHasMap?.markAsHaving(bundleInfo); this.sendAck(bundleInfo); } sendAck(changeSetInfo) { const ack = new builders_1.AckBuilder(); ack.setMedallion(changeSetInfo.medallion); ack.setChainStart(changeSetInfo.chainStart); ack.setTimestamp(changeSetInfo.timestamp); const syncMessageBuilder = new builders_1.SyncMessageBuilder(); syncMessageBuilder.setAck(ack); const bytes = syncMessageBuilder.serializeBinary(); this.send(bytes); } } exports.AbstractConnection = AbstractConnection; //# sourceMappingURL=AbstractConnection.js.map