@x5e/gink
Version:
an eventually consistent database
140 lines (130 loc) • 4.95 kB
text/typescript
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)) + "]";
}
}