@x5e/gink
Version:
an eventually consistent database
244 lines • 11 kB
JavaScript
;
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeySet = void 0;
const Container_1 = require("./Container");
const Bundler_1 = require("./Bundler");
const utils_1 = require("./utils");
const factories_1 = require("./factories");
const builders_1 = require("./builders");
class KeySet extends Container_1.Container {
constructor(database, address, containerBuilder) {
super(database, address, builders_1.Behavior.KEY_SET);
if (this.address.timestamp < 0) {
(0, utils_1.ensure)(address.offset === builders_1.Behavior.KEY_SET);
}
else {
(0, utils_1.ensure)(containerBuilder.getBehavior() === builders_1.Behavior.KEY_SET);
}
}
/**
* Adds a key to the keyset.
* If a bundler is supplied, the function will add the entry to that bundler
* and return immediately (presumably you know what to do with a CS if you passed it in).
* If the caller does not supply a bundler, then one is created on the fly, and
* then this method will await on the CS being added to the database instance.
* This is to allow simple console usage like:
* await myKeySet.add("foo");
* @param key
* @param change an optional bundler to put this in.
* @returns a promise that resolves to the address of the newly created entry
*/
async add(key, change) {
return await this.addEntry(key, Container_1.Container.INCLUSION, change);
}
/**
* Similar to add method, but for multiple entries.
* @param keys an iterable of keys to add to the key set
* @param change an optional bundler to put this in.
* @returns a promise that resolves to a Bundler object for the created entries.
*/
async update(keys, change) {
let bundler;
if (change instanceof Bundler_1.Bundler) {
bundler = change;
}
else {
bundler = new Bundler_1.Bundler(change);
}
for (const key of keys) {
await this.addEntry(key, Container_1.Container.INCLUSION, bundler);
}
await this.database.addBundler(bundler);
return bundler;
}
/**
* 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);
}
/**
* Function to iterate over the contents of the key set.
* @param asOf
* @returns an async iterator across everything in the key set, with values returned as pairs of Key, Key
*/
entries(asOf) {
const thisSet = this;
return (function () {
return __asyncGenerator(this, arguments, function* () {
const entries = yield __await(thisSet.database.store.getKeyedEntries(thisSet.address, asOf));
for (const [key, entry] of entries) {
const storageKey = entry.storageKey;
yield yield __await([storageKey, storageKey]);
}
});
})();
}
/**
* Returns whether the key set has a key or not.
* @param key
* @param asOf
* @returns true if the key set has the key, false if not.
*/
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;
}
/**
*
* @param args Optional arguments, including:
* @argument toTime Optional time to reset to. If absent, the container will be cleared.
* @argument bundlerOrComment Optional bundler or comment to add this change to
* @argument skipProperties If true, do not reset properties of this container. By default,
* all properties associated with this container will be reset to the time specified in toTime.
* @argument recurse NOTE: THIS FLAG IS IGNORED. Recursive reset for Inclusion-based containers
* is not yet implemented, but this arg needs to be accepted for other containers recursively
* resetting this one.
* @argument seen NOTE: THIS FLAG IS IGNORED. Recursive reset for Inclusion-based containers
* is not yet implemented, but this arg needs to be accepted for other containers recursively
* resetting this one.
*/
async reset(args) {
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;
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 union = new Set();
const entriesThen = await this.database.store.getKeyedEntries(this.address, toTime);
const entriesNow = await this.database.store.getKeyedEntries(this.address);
for (const [key, entry] of entriesThen) {
const storageKey = entry.storageKey;
union.add(storageKey);
}
for (const [key, entry] of entriesNow) {
const storageKey = entry.storageKey;
union.add(storageKey);
}
for (const key of union) {
const genericKey = (0, utils_1.fromStorageKey)(key);
const thenEntry = await this.database.store.getEntryByKey(this.address, genericKey, toTime);
const nowEntry = await this.database.store.getEntryByKey(this.address, genericKey);
(0, utils_1.ensure)(nowEntry || thenEntry, "both then and now undefined?");
if (!nowEntry) {
// This key was present then, but not now, so we need to add it back
(0, utils_1.ensure)(thenEntry, "missing then entry?");
await this.addEntry(genericKey, thenEntry.value, bundler);
}
else if (!thenEntry) {
// This key is present now, but not then, so we need to delete it
(0, utils_1.ensure)(nowEntry, "missing now entry?");
await this.addEntry(genericKey, Container_1.Container.DELETION, bundler);
}
else if (nowEntry.deletion !== thenEntry.deletion) {
if (nowEntry.deletion) {
// Present then, deleted now. Need to revive.
await this.addEntry(genericKey, Container_1.Container.INCLUSION, bundler);
}
else if (thenEntry.deletion) {
// Present now, deleted then. Need to delete.
await this.addEntry(genericKey, Container_1.Container.DELETION, bundler);
}
}
else {
(0, utils_1.ensure)(nowEntry.deletion === thenEntry.deletion, "last case should be same entry");
}
}
}
if (!skipProperties) {
await this.resetProperties(toTime, bundler);
}
if (immediate) {
await this.database.addBundler(bundler);
}
}
/**
* Returns the contents of the key set as a set.
* @param asOf
* @returns a promise that resolves to a set with KeyTypes.
*/
async toSet(asOf) {
const entries = await this.database.store.getKeyedEntries(this.address, asOf);
const resultSet = new Set();
for (const [key, entry] of entries) {
const storageKey = entry.storageKey;
resultSet.add(storageKey);
}
return resultSet;
}
/**
* How many entries are in the key set.
* @param asOf
* @returns a promise that resolves to a number.
*/
async size(asOf) {
const entries = await this.database.store.getKeyedEntries(this.address, asOf);
return entries.size;
}
/**
* Generates a JSON representation of the data in the key set.
* 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) {
//TODO(https://github.com/google/gink/issues/62): add indentation
(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 asSet = await this.toSet(asOf);
let returning = "[";
let first = true;
for (const key of asSet) {
if (first) {
first = false;
}
else {
returning += ",";
}
// returning += `"${key}"`;
returning += await (0, factories_1.toJson)(key, indent === false ? false : +indent + 1, asOf, seen);
}
returning += "]";
return returning;
}
}
exports.KeySet = KeySet;
//# sourceMappingURL=KeySet.js.map