UNPKG

@x5e/gink

Version:

an eventually consistent database

140 lines (130 loc) 4.95 kB
import { Database } from "./Database"; import { Container } from "./Container"; import { Value, Muid, AsOf, Meta } from "./typedefs"; import { ensure, muidToString } from "./utils"; import { toJson, interpret } from "./factories"; import { Behavior } from "./builders"; export class Box extends Container { private constructor(database: Database, address: Muid) { super(database, address, Behavior.BOX); if (this.address.timestamp < 0) { ensure(address.offset === Behavior.BOX); } } static get(database?: Database, muid?: Muid): Box { if (!muid) { muid = { timestamp: -1, medallion: -1, offset: Behavior.BOX }; } database = database || Database.recent; return new Box(database, muid); } static async create(database?: Database, meta?: Meta): Promise<Box> { database = database || Database.recent; const muid = await Container.addContainer({ behavior: Behavior.BOX, database, meta, }); return new Box(database, muid); } /** * Puts a value or a reference to another container in this box. * 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 myBox.put("some value"); * @param value * @param change an optional bundler to put this in. * @returns a promise that resolves to the address of the newly created entry */ async set(value: Value | Container, meta?: Meta): Promise<Muid> { return this.addEntry(undefined, value, meta); } /** * Returns a promise that resolves to the most recent value put in the box, or undefined. * @returns undefined, a basic value, or a container */ async get(asOf?: AsOf): Promise<Container | Value | undefined> { const entry = await this.database.store.getEntryByKey( this.address, undefined, asOf, ); return interpret(entry, this.database); } /** * checks to see how many things are in the box (will be either 0 or 1) * @param asOf Historical time to look * @returns 0 or 1 depending on whether there's something in the box. */ async size(asOf?: AsOf): Promise<number> { const entry = await this.database.store.getEntryByKey( this.address, undefined, asOf, ); return +!(entry === undefined || entry.deletion); } async reset(toTime?: AsOf, recurse?, meta?: Meta): Promise<void> { if (recurse === true) { recurse = new Set(); } if (recurse instanceof Set) { recurse.add(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 thereNow = await this.get(); const thereThen = await this.get(toTime); if (thereThen !== thereNow) { await this.set(thereThen, { bundler }); } if ( recurse && thereThen instanceof Container && !recurse.has(muidToString(thereThen.address)) ) { await thereThen.reset(toTime, recurse, { bundler }); } } if (!meta?.bundler) { await bundler.commit(); } } /** * checks to see if something is in the box * @param asOf * @returns true if no value or container is in the box */ async isEmpty(asOf?: AsOf): Promise<boolean> { const entry = await this.database.store.getEntryByKey( this.address, undefined, asOf, ); return entry === undefined || entry.deletion; } /** * Generates a JSON representation of the data in the box (the box itself is transparent). * Mostly intended for demo/debug purposes. * @param indent true to pretty print * @param asOf effective time * @param seen (internal use only! Prevent cycles from breaking things) * @returns a JSON string */ async toJson( indent: number | boolean = false, asOf?: AsOf, seen?: Set<string>, ): Promise<string> { if (seen === undefined) seen = new Set(); const contents = await this.get(asOf); if (contents === undefined) return "[null]"; return "[" + (await toJson(contents, indent, asOf, seen)) + "]"; } }