UNPKG

@x5e/gink

Version:

an eventually consistent database

244 lines 11 kB
"use strict"; 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