@x5e/gink
Version:
an eventually consistent database
209 lines • 9.38 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Keyed = void 0;
const Container_1 = require("./Container");
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, meta) {
if (value === undefined) {
throw new Error("value is undefined");
}
return this.addEntry(key, value, meta);
}
/**
* 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, meta) {
return this.addEntry(key, Container_1.Container.DELETION, meta);
}
/**
* 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(toTime, recurse, meta) {
if (this.behavior === builders_1.Behavior.PROPERTY) {
throw new Error("Cannot directly reset a property. " +
"Calling reset on a Container will reset its properties.");
}
if (recurse === true) {
recurse = new Set();
}
if (recurse instanceof Set) {
recurse.add((0, utils_1.muidToString)(this.address));
}
const bundler = await this.database.startBundle(meta);
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?.pointeeList.length > 0
? await (0, factories_1.construct)((0, utils_1.muidTupleToMuid)(thenEntry.pointeeList[0]), this.database)
: thenEntry?.value;
const nowEntry = await this.database.store.getEntryByKey(this.address, genericKey);
const nowValue = nowEntry?.pointeeList.length > 0
? await (0, factories_1.construct)((0, utils_1.muidTupleToMuid)(nowEntry.pointeeList[0]), this.database)
: nowEntry?.value;
if (!nowEntry) {
// This key was present then, but not now, so we need to add it back
if (thenEntry && !thenEntry.deletion) {
(0, utils_1.ensure)(thenValue, `missing for key: ${key}, ${JSON.stringify(genericKey)}`);
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 (recurse &&
thenValue instanceof Container_1.Container &&
!recurse.has((0, utils_1.muidToString)(thenValue.address))) {
await thenValue.reset(toTime, recurse, { bundler });
}
}
}
if (!meta?.bundler) {
await bundler.commit();
}
}
/**
* 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)(pointee, this.database);
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)((0, utils_1.muidTupleToMuid)(entry.pointeeList[0]), this.database)).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