UNPKG

@x5e/gink

Version:

an eventually consistent database

221 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Keyed = void 0; const Container_1 = require("./Container"); const Bundler_1 = require("./Bundler"); const utils_1 = require("./utils"); const factories_1 = require("./factories"); const store_utils_1 = require("./store_utils"); const builders_1 = require("./builders"); class Keyed extends Container_1.Container { /** * Sets a key/value association in a directory. * If a bundler is supplied, the function will add the entry to that bundler * and return immediately (you'll need to addBundler manually in that case). * If the caller does not supply a bundler, then one is created on the fly, and * then this method will await on the bundler being added to the database instance. * This is to allow simple console usage like: * await myDirectory.set("foo", "bar"); * @param key * @param value * @param change an optional bundler to put this in. * @returns a promise that resolves to the address of the newly created entry */ set(key, value, change) { return this.addEntry(key, value, change); } /** * Adds a deletion marker (tombstone) for a particular key in the directory. * The corresponding value will be seen to be unset in the data model. * @param key * @param change an optional bundler to put this in. * @returns a promise that resolves to the address of the newly created deletion entry */ async delete(key, change) { return await this.addEntry(key, Container_1.Container.DELETION, change); } /** * Returns a promise that resolves to the most recent value set for the given key, or undefined. * @param key * @param asOf * @returns undefined, a basic value, or a container */ async get(key, asOf) { const entry = await this.database.store.getEntryByKey(this.address, key, asOf); return (0, factories_1.interpret)(entry, this.database); } async size(asOf) { const entries = await this.database.store.getKeyedEntries(this.address, asOf); return entries.size; } async has(key, asOf) { const result = await this.database.store.getEntryByKey(this.address, key, asOf); if (result !== undefined && result.deletion) { return false; } return result !== undefined; } async reset(args) { var _a; if (this.behavior === builders_1.Behavior.PROPERTY) { throw new Error("Cannot directly reset a property. " + "Calling reset on a Container will reset its properties."); } const toTime = args === null || args === void 0 ? void 0 : args.toTime; const bundlerOrComment = args === null || args === void 0 ? void 0 : args.bundlerOrComment; const skipProperties = args === null || args === void 0 ? void 0 : args.skipProperties; const recurse = args === null || args === void 0 ? void 0 : args.recurse; const seen = recurse ? ((_a = args === null || args === void 0 ? void 0 : args.seen) !== null && _a !== void 0 ? _a : new Set()) : undefined; if (seen) { seen.add((0, utils_1.muidToString)(this.address)); } let immediate = false; let bundler; if (bundlerOrComment instanceof Bundler_1.Bundler) { bundler = bundlerOrComment; } else { immediate = true; bundler = new Bundler_1.Bundler(bundlerOrComment); } if (!toTime) { // If no time is specified, we are resetting to epoch, which is just a clear this.clear(false, bundler); } else { const keys = new Set(); const thenEntries = await this.database.store.getKeyedEntries(this.address, toTime); for (const [key, entry] of thenEntries) { keys.add(entry.storageKey); } const nowEntries = await this.database.store.getKeyedEntries(this.address); for (const [key, entry] of nowEntries) { keys.add(entry.storageKey); } for (const key of keys) { const genericKey = (0, utils_1.fromStorageKey)(key); const thenEntry = await this.database.store.getEntryByKey(this.address, genericKey, toTime); const thenValue = (thenEntry === null || thenEntry === void 0 ? void 0 : thenEntry.pointeeList.length) > 0 ? await (0, factories_1.construct)(this.database, (0, utils_1.muidTupleToMuid)(thenEntry.pointeeList[0])) : thenEntry === null || thenEntry === void 0 ? void 0 : thenEntry.value; const nowEntry = await this.database.store.getEntryByKey(this.address, genericKey); const nowValue = (nowEntry === null || nowEntry === void 0 ? void 0 : nowEntry.pointeeList.length) > 0 ? await (0, factories_1.construct)(this.database, (0, utils_1.muidTupleToMuid)(nowEntry.pointeeList[0])) : nowEntry === null || nowEntry === void 0 ? void 0 : nowEntry.value; if (!nowEntry) { // This key was present then, but not now, so we need to add it back (0, utils_1.ensure)(thenEntry && thenValue, "missing value?"); await this.addEntry(genericKey, thenValue, bundler); } else if (!thenEntry) { // This key is present now, but not then, so we need to delete it (0, utils_1.ensure)(nowEntry && nowValue, "missing value?"); await this.addEntry(genericKey, Container_1.Container.DELETION, bundler); } else { // Present both then and now. Check if the values are different if (nowValue !== thenValue) { // Make sure the values are not the same container if (!(nowValue instanceof Container_1.Container && thenValue instanceof Container_1.Container && (0, utils_1.muidToString)(nowValue.address) === (0, utils_1.muidToString)(thenValue.address))) { // Update the entry await this.addEntry(genericKey, thenValue, bundler); } } } if (seen && thenValue instanceof Container_1.Container && !seen.has((0, utils_1.muidToString)(thenValue.address))) { await thenValue.reset({ toTime, bundlerOrComment: bundler, skipProperties, recurse, seen, }); } } } if (!skipProperties) { await this.resetProperties(toTime, bundler); } if (immediate) { await this.database.addBundler(bundler); } } /** * Dumps the contents of this directory into a javascript Map; mostly useful for * debugging though also could be used to create a backup of a database. * @param asOf effective time to get the dump for, or undefined for the present * @returns a javascript map from keys (numbers or strings) to values or containers */ async toMap(asOf) { const entries = await this.database.store.getKeyedEntries(this.address, asOf); const resultMap = new Map(); for (const [key, entry] of entries) { const pointee = entry.pointeeList.length > 0 ? (0, utils_1.muidTupleToMuid)(entry.pointeeList[0]) : undefined; const val = entry.value !== undefined ? entry.value : await (0, factories_1.construct)(this.database, pointee); resultMap.set(entry.storageKey, val); } return resultMap; } /** * Generates a JSON representation of the data in this container. * Mostly intended for demo/debug purposes. * @param indent true to pretty print * @param asOf effective time * @param seen (internal use only! This prevents cycles from breaking things) * @returns a JSON string */ async toJson(indent = false, asOf, seen) { (0, utils_1.ensure)(indent === false, "indent not implemented"); if (seen === undefined) seen = new Set(); const mySig = (0, utils_1.muidToString)(this.address); if (seen.has(mySig)) return "null"; seen.add(mySig); const asMap = await this.toMap(asOf); let returning = "{"; let first = true; const entries = (await this.database.store.getKeyedEntries(this.address, asOf)).values(); for (const entry of entries) { if (first) { first = false; } else { returning += ","; } const storageKey = entry.storageKey; if (typeof storageKey === "string") { returning += JSON.stringify(storageKey); } else if (storageKey instanceof Uint8Array) { returning += '"' + storageKey.toString() + '"'; } else { returning += JSON.stringify((0, store_utils_1.storageKeyToString)(storageKey)); } returning += ":"; if (entry.value !== undefined) { returning += (0, utils_1.valueToJson)(entry.value); } else if (entry.pointeeList.length > 0) { returning += await (await (0, factories_1.construct)(this.database, (0, utils_1.muidTupleToMuid)(entry.pointeeList[0]))).toJson(indent === false ? false : +indent + 1, asOf, seen); } else { throw new Error(`don't know how to interpret: ${entry}`); } } returning += "}"; return returning; } } exports.Keyed = Keyed; //# sourceMappingURL=Keyed.js.map