@deno/kv
Version:
A Deno KV client library optimized for Node.js.
159 lines (158 loc) • 6.49 kB
JavaScript
;
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
Object.defineProperty(exports, "__esModule", { value: true });
exports.isNapiInterface = exports.NAPI_FUNCTIONS = exports.makeNapiBasedService = void 0;
const check_js_1 = require("./check.js");
const kv_connect_api_js_1 = require("./kv_connect_api.js");
const kv_key_js_1 = require("./kv_key.js");
const Watch_js_1 = require("./proto/messages/com/deno/kv/datapath/Watch.js");
const WatchOutput_js_1 = require("./proto/messages/com/deno/kv/datapath/WatchOutput.js");
const proto_based_js_1 = require("./proto_based.js");
const unraw_watch_stream_js_1 = require("./unraw_watch_stream.js");
/**
* Return a KVService that creates KV instances backed by a native Node NAPI interface.
*/
function makeNapiBasedService(opts) {
return {
openKv: (v) => Promise.resolve(NapiBasedKv.of(v, opts)),
};
}
exports.makeNapiBasedService = makeNapiBasedService;
exports.NAPI_FUNCTIONS = [
"open",
"close",
"snapshotRead",
"atomicWrite",
"dequeueNextMessage",
"finishMessage",
"startWatch",
"dequeueNextWatchMessage",
"endWatch",
];
function isNapiInterface(obj) {
return (0, check_js_1.isRecord)(obj) &&
exports.NAPI_FUNCTIONS.every((v) => typeof obj[v] === "function");
}
exports.isNapiInterface = isNapiInterface;
//
// deno-lint-ignore no-explicit-any
const DEFAULT_NAPI_INTERFACE = require('./_napi_index.cjs');
class NapiBasedKv extends proto_based_js_1.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) {
(0, check_js_1.checkOptionalString)("url", url);
(0, check_js_1.checkRecord)("opts", opts);
(0, check_js_1.checkOptionalBoolean)("opts.debug", opts.debug);
(0, check_js_1.checkOptionalObject)("opts.napi", opts.napi);
(0, check_js_1.checkOptionalFunction)("opts.decodeV8", opts.decodeV8);
(0, check_js_1.checkOptionalFunction)("opts.encodeV8", opts.encodeV8);
(0, check_js_1.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, (0, kv_connect_api_js_1.encodeSnapshotRead)(req), debug);
return (0, kv_connect_api_js_1.decodeSnapshotReadOutput)(res);
}
async atomicWrite(req) {
const { napi, dbId, debug } = this;
const res = await napi.atomicWrite(dbId, (0, kv_connect_api_js_1.encodeAtomicWrite)(req), debug);
return (0, kv_connect_api_js_1.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: (0, kv_key_js_1.packKey)(v) })),
};
let watchId;
let ended = false;
const endWatchIfNecessary = () => {
if (watchId !== undefined && !ended)
endWatch(dbId, watchId, debug);
ended = true;
};
const cache = new proto_based_js_1.WatchCache(decodeV8, keys);
const rawStream = new ReadableStream({
async pull(controller) {
if (watchId === undefined) {
watchId = await startWatch(dbId, (0, Watch_js_1.encodeBinary)(watch), debug);
}
const watchOutputBytes = await dequeueNextWatchMessage(dbId, watchId, debug);
if (watchOutputBytes === undefined) {
endWatchIfNecessary();
controller.close();
return;
}
const watchOutput = (0, WatchOutput_js_1.decodeBinary)(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
: (0, unraw_watch_stream_js_1.makeUnrawWatchStream)(rawStream, endWatchIfNecessary);
}
}