@x5e/gink
Version:
an eventually consistent database
742 lines • 34.3 kB
JavaScript
;
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.accumulatorTotals = new Map();
this.ready = utils_1.librariesReady;
}
getBillionths(muid, asOf) {
if (asOf)
throw new Error("asOf Not implemented for accumulators yet");
const key = (0, utils_1.muidToString)(muid);
return Promise.resolve(this.accumulatorTotals.get(key) ?? BigInt(0));
}
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)));
}
acquireChain(identity) {
return Promise.resolve(null);
}
dumpEntries() {
(0, utils_1.dumpTree)(this.placements);
}
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?.timestamp >= timestamp) {
return false;
}
if (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 = {
...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 = {
...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 {
// 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) {
const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)();
const desiredSrc = [
container?.timestamp ?? 0,
container?.medallion ?? 0,
container?.offset ?? 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) {
const asOfTs = asOf ? this.asOfToTimestamp(asOf) : (0, utils_1.generateTimestamp)();
const asOfTsStr = (0, utils_1.muidTupleToString)([asOfTs, 0, 0]);
const desiredSrc = [
container?.timestamp ?? 0,
container?.medallion ?? 0,
container?.offset ?? 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 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 propertyMuid = (0, utils_1.muidTupleToString)(entry.containerId);
const clearTime = this.getLastClearanceTime(propertyMuid, asOfTs);
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) {
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 = [
container?.timestamp ?? 0,
container?.medallion ?? 0,
container?.offset ?? 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);
if (behavior == builders_1.Behavior.ACCUMULATOR) {
const delta = entry.value;
if (typeof delta !== "bigint")
throw new Error("Accumulator increment not an integer?");
let total = this.accumulatorTotals.get(containerIdStr) ?? BigInt(0);
total = total + delta;
this.accumulatorTotals.set(containerIdStr, total);
return;
}
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