@deno/kv
Version:
A Deno KV client library optimized for Node.js.
129 lines (128 loc) • 5.72 kB
JavaScript
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import * as dntShim from "./_dnt.shims.js";
import { check, checkOptionalBoolean, checkOptionalString, checkRecord, } from "./check.js";
import { makeInMemoryService } from "./in_memory.js";
import { isNapiInterface, makeNapiBasedService } from "./napi_based.js";
import { makeNativeService } from "./native.js";
import { makeRemoteService } from "./remote.js";
export * from "./napi_based.js";
export * from "./remote.js";
export * from "./in_memory.js";
export * from "./kv_types.js";
export { makeLimitedV8Serializer, UnknownV8 } from "./v8.js";
/**
* Open a new {@linkcode Kv} connection to persist data.
*
* When an url is provided, this will connect to a remote [Deno Deploy](https://deno.com/deploy) database
* or any other endpoint that supports the open [KV Connect](https://github.com/denoland/denokv/blob/main/proto/kv-connect.md) protocol.
*
* When a local path is provided, this will use a sqlite database on disk. Read and write access to the file is required.
*
* When no path is provided, this will use an ephemeral in-memory implementation.
*/
export async function openKv(path, opts = {}) {
checkOptionalString("path", path);
checkRecord("opts", opts);
checkOptionalBoolean("opts.debug", opts.debug);
check("opts.implementation", opts.implementation, opts.implementation === undefined ||
["in-memory", "sqlite", "remote"].includes(opts.implementation));
const { debug, implementation } = opts;
// use built-in native implementation if available when running on Deno
if ("Deno" in dntShim.dntGlobalThis && !implementation) {
// deno-lint-ignore no-explicit-any
const { openKv } = dntShim.dntGlobalThis.Deno;
if (typeof openKv === "function")
return makeNativeService().openKv(path);
}
// use in-memory implementation if no path provided
if (path === undefined || path === "" || implementation === "in-memory") {
const maxQueueAttempts = typeof opts.maxQueueAttempts === "number"
? opts.maxQueueAttempts
: undefined;
return await makeInMemoryService({ debug, maxQueueAttempts }).openKv(path);
}
const { encodeV8, decodeV8 } = await (async () => {
const { encodeV8, decodeV8 } = opts;
const defined = [encodeV8, decodeV8].filter((v) => v !== undefined).length;
if (defined === 1) {
throw new Error(`Provide both 'encodeV8' or 'decodeV8', or neither`);
}
if (defined > 0) {
if (typeof encodeV8 !== "function") {
throw new Error(`Unexpected 'encodeV8': ${encodeV8}`);
}
if (typeof decodeV8 !== "function") {
throw new Error(`Unexpected 'decodeV8': ${decodeV8}`);
}
return { encodeV8: encodeV8, decodeV8: decodeV8 };
}
if ("Bun" in dntShim.dntGlobalThis) {
throw new Error(`Bun provides v8.serialize/deserialize, but it uses an incompatible format (JavaScriptCore). Provide explicit 'encodeV8' and 'decodeV8' functions via options. See https://www.npmjs.com/package/@deno/kv#other-runtimes`); // https://discord.com/channels/876711213126520882/888937948345684008/1150135641137487892
}
const v8 = await import(`${"v8"}`);
if (!v8)
throw new Error(`Unable to import the v8 module`);
const { serialize, deserialize } = v8;
if (typeof serialize !== "function") {
throw new Error(`Unexpected 'serialize': ${serialize}`);
}
if (typeof deserialize !== "function") {
throw new Error(`Unexpected 'deserialize': ${deserialize}`);
}
return {
encodeV8: serialize,
decodeV8: deserialize,
};
})();
// use remote implementation if path looks like a url
if (/^https?:\/\//i.test(path) || implementation === "remote") {
let accessToken = typeof opts.accessToken === "string" && opts.accessToken !== ""
? opts.accessToken
: undefined;
if (accessToken === undefined) {
// deno-lint-ignore no-explicit-any
accessToken = dntShim.dntGlobalThis?.process?.env?.DENO_KV_ACCESS_TOKEN;
if (typeof accessToken !== "string") {
throw new Error(`Set the DENO_KV_ACCESS_TOKEN to your access token`);
}
}
const fetcher = typeof opts.fetcher === "function"
? opts.fetcher
: undefined;
const maxRetries = typeof opts.maxRetries === "number"
? opts.maxRetries
: undefined;
const wrapUnknownValues = typeof opts.wrapUnknownValues === "boolean"
? opts.wrapUnknownValues
: undefined;
const supportedVersions = Array.isArray(opts.supportedVersions) &&
opts.supportedVersions.every((v) => typeof v === "number")
? opts.supportedVersions
: undefined;
return await makeRemoteService({
debug,
accessToken,
encodeV8,
decodeV8,
fetcher,
maxRetries,
supportedVersions,
wrapUnknownValues,
}).openKv(path);
}
// else use the sqlite napi implementation
const { napi } = opts;
if (napi !== undefined && !isNapiInterface(napi)) {
throw new Error(`Unexpected napi interface for sqlite`);
}
const inMemory = typeof opts.inMemory === "boolean"
? opts.inMemory
: undefined;
return await makeNapiBasedService({
debug,
encodeV8,
decodeV8,
napi,
inMemory,
}).openKv(path);
}