UNPKG

@x5e/gink

Version:

an eventually consistent database

714 lines 34.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryStore = void 0; const utils_1 = require("./utils"); const builders_1 = require("./builders"); const jstreemap_1 = require("jstreemap"); const store_utils_1 = require("./store_utils"); const Decomposition_1 = require("./Decomposition"); class MemoryStore { constructor(keepingHistory = true) { this.keepingHistory = keepingHistory; this.foundBundleCallBacks = []; // Awkward, but need to use strings to represent objects, since we won't always // have the original reference to use. this.trxns = new jstreemap_1.TreeMap(); // BundleInfoTuple => bytes this.chainInfos = new jstreemap_1.TreeMap(); // [Medallion, ChainStart] => BundleInfo this.activeChains = []; this.clearances = new jstreemap_1.TreeMap(); // ContainerId,ClearanceId => Clearance this.containers = new jstreemap_1.TreeMap(); // ContainerId => bytes this.removals = new jstreemap_1.TreeMap(); // placementId,removalId => "" this.placements = new jstreemap_1.TreeMap(); // ContainerID,Key,PlacementId => Entry // key-placement index used to find properties by the container they describe (the key) this.byKeyPlacement = new jstreemap_1.TreeMap(); // Key,PlacementId => Entry this.identities = new Map(); // Medallion,chainStart => identity this.locations = new jstreemap_1.TreeMap(); // entryId,placementId => containerId,key,placementId this.byName = new jstreemap_1.TreeMap(); // name,entryMuid => describingMuid this.bySource = new jstreemap_1.TreeMap(); this.byTarget = new jstreemap_1.TreeMap(); this.verifyKeys = new Map(); this.secretKeys = new Map(); this.symmetricKeys = new Map(); // keyId => symmetricKey this.ready = utils_1.librariesReady; } saveKeyPair(keyPair) { this.secretKeys.set((0, utils_1.bytesToHex)(keyPair.publicKey), keyPair); return Promise.resolve(); } pullKeyPair(publicKey) { return Promise.resolve(this.secretKeys.get((0, utils_1.bytesToHex)(publicKey))); } async saveSymmetricKey(symmetricKey) { if (symmetricKey.length !== 32) { throw new Error("symmetric key must be 32 bytes"); } await this.ready; const keyId = (0, utils_1.shorterHash)(symmetricKey); this.symmetricKeys.set(keyId, symmetricKey); return keyId; } async getSymmetricKey(keyId) { await this.ready; return this.symmetricKeys.get(keyId); } dropHistory(container, before) { throw new Error("not implemented"); } getVerifyKey(chainInfo) { return Promise.resolve(this.verifyKeys.get(`${chainInfo[0]},${chainInfo[1]}`)); } stopHistory() { this.keepingHistory = false; this.dropHistory(); } startHistory() { this.keepingHistory = true; } getClaimedChains() { const result = new Map(); let lastTime = 0; this.activeChains.sort((a, b) => a.claimTime - b.claimTime); for (let chain of this.activeChains) { (0, utils_1.ensure)(chain.claimTime > lastTime); lastTime = chain.claimTime; result.set(chain.medallion, chain); } return Promise.resolve(result); } claimChain(medallion, chainStart, actorId) { const claim = { medallion, chainStart, actorId: actorId || 0, claimTime: (0, utils_1.generateTimestamp)(), }; this.activeChains.push(claim); return Promise.resolve(claim); } getChainIdentity(chainInfo) { return Promise.resolve(this.identities.get(`${chainInfo[0]},${chainInfo[1]}`)); } async getChainTracker() { const chainInfos = this.getChainInfos(); const chainTracker = (0, store_utils_1.buildChainTracker)(chainInfos); return Promise.resolve(chainTracker); } getChainInfos() { return this.chainInfos.values(); } async addBundle(bundle, claimChain) { await this.ready; const bundleBuilder = bundle.builder; const bundleInfo = bundle.info; const { timestamp, medallion, chainStart, priorTime } = bundleInfo; const oldChainInfo = this.chainInfos.get((0, store_utils_1.medallionChainStartToString)([medallion, chainStart])); if (oldChainInfo || priorTime) { if ((oldChainInfo === null || oldChainInfo === void 0 ? void 0 : oldChainInfo.timestamp) >= timestamp) { return false; } if ((oldChainInfo === null || oldChainInfo === void 0 ? void 0 : oldChainInfo.timestamp) !== priorTime) { throw new Error(`missing prior chain entry for ${JSON.stringify(bundleInfo)}, ` + `have ${JSON.stringify(oldChainInfo)}`); } const priorHash = bundleBuilder.getPriorHash(); if (priorHash.length != 32 || !(0, utils_1.sameData)(oldChainInfo.hashCode, priorHash)) { throw new Error("bad prior hash"); } } const chainInfo = [ bundleInfo.medallion, bundleInfo.chainStart, ]; let verifyKey = utils_1.emptyBytes; const identity = bundleBuilder.getIdentity(); if (bundleInfo.timestamp === bundleInfo.chainStart) { (0, utils_1.ensure)(identity, "chain start bundle missing identity"); this.identities.set(`${chainInfo[0]},${chainInfo[1]}`, identity); verifyKey = (0, utils_1.ensure)(bundleBuilder.getVerifyKey()); this.verifyKeys.set(`${chainInfo[0]},${chainInfo[1]}`, verifyKey); } else { (0, utils_1.ensure)(!identity, "non-chain start bundle has identity"); verifyKey = this.verifyKeys.get(`${chainInfo[0]},${chainInfo[1]}`); } (0, utils_1.verifyBundle)(bundle.bytes, verifyKey); if (claimChain) { (0, utils_1.ensure)(bundleInfo.timestamp === bundleInfo.chainStart); this.claimChain(bundleInfo.medallion, bundleInfo.chainStart, (0, utils_1.getActorId)()); } this.chainInfos.set((0, store_utils_1.medallionChainStartToString)([medallion, chainStart]), bundleInfo); const bundleKey = MemoryStore.bundleInfoToKey(bundleInfo); this.trxns.set(bundleKey, bundle.bytes); // Decrypt bundle const encrypted = bundleBuilder.getEncrypted(); let changesList; if (encrypted) { const keyId = bundleBuilder.getKeyId(); if (bundleBuilder.getChangesList().length > 0) { throw new Error("did not expect plain changes when using encryption"); } if (!keyId) { throw new Error("expected keyId with encrypted bundle"); } const symmetricKey = (0, utils_1.ensure)(await this.getSymmetricKey(keyId), "could not find symmetric key referenced in bundle"); const decrypted = (0, utils_1.decryptMessage)(encrypted, symmetricKey); const innerBundleBuilder = (builders_1.BundleBuilder.deserializeBinary(decrypted)); changesList = innerBundleBuilder.getChangesList(); } // Changes list will either come from getChangesList in an unencrypted bundle, or // getChangesList from the decrypted inner bundle. if (!changesList) changesList = bundleBuilder.getChangesList(); for (let index = 0; index < changesList.length; index++) { const offset = index + 1; const changeBuilder = changesList[index]; (0, utils_1.ensure)(offset > 0); const changeAddressTuple = [ timestamp, medallion, offset, ]; const changeAddress = { timestamp, medallion, offset }; if (changeBuilder.hasContainer()) { const containerBytes = changeBuilder .getContainer() .serializeBinary(); this.containers.set((0, utils_1.muidTupleToString)(changeAddressTuple), containerBytes); continue; } if (changeBuilder.hasEntry()) { const entryBuilder = changeBuilder.getEntry(); let pointeeList = []; if (entryBuilder.hasPointee()) { pointeeList = (0, store_utils_1.buildPointeeList)(entryBuilder, bundleInfo); } let sourceList = []; let targetList = []; if (entryBuilder.hasPair()) { [sourceList, targetList] = (0, store_utils_1.buildPairLists)(entryBuilder, bundleInfo); } const entry = { behavior: entryBuilder.getBehavior(), containerId: (0, store_utils_1.extractContainerMuid)(entryBuilder, bundleInfo), storageKey: (0, store_utils_1.getStorageKey)(entryBuilder, changeAddress), entryId: [timestamp, medallion, offset], pointeeList, value: entryBuilder.hasValue() ? (0, utils_1.unwrapValue)(entryBuilder.getValue()) : undefined, expiry: entryBuilder.getExpiry() || undefined, deletion: entryBuilder.getDeletion(), placementId: [timestamp, medallion, offset], sourceList, targetList, purging: entryBuilder.getPurge(), }; this.addEntry(entry); continue; } if (changeBuilder.hasMovement()) { this.applyMovement((0, store_utils_1.extractMovement)(changeBuilder, bundleInfo, offset)); continue; } if (changeBuilder.hasClearance()) { const clearanceBuilder = changeBuilder.getClearance(); const container = (0, utils_1.builderToMuid)(clearanceBuilder.getContainer(), { timestamp, medallion, offset }); const containerMuidTuple = [ container.timestamp, container.medallion, container.offset, ]; const containerIdStr = (0, utils_1.muidTupleToString)(containerMuidTuple); if (clearanceBuilder.getPurge()) { // When purging, remove all entries from the container. while (true) { const it = this.placements.lowerBound(containerIdStr); const to = this.placements.upperBound(`${containerIdStr},~,~`); if (it.equals(to) || !it.key) break; this.placements.erase(it); this.byKeyPlacement.delete(it.key.slice(35)); // TODO: also delete removals, locations } // When doing a purging clear, remove previous clearances for the container. const lowerClearances = this.clearances.lowerBound(`${containerIdStr}`); const upperClearances = this.clearances.upperBound(`${containerIdStr},~`); while (lowerClearances) { if (lowerClearances.equals(upperClearances)) break; if ((0, utils_1.muidTupleToString)(lowerClearances.value.containerId) !== containerIdStr) break; this.clearances.delete(lowerClearances.key); lowerClearances.next(); } } const clearance = { containerId: containerMuidTuple, clearanceId: changeAddressTuple, purging: clearanceBuilder.getPurge(), }; // TODO: have entries check to see if there's a purging clearance when accepting an entry this.clearances.set(`${containerIdStr},${(0, utils_1.muidTupleToString)(clearance.clearanceId)}`, clearance); continue; } throw new Error("don't know how to apply this kind of change"); } return true; } static bundleInfoToKey(bundleInfo) { return [ bundleInfo.timestamp, bundleInfo.medallion, bundleInfo.chainStart, bundleInfo.priorTime || 0, bundleInfo.comment || "", ]; } applyMovement(movement) { const { entryId, movementId, dest, purge } = movement; const entryIdStr = (0, utils_1.muidTupleToString)(entryId); const movementIdStr = (0, utils_1.muidTupleToString)(movementId); let entry; if (purge || !this.keepingHistory) { const iterator = this.locations.lowerBound(entryIdStr); while (true) { if (iterator.equals(this.locations.end())) break; if (!iterator.key.startsWith(entryIdStr)) break; entry = Object.assign(Object.assign({}, this.placements.get(iterator.value)), { placementId: movementId, storageKey: dest }); this.placements.delete(iterator.value); this.byKeyPlacement.delete(iterator.value.slice(35)); this.locations.erase(iterator); iterator.next(); } } else { const iterator = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.locations, entryIdStr); if (!iterator) { console.error(`attempting to move something I don't have any record of: ${entryIdStr}`); return; } (0, utils_1.ensure)(iterator.key && iterator.key.startsWith(entryIdStr)); entry = Object.assign(Object.assign({}, this.placements.get(iterator.value)), { placementId: movementId, storageKey: dest }); const removingIdStr = iterator.value.slice(-34); this.removals.set(`${removingIdStr},${movementIdStr}`, ""); } if (dest !== 0) { this.addEntry(entry); } } async getContainerBytes(address) { return this.containers.get((0, utils_1.muidToString)(address)); } asOfToTimestamp(asOf) { if (asOf instanceof Date) { return asOf.getTime() * 1000; } if (asOf > MemoryStore.YEAR_2020) { return asOf; } if (asOf < 0 && asOf > -1000) { // Interpret as number of bundles in the past. try { const trxnKeyArray = Array.from(this.trxns.keys()); const key = trxnKeyArray[trxnKeyArray.length + asOf]; return key[0]; } catch (_a) { // Looking further back than we have bundles. throw new Error("no bundles that far back"); } } throw new Error(`don't know how to interpret asOf=${asOf}`); } getEntryByKey(container, key, asOf) { try { return Promise.resolve(this.getEntryByKeyHelper(container, key, asOf)); } catch (error) { return Promise.reject(error); } } getEntryByKeyHelper(container, key, asOf) { var _a, _b, _c; const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const desiredSrc = [ (_a = container === null || container === void 0 ? void 0 : container.timestamp) !== null && _a !== void 0 ? _a : 0, (_b = container === null || container === void 0 ? void 0 : container.medallion) !== null && _b !== void 0 ? _b : 0, (_c = container === null || container === void 0 ? void 0 : container.offset) !== null && _c !== void 0 ? _c : 0, ]; const srcAsStr = (0, utils_1.muidTupleToString)(desiredSrc); let clearanceTime = this.getLastClearanceTime(srcAsStr, asOfTs); const semanticKey = (0, store_utils_1.toStorageKey)(key); const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const prefix = `${srcAsStr},${(0, store_utils_1.storageKeyToString)(semanticKey)},`; const iterator = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.placements, prefix, asOfTsStr); if (!iterator) return undefined; const entry = iterator.value; if (!entry) throw new Error(`missing entry for: ${iterator.value}`); if (entry.placementId[0] < clearanceTime) { // container was cleared after this entry return undefined; } return entry; } async getBundles(callBack) { for (const [_, val] of this.trxns) { callBack(new Decomposition_1.Decomposition(val)); } } getEntryById(entryMuid, asOf) { return Promise.resolve(this.getEntryByIdSync(entryMuid)); } // TODO: allow this to take an as-of timestamp getEntryByIdSync(entryMuid) { const entryIdStr = (0, utils_1.muidToString)(entryMuid); const it = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.locations, entryIdStr); if (!it) return undefined; return this.placements.get(it.value); } async getKeyedEntries(container, asOf) { var _a, _b, _c; const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const desiredSrc = [ (_a = container === null || container === void 0 ? void 0 : container.timestamp) !== null && _a !== void 0 ? _a : 0, (_b = container === null || container === void 0 ? void 0 : container.medallion) !== null && _b !== void 0 ? _b : 0, (_c = container === null || container === void 0 ? void 0 : container.offset) !== null && _c !== void 0 ? _c : 0, ]; const srcAsStr = (0, utils_1.muidTupleToString)(desiredSrc); const clearanceTime = this.getLastClearanceTime(srcAsStr, asOfTs); const clearTimeStr = (0, utils_1.muidTupleToString)([clearanceTime, 0, 0]); const iterator = this.placements.lowerBound(srcAsStr); const result = new Map(); for (; iterator && iterator.key && !iterator.equals(this.placements.end()); iterator.next()) { const parts = iterator.key.split(","); if (parts[0] !== srcAsStr) break; const placementIdStr = parts[parts.length - 1]; if (placementIdStr < clearTimeStr || placementIdStr > asOfTsStr) continue; const entry = iterator.value; (0, utils_1.ensure)(entry.behavior === builders_1.Behavior.DIRECTORY || entry.behavior === builders_1.Behavior.KEY_SET || entry.behavior === builders_1.Behavior.GROUP || entry.behavior === builders_1.Behavior.PAIR_SET || entry.behavior === builders_1.Behavior.PAIR_MAP || entry.behavior === builders_1.Behavior.PROPERTY); const key = (0, store_utils_1.storageKeyToString)(entry.storageKey); if (entry.deletion) result.delete(key); else result.set(key, entry); } return result; } async getContainersByName(name, asOf) { const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const desiredSrc = [ -1, -1, builders_1.Behavior.PROPERTY, ]; const srcAsStr = (0, utils_1.muidTupleToString)(desiredSrc); const clearanceTime = this.getLastClearanceTime(srcAsStr, asOfTs); const clearTimeStr = (0, utils_1.muidTupleToString)([clearanceTime, 0, 0]); const iterator = this.byName.lowerBound(name); const result = []; for (; iterator && iterator.key && !iterator.equals(this.byName.end()); iterator.next()) { const parts = iterator.key.split(","); if (parts[0] !== name) break; const entryMuidStr = parts[1]; if (entryMuidStr < clearTimeStr || entryMuidStr > asOfTsStr) continue; const describingMuid = (0, utils_1.strToMuid)(iterator.value); result.push(describingMuid); } return result; } async getContainerProperties(containerMuid, asOf) { const resultMap = new Map(); const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const strMuid = (0, utils_1.muidToString)(containerMuid); const clearTime = this.getLastClearanceTime(strMuid, asOfTs); const iterator = this.byKeyPlacement.lowerBound(strMuid); for (; iterator && iterator.key && !iterator.equals(this.byKeyPlacement.end()); iterator.next()) { const parts = iterator.key.split(","); if (parts[0] !== strMuid) break; const entry = iterator.value; if (!(entry.behavior === builders_1.Behavior.PROPERTY)) continue; const entryMuid = (0, utils_1.strToMuid)(parts[1]); if (entryMuid.timestamp < clearTime || entryMuid.timestamp > asOfTs) { continue; } const containerMuid = entry.containerId; if (entry.deletion) { resultMap.delete((0, utils_1.muidTupleToString)(containerMuid)); } else { resultMap.set((0, utils_1.muidTupleToString)(containerMuid), entry.value); } } return resultMap; } /** * Returns entry data for a List. * @param container to get entries for * @param through number to get, negative for starting from end * @param asOf show results as of a time in the past * @returns a promise of a list of ChangePairs */ async getOrderedEntries(container, through = Infinity, asOf) { return Promise.resolve(this.getOrderedEntriesSync(container, through, asOf)); } getOrderedEntriesSync(container, through = Infinity, asOf) { var _a, _b, _c; const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const commaAsOfTsStr = "," + (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const containerId = [ (_a = container === null || container === void 0 ? void 0 : container.timestamp) !== null && _a !== void 0 ? _a : 0, (_b = container === null || container === void 0 ? void 0 : container.medallion) !== null && _b !== void 0 ? _b : 0, (_c = container === null || container === void 0 ? void 0 : container.offset) !== null && _c !== void 0 ? _c : 0, ]; const containerIdStr = (0, utils_1.muidTupleToString)(containerId); let clearanceTime = this.getLastClearanceTime(containerIdStr, asOfTs); const clearanceTimeStr = (0, utils_1.muidTupleToString)([clearanceTime, 0, 0]); // TODO: switch sequence key to be appropriately padded/encoded const lower = this.placements.lowerBound(`${containerIdStr}`); const upper = this.placements.upperBound(`${containerIdStr},${asOfTs}`); const returning = new Map(); // If we need to iterate forward or backward let it; if (through < 0) { it = upper; it.prev(); } else { it = lower; } const needed = through < 0 ? -through : through + 1; for (; returning.size < needed; through < 0 ? it.prev() : it.next()) { // '0616C86BB4D7B4-1A86FC813F72F-00001,1713899916549000,0616C86BB4DB88-1A86FC813F72F-00001' const placementKey = it.key; if (!placementKey) break; const foundContainerStr = placementKey.substring(0, 34); if (foundContainerStr !== containerIdStr) break; const placementIdStr = placementKey.slice(-34); if (placementIdStr < clearanceTimeStr || placementIdStr > asOfTsStr) continue; if ((0, utils_1.toLastWithPrefixBeforeSuffix)(this.removals, placementIdStr, commaAsOfTsStr)) continue; const returningKey = placementKey.substring(35); const entry = it.value; (0, utils_1.ensure)((0, utils_1.muidTupleToString)(entry.containerId) === containerIdStr); returning.set(returningKey, entry); } return returning; } getLocation(entry, asOf) { const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const prefix = `${(0, utils_1.muidToString)(entry)}`; const suffix = `${(0, utils_1.muidToString)(entry)},${asOfTsStr}`; const it = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.locations, prefix, suffix); if (!it) return undefined; const parts = it.value.split(","); const containerIdStr = parts[0]; const key = parts[1]; const placementIdStr = parts[2]; const placementTuple = (0, utils_1.strToMuidTuple)(placementIdStr); const lastClear = this.getLastClearanceTime(containerIdStr, asOfTs); const removal = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.removals, placementIdStr, `${placementIdStr},${asOfTsStr}`); if (lastClear > placementTuple[0] || removal) return undefined; return Promise.resolve({ container: (0, utils_1.strToMuidTuple)(containerIdStr), key: key, placement: placementTuple, }); } addEntry(entry) { const entryIdStr = (0, utils_1.muidTupleToString)(entry.entryId); const containerIdStr = (0, utils_1.muidTupleToString)(entry.containerId); const placementIdStr = (0, utils_1.muidTupleToString)(entry.placementId); const placementKey = `${containerIdStr},${(0, store_utils_1.storageKeyToString)(entry.storageKey)},${placementIdStr}`; const behavior = entry.behavior; if (behavior === builders_1.Behavior.SEQUENCE || behavior === builders_1.Behavior.EDGE_TYPE) { this.locations.set(`${entryIdStr},${placementIdStr}`, placementKey); } else { const containerIdStr = (0, utils_1.muidTupleToString)(entry.containerId); const prefix = `${containerIdStr},${(0, store_utils_1.storageKeyToString)(entry.storageKey)}`; for (let iterator = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.placements, prefix); iterator && iterator.key && iterator.key.startsWith(prefix); iterator.prev()) { if (entry.purging || !this.keepingHistory) { this.placements.erase(iterator); this.byKeyPlacement.delete(iterator.key.slice(35)); } else { const placementIdStr = iterator.key.slice(-34); this.removals.set(`${placementIdStr},${entryIdStr}`, ""); } } } this.placements.set(placementKey, entry); this.byKeyPlacement.set(`${(0, store_utils_1.storageKeyToString)(entry.storageKey)},${placementIdStr}`, entry); if (entry.sourceList.length) { // TODO: remove these on deletion/purge const middle = behavior === builders_1.Behavior.EDGE_TYPE ? (0, store_utils_1.storageKeyToString)(entry.storageKey) : ""; const sourceIdStr = (0, utils_1.muidTupleToString)(entry.sourceList[0]); this.bySource.set(`${sourceIdStr},${middle},${placementIdStr}`, entry); (0, utils_1.ensure)(entry.targetList.length); const targetIdStr = (0, utils_1.muidTupleToString)(entry.targetList[0]); this.byTarget.set(`${targetIdStr},${middle},${placementIdStr}`, entry); } if (entry.behavior == builders_1.Behavior.PROPERTY && entry.containerId[0] == -1) { if (Array.isArray(entry.storageKey) && entry.storageKey.length == 3) { let globalPropIterator = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.placements, containerIdStr); for (let iterator = globalPropIterator; iterator && iterator.key && iterator.key.startsWith(containerIdStr); iterator.prev()) { const foundEntry = iterator.value; if (foundEntry.storageKey.toString() === entry.storageKey.toString() && foundEntry.value !== entry.value) { this.byName.delete(foundEntry.value + "," + (0, utils_1.muidTupleToString)(foundEntry.entryId)); } } this.byName.set(entry.value + "," + (0, utils_1.muidTupleToString)(entry.entryId), (0, utils_1.muidTupleToString)(entry.storageKey)); } else { throw new Error("global property storage key isnt a tuple?"); } } if (entry.behavior == builders_1.Behavior.PROPERTY && entry.containerId[0] == -1) { if (Array.isArray(entry.storageKey) && entry.storageKey.length == 3) { let globalPropIterator = (0, utils_1.toLastWithPrefixBeforeSuffix)(this.placements, containerIdStr); for (let iterator = globalPropIterator; iterator && iterator.key && iterator.key.startsWith(containerIdStr); iterator.prev()) { const foundEntry = iterator.value; if (foundEntry.storageKey.toString() === entry.storageKey.toString() && foundEntry.value !== entry.value) { this.byName.delete(foundEntry.value + "," + (0, utils_1.muidTupleToString)(foundEntry.entryId)); } } this.byName.set(entry.value + "," + (0, utils_1.muidTupleToString)(entry.entryId), (0, utils_1.muidTupleToString)(entry.storageKey)); } else { throw new Error("global property storage key isnt a tuple?"); } } } /** * Returns the timestamp of the last clearance for any given container. * @param containerId container muid as a string * @param asOf optional timestamp to query - finds the last clearance within the timeframe. * @returns the timestamp of the last clearance, or 0 if one wasn't found. */ getLastClearanceTime(containerId, asOf) { const asOfStr = asOf ? (0, utils_1.muidTupleToString)([asOf, 0, 0]) : "~"; const upperClearance = this.clearances.upperBound(`${containerId},${asOfStr}`); upperClearance.prev(); let clearanceTime = 0; if (upperClearance.value && (0, utils_1.sameData)(containerId, (0, utils_1.muidTupleToString)(upperClearance.value.containerId))) { clearanceTime = upperClearance.value.clearanceId[0]; } return clearanceTime; } getEntriesBySourceOrTarget(vertex, source, asOf) { return Promise.resolve(this.getEntriesBySourceOrTargetSync(vertex, source, asOf)); } getEntriesBySourceOrTargetSync(vertex, source, asOf) { const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)(); const vertexIdStr = (0, utils_1.muidToString)(vertex); const map = source ? this.bySource : this.byTarget; const entries = []; for (const it = map.lowerBound(vertexIdStr); it.key && it.key.startsWith(vertexIdStr); it.next()) { const entry = it.value; if (this.isSoftDeleted(entry, asOfTs)) continue; if (entry.placementId[0] > asOfTs) continue; entries.push(it.value); } return entries; } getAllContainerTuples() { const arr = []; arr.push([-1, -1, 4], [-1, -1, 10]); for (const containerIdStr of this.containers.keys()) { arr.push((0, utils_1.strToMuidTuple)(containerIdStr)); } return Promise.resolve(arr); } isSoftDeleted(entry, asOfTs) { const placementIdStr = (0, utils_1.muidTupleToString)(entry.placementId); const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]); const upperBound = `${placementIdStr},${asOfTsStr}`; for (const it = this.removals.lowerBound(placementIdStr); it.key && it.key < upperBound; it.next()) { return true; } return false; } async close() { await this.ready; delete this.trxns; delete this.chainInfos; delete this.activeChains; delete this.clearances; delete this.containers; delete this.removals; delete this.placements; return Promise.resolve(); } // for debugging, not part of the api/interface getAllEntryKeys() { return Array.from(this.placements.keys()); } // for debugging, not part of the api/interface getAllEntries() { return Array.from(this.placements.values()); } // for debugging, not part of the api/interface getAllRemovals() { return this.removals; } addFoundBundleCallBack(callback) { this.foundBundleCallBacks.push(callback); } } exports.MemoryStore = MemoryStore; MemoryStore.YEAR_2020 = new Date("2020-01-01").getTime() * 1000; //# sourceMappingURL=MemoryStore.js.map