UNPKG

@worker-tools/deno-kv-storage

Version:

An implementation of the StorageArea (1,2,3) interface for Deno with an extensible system for supporting various database backends.

207 lines (181 loc) 5.91 kB
/*! * Adapted directly from https://github.com/brianc/node-buffer-writer * which is licensed as follows: * * The MIT License (MIT) * * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * 'Software'), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { copy } from "../deps.js"; import { readInt16BE, readInt32BE } from "../utils/utils.js"; export class PacketReader { #buffer: Uint8Array; #decoder = new TextDecoder(); #offset = 0; constructor(buffer: Uint8Array) { this.#buffer = buffer; } readInt16(): number { const value = readInt16BE(this.#buffer, this.#offset); this.#offset += 2; return value; } readInt32(): number { const value = readInt32BE(this.#buffer, this.#offset); this.#offset += 4; return value; } readByte(): number { return this.readBytes(1)[0]; } readBytes(length: number): Uint8Array { const start = this.#offset; const end = start + length; const slice = this.#buffer.slice(start, end); this.#offset = end; return slice; } readAllBytes(): Uint8Array { const slice = this.#buffer.slice(this.#offset); this.#offset = this.#buffer.length; return slice; } readString(length: number): string { const bytes = this.readBytes(length); return this.#decoder.decode(bytes); } readCString(): string { const start = this.#offset; // find next null byte const end = this.#buffer.indexOf(0, start); const slice = this.#buffer.slice(start, end); // add +1 for null byte this.#offset = end + 1; return this.#decoder.decode(slice); } } export class PacketWriter { #buffer: Uint8Array; #encoder = new TextEncoder(); #headerPosition: number; #offset: number; #size: number; constructor(size?: number) { this.#size = size || 1024; this.#buffer = new Uint8Array(this.#size + 5); this.#offset = 5; this.#headerPosition = 0; } #ensure(size: number) { const remaining = this.#buffer.length - this.#offset; if (remaining < size) { const oldBuffer = this.#buffer; // exponential growth factor of around ~ 1.5 // https://stackoverflow.com/questions/2269063/#buffer-growth-strategy const newSize = oldBuffer.length + (oldBuffer.length >> 1) + size; this.#buffer = new Uint8Array(newSize); copy(oldBuffer, this.#buffer); } } addInt32(num: number) { this.#ensure(4); this.#buffer[this.#offset++] = (num >>> 24) & 0xff; this.#buffer[this.#offset++] = (num >>> 16) & 0xff; this.#buffer[this.#offset++] = (num >>> 8) & 0xff; this.#buffer[this.#offset++] = (num >>> 0) & 0xff; return this; } addInt16(num: number) { this.#ensure(2); this.#buffer[this.#offset++] = (num >>> 8) & 0xff; this.#buffer[this.#offset++] = (num >>> 0) & 0xff; return this; } addCString(string?: string) { // just write a 0 for empty or null strings if (!string) { this.#ensure(1); } else { const encodedStr = this.#encoder.encode(string); this.#ensure(encodedStr.byteLength + 1); // +1 for null terminator copy(encodedStr, this.#buffer, this.#offset); this.#offset += encodedStr.byteLength; } this.#buffer[this.#offset++] = 0; // null terminator return this; } addChar(c: string) { if (c.length != 1) { throw new Error("addChar requires single character strings"); } this.#ensure(1); copy(this.#encoder.encode(c), this.#buffer, this.#offset); this.#offset++; return this; } addString(string?: string) { string = string || ""; const encodedStr = this.#encoder.encode(string); this.#ensure(encodedStr.byteLength); copy(encodedStr, this.#buffer, this.#offset); this.#offset += encodedStr.byteLength; return this; } add(otherBuffer: Uint8Array) { this.#ensure(otherBuffer.length); copy(otherBuffer, this.#buffer, this.#offset); this.#offset += otherBuffer.length; return this; } clear() { this.#offset = 5; this.#headerPosition = 0; } // appends a header block to all the written data since the last // subsequent header or to the beginning if there is only one data block addHeader(code: number, last?: boolean) { const origOffset = this.#offset; this.#offset = this.#headerPosition; this.#buffer[this.#offset++] = code; // length is everything in this packet minus the code this.addInt32(origOffset - (this.#headerPosition + 1)); // set next header position this.#headerPosition = origOffset; // make space for next header this.#offset = origOffset; if (!last) { this.#ensure(5); this.#offset += 5; } return this; } join(code?: number) { if (code) { this.addHeader(code, true); } return this.#buffer.slice(code ? 0 : 5, this.#offset); } flush(code?: number) { const result = this.join(code); this.clear(); return result; } }