UNPKG

xitdb

Version:
1,717 lines (1,655 loc) 125 kB
// @bun // src/tag.ts var Tag; ((Tag2) => { Tag2[Tag2["NONE"] = 0] = "NONE"; Tag2[Tag2["INDEX"] = 1] = "INDEX"; Tag2[Tag2["ARRAY_LIST"] = 2] = "ARRAY_LIST"; Tag2[Tag2["LINKED_ARRAY_LIST"] = 3] = "LINKED_ARRAY_LIST"; Tag2[Tag2["HASH_MAP"] = 4] = "HASH_MAP"; Tag2[Tag2["KV_PAIR"] = 5] = "KV_PAIR"; Tag2[Tag2["BYTES"] = 6] = "BYTES"; Tag2[Tag2["SHORT_BYTES"] = 7] = "SHORT_BYTES"; Tag2[Tag2["UINT"] = 8] = "UINT"; Tag2[Tag2["INT"] = 9] = "INT"; Tag2[Tag2["FLOAT"] = 10] = "FLOAT"; Tag2[Tag2["HASH_SET"] = 11] = "HASH_SET"; Tag2[Tag2["COUNTED_HASH_MAP"] = 12] = "COUNTED_HASH_MAP"; Tag2[Tag2["COUNTED_HASH_SET"] = 13] = "COUNTED_HASH_SET"; })(Tag ||= {}); function tagValueOf(n) { if (n < 0 || n > 13) { throw new Error(`Invalid tag value: ${n}`); } return n; } // src/slot.ts class Slot { static LENGTH = 9; value; tag; full; constructor(value = 0n, tag = 0 /* NONE */, full = false) { this.value = typeof value === "bigint" ? value : BigInt(value); this.tag = tag; this.full = full; } withTag(tag) { return new Slot(this.value, tag, this.full); } withFull(full) { return new Slot(this.value, this.tag, full); } empty() { return this.tag === 0 /* NONE */ && !this.full; } toBytes() { const buffer = new ArrayBuffer(Slot.LENGTH); const view = new DataView(buffer); let tagInt = this.full ? 128 : 0; tagInt = tagInt | this.tag; view.setUint8(0, tagInt); view.setBigInt64(1, this.value, false); return new Uint8Array(buffer); } static fromBytes(bytes) { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); const tagByte = view.getUint8(0); const full = (tagByte & 128) !== 0; const tag = tagValueOf(tagByte & 127); const value = view.getBigInt64(1, false); return new Slot(value, tag, full); } equals(other) { return this.value === other.value && this.tag === other.tag && this.full === other.full; } } // src/slot-pointer.ts class SlotPointer { position; slot; constructor(position, slot) { this.position = position; this.slot = slot; } withSlot(slot) { return new SlotPointer(this.position, slot); } } // src/exceptions.ts class DatabaseException extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class NotImplementedException extends DatabaseException { } class UnreachableException extends DatabaseException { } class InvalidDatabaseException extends DatabaseException { } class InvalidVersionException extends DatabaseException { } class InvalidHashSizeException extends DatabaseException { } class KeyNotFoundException extends DatabaseException { } class WriteNotAllowedException extends DatabaseException { } class UnexpectedTagException extends DatabaseException { } class CursorNotWriteableException extends DatabaseException { } class ExpectedTxStartException extends DatabaseException { } class KeyOffsetExceededException extends DatabaseException { } class PathPartMustBeAtEndException extends DatabaseException { } class StreamTooLongException extends DatabaseException { } class EndOfStreamException extends DatabaseException { } class InvalidOffsetException extends DatabaseException { } class InvalidTopLevelTypeException extends DatabaseException { } class ExpectedUnsignedLongException extends DatabaseException { } class NoAvailableSlotsException extends DatabaseException { } class MustSetNewSlotsToFullException extends DatabaseException { } class EmptySlotException extends DatabaseException { } class ExpectedRootNodeException extends DatabaseException { } class InvalidFormatTagSizeException extends DatabaseException { } class UnexpectedWriterPositionException extends DatabaseException { } class MaxShiftExceededException extends DatabaseException { } class Uint64OverflowException extends DatabaseException { } class Int64OverflowException extends DatabaseException { } // src/writeable-data.ts var UINT64_MAX = 2n ** 64n - 1n; var INT64_MIN = -(2n ** 63n); var INT64_MAX = 2n ** 63n - 1n; class Uint { value; constructor(value) { const bigintValue = BigInt(value); if (bigintValue < 0n || bigintValue > UINT64_MAX) { throw new Uint64OverflowException; } this.value = bigintValue; } } class Int { value; constructor(value) { const bigintValue = BigInt(value); if (bigintValue < INT64_MIN || bigintValue > INT64_MAX) { throw new Int64OverflowException; } this.value = bigintValue; } } class Float { value; constructor(value) { this.value = value; } } class Bytes { value; formatTag; constructor(value, formatTag) { if (typeof value === "string") { this.value = new TextEncoder().encode(value); } else { this.value = value; } if (formatTag === undefined || formatTag === null) { this.formatTag = null; } else if (typeof formatTag === "string") { const encoded = new TextEncoder().encode(formatTag); if (encoded.length !== 2) { throw new InvalidFormatTagSizeException; } this.formatTag = encoded; } else { if (formatTag.length !== 2) { throw new InvalidFormatTagSizeException; } this.formatTag = formatTag; } } isShort() { const totalSize = this.formatTag !== null ? 6 : 8; if (this.value.length > totalSize) return false; for (const b of this.value) { if (b === 0) return false; } return true; } } // src/core-memory.ts class CoreMemory { memory; constructor() { this.memory = new RandomAccessMemory; } reader() { return this.memory; } writer() { return this.memory; } length() { return this.memory.size(); } seek(pos) { this.memory.seek(pos); } position() { return this.memory.getPosition(); } setLength(len) { this.memory.setLength(len); } flush() {} sync() {} } class RandomAccessMemory { buffer; _position = 0; _count = 0; constructor(initialSize = 1024) { this.buffer = new Uint8Array(initialSize); } ensureCapacity(minCapacity) { if (minCapacity > this.buffer.length) { let newCapacity = this.buffer.length * 2; if (newCapacity < minCapacity) { newCapacity = minCapacity; } const newBuffer = new Uint8Array(newCapacity); newBuffer.set(this.buffer.subarray(0, this._count)); this.buffer = newBuffer; } } size() { return this._count; } seek(pos) { if (pos > this._count) { this._position = this._count; } else { this._position = pos; } } getPosition() { return this._position; } setLength(len) { if (len === 0) { this.reset(); } else { if (len > this._count) throw new Error("Cannot extend length"); this._count = len; if (this._position > len) { this._position = len; } } } reset() { this._count = 0; this._position = 0; } toByteArray() { return this.buffer.slice(0, this._count); } write(data) { const pos = this._position; if (pos < this._count) { const bytesBeforeEnd = Math.min(data.length, this._count - pos); for (let i = 0;i < bytesBeforeEnd; i++) { this.buffer[pos + i] = data[i]; } if (bytesBeforeEnd < data.length) { const bytesAfterEnd = data.length - bytesBeforeEnd; this.ensureCapacity(this._count + bytesAfterEnd); this.buffer.set(data.subarray(bytesBeforeEnd), this._count); this._count += bytesAfterEnd; } } else { this.ensureCapacity(this._count + data.length); this.buffer.set(data, this._count); this._count += data.length; } this._position = pos + data.length; } writeByte(v) { this.write(new Uint8Array([v & 255])); } writeShort(v) { const buffer = new ArrayBuffer(2); const view = new DataView(buffer); view.setInt16(0, v, false); this.write(new Uint8Array(buffer)); } writeLong(v) { const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, BigInt(v), false); this.write(new Uint8Array(buffer)); } readFully(b) { const pos = this._position; if (pos + b.length > this._count) { throw new Error("End of stream"); } b.set(this.buffer.subarray(pos, pos + b.length)); this._position = pos + b.length; } readByte() { const bytes = new Uint8Array(1); this.readFully(bytes); return bytes[0]; } readShort() { const bytes = new Uint8Array(2); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt16(0, false); } readInt() { const bytes = new Uint8Array(4); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt32(0, false); } readLong() { const bytes = new Uint8Array(8); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return Number(view.getBigInt64(0, false)); } } // src/core-file.ts import * as fs from "fs"; class CoreFile { filePath; _position = 0; fd; constructor(filePath) { this.filePath = filePath; try { fs.accessSync(filePath); } catch { fs.writeFileSync(filePath, new Uint8Array(0)); } this.fd = fs.openSync(filePath, "r+"); } reader() { return new FileDataReader(this); } writer() { return new FileDataWriter(this); } length() { const stats = fs.fstatSync(this.fd); return stats.size; } seek(pos) { this._position = pos; } position() { return this._position; } setLength(len) { fs.ftruncateSync(this.fd, len); } flush() {} sync() { fs.fsyncSync(this.fd); } [Symbol.dispose]() { fs.closeSync(this.fd); } } class FileDataReader { core; constructor(core) { this.core = core; } readFully(b) { const position = this.core.position(); fs.readSync(this.core.fd, b, 0, b.length, position); this.core.seek(position + b.length); } readByte() { const bytes = new Uint8Array(1); this.readFully(bytes); return bytes[0]; } readShort() { const bytes = new Uint8Array(2); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt16(0, false); } readInt() { const bytes = new Uint8Array(4); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt32(0, false); } readLong() { const bytes = new Uint8Array(8); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return Number(view.getBigInt64(0, false)); } } class FileDataWriter { core; constructor(core) { this.core = core; } write(buffer) { const position = this.core.position(); fs.writeSync(this.core.fd, buffer, 0, buffer.length, position); this.core.seek(position + buffer.length); } writeByte(v) { this.write(new Uint8Array([v & 255])); } writeShort(v) { const buffer = new ArrayBuffer(2); const view = new DataView(buffer); view.setInt16(0, v, false); this.write(new Uint8Array(buffer)); } writeLong(v) { const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, BigInt(v), false); this.write(new Uint8Array(buffer)); } } // src/core-buffered-file.ts class CoreBufferedFile { file; constructor(filePath, bufferSize) { this.file = new RandomAccessBufferedFile(filePath, bufferSize); } reader() { return this.file; } writer() { return this.file; } length() { return this.file.length(); } seek(pos) { this.file.seek(pos); } position() { return this.file.position(); } setLength(len) { this.file.setLength(len); } flush() { this.file.flush(); } sync() { this.file.sync(); } [Symbol.dispose]() { this.file.file[Symbol.dispose](); } } var DEFAULT_BUFFER_SIZE = 8 * 1024 * 1024; class RandomAccessBufferedFile { file; memory; bufferSize; filePos; memoryPos; constructor(filePath, bufferSize = DEFAULT_BUFFER_SIZE) { this.file = new CoreFile(filePath); this.memory = new CoreMemory; this.bufferSize = bufferSize; this.filePos = 0; this.memoryPos = 0; } seek(pos) { if (pos > this.memoryPos + this.memory.length()) { this.flush(); } this.filePos = pos; if (this.memory.length() === 0) { this.memoryPos = pos; } } length() { return Math.max(this.memoryPos + this.memory.length(), this.file.length()); } position() { return this.filePos; } setLength(len) { this.flush(); this.file.setLength(len); this.filePos = Math.min(len, this.filePos); } flush() { if (this.memory.length() > 0) { this.file.seek(this.memoryPos); this.file.writer().write(this.memory.memory.toByteArray()); this.memoryPos = 0; this.memory.memory.reset(); } } sync() { this.flush(); this.file.sync(); } write(buffer) { if (this.memory.length() + buffer.length > this.bufferSize) { this.flush(); } if (this.filePos >= this.memoryPos && this.filePos <= this.memoryPos + this.memory.length()) { this.memory.seek(this.filePos - this.memoryPos); this.memory.memory.write(buffer); } else { this.file.seek(this.filePos); this.file.writer().write(buffer); } this.filePos += buffer.length; } writeByte(v) { this.write(new Uint8Array([v & 255])); } writeShort(v) { const buffer = new ArrayBuffer(2); const view = new DataView(buffer); view.setInt16(0, v & 65535, false); this.write(new Uint8Array(buffer)); } writeLong(v) { const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, BigInt(v), false); this.write(new Uint8Array(buffer)); } readFully(buffer) { let pos = 0; if (this.filePos < this.memoryPos) { const sizeBeforeMem = Math.min(this.memoryPos - this.filePos, buffer.length); const tempBuffer = new Uint8Array(sizeBeforeMem); this.file.seek(this.filePos); this.file.reader().readFully(tempBuffer); buffer.set(tempBuffer, pos); pos += sizeBeforeMem; this.filePos += sizeBeforeMem; } if (pos === buffer.length) return; if (this.filePos >= this.memoryPos && this.filePos < this.memoryPos + this.memory.length()) { const memPos = this.filePos - this.memoryPos; const sizeInMem = Math.min(this.memory.length() - memPos, buffer.length - pos); this.memory.seek(memPos); const memBuffer = new Uint8Array(sizeInMem); this.memory.memory.readFully(memBuffer); buffer.set(memBuffer, pos); pos += sizeInMem; this.filePos += sizeInMem; } if (pos === buffer.length) return; if (this.filePos >= this.memoryPos + this.memory.length()) { const sizeAfterMem = buffer.length - pos; const tempBuffer = new Uint8Array(sizeAfterMem); this.file.seek(this.filePos); this.file.reader().readFully(tempBuffer); buffer.set(tempBuffer, pos); pos += sizeAfterMem; this.filePos += sizeAfterMem; } } readByte() { const bytes = new Uint8Array(1); this.readFully(bytes); return bytes[0]; } readShort() { const bytes = new Uint8Array(2); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt16(0, false); } readInt() { const bytes = new Uint8Array(4); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt32(0, false); } readLong() { const bytes = new Uint8Array(8); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return Number(view.getBigInt64(0, false)); } } // src/hasher.ts import { createHash } from "crypto"; class Hasher { algorithm; nodeAlgorithm; id; digestLength; constructor(algorithm, id = 0) { this.algorithm = algorithm; this.id = id; switch (algorithm) { case "SHA-1": this.digestLength = 20; this.nodeAlgorithm = "sha1"; break; case "SHA-256": this.digestLength = 32; this.nodeAlgorithm = "sha256"; break; case "SHA-384": this.digestLength = 48; this.nodeAlgorithm = "sha384"; break; case "SHA-512": this.digestLength = 64; this.nodeAlgorithm = "sha512"; break; default: throw new Error(`Unsupported hash algorithm: ${algorithm}`); } } digest(data) { return new Uint8Array(createHash(this.nodeAlgorithm).update(data).digest()); } static stringToId(hashIdName) { const bytes = new TextEncoder().encode(hashIdName); if (bytes.length !== 4) { throw new Error("Name must be exactly four bytes long"); } const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt32(0, false); } static idToString(id) { const buffer = new ArrayBuffer(4); const view = new DataView(buffer); view.setInt32(0, id, false); return new TextDecoder().decode(new Uint8Array(buffer)); } } // src/read-cursor.ts class KeyValuePairCursor { valueCursor; keyCursor; hash; constructor(valueCursor, keyCursor, hash) { this.valueCursor = valueCursor; this.keyCursor = keyCursor; this.hash = hash; } } class ReadCursor { slotPtr; db; constructor(slotPtr, db) { this.slotPtr = slotPtr; this.db = db; } slot() { return this.slotPtr.slot; } readPath(path) { try { const slotPtr = this.db.readSlotPointer(0 /* READ_ONLY */, path, 0, this.slotPtr); return new ReadCursor(slotPtr, this.db); } catch (e) { if (e instanceof KeyNotFoundException) { return null; } throw e; } } readPathSlot(path) { try { const slotPtr = this.db.readSlotPointer(0 /* READ_ONLY */, path, 0, this.slotPtr); if (!slotPtr.slot.empty()) { return slotPtr.slot; } else { return null; } } catch (e) { if (e instanceof KeyNotFoundException) { return null; } throw e; } } readUint() { if (this.slotPtr.slot.tag !== 8 /* UINT */) { throw new UnexpectedTagException; } if (this.slotPtr.slot.value < 0n) throw new ExpectedUnsignedLongException; return Number(this.slotPtr.slot.value); } readInt() { if (this.slotPtr.slot.tag !== 9 /* INT */) { throw new UnexpectedTagException; } return Number(this.slotPtr.slot.value); } readFloat() { if (this.slotPtr.slot.tag !== 10 /* FLOAT */) { throw new UnexpectedTagException; } const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, this.slotPtr.slot.value, false); return view.getFloat64(0, false); } readBytes(maxSizeMaybe = null) { const bytesObj = this.readBytesObject(maxSizeMaybe); return bytesObj.value; } readBytesObject(maxSizeMaybe = null) { const reader = this.db.core.reader(); switch (this.slotPtr.slot.tag) { case 0 /* NONE */: return new Bytes(new Uint8Array(0)); case 6 /* BYTES */: { this.db.core.seek(Number(this.slotPtr.slot.value)); const valueSize = reader.readLong(); if (maxSizeMaybe !== null && valueSize > maxSizeMaybe) { throw new StreamTooLongException; } const startPosition = this.db.core.position(); const value = new Uint8Array(valueSize); reader.readFully(value); let formatTag = null; if (this.slotPtr.slot.full) { this.db.core.seek(startPosition + valueSize); formatTag = new Uint8Array(2); reader.readFully(formatTag); } return new Bytes(value, formatTag); } case 7 /* SHORT_BYTES */: { const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, this.slotPtr.slot.value, false); const bytes = new Uint8Array(buffer); const totalSize = this.slotPtr.slot.full ? bytes.length - 2 : bytes.length; let valueSize = 0; for (const b of bytes) { if (b === 0 || valueSize === totalSize) break; valueSize += 1; } if (maxSizeMaybe !== null && valueSize > maxSizeMaybe) { throw new StreamTooLongException; } let formatTag = null; if (this.slotPtr.slot.full) { formatTag = bytes.slice(totalSize, bytes.length); } return new Bytes(bytes.slice(0, valueSize), formatTag); } default: throw new UnexpectedTagException; } } readKeyValuePair() { const reader = this.db.core.reader(); if (this.slotPtr.slot.tag !== 5 /* KV_PAIR */) { throw new UnexpectedTagException; } this.db.core.seek(Number(this.slotPtr.slot.value)); const kvPairBytes = new Uint8Array(KeyValuePair.length(this.db.header.hashSize)); reader.readFully(kvPairBytes); const kvPair = KeyValuePair.fromBytes(kvPairBytes, this.db.header.hashSize); const hashPos = Number(this.slotPtr.slot.value); const keySlotPos = hashPos + this.db.header.hashSize; const valueSlotPos = keySlotPos + Slot.LENGTH; return new KeyValuePairCursor(new ReadCursor(new SlotPointer(valueSlotPos, kvPair.valueSlot), this.db), new ReadCursor(new SlotPointer(keySlotPos, kvPair.keySlot), this.db), kvPair.hash); } reader() { const reader = this.db.core.reader(); switch (this.slotPtr.slot.tag) { case 6 /* BYTES */: { this.db.core.seek(Number(this.slotPtr.slot.value)); const size = reader.readLong(); const startPosition = this.db.core.position(); return new Reader(this, size, startPosition, 0); } case 7 /* SHORT_BYTES */: { const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, this.slotPtr.slot.value, false); const bytes = new Uint8Array(buffer); const totalSize = this.slotPtr.slot.full ? bytes.length - 2 : bytes.length; let valueSize = 0; for (const b of bytes) { if (b === 0 || valueSize === totalSize) break; valueSize += 1; } const startPosition = this.slotPtr.position + 1; return new Reader(this, valueSize, startPosition, 0); } default: throw new UnexpectedTagException; } } count() { const reader = this.db.core.reader(); switch (this.slotPtr.slot.tag) { case 0 /* NONE */: return 0; case 2 /* ARRAY_LIST */: { this.db.core.seek(Number(this.slotPtr.slot.value)); const headerBytes = new Uint8Array(ArrayListHeader.LENGTH); reader.readFully(headerBytes); const header = ArrayListHeader.fromBytes(headerBytes); return header.size; } case 3 /* LINKED_ARRAY_LIST */: { this.db.core.seek(Number(this.slotPtr.slot.value)); const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH); reader.readFully(headerBytes); const header = LinkedArrayListHeader.fromBytes(headerBytes); return header.size; } case 6 /* BYTES */: { this.db.core.seek(Number(this.slotPtr.slot.value)); return reader.readLong(); } case 7 /* SHORT_BYTES */: { const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigInt64(0, this.slotPtr.slot.value, false); const bytes = new Uint8Array(buffer); const totalSize = this.slotPtr.slot.full ? bytes.length - 2 : bytes.length; let size = 0; for (const b of bytes) { if (b === 0 || size === totalSize) break; size += 1; } return size; } case 12 /* COUNTED_HASH_MAP */: case 13 /* COUNTED_HASH_SET */: { this.db.core.seek(Number(this.slotPtr.slot.value)); return reader.readLong(); } default: throw new UnexpectedTagException; } } *[Symbol.iterator]() { const iterator = this.iterator(); while (iterator.hasNext()) { const next = iterator.next(); if (next !== null) { yield next; } } } iterator() { const iterator = new CursorIterator(this); iterator.init(); return iterator; } } class Reader { parent; size; startPosition; relativePosition; constructor(parent, size, startPosition, relativePosition) { this.parent = parent; this.size = size; this.startPosition = startPosition; this.relativePosition = relativePosition; } read(buffer) { if (this.size < this.relativePosition) throw new EndOfStreamException; this.parent.db.core.seek(this.startPosition + this.relativePosition); const readSize = Math.min(buffer.length, this.size - this.relativePosition); if (readSize === 0) return -1; const reader = this.parent.db.core.reader(); const tempBuffer = new Uint8Array(readSize); reader.readFully(tempBuffer); buffer.set(tempBuffer); this.relativePosition += readSize; return readSize; } readFully(buffer) { if (this.size < this.relativePosition || this.size - this.relativePosition < buffer.length) { throw new EndOfStreamException; } this.parent.db.core.seek(this.startPosition + this.relativePosition); const reader = this.parent.db.core.reader(); reader.readFully(buffer); this.relativePosition += buffer.length; } readByte() { const bytes = new Uint8Array(1); this.readFully(bytes); return bytes[0]; } readShort() { const readSize = 2; const bytes = new Uint8Array(readSize); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt16(0, false); } readInt() { const readSize = 4; const bytes = new Uint8Array(readSize); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getInt32(0, false); } readLong() { const readSize = 8; const bytes = new Uint8Array(readSize); this.readFully(bytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return Number(view.getBigInt64(0, false)); } seek(position) { if (position > this.size) { throw new InvalidOffsetException; } this.relativePosition = position; } } class IteratorLevel { position; block; index; constructor(position, block, index) { this.position = position; this.block = block; this.index = index; } } class CursorIterator { cursor; size = 0; index = 0; stack = []; nextCursorMaybe = null; constructor(cursor) { this.cursor = cursor; } init() { switch (this.cursor.slotPtr.slot.tag) { case 0 /* NONE */: this.size = 0; this.index = 0; this.stack = []; break; case 2 /* ARRAY_LIST */: { const position = Number(this.cursor.slotPtr.slot.value); this.cursor.db.core.seek(position); const reader = this.cursor.db.core.reader(); const headerBytes = new Uint8Array(ArrayListHeader.LENGTH); reader.readFully(headerBytes); const header = ArrayListHeader.fromBytes(headerBytes); this.size = this.cursor.count(); this.index = 0; this.stack = this.initStack(this.cursor, header.ptr, INDEX_BLOCK_SIZE); break; } case 3 /* LINKED_ARRAY_LIST */: { const position = Number(this.cursor.slotPtr.slot.value); this.cursor.db.core.seek(position); const reader = this.cursor.db.core.reader(); const headerBytes = new Uint8Array(LinkedArrayListHeader.LENGTH); reader.readFully(headerBytes); const header = LinkedArrayListHeader.fromBytes(headerBytes); this.size = this.cursor.count(); this.index = 0; this.stack = this.initStack(this.cursor, header.ptr, LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE); break; } case 4 /* HASH_MAP */: case 11 /* HASH_SET */: this.size = 0; this.index = 0; this.stack = this.initStack(this.cursor, Number(this.cursor.slotPtr.slot.value), INDEX_BLOCK_SIZE); break; case 12 /* COUNTED_HASH_MAP */: case 13 /* COUNTED_HASH_SET */: this.size = 0; this.index = 0; this.stack = this.initStack(this.cursor, Number(this.cursor.slotPtr.slot.value) + 8, INDEX_BLOCK_SIZE); break; default: throw new UnexpectedTagException; } } initStack(cursor, position, blockSize) { cursor.db.core.seek(position); const reader = cursor.db.core.reader(); const indexBlockBytes = new Uint8Array(blockSize); reader.readFully(indexBlockBytes); const indexBlock = new Array(SLOT_COUNT); const slotSize = blockSize / SLOT_COUNT; for (let i = 0;i < SLOT_COUNT; i++) { const slotBytes = indexBlockBytes.slice(i * slotSize, i * slotSize + Slot.LENGTH); indexBlock[i] = Slot.fromBytes(slotBytes); } return [new IteratorLevel(position, indexBlock, 0)]; } hasNext() { switch (this.cursor.slotPtr.slot.tag) { case 0 /* NONE */: return false; case 2 /* ARRAY_LIST */: return this.index < this.size; case 3 /* LINKED_ARRAY_LIST */: return this.index < this.size; case 4 /* HASH_MAP */: case 11 /* HASH_SET */: case 12 /* COUNTED_HASH_MAP */: case 13 /* COUNTED_HASH_SET */: if (this.nextCursorMaybe === null) { this.nextCursorMaybe = this.nextInternal(INDEX_BLOCK_SIZE); } return this.nextCursorMaybe !== null; default: return false; } } next() { switch (this.cursor.slotPtr.slot.tag) { case 0 /* NONE */: return null; case 2 /* ARRAY_LIST */: if (!this.hasNext()) return null; this.index += 1; return this.nextInternal(INDEX_BLOCK_SIZE); case 3 /* LINKED_ARRAY_LIST */: if (!this.hasNext()) return null; this.index += 1; return this.nextInternal(LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE); case 4 /* HASH_MAP */: case 11 /* HASH_SET */: case 12 /* COUNTED_HASH_MAP */: case 13 /* COUNTED_HASH_SET */: if (this.nextCursorMaybe !== null) { const nextCursor = this.nextCursorMaybe; this.nextCursorMaybe = null; return nextCursor; } else { return this.nextInternal(INDEX_BLOCK_SIZE); } default: throw new UnexpectedTagException; } } nextInternal(blockSize) { while (this.stack.length > 0) { const level = this.stack[this.stack.length - 1]; if (level.index === level.block.length) { this.stack.pop(); if (this.stack.length > 0) { this.stack[this.stack.length - 1].index += 1; } continue; } else { const nextSlot = level.block[level.index]; if (nextSlot.tag === 1 /* INDEX */) { const nextPos = Number(nextSlot.value); this.cursor.db.core.seek(nextPos); const reader = this.cursor.db.core.reader(); const indexBlockBytes = new Uint8Array(blockSize); reader.readFully(indexBlockBytes); const indexBlock = new Array(SLOT_COUNT); const slotSize = blockSize / SLOT_COUNT; for (let i = 0;i < SLOT_COUNT; i++) { const slotBytes = indexBlockBytes.slice(i * slotSize, i * slotSize + Slot.LENGTH); indexBlock[i] = Slot.fromBytes(slotBytes); } this.stack.push(new IteratorLevel(nextPos, indexBlock, 0)); continue; } else { this.stack[this.stack.length - 1].index += 1; if (!nextSlot.empty()) { const position = level.position + level.index * Slot.LENGTH; return new ReadCursor(new SlotPointer(position, nextSlot), this.cursor.db); } else { continue; } } } } return null; } } // src/write-cursor.ts class WriteKeyValuePairCursor extends KeyValuePairCursor { valueCursor; keyCursor; constructor(valueCursor, keyCursor, hash) { super(valueCursor, keyCursor, hash); this.valueCursor = valueCursor; this.keyCursor = keyCursor; } } class WriteCursor extends ReadCursor { constructor(slotPtr, db) { super(slotPtr, db); } writePath(path) { const slotPtr = this.db.readSlotPointer(1 /* READ_WRITE */, path, 0, this.slotPtr); if (this.db.txStart === null) { this.db.core.sync(); } return new WriteCursor(slotPtr, this.db); } write(data) { const cursor = this.writePath([new WriteData(data)]); this.slotPtr = cursor.slotPtr; } writeIfEmpty(data) { if (this.slotPtr.slot.empty()) { this.write(data); } } readKeyValuePair() { const kvPairCursor = super.readKeyValuePair(); return new WriteKeyValuePairCursor(new WriteCursor(kvPairCursor.valueCursor.slotPtr, this.db), new WriteCursor(kvPairCursor.keyCursor.slotPtr, this.db), kvPairCursor.hash); } writer() { const writer = this.db.core.writer(); const ptrPos = this.db.core.length(); this.db.core.seek(ptrPos); writer.writeLong(0); const startPosition = this.db.core.length(); return new Writer(this, 0, new Slot(ptrPos, 6 /* BYTES */), startPosition, 0); } *[Symbol.iterator]() { const iterator = this.iterator(); while (iterator.hasNext()) { const next = iterator.next(); if (next !== null) { yield next; } } } iterator() { const iterator = new WriteCursorIterator(this); iterator.init(); return iterator; } } class Writer { parent; size; slot; startPosition; relativePosition; formatTag = null; constructor(parent, size, slot, startPosition, relativePosition) { this.parent = parent; this.size = size; this.slot = slot; this.startPosition = startPosition; this.relativePosition = relativePosition; } write(buffer) { if (this.size < this.relativePosition) throw new EndOfStreamException; this.parent.db.core.seek(this.startPosition + this.relativePosition); const writer = this.parent.db.core.writer(); writer.write(buffer); this.relativePosition += buffer.length; if (this.relativePosition > this.size) { this.size = this.relativePosition; } } finish() { const writer = this.parent.db.core.writer(); if (this.formatTag !== null) { this.slot = this.slot.withFull(true); const formatTagPos = this.parent.db.core.length(); this.parent.db.core.seek(formatTagPos); if (this.startPosition + this.size !== formatTagPos) throw new UnexpectedWriterPositionException; writer.write(this.formatTag); } this.parent.db.core.seek(Number(this.slot.value)); writer.writeLong(this.size); if (this.parent.slotPtr.position === null) throw new CursorNotWriteableException; const position = this.parent.slotPtr.position; this.parent.db.core.seek(position); writer.write(this.slot.toBytes()); this.parent.slotPtr = this.parent.slotPtr.withSlot(this.slot); } seek(position) { if (position <= this.size) { this.relativePosition = position; } } } class WriteCursorIterator extends CursorIterator { constructor(cursor) { super(cursor); } next() { const readCursor = super.next(); if (readCursor !== null) { return new WriteCursor(readCursor.slotPtr, readCursor.db); } else { return null; } } } // src/database.ts var VERSION = 0; var MAGIC_NUMBER = new Uint8Array([120, 105, 116]); var BIT_COUNT = 4; var SLOT_COUNT = 1 << BIT_COUNT; var MASK = BigInt(SLOT_COUNT - 1); var INDEX_BLOCK_SIZE = Slot.LENGTH * SLOT_COUNT; var LINKED_ARRAY_LIST_SLOT_LENGTH = 8 + Slot.LENGTH; var LINKED_ARRAY_LIST_INDEX_BLOCK_SIZE = LINKED_ARRAY_LIST_SLOT_LENGTH * SLOT_COUNT; var MAX_BRANCH_LENGTH = 16; var WriteMode; ((WriteMode2) => { WriteMode2[WriteMode2["READ_ONLY"] = 0] = "READ_ONLY"; WriteMode2[WriteMode2["READ_WRITE"] = 1] = "READ_WRITE"; })(WriteMode ||= {}); class Header { hashId; hashSize; version; tag; magicNumber; static LENGTH = 12; constructor(hashId, hashSize, version, tag, magicNumber) { this.hashId = hashId; this.hashSize = hashSize; this.version = version; this.tag = tag; this.magicNumber = magicNumber; } toBytes() { const buffer = new ArrayBuffer(Header.LENGTH); const view = new DataView(buffer); const arr = new Uint8Array(buffer); arr.set(this.magicNumber, 0); view.setUint8(3, this.tag); view.setInt16(4, this.version, false); view.setInt16(6, this.hashSize, false); view.setInt32(8, this.hashId, false); return arr; } static read(core) { const reader = core.reader(); const magicNumber = new Uint8Array(3); reader.readFully(magicNumber); const tagByte = reader.readByte(); const tag = tagValueOf(tagByte & 127); const version = reader.readShort(); const hashSize = reader.readShort(); const hashId = reader.readInt(); return new Header(hashId, hashSize, version, tag, magicNumber); } write(core) { const writer = core.writer(); writer.write(this.toBytes()); } validate() { if (!arraysEqual(this.magicNumber, MAGIC_NUMBER)) { throw new InvalidDatabaseException; } if (this.version > VERSION) { throw new InvalidVersionException; } } withTag(tag) { return new Header(this.hashId, this.hashSize, this.version, tag, this.magicNumber); } } class ArrayListHeader { ptr; size; static LENGTH = 16; constructor(ptr, size) { this.ptr = ptr; this.size = size; } toBytes() { const buffer = new ArrayBuffer(ArrayListHeader.LENGTH); const view = new DataView(buffer); view.setBigInt64(0, BigInt(this.size), false); view.setBigInt64(8, BigInt(this.ptr), false); return new Uint8Array(buffer); } static fromBytes(bytes) { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); const size = Number(view.getBigInt64(0, false)); checkLong(size); const ptr = Number(view.getBigInt64(8, false)); checkLong(ptr); return new ArrayListHeader(ptr, size); } withPtr(ptr) { return new ArrayListHeader(ptr, this.size); } } class TopLevelArrayListHeader { fileSize; parent; static LENGTH = 8 + ArrayListHeader.LENGTH; constructor(fileSize, parent) { this.fileSize = fileSize; this.parent = parent; } toBytes() { const buffer = new ArrayBuffer(TopLevelArrayListHeader.LENGTH); const view = new DataView(buffer); const arr = new Uint8Array(buffer); arr.set(this.parent.toBytes(), 0); view.setBigInt64(ArrayListHeader.LENGTH, BigInt(this.fileSize), false); return arr; } } class LinkedArrayListHeader { shift; ptr; size; static LENGTH = 17; constructor(shift, ptr, size) { this.shift = shift; this.ptr = ptr; this.size = size; } toBytes() { const buffer = new ArrayBuffer(LinkedArrayListHeader.LENGTH); const view = new DataView(buffer); view.setBigInt64(0, BigInt(this.size), false); view.setBigInt64(8, BigInt(this.ptr), false); view.setUint8(16, this.shift & 63); return new Uint8Array(buffer); } static fromBytes(bytes) { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); const size = Number(view.getBigInt64(0, false)); checkLong(size); const ptr = Number(view.getBigInt64(8, false)); checkLong(ptr); const shift = view.getUint8(16) & 63; return new LinkedArrayListHeader(shift, ptr, size); } withPtr(ptr) { return new LinkedArrayListHeader(this.shift, ptr, this.size); } } class KeyValuePair { valueSlot; keySlot; hash; constructor(valueSlot, keySlot, hash) { this.valueSlot = valueSlot; this.keySlot = keySlot; this.hash = hash; } static length(hashSize) { return hashSize + Slot.LENGTH * 2; } toBytes() { const buffer = new Uint8Array(KeyValuePair.length(this.hash.length)); buffer.set(this.hash, 0); buffer.set(this.keySlot.toBytes(), this.hash.length); buffer.set(this.valueSlot.toBytes(), this.hash.length + Slot.LENGTH); return buffer; } static fromBytes(bytes, hashSize) { const hash = bytes.slice(0, hashSize); const keySlotBytes = bytes.slice(hashSize, hashSize + Slot.LENGTH); const keySlot = Slot.fromBytes(keySlotBytes); const valueSlotBytes = bytes.slice(hashSize + Slot.LENGTH, hashSize + Slot.LENGTH * 2); const valueSlot = Slot.fromBytes(valueSlotBytes); return new KeyValuePair(valueSlot, keySlot, hash); } } class LinkedArrayListSlot2 { size; slot; static LENGTH = 8 + Slot.LENGTH; constructor(size, slot) { this.size = size; this.slot = slot; } withSize(size) { return new LinkedArrayListSlot2(size, this.slot); } toBytes() { const buffer = new ArrayBuffer(LinkedArrayListSlot2.LENGTH); const view = new DataView(buffer); const arr = new Uint8Array(buffer); arr.set(this.slot.toBytes(), 0); view.setBigInt64(Slot.LENGTH, BigInt(this.size), false); return arr; } static fromBytes(bytes) { const slotBytes = bytes.slice(0, Slot.LENGTH); const slot = Slot.fromBytes(slotBytes); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); const size = Number(view.getBigInt64(Slot.LENGTH, false)); checkLong(size); return new LinkedArrayListSlot2(size, slot); } } class LinkedArrayListSlotPointer { slotPtr; leafCount; constructor(slotPtr, leafCount) { this.slotPtr = slotPtr; this.leafCount = leafCount; } withSlotPointer(slotPtr) { return new LinkedArrayListSlotPointer(slotPtr, this.leafCount); } } class LinkedArrayListBlockInfo { block; i; parentSlot; constructor(block, i, parentSlot) { this.block = block; this.i = i; this.parentSlot = parentSlot; } } class HashMapGetKVPair { hash; kind = "kv_pair"; constructor(hash) { this.hash = hash; } } class HashMapGetKey { hash; kind = "key"; constructor(hash) { this.hash = hash; } } class HashMapGetValue { hash; kind = "value"; constructor(hash) { this.hash = hash; } } class ArrayListInit { kind = "ArrayListInit"; readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) { if (writeMode === 0 /* READ_ONLY */) throw new WriteNotAllowedException; if (isTopLevel) { const writer = db.core.writer(); if (db.header.tag === 0 /* NONE */) { db.core.seek(Header.LENGTH); const arrayListPtr = Header.LENGTH + TopLevelArrayListHeader.LENGTH; writer.write(new TopLevelArrayListHeader(0, new ArrayListHeader(arrayListPtr, 0)).toBytes()); writer.write(new Uint8Array(INDEX_BLOCK_SIZE)); db.core.seek(0); db.header = db.header.withTag(2 /* ARRAY_LIST */); writer.write(db.header.toBytes()); } const nextSlotPtr = slotPtr.withSlot(slotPtr.slot.withTag(2 /* ARRAY_LIST */)); return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr); } if (slotPtr.position === null) throw new CursorNotWriteableException; const position = slotPtr.position; switch (slotPtr.slot.tag) { case 0 /* NONE */: { const writer = db.core.writer(); let arrayListStart = db.core.length(); db.core.seek(arrayListStart); const arrayListPtr = arrayListStart + ArrayListHeader.LENGTH; writer.write(new ArrayListHeader(arrayListPtr, 0).toBytes()); writer.write(new Uint8Array(INDEX_BLOCK_SIZE)); const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, 2 /* ARRAY_LIST */)); db.core.seek(position); writer.write(nextSlotPtr.slot.toBytes()); return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr); } case 2 /* ARRAY_LIST */: { const reader = db.core.reader(); const writer = db.core.writer(); let arrayListStart = Number(slotPtr.slot.value); if (db.txStart !== null) { if (arrayListStart < db.txStart) { db.core.seek(arrayListStart); const headerBytes = new Uint8Array(ArrayListHeader.LENGTH); reader.readFully(headerBytes); const header = ArrayListHeader.fromBytes(headerBytes); db.core.seek(header.ptr); const arrayListIndexBlock = new Uint8Array(INDEX_BLOCK_SIZE); reader.readFully(arrayListIndexBlock); arrayListStart = db.core.length(); db.core.seek(arrayListStart); const nextArrayListPtr = arrayListStart + ArrayListHeader.LENGTH; const newHeader = header.withPtr(nextArrayListPtr); writer.write(newHeader.toBytes()); writer.write(arrayListIndexBlock); } } else if (db.header.tag === 2 /* ARRAY_LIST */) { throw new ExpectedTxStartException; } const nextSlotPtr = new SlotPointer(position, new Slot(arrayListStart, 2 /* ARRAY_LIST */)); db.core.seek(position); writer.write(nextSlotPtr.slot.toBytes()); return db.readSlotPointer(writeMode, path, pathI + 1, nextSlotPtr); } default: throw new UnexpectedTagException; } } } class ArrayListGet2 { index; kind = "ArrayListGet"; constructor(index) { this.index = index; } readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) { const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag; switch (tag) { case 0 /* NONE */: throw new KeyNotFoundException; case 2 /* ARRAY_LIST */: break; default: throw new UnexpectedTagException; } const nextArrayListStart = Number(slotPtr.slot.value); let index = this.index; db.core.seek(nextArrayListStart); const reader = db.core.reader(); const headerBytes = new Uint8Array(ArrayListHeader.LENGTH); reader.readFully(headerBytes); const header = ArrayListHeader.fromBytes(headerBytes); if (index >= header.size || index < -header.size) { throw new KeyNotFoundException; } const key = index < 0 ? header.size - Math.abs(index) : index; const lastKey = header.size - 1; const shift = lastKey < SLOT_COUNT ? 0 : Math.floor(Math.log(lastKey) / Math.log(SLOT_COUNT)); const finalSlotPtr = db.readArrayListSlot(header.ptr, key, shift, writeMode, isTopLevel); return db.readSlotPointer(writeMode, path, pathI + 1, finalSlotPtr); } } class ArrayListAppend { kind = "ArrayListAppend"; readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) { if (writeMode === 0 /* READ_ONLY */) throw new WriteNotAllowedException; const tag = isTopLevel ? db.header.tag : slotPtr.slot.tag; if (tag !== 2 /* ARRAY_LIST */) throw new UnexpectedTagException; const reader = db.core.reader(); const nextArrayListStart = Number(slotPtr.slot.value); db.core.seek(nextArrayListStart); const headerBytes = new Uint8Array(ArrayListHeader.LENGTH); reader.readFully(headerBytes); const origHeader = ArrayListHeader.fromBytes(headerBytes); const appendResult = db.readArrayListSlotAppend(origHeader, writeMode, isTopLevel); const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, appendResult.slotPtr); const writer = db.core.writer(); if (isTopLevel) { db.core.flush(); const fileSize = db.core.length(); const header = new TopLevelArrayListHeader(fileSize, appendResult.header); db.core.seek(nextArrayListStart); writer.write(header.toBytes()); } else { db.core.seek(nextArrayListStart); writer.write(appendResult.header.toBytes()); } return finalSlotPtr; } } class ArrayListSlice { size; kind = "ArrayListSlice"; constructor(size) { this.size = size; } readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) { if (writeMode === 0 /* READ_ONLY */) throw new WriteNotAllowedException; if (slotPtr.slot.tag !== 2 /* ARRAY_LIST */) throw new UnexpectedTagException; const reader = db.core.reader(); const nextArrayListStart = Number(slotPtr.slot.value); db.core.seek(nextArrayListStart); const headerBytes = new Uint8Array(ArrayListHeader.LENGTH); reader.readFully(headerBytes); const origHeader = ArrayListHeader.fromBytes(headerBytes); const sliceHeader = db.readArrayListSlice(origHeader, this.size); const finalSlotPtr = db.readSlotPointer(writeMode, path, pathI + 1, slotPtr); const writer = db.core.writer(); db.core.seek(nextArrayListStart); writer.write(sliceHeader.toBytes()); return finalSlotPtr; } } class LinkedArrayListInit { kind = "LinkedArrayListInit"; readSlotPointer(db, isTopLevel, writeMode, path, pathI, slotPtr) { if (writeMode === 0 /* READ_ONLY */) throw new WriteNotAllowedException; if (isTopLevel) throw new InvalidTopLevelTypeException; if (slotPtr.position === null) throw new CursorNotWriteableException; const position = slotPtr.position; switch (slotPtr.slot.tag) { case 0 /* NONE */: { const writer = db.core.writer(); const arrayListStart = db.core.length(); db.core.seek(arrayListStart); const arrayListPtr = arrayListStart + LinkedArrayListHeader.LENGTH; writer.write(new LinkedA