@dstanesc/o-o-o-o-o-o-o
Version:
O-O-O-O-O-O-O is a collection of content addressed persistent data structures
772 lines • 23.7 kB
JavaScript
import { fastCloneVersionDetails } from './clone';
const REF_EXISTS = 0x1;
const TYPE_EXISTS = 0x1;
const LINK_EXISTS = 0x1;
class BinaryEncoder {
constructor(size) {
this.buffer = new Uint8Array(size);
this.cursor = 0;
}
content() {
return this.buffer.subarray(0, this.cursor); // identical w/ fixed size buffer
}
/*
* String, fixed size, first 4 bytes hold the actual size
*/
// writeStringFixedSize(value: string, size: number) { // size + 4
// const bytes = pack(value)
// if (bytes.length > size) throw new Error(`String too large - ${bytes.length}, max allowed ${size} bytes`)
// const length = bytes.length
// this.writeUInt(length)
// const fixedSizeBuffer = new Uint8Array(size)
// fixedSizeBuffer.set(bytes, 0)
// return this.writeBytes(fixedSizeBuffer)
// }
writeByte(byte) {
this.buffer[this.cursor] = byte;
this.cursor += 1;
}
writeBytes(bytes) {
const start = this.cursor;
this.buffer.set(bytes, start);
this.cursor += bytes.byteLength;
return this.cursor;
}
/*
* Unsigned 32-bit integer, little endian
*/
writeUInt(value) {
if (value < 0 || value > 0xffffffff)
throw new Error('Integer out of range');
const start = this.cursor;
this.buffer[start] = value & 0xff;
this.buffer[start + 1] = value >>> 8;
this.buffer[start + 2] = value >>> 16;
this.buffer[start + 3] = value >>> 24;
this.cursor += 4;
return this.cursor;
}
/*
* 32-bit integer, little endian
*/
writeInt(value) {
if (value < -0x80000000 || value > 0x7fffffff)
throw new Error('Integer out of range');
const start = this.cursor;
this.buffer[start] = value & 0xff;
this.buffer[start + 1] = value >>> 8;
this.buffer[start + 2] = value >>> 16;
this.buffer[start + 3] = value >>> 24;
this.cursor += 4;
return this.cursor;
}
writeReserved(flags) {
return this.writeByte(flags);
}
writeRefExists() {
let flags = 0;
flags |= REF_EXISTS;
return this.writeByte(flags);
}
writeOffset(offset) {
//if (this.cursor !== offset) throw new Error(`Writing invalid offset ${offset}, current offset is ${this.cursor}`)
return this.writeUInt(offset);
}
writeRef(ref) {
// 5
this.writeRefExists(); // 1
return this.writeUInt(ref); // 4
}
writeTypeExists() {
let flags = 0;
flags |= TYPE_EXISTS;
return this.writeByte(flags);
}
writeStatus(flag) {
let flags = 0;
flags |= flag;
return this.writeByte(flags);
}
writeType(type) {
//5
this.writeTypeExists(); // 1
return this.writeUInt(type); // 4
}
writeLinkExists() {
let flags = 0;
flags |= LINK_EXISTS;
return this.writeByte(flags);
}
writeLink(link) {
this.writeBytes(link.bytes);
}
skipBytes(length) {
this.cursor += length;
return this.cursor;
}
skipReserved() {
return this.skipBytes(1);
}
skipUInt() {
// 4
return this.skipBytes(4);
}
skipRef() {
// 5
return this.skipBytes(5);
}
skipType() {
// 5
return this.skipBytes(5);
}
}
class BinaryDecoder {
constructor(buffer) {
this.buffer = buffer;
this.cursor = 0;
}
readByte() {
const start = this.cursor;
this.cursor += 1;
return this.buffer[start];
}
readBytes(length) {
const start = this.cursor;
this.cursor += length;
const bytes = this.buffer.subarray(start, this.cursor);
return bytes;
}
// readStringFixedSize(bufferSize: number) {
// const stringSize = this.readUInt()
// const stringBytes = this.readBytes(stringSize)
// this.cursor += bufferSize - stringSize
// return unpack(stringBytes)
// }
/*
* Unsigned 32-bit integer, little endian
*/
readUInt() {
const start = this.cursor;
const value = (this.buffer[start] |
(this.buffer[start + 1] << 8) |
(this.buffer[start + 2] << 16)) +
this.buffer[start + 3] * 0x1000000;
this.cursor += 4;
return value;
}
/*
* 32-bit integer, little endian
*/
readInt() {
const start = this.cursor;
const value = this.buffer[start] |
(this.buffer[start + 1] << 8) |
(this.buffer[start + 2] << 16) |
(this.buffer[start + 3] << 24);
this.cursor += 4;
return value;
}
readOffset() {
return this.readUInt();
}
readRefExists() {
// 1
return this.readByte();
}
readTypeExists() {
// 1
return this.readByte();
}
readLinkExists() {
// 1
return this.readByte();
}
readStatus() {
// 1
const flags = this.readByte();
return flags;
}
readRef() {
// 5
const flags = this.readRefExists(); // 1
if (flags & REF_EXISTS) {
return this.readOffset(); // 4
}
else {
this.skipUInt();
return undefined;
}
}
readType() {
// 5
const flags = this.readTypeExists(); // 1
if (flags & TYPE_EXISTS) {
return this.readUInt(); // 4
}
else {
this.skipUInt();
return undefined;
}
}
readLink(decode) {
const bytes = this.readBytes(36);
return decode(bytes);
}
readOptionalLink(decode) {
const flags = this.readLinkExists(); // 1
if (flags & LINK_EXISTS) {
return this.readLink(decode);
}
else {
this.skipBytes(36);
return undefined;
}
}
skipBytes(length) {
this.cursor += length;
}
skipReserved() {
// 1
return this.skipBytes(1);
}
skipUInt() {
// 4
return this.skipBytes(4);
}
skipRef() {
// 5
return this.skipBytes(5);
}
skipType() {
// 5
return this.skipBytes(5);
}
}
const VERTEX_SIZE_BYTES = 25;
class VertexEncoder extends BinaryEncoder {
constructor(vertices) {
super(vertices.length * VERTEX_SIZE_BYTES);
this.vertices = vertices;
}
writeVertex(vertex) {
// 20
this.writeOffset(vertex.offset); // 4
if (vertex.type !== undefined)
// 5
this.writeType(vertex.type);
else
this.skipType();
if (vertex.nextEdge !== undefined)
// 5
this.writeRef(vertex.nextEdge);
else
this.skipRef();
if (vertex.nextProp !== undefined)
// 5
this.writeRef(vertex.nextProp);
else
this.skipRef();
if (vertex.nextIndex !== undefined)
// 5
this.writeRef(vertex.nextIndex);
else
this.skipRef();
this.writeStatus(vertex.status); // 1
}
write() {
for (const vertex of this.vertices)
this.writeVertex(vertex);
return this.content();
}
}
class VertexDecoder extends BinaryDecoder {
readVertex() {
const offset = this.readOffset();
const type = this.readType();
const nextEdge = this.readRef();
const nextProp = this.readRef();
const nextIndex = this.readRef();
const status = this.readStatus();
const vertex = { status, offset };
if (type !== undefined)
vertex.type = type;
if (nextEdge !== undefined)
vertex.nextEdge = nextEdge;
if (nextProp !== undefined)
vertex.nextProp = nextProp;
if (nextIndex !== undefined)
vertex.nextIndex = nextIndex;
return vertex;
}
read() {
if (this.buffer.byteLength % VERTEX_SIZE_BYTES !== 0)
throw new Error('Invalid vertex serialization');
const size = Math.trunc(this.buffer.byteLength / VERTEX_SIZE_BYTES);
const vertices = [];
for (let i = 0; i < size; i++) {
vertices.push(this.readVertex());
}
return vertices;
}
}
const EDGE_SIZE_BYTES = 45;
class EdgeEncoder extends BinaryEncoder {
constructor(edges) {
super(edges.length * EDGE_SIZE_BYTES);
this.edges = edges;
}
writeEdge(edge) {
this.writeOffset(edge.offset); // 4
if (edge.type !== undefined)
// 5
this.writeType(edge.type);
else
this.skipType();
this.writeRef(edge.source); // 5
this.writeRef(edge.target); // 5
if (edge.sourcePrev !== undefined)
// 5
this.writeRef(edge.sourcePrev);
else
this.skipRef();
if (edge.sourceNext !== undefined)
// 5
this.writeRef(edge.sourceNext);
else
this.skipRef();
if (edge.targetPrev !== undefined)
// 5
this.writeRef(edge.targetPrev);
else
this.skipRef();
if (edge.targetNext !== undefined)
// 5
this.writeRef(edge.targetNext);
else
this.skipRef();
if (edge.nextProp !== undefined)
// 5
this.writeRef(edge.nextProp);
else
this.skipRef();
this.writeStatus(edge.status); // 1
}
write() {
for (const edge of this.edges)
this.writeEdge(edge);
return this.content();
}
}
class EdgeDecoder extends BinaryDecoder {
readEdge() {
const offset = this.readOffset();
const type = this.readType();
const source = this.readRef();
const target = this.readRef();
const sourcePrev = this.readRef();
const sourceNext = this.readRef();
const targetPrev = this.readRef();
const targetNext = this.readRef();
const nextProp = this.readRef();
const status = this.readStatus();
const edge = { status, offset, source, target };
if (type !== undefined)
edge.type = type;
if (sourcePrev !== undefined)
edge.sourcePrev = sourcePrev;
if (sourceNext !== undefined)
edge.sourceNext = sourceNext;
if (targetPrev !== undefined)
edge.targetPrev = targetPrev;
if (targetNext !== undefined)
edge.targetNext = targetNext;
if (nextProp !== undefined)
edge.nextProp = nextProp;
return edge;
}
read() {
if (this.buffer.byteLength % EDGE_SIZE_BYTES !== 0)
throw new Error('Invalid edge serialization');
const size = Math.trunc(this.buffer.byteLength / EDGE_SIZE_BYTES);
const edges = [];
for (let i = 0; i < size; i++) {
edges.push(this.readEdge());
}
return edges;
}
}
const PROP_SIZE_BYTES = 32;
class PropEncoder extends BinaryEncoder {
constructor(props, refs) {
super(props.length * PROP_SIZE_BYTES);
this.props = props;
this.refs = refs;
}
async writeValue(propRef, value) {
// 13
const valueRef = this.refs.get(propRef);
this.writeOffset(propRef); // 4
if (valueRef !== undefined) {
this.writeRef(valueRef.ref); // 5
this.writeUInt(valueRef.length); // 4
}
else {
this.skipRef();
this.skipUInt();
}
}
async writeProp(prop) {
// 32
this.writeOffset(prop.offset); // 4
if (prop.type !== undefined)
// 5
this.writeType(prop.type);
else
this.skipType();
this.writeUInt(prop.key); // 4
await this.writeValue(prop.offset, prop.value); // 13
if (prop.nextProp !== undefined)
// 5
this.writeRef(prop.nextProp);
else
this.skipRef();
this.writeStatus(prop.status); // 1
}
async write() {
for (const prop of this.props)
await this.writeProp(prop);
return this.content();
}
}
class PropRecordDecoder extends BinaryDecoder {
constructor(buffer) {
super(buffer);
}
async readPropValueRef() {
const propRef = this.readOffset();
const ref = this.readRef();
if (ref !== undefined) {
const length = this.readUInt();
const valueRef = { propRef, ref, length };
return valueRef;
}
else {
this.skipUInt();
return undefined;
}
}
async readProp() {
const offset = this.readOffset(); //4
const type = this.readType(); //5
const key = this.readUInt(); //4
const valueRef = await this.readPropValueRef(); // 13
const nextProp = this.readRef(); //5
const status = this.readStatus(); //1
const propRecord = { status, offset, key, valueRef };
if (type !== undefined)
propRecord.type = type;
if (nextProp !== undefined)
propRecord.nextProp = nextProp;
return propRecord;
}
async read() {
if (this.buffer.byteLength % PROP_SIZE_BYTES !== 0)
throw new Error('Invalid prop serialization');
const size = Math.trunc(this.buffer.byteLength / PROP_SIZE_BYTES);
const props = [];
for (let i = 0; i < size; i++) {
props.push(await this.readProp());
}
return props;
}
}
class PropDecoder extends BinaryDecoder {
constructor(buffer, valueGet) {
super(buffer);
this.valueGet = valueGet;
}
async readPropValue() {
const propRef = this.readOffset();
const ref = this.readRef();
if (ref !== undefined) {
const length = this.readUInt();
const valueRef = { propRef, ref, length };
return await this.valueGet(valueRef);
}
else {
this.skipUInt();
return undefined;
}
}
async readProp() {
const offset = this.readOffset(); //4
const type = this.readType(); //5
const key = this.readUInt(); //4
const value = await this.readPropValue(); // 13
const nextProp = this.readRef(); //5
const status = this.readStatus(); //1
const prop = { status, offset, key, value };
if (type !== undefined)
prop.type = type;
if (nextProp !== undefined)
prop.nextProp = nextProp;
return prop;
}
async read() {
if (this.buffer.byteLength % PROP_SIZE_BYTES !== 0)
throw new Error('Invalid prop serialization');
const size = Math.trunc(this.buffer.byteLength / PROP_SIZE_BYTES);
const props = [];
for (let i = 0; i < size; i++) {
props.push(await this.readProp());
}
return props;
}
}
class PropValueEncoder extends BinaryEncoder {
constructor(refOffset, props, valueEncode) {
super(props
.map((prop) => valueEncode(prop.value).byteLength)
.reduce((a, b) => a + b, 0));
this.refOffset = refOffset;
this.props = props;
this.valueEncode = valueEncode;
}
writeValue(propRef, value) {
const bytes = this.valueEncode(value);
const ref = this.cursor + this.refOffset;
const length = bytes.byteLength;
this.writeBytes(bytes);
return { propRef, ref, length };
}
writeProp(prop) {
const valueRef = this.writeValue(prop.offset, prop.value);
return valueRef;
}
write() {
const refs = new Map();
for (const prop of this.props) {
const valueRef = this.writeProp(prop);
refs.set(prop.offset, valueRef);
}
const buf = this.content();
return { buf, refs };
}
}
class PropValueDecoder extends BinaryDecoder {
constructor(buffer, valueDecode) {
super(buffer);
this.valueDecode = valueDecode;
}
readValue(valueRef) {
const bytes = this.readBytes(valueRef.length);
return this.valueDecode(bytes);
}
read(valueRefs) {
const values = new Map();
for (const valueRef of valueRefs) {
const value = this.readValue(valueRef);
values.set(valueRef.propRef, value);
}
return values;
}
}
const INDEX_SIZE_BYTES = 56;
class IndexEncoder extends BinaryEncoder {
constructor(indices) {
super(indices.length * INDEX_SIZE_BYTES);
this.indices = indices;
}
async writeIndex(index) {
// 56
this.writeOffset(index.offset); // 4
if (index.type !== undefined)
// 5
this.writeType(index.type);
else
this.skipType();
this.writeType(index.key); // 5
this.writeLink(index.value); // 36
if (index.nextIndex !== undefined)
// 5
this.writeRef(index.nextIndex);
else
this.skipRef();
this.writeStatus(index.status); // 1
}
async write() {
for (const index of this.indices)
await this.writeIndex(index);
return this.content();
}
}
class IndexDecoder extends BinaryDecoder {
constructor(buffer, linkDecode) {
super(buffer);
this.linkDecode = linkDecode;
}
async readIndex() {
const offset = this.readOffset();
const type = this.readType();
const key = this.readType();
const value = this.readLink(this.linkDecode);
const nextIndex = this.readRef();
const status = this.readStatus();
const index = { status, offset, key, value };
if (type !== undefined)
index.type = type;
if (nextIndex !== undefined)
index.nextIndex = nextIndex;
return index;
}
async read() {
if (this.buffer.byteLength % INDEX_SIZE_BYTES !== 0)
throw new Error('Invalid index serialization');
const size = Math.trunc(this.buffer.byteLength / INDEX_SIZE_BYTES);
const indices = [];
for (let i = 0; i < size; i++) {
indices.push(await this.readIndex());
}
return indices;
}
}
class RootEncoder extends BinaryEncoder {
constructor({ vertexRoot, vertexOffset, edgeRoot, edgeOffset, propRoot, propOffset, valueRoot, valueOffset, indexRoot, indexOffset, }) {
super(200);
this.vertexRoot = vertexRoot;
this.vertexOffset = vertexOffset;
this.edgeRoot = edgeRoot;
this.edgeOffset = edgeOffset;
this.propRoot = propRoot;
this.propOffset = propOffset;
this.valueRoot = valueRoot;
this.valueOffset = valueOffset;
this.indexRoot = indexRoot;
this.indexOffset = indexOffset;
}
write() {
this.writeUInt(this.vertexOffset); //4
this.writeUInt(this.edgeOffset); //4
this.writeUInt(this.propOffset); //4
this.writeUInt(this.valueOffset); //4
this.writeUInt(this.indexOffset); //4
this.writeLink(this.vertexRoot); //36
this.writeLink(this.edgeRoot); //36
this.writeLink(this.propRoot); //36
this.writeLink(this.valueRoot); //36
this.writeLink(this.indexRoot); //36
return this;
}
}
class RootDecoder extends BinaryDecoder {
constructor(buffer, linkDecode) {
super(buffer);
this.linkDecode = linkDecode;
}
read() {
const vertexOffset = this.readOffset();
const edgeOffset = this.readOffset();
const propOffset = this.readOffset();
const valueOffset = this.readOffset();
const indexOffset = this.readOffset();
const vertexRoot = this.readLink(this.linkDecode);
const edgeRoot = this.readLink(this.linkDecode);
const propRoot = this.readLink(this.linkDecode);
const valueRoot = this.readLink(this.linkDecode);
const indexRoot = this.readLink(this.linkDecode);
return {
vertexRoot,
vertexOffset,
edgeRoot,
edgeOffset,
propRoot,
propOffset,
valueRoot,
valueOffset,
indexRoot,
indexOffset,
};
}
}
const VERSION_CONST_SIZE_BYTES = 114; // 77
const VERSION_ID_SIZE_BYTES = 36;
class VersionEncoder extends BinaryEncoder {
constructor(id, versions, valueEncode) {
super(VERSION_ID_SIZE_BYTES +
versions
.map((version) => valueEncode(version.details).byteLength +
VERSION_CONST_SIZE_BYTES)
.reduce((a, b) => a + b, 0));
this.id = id;
this.versions = versions;
this.valueEncode = valueEncode;
}
writeDetails(details) {
const purgedDetails = fastCloneVersionDetails(details);
const bytes = this.valueEncode(purgedDetails);
const length = bytes.byteLength;
this.writeUInt(length); // 4
this.writeBytes(bytes); // n
}
async writeVersion(version) {
this.writeLink(version.root); // 36
if (version.parent !== undefined) {
this.writeLinkExists(); // 1
this.writeLink(version.parent); // 36
}
else
this.skipBytes(37);
if (version.mergeParent !== undefined) {
this.writeLinkExists(); // 1
this.writeLink(version.mergeParent); // 36
}
else
this.skipBytes(37);
this.writeDetails(version.details); // 4 + n
}
write() {
this.writeLink(this.id); // 36
for (const version of this.versions)
this.writeVersion(version);
return this.content();
}
}
class VersionDecoder extends BinaryDecoder {
constructor(buffer, linkDecode, valueDecode) {
super(buffer);
this.linkDecode = linkDecode;
this.valueDecode = valueDecode;
}
readVersionDetails() {
const length = this.readUInt();
const bytes = this.readBytes(length);
return fastCloneVersionDetails(this.valueDecode(bytes));
}
readVersion() {
const root = this.readLink(this.linkDecode);
const parent = this.readOptionalLink(this.linkDecode);
const mergeParent = this.readOptionalLink(this.linkDecode);
const details = this.readVersionDetails();
const version = { root, details };
if (parent !== undefined)
version.parent = parent;
if (mergeParent !== undefined)
version.mergeParent = mergeParent;
return version;
}
read() {
const id = this.readLink(this.linkDecode);
const versions = [];
while (this.cursor < this.buffer.byteLength)
versions.push(this.readVersion());
return { id, versions };
}
}
const OFFSET_INCREMENTS = {
VERTEX_INCREMENT: VERTEX_SIZE_BYTES,
EDGE_INCREMENT: EDGE_SIZE_BYTES,
PROP_INCREMENT: PROP_SIZE_BYTES,
INDEX_INCREMENT: INDEX_SIZE_BYTES,
};
export { BinaryEncoder, BinaryDecoder, VertexEncoder, VertexDecoder, EdgeEncoder, EdgeDecoder, PropRecordDecoder, PropEncoder, PropDecoder, PropValueEncoder, PropValueDecoder, IndexEncoder, IndexDecoder, RootEncoder, RootDecoder, VersionEncoder, VersionDecoder, OFFSET_INCREMENTS, };
//# sourceMappingURL=serde.js.map