@deno/kv
Version:
A Deno KV client library optimized for Node.js.
432 lines (431 loc) • 15.1 kB
JavaScript
"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;