UNPKG

@deno/kv

Version:

A Deno KV client library optimized for Node.js.

154 lines (153 loc) 6.09 kB
// Copyright 2023 the Deno authors. All rights reserved. MIT license. import { checkOptionalBoolean, checkOptionalFunction, checkOptionalObject, checkOptionalString, checkRecord, isRecord, } from "./check.js"; import { decodeAtomicWriteOutput, decodeSnapshotReadOutput, encodeAtomicWrite, encodeSnapshotRead, } from "./kv_connect_api.js"; import { packKey } from "./kv_key.js"; import { encodeBinary as encodeWatch } from "./proto/messages/com/deno/kv/datapath/Watch.js"; import { decodeBinary as decodeWatchOutput } from "./proto/messages/com/deno/kv/datapath/WatchOutput.js"; import { ProtoBasedKv, WatchCache } from "./proto_based.js"; import { makeUnrawWatchStream } from "./unraw_watch_stream.js"; /** * Return a KVService that creates KV instances backed by a native Node NAPI interface. */ export function makeNapiBasedService(opts) { return { openKv: (v) => Promise.resolve(NapiBasedKv.of(v, opts)), }; } export const NAPI_FUNCTIONS = [ "open", "close", "snapshotRead", "atomicWrite", "dequeueNextMessage", "finishMessage", "startWatch", "dequeueNextWatchMessage", "endWatch", ]; export function isNapiInterface(obj) { return isRecord(obj) && NAPI_FUNCTIONS.every((v) => typeof obj[v] === "function"); } // // deno-lint-ignore no-explicit-any const DEFAULT_NAPI_INTERFACE = await import('./_napi_index.cjs'); class NapiBasedKv extends ProtoBasedKv { constructor(debug, napi, dbId, decodeV8, encodeV8) { super(debug, decodeV8, encodeV8); Object.defineProperty(this, "napi", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "dbId", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.napi = napi; this.dbId = dbId; } static of(url, opts) { checkOptionalString("url", url); checkRecord("opts", opts); checkOptionalBoolean("opts.debug", opts.debug); checkOptionalObject("opts.napi", opts.napi); checkOptionalFunction("opts.decodeV8", opts.decodeV8); checkOptionalFunction("opts.encodeV8", opts.encodeV8); checkOptionalBoolean("opts.inMemory", opts.inMemory); const { debug = false, napi = DEFAULT_NAPI_INTERFACE, decodeV8, encodeV8, inMemory, } = opts; if (typeof url !== "string" || /^https?:\/\//i.test(url)) { throw new Error(`Invalid path: ${url}`); } if (napi === undefined) { throw new Error(`No default napi interface, provide one via the 'napi' option.`); } const dbId = napi.open(url, inMemory, debug); return new NapiBasedKv(debug, napi, dbId, decodeV8, encodeV8); } async listenQueue_(handler) { const { napi, dbId, decodeV8, debug } = this; while (true) { if (debug) console.log(`listenQueue_: before dequeueNextMessage`); const result = await napi.dequeueNextMessage(dbId, debug); if (result === undefined) return; const { bytes, messageId } = result; const value = decodeV8(bytes); if (debug) console.log(`listenQueue_: after value ${value}`); try { await Promise.resolve(handler(value)); await napi.finishMessage(dbId, messageId, true, debug); } catch (e) { if (debug) console.log(`listenQueue_: handler failed ${e.stack || e}`); await napi.finishMessage(dbId, messageId, false, debug); } } } close_() { const { napi, dbId, debug } = this; napi.close(dbId, debug); } async snapshotRead(req, _consistency) { const { napi, dbId, debug } = this; const res = await napi.snapshotRead(dbId, encodeSnapshotRead(req), debug); return decodeSnapshotReadOutput(res); } async atomicWrite(req) { const { napi, dbId, debug } = this; const res = await napi.atomicWrite(dbId, encodeAtomicWrite(req), debug); return decodeAtomicWriteOutput(res); } watch_(keys, raw) { const { napi, dbId, debug, decodeV8 } = this; const { startWatch, dequeueNextWatchMessage, endWatch } = napi; if (startWatch === undefined || dequeueNextWatchMessage === undefined || endWatch === undefined) { throw new Error("watch: not implemented"); } const watch = { keys: keys.map((v) => ({ key: packKey(v) })), }; let watchId; let ended = false; const endWatchIfNecessary = () => { if (watchId !== undefined && !ended) endWatch(dbId, watchId, debug); ended = true; }; const cache = new WatchCache(decodeV8, keys); const rawStream = new ReadableStream({ async pull(controller) { if (watchId === undefined) { watchId = await startWatch(dbId, encodeWatch(watch), debug); } const watchOutputBytes = await dequeueNextWatchMessage(dbId, watchId, debug); if (watchOutputBytes === undefined) { endWatchIfNecessary(); controller.close(); return; } const watchOutput = decodeWatchOutput(watchOutputBytes); const { status, keys: outputKeys } = watchOutput; if (status !== "SR_SUCCESS") { throw new Error(`Unexpected status: ${status}`); } const entries = cache.processOutputKeys(outputKeys); controller.enqueue(entries); }, cancel() { endWatchIfNecessary(); }, }); return raw ? rawStream : makeUnrawWatchStream(rawStream, endWatchIfNecessary); } }