@x5e/gink
Version:
an eventually consistent database
256 lines (240 loc) • 9.17 kB
text/typescript
import { Value, ScalarKey, Muid, AsOf, Meta, Bundler } from "./typedefs";
import { muidToBuilder, wrapValue, wrapKey, strToMuid } from "./utils";
import { bundlePropertyEntry } from "./store_utils";
import { Deletion } from "./Deletion";
import { Inclusion } from "./Inclusion";
import { Database } from "./Database";
import {
EntryBuilder,
ChangeBuilder,
Behavior,
ClearanceBuilder,
ContainerBuilder,
} from "./builders";
import { PairBuilder } from "./builders";
import { Addressable } from "./Addressable";
import { interpret } from "./factories";
import { inspectSymbol } from "./utils";
export abstract class Container extends Addressable {
protected static readonly DELETION = new Deletion();
protected static readonly INCLUSION = new Inclusion();
protected static globalPropertyMuid = {
medallion: -1,
timestamp: -1,
offset: Behavior.PROPERTY,
};
protected constructor(
readonly database: Database,
address: Muid,
readonly behavior: Behavior,
) {
super(address);
}
[inspectSymbol](depth, opts) {
/*
const timestamp = this.address.timestamp;
const medallion = this.address.medallion;
const offset = this.address.offset;
{timestamp: ${timestamp}, medallion: ${medallion}, offset: ${offset}}
*/
/*
{
stylize: [Function: stylizeWithColor],
showHidden: false,
depth: 2,
colors: true,
customInspect: true,
showProxy: true,
maxArrayLength: 100,
maxStringLength: 10000,
breakLength: 80,
compact: 3,
sorted: false,
getters: false,
numericSeparator: false
}
*/
return `[${this.whatAmI()}]`;
}
public whatAmI() {
const name = this.constructor?.name;
if (name && name.length > 2) return name;
return "(Minified)";
}
protected static async addContainer({
database,
behavior,
meta,
}: {
database: Database;
behavior: Behavior;
meta?: Meta;
}): Promise<Muid> {
const bundler = await database.startBundle(meta);
const containerBuilder = new ContainerBuilder();
containerBuilder.setBehavior(behavior);
const muid = bundler.addChange(
new ChangeBuilder().setContainer(containerBuilder),
);
if (!meta?.bundler) {
await bundler.commit();
}
return muid;
}
abstract toJson(
indent: number | boolean,
asOf?: AsOf,
seen?: Set<string>,
): Promise<string>;
public async setName(name: string, meta?: Meta): Promise<Muid> {
return await this.addEntry(
this,
name,
meta,
Container.globalPropertyMuid,
);
}
public async getName(asOf?: AsOf): Promise<string | undefined> {
const entry = await this.database.store.getEntryByKey(
Container.globalPropertyMuid,
this.address,
asOf,
);
const result = await interpret(entry, this.database);
return <string | undefined>result;
}
public async clear(purge?: boolean, meta?: Meta): Promise<Muid> {
const bundler = await this.database.startBundle(meta);
const clearanceBuilder = new ClearanceBuilder();
clearanceBuilder.setPurge(purge || false);
clearanceBuilder.setContainer(
muidToBuilder(this.address, bundler.medallion),
);
const changeBuilder = new ChangeBuilder();
changeBuilder.setClearance(clearanceBuilder);
const address = bundler.addChange(changeBuilder);
if (!meta?.bundler) {
await bundler.commit();
}
return address;
}
/**
* Reset this Container to a previous time. If no time is specified, the container will
* be cleared.
* @argument toTime Optional time to reset to. If absent, the container will be cleared.
* @argument recurse Recursively reset all child containers held by this container at reset time?
* @argument meta Metadata to be used in the reset.
*/
public abstract reset(toTime?: AsOf, recurse?, meta?: Meta): Promise<void>;
public abstract size(asOf?: AsOf): Promise<number>;
protected addEntry(
key?:
| ScalarKey
| Addressable
| [Addressable, Addressable]
| Muid
| [Muid, Muid],
value?: Container | Value | Deletion | Inclusion,
meta?: Meta,
onContainer?: Muid,
): Promise<Muid> {
return this.database.startBundle(meta).then((bundler) => {
const entryBuilder = new EntryBuilder();
if (!this.address) throw new Error("unexpected");
entryBuilder.setContainer(
muidToBuilder(onContainer ?? this.address, bundler.medallion),
);
let behavior = this.behavior;
if (onContainer) {
if (onContainer.timestamp !== -1) throw new Error("unexpected");
behavior = onContainer.offset;
}
entryBuilder.setBehavior(behavior);
if (
typeof key === "number" ||
typeof key === "string" ||
key instanceof Uint8Array
) {
entryBuilder.setKey(wrapKey(key));
} else if (Array.isArray(key)) {
const pair = new PairBuilder();
let key1 = key[0];
let key2 = key[1];
if ("address" in key[0] && "address" in key[1]) {
key1 = key[0].address;
key2 = key[1].address;
}
pair.setLeft(muidToBuilder(key1));
pair.setRite(muidToBuilder(key2));
entryBuilder.setPair(pair);
} else if (key instanceof Addressable) {
entryBuilder.setDescribing(muidToBuilder(key.address));
} else if (key && "timestamp" in key) {
entryBuilder.setDescribing(muidToBuilder(key));
}
// TODO: check that the destination/value is compatible with Container
if (value !== undefined) {
if (value instanceof Addressable) {
entryBuilder.setPointee(
muidToBuilder(value.address, bundler.medallion),
);
} else if (value instanceof Deletion) {
entryBuilder.setDeletion(true);
} else if (value instanceof Inclusion) {
} else {
entryBuilder.setValue(wrapValue(value));
}
}
const changeBuilder = new ChangeBuilder();
changeBuilder.setEntry(entryBuilder);
const address = bundler.addChange(changeBuilder);
if (!meta?.bundler) {
return bundler.commit().then(() => address);
}
return address;
});
}
/**
* Reset the properties associated with this container to a previous time.
* @param toTime optional timestamp to reset to. If not provided, the properties will be deleted.
* @param bundlerOrComment optional bundler to add this change to, or a string to add a comment to a new bundle.
*/
public async resetProperties(toTime?: AsOf, meta?: Meta): Promise<void> {
const bundler: Bundler = await this.database.startBundle(meta);
const propertiesNow =
await this.database.store.getContainerProperties(this);
if (!toTime) {
for (const [key, _] of propertiesNow.entries()) {
const propertyMuid = strToMuid(key);
// Omitting value parameter creates a deleting entry
bundlePropertyEntry(bundler, propertyMuid, this.address);
}
} else {
const propertiesThen =
await this.database.store.getContainerProperties(this, toTime);
for (const [key, value] of propertiesThen.entries()) {
if (value !== propertiesNow.get(key)) {
const propertyMuid = strToMuid(key);
bundlePropertyEntry(
bundler,
propertyMuid,
this.address,
value,
);
}
// Remove from propertiesNow so we can delete the rest
// after this iteration
propertiesNow.delete(key);
}
// Now loop through the remaining propertiesNow and delete them
for (const [key, _] of propertiesNow.entries()) {
const propertyMuid = strToMuid(key);
// Omitting value parameter creates a deleting entry
bundlePropertyEntry(bundler, propertyMuid, this.address);
}
}
if (!meta?.bundler) {
await bundler.commit();
}
}
}