UNPKG

@x5e/gink

Version:

an eventually consistent database

146 lines 6.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HasMap = void 0; const builders_1 = require("./builders"); /** * A class to keep track of what data a given instance (self or peer) has for each * chain. So it's kind of like Map<[Medallion, ChainStart], SeenThrough>. * This is essentially the same data that's in the Greeting message, so I've included * functionality to convert from/to Greeting objects. */ class HasMap { constructor({ greetingBytes = null, greeting = null }) { this.data = new Map(); this.waiters = new Map(); if (greetingBytes) { greeting = builders_1.GreetingBuilder.deserializeBinary(greetingBytes); } if (!greeting) return; for (const entry of greeting.getEntriesList()) { const medallion = entry.getMedallion(); const chainStart = entry.getChainStart(); const timestamp = entry.getSeenThrough(); if (!this.data.has(medallion)) { this.data.set(medallion, new Map()); } this.data .get(medallion) .set(chainStart, { medallion, chainStart, timestamp }); } } /** * Allows you to wait until an instance has seen a particular bundle. * @param what either a muid address or a bundle info (indicates what to watch for) * @param timeoutMs how long to wait before giving up, default of undefined doesn't time out * @returns a promise that resolves when the thing has been marked as seen, or rejects at timeout */ waitTillHas({ medallion, timestamp }, timeoutMs) { const innerMap = this.data.get(medallion); if (innerMap) { for (const [chainStart, bundleInfo] of innerMap.entries()) { if (chainStart <= timestamp && bundleInfo.timestamp >= timestamp) return Promise.resolve(); } } const waiters = this.waiters; //TODO: prune waiters after their timeout return new Promise((resolve, reject) => { if (timeoutMs) setTimeout(reject, timeoutMs); waiters.set(resolve, [medallion, timestamp]); }); } /** * First, determine if the bundle is novel (represents data not previously marked), * then second, mark the data in the data structure (possibly checking that it's a sensible extension). * Note that checkValidExtension is used here as a safeguard to make sure we don't * send broken chains to the peer; the store should have its own check for receiving. * @param bundleInfo Meta about a particular bundle. * @param checkValidExtension If true then barfs if this bundle isn't a valid extension. * @returns true if the bundle represents data not seen before */ markAsHaving(bundleInfo, checkValidExtension) { if (!this.data.has(bundleInfo.medallion)) this.data.set(bundleInfo.medallion, new Map()); const innerMap = this.data.get(bundleInfo.medallion); const seenThrough = innerMap.get(bundleInfo.chainStart)?.timestamp || 0; if (bundleInfo.timestamp > seenThrough) { if (checkValidExtension) { if (bundleInfo.timestamp !== bundleInfo.chainStart && !bundleInfo.priorTime) throw new Error(`bundleInfo appears to be invalid: ${JSON.stringify(bundleInfo)}`); if ((bundleInfo.priorTime ?? 0) !== seenThrough) throw new Error(`proposed bundle would be an invalid extension ${JSON.stringify(bundleInfo)}`); } innerMap.set(bundleInfo.chainStart, bundleInfo); for (const [cb, pair] of this.waiters) { if (pair[0] === bundleInfo.medallion && pair[1] >= bundleInfo.chainStart && pair[1] <= bundleInfo.timestamp) { this.waiters.delete(cb); cb(); } } return true; } return false; } /** * Constructs the greeting for use during the initial handshake. Note that * the priorTimes aren't included, so recipient should not markIfNovel using * @returns */ constructGreeting() { const greeting = new builders_1.GreetingBuilder(); for (const [medallion, medallionMap] of this.data) { for (const [chainStart, bundleInfo] of medallionMap) { const entry = new builders_1.GreetingEntryBuilder(); entry.setMedallion(medallion); entry.setChainStart(chainStart); entry.setSeenThrough(bundleInfo.timestamp); greeting.addEntries(entry); } } return greeting; } /** * @returns bytes that can be sent during the initial handshake */ getGreetingMessageBytes() { const greeting = this.constructGreeting(); const msg = new builders_1.SyncMessageBuilder(); msg.setGreeting(greeting); return msg.serializeBinary(); } /** * Returns how far along data is seen for a particular chain. * @param key A [Medallion, ChainStart] tuple * @returns SeenThrough (a Timestamp) or undefined if not yet seen */ getBundleInfo(key) { const inner = this.data.get(key[0]); if (!inner) return undefined; return inner.get(key[1]); } /** * Gets a list of chains seen for a particular medallion, or a list of all seen chains * @param singleMedallion The single medallion to get chains for (returns all if undefined) * @returns a list of known chains */ getChains(singleMedallion) { const result = []; for (const [medallion, map] of this.data.entries()) { if (singleMedallion && medallion !== singleMedallion) continue; for (const chainStart of map.keys()) { result.push([medallion, chainStart]); } } return result; } } exports.HasMap = HasMap; //# sourceMappingURL=HasMap.js.map