UNPKG

@deno/kv

Version:

A Deno KV client library optimized for Node.js.

432 lines (431 loc) 15.1 kB
"use strict"; // Copyright 2023 the Deno authors. All rights reserved. MIT license. Object.defineProperty(exports, "__esModule", { value: true }); exports.QueueWorker = exports.Expirer = exports.BaseKv = exports.GenericAtomicOperation = exports.GenericKvListIterator = exports.CursorHolder = exports.replacer = exports.isValidVersionstamp = exports.unpackVersionstamp = exports.packVersionstamp = exports.checkListOptions = exports.checkListSelector = exports.unpackCursor = exports.packCursor = exports.packKvValue = exports.readValue = exports.packKvu = exports.unpackKvu = void 0; const bytes_js_1 = require("./bytes.js"); const check_js_1 = require("./check.js"); const kv_u64_js_1 = require("./kv_u64.js"); const observer_js_1 = require("./proto/runtime/async/observer.js"); const base64_js_1 = require("./proto/runtime/base64.js"); function unpackKvu(bytes) { if (bytes.length !== 8) throw new Error(); if (bytes.buffer.byteLength !== 8) bytes = new Uint8Array(bytes); const rt = new DataView(bytes.buffer).getBigUint64(0, true); return new kv_u64_js_1._KvU64(rt); } exports.unpackKvu = unpackKvu; function packKvu(value) { const rt = new Uint8Array(8); new DataView(rt.buffer).setBigUint64(0, value.value, true); return rt; } exports.packKvu = packKvu; function readValue(bytes, encoding, decodeV8) { if (encoding === "VE_V8") return decodeV8(bytes); if (encoding === "VE_LE64") return unpackKvu(bytes); if (encoding === "VE_BYTES") return bytes; throw new Error(`Unsupported encoding: ${encoding} [${[...bytes].join(", ")}]`); } exports.readValue = readValue; function packKvValue(value, encodeV8) { if (value instanceof kv_u64_js_1._KvU64) { return { encoding: "VE_LE64", data: packKvu(value) }; } if (value instanceof Uint8Array) return { encoding: "VE_BYTES", data: value }; return { encoding: "VE_V8", data: encodeV8(value) }; } exports.packKvValue = packKvValue; function packCursor({ lastYieldedKeyBytes }) { return (0, base64_js_1.encode)(JSON.stringify({ lastYieldedKeyBytes: (0, bytes_js_1.encodeHex)(lastYieldedKeyBytes) })); } exports.packCursor = packCursor; function unpackCursor(str) { try { const { lastYieldedKeyBytes } = JSON.parse(new TextDecoder().decode((0, base64_js_1.decode)(str))); if (typeof lastYieldedKeyBytes === "string") { return { lastYieldedKeyBytes: (0, bytes_js_1.decodeHex)(lastYieldedKeyBytes) }; } } catch { // noop } throw new Error(`Invalid cursor`); } exports.unpackCursor = unpackCursor; function checkListSelector(selector) { if (!(0, check_js_1.isRecord)(selector)) { throw new TypeError(`Bad selector: ${JSON.stringify(selector)}`); } if ("prefix" in selector && "start" in selector && "end" in selector) { throw new TypeError(`Selector can not specify both 'start' and 'end' key when specifying 'prefix'`); } } exports.checkListSelector = checkListSelector; function checkListOptions(options) { if (!(0, check_js_1.isRecord)(options)) { throw new TypeError(`Bad options: ${JSON.stringify(options)}`); } const { limit, cursor, consistency, batchSize } = options; if (!(limit === undefined || typeof limit === "number" && limit > 0 && Number.isSafeInteger(limit))) throw new TypeError(`Bad 'limit': ${limit}`); if (!(cursor === undefined || typeof cursor === "string")) { throw new TypeError(`Bad 'cursor': ${limit}`); } const reverse = options.reverse === true; // follow native logic if (!(consistency === undefined || consistency === "strong" || consistency === "eventual")) throw new TypeError(`Bad 'consistency': ${consistency}`); if (!(batchSize === undefined || typeof batchSize === "number" && batchSize > 0 && Number.isSafeInteger(batchSize) && batchSize <= 1000)) throw new TypeError(`Bad 'batchSize': ${batchSize}`); return { limit, cursor, reverse, consistency, batchSize }; } exports.checkListOptions = checkListOptions; const packVersionstamp = (version) => `${version.toString().padStart(16, "0")}0000`; exports.packVersionstamp = packVersionstamp; const unpackVersionstamp = (versionstamp) => parseInt((0, check_js_1.checkMatches)("versionstamp", versionstamp, /^(\d{16})0000$/)[1]); exports.unpackVersionstamp = unpackVersionstamp; const isValidVersionstamp = (versionstamp) => /^(\d{16})0000$/.test(versionstamp); exports.isValidVersionstamp = isValidVersionstamp; const replacer = (_this, v) => typeof v === "bigint" ? v.toString() : v; exports.replacer = replacer; class CursorHolder { constructor() { Object.defineProperty(this, "cursor", { enumerable: true, configurable: true, writable: true, value: void 0 }); } get() { const { cursor } = this; if (cursor === undefined) { throw new Error(`Cannot get cursor before first iteration`); } return cursor; } set(cursor) { this.cursor = cursor; } } exports.CursorHolder = CursorHolder; class GenericKvListIterator { constructor(generator, cursor) { Object.defineProperty(this, "generator", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_cursor", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.generator = generator; this._cursor = cursor; } get cursor() { return this._cursor(); } next() { return this.generator.next(); } [Symbol.asyncIterator]() { return this.generator[Symbol.asyncIterator](); } // deno-lint-ignore no-explicit-any return(value) { return this.generator.return(value); } // deno-lint-ignore no-explicit-any throw(e) { return this.generator.throw(e); } } exports.GenericKvListIterator = GenericKvListIterator; class GenericAtomicOperation { constructor(commit) { Object.defineProperty(this, "commitFn", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checks", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "mutations", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "enqueues", { enumerable: true, configurable: true, writable: true, value: [] }); this.commitFn = commit; } check(...checks) { this.checks.push(...checks); return this; } mutate(...mutations) { mutations.map((v) => v.key).forEach(check_js_1.checkKeyNotEmpty); mutations.forEach((v) => v.type === "set" && (0, check_js_1.checkExpireIn)(v.expireIn)); this.mutations.push(...mutations); return this; } sum(key, n) { (0, check_js_1.checkKeyNotEmpty)(key); return this.mutate({ type: "sum", key, value: new kv_u64_js_1._KvU64(n) }); } min(key, n) { (0, check_js_1.checkKeyNotEmpty)(key); return this.mutate({ type: "min", key, value: new kv_u64_js_1._KvU64(n) }); } max(key, n) { (0, check_js_1.checkKeyNotEmpty)(key); return this.mutate({ type: "max", key, value: new kv_u64_js_1._KvU64(n) }); } set(key, value, { expireIn } = {}) { (0, check_js_1.checkExpireIn)(expireIn); (0, check_js_1.checkKeyNotEmpty)(key); return this.mutate({ type: "set", key, value, expireIn }); } delete(key) { (0, check_js_1.checkKeyNotEmpty)(key); return this.mutate({ type: "delete", key }); } enqueue(value, opts) { this.enqueues.push({ value, opts }); return this; } async commit() { return await this.commitFn(this.checks, this.mutations, this.enqueues); } } exports.GenericAtomicOperation = GenericAtomicOperation; class BaseKv { constructor({ debug }) { Object.defineProperty(this, "debug", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "closed", { enumerable: true, configurable: true, writable: true, value: false }); this.debug = debug; } async get(key, { consistency } = {}) { this.checkOpen("get"); (0, check_js_1.checkKeyNotEmpty)(key); return await this.get_(key, consistency); } // deno-lint-ignore no-explicit-any async getMany(keys, { consistency } = {}) { this.checkOpen("getMany"); keys.forEach(check_js_1.checkKeyNotEmpty); if (keys.length === 0) return []; return await this.getMany_(keys, consistency); } async set(key, value, { expireIn } = {}) { this.checkOpen("set"); const result = await this.atomic().set(key, value, { expireIn }).commit(); if (!result.ok) throw new Error(`set failed`); // should never happen, there are no checks return result; } async delete(key) { this.checkOpen("delete"); const result = await this.atomic().delete(key).commit(); if (!result.ok) throw new Error(`delete failed`); // should never happen, there are no checks } async enqueue(value, opts) { this.checkOpen("enqueue"); const result = await this.atomic().enqueue(value, opts).commit(); if (!result.ok) throw new Error(`enqueue failed`); // should never happen, there are no checks return result; } list(selector, options = {}) { this.checkOpen("list"); checkListSelector(selector); options = checkListOptions(options); const outCursor = new CursorHolder(); const generator = this.listStream(outCursor, selector, options); return new GenericKvListIterator(generator, () => outCursor.get()); } async listenQueue(handler) { this.checkOpen("listenQueue"); return await this.listenQueue_(handler); } atomic(additionalWork) { return new GenericAtomicOperation(async (checks, mutations, enqueues) => { this.checkOpen("commit"); return await this.commit(checks, mutations, enqueues, additionalWork); }); } // deno-lint-ignore no-explicit-any watch(keys, options) { this.checkOpen("watch"); if (keys.length === 0) throw new Error("Provide at least one key to watch"); keys.forEach(check_js_1.checkKeyNotEmpty); if (!(options === undefined || (0, check_js_1.isRecord)(options) && (options.raw === undefined || typeof options.raw === "boolean"))) throw new Error(`Unexpected options: ${JSON.stringify(options)}`); const { raw } = options ?? {}; return this.watch_(keys, raw); } close() { this.checkOpen("close"); this.closed = true; this.close_(); } [Symbol.dispose]() { this.close(); } // checkOpen(method) { if (this.closed) { throw new Error(`Cannot call '.${method}' after '.close' is called`); } } } exports.BaseKv = BaseKv; class Expirer { constructor(debug, expireFn) { Object.defineProperty(this, "debug", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "expireFn", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "minExpires", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "expirerTimeout", { enumerable: true, configurable: true, writable: true, value: 0 }); this.debug = debug; this.expireFn = expireFn; } init(minExpires) { this.minExpires = minExpires; if (this.minExpires !== undefined) this.rescheduleExpirer(this.minExpires); } rescheduleExpirer(expires) { const { minExpires, debug, expirerTimeout } = this; if (minExpires !== undefined && minExpires < expires) return; this.minExpires = expires; clearTimeout(expirerTimeout); const delay = expires - Date.now(); if (debug) console.log(`rescheduleExpirer: run in ${delay}ms`); this.expirerTimeout = setTimeout(() => this.runExpirer(), delay); } finalize() { clearTimeout(this.expirerTimeout); } // runExpirer() { const { expireFn } = this; const newMinExpires = expireFn(); this.minExpires = newMinExpires; if (newMinExpires !== undefined) { this.rescheduleExpirer(newMinExpires); } else { clearTimeout(this.expirerTimeout); } } } exports.Expirer = Expirer; class QueueWorker { constructor(workerFn) { Object.defineProperty(this, "workerFn", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "workerTimeout", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "queueHandler", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "queueHandlerPromise", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.workerFn = workerFn; } listen(handler) { if (this.queueHandler) throw new Error(`Already called 'listenQueue'`); // for now this.queueHandler = handler; const rt = (0, observer_js_1.defer)(); this.queueHandlerPromise = rt; this.rescheduleWorker(); return rt; } rescheduleWorker(delay = 0) { clearTimeout(this.workerTimeout); if (this.queueHandler) { this.workerTimeout = setTimeout(() => this.workerFn(this.queueHandler), delay); } } finalize() { clearTimeout(this.workerTimeout); this.queueHandlerPromise?.resolve(); } } exports.QueueWorker = QueueWorker;