@deno/kv
Version:
A Deno KV client library optimized for Node.js.
122 lines (121 loc) • 6.69 kB
JavaScript
;
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchWatchStream = exports.fetchAtomicWrite = exports.fetchSnapshotRead = exports.fetchDatabaseMetadata = exports.encodeSnapshotRead = exports.encodeAtomicWrite = exports.decodeSnapshotReadOutput = exports.decodeAtomicWriteOutput = void 0;
const Watch_js_1 = require("./proto/messages/com/deno/kv/datapath/Watch.js");
const AtomicWrite_js_1 = require("./proto/messages/com/deno/kv/datapath/AtomicWrite.js");
Object.defineProperty(exports, "encodeAtomicWrite", { enumerable: true, get: function () { return AtomicWrite_js_1.encodeBinary; } });
const SnapshotRead_js_1 = require("./proto/messages/com/deno/kv/datapath/SnapshotRead.js");
Object.defineProperty(exports, "encodeSnapshotRead", { enumerable: true, get: function () { return SnapshotRead_js_1.encodeBinary; } });
const SnapshotReadOutput_js_1 = require("./proto/messages/com/deno/kv/datapath/SnapshotReadOutput.js");
Object.defineProperty(exports, "decodeSnapshotReadOutput", { enumerable: true, get: function () { return SnapshotReadOutput_js_1.decodeBinary; } });
const AtomicWriteOutput_js_1 = require("./proto/messages/com/deno/kv/datapath/AtomicWriteOutput.js");
Object.defineProperty(exports, "decodeAtomicWriteOutput", { enumerable: true, get: function () { return AtomicWriteOutput_js_1.decodeBinary; } });
const check_js_1 = require("./check.js");
const sleep_js_1 = require("./sleep.js");
async function fetchDatabaseMetadata(url, accessToken, fetcher, maxRetries, supportedVersions) {
return await (0, sleep_js_1.executeWithRetries)("fetchDatabaseMetadata", async () => {
const res = await fetcher(url, {
method: "POST",
headers: {
authorization: `Bearer ${accessToken}`,
"content-type": "application/json",
},
body: JSON.stringify({ supportedVersions }),
});
if (res.status !== 200) {
throw new (res.status >= 500 && res.status < 600
? sleep_js_1.RetryableError
: Error)(`Unexpected response status: ${res.status} ${await res.text()}`);
}
const contentType = res.headers.get("content-type") ?? undefined;
if (contentType !== "application/json") {
throw new Error(`Unexpected response content-type: ${contentType} ${await res.text()}`);
}
const metadata = await res.json();
if (!isDatabaseMetadata(metadata)) {
throw new Error(`Bad DatabaseMetadata: ${JSON.stringify(metadata)}`);
}
return { metadata, responseUrl: res.url };
}, { maxRetries });
}
exports.fetchDatabaseMetadata = fetchDatabaseMetadata;
async function fetchSnapshotRead(url, accessToken, databaseId, req, fetcher, maxRetries, version) {
return (0, SnapshotReadOutput_js_1.decodeBinary)(await fetchProtobuf(url, accessToken, databaseId, (0, SnapshotRead_js_1.encodeBinary)(req), fetcher, maxRetries, version, false));
}
exports.fetchSnapshotRead = fetchSnapshotRead;
async function fetchAtomicWrite(url, accessToken, databaseId, write, fetcher, maxRetries, version) {
return (0, AtomicWriteOutput_js_1.decodeBinary)(await fetchProtobuf(url, accessToken, databaseId, (0, AtomicWrite_js_1.encodeBinary)(write), fetcher, maxRetries, version, false));
}
exports.fetchAtomicWrite = fetchAtomicWrite;
async function fetchWatchStream(url, accessToken, databaseId, watch, fetcher, maxRetries, version) {
return await fetchProtobuf(url, accessToken, databaseId, (0, Watch_js_1.encodeBinary)(watch), fetcher, maxRetries, version, true);
}
exports.fetchWatchStream = fetchWatchStream;
async function fetchProtobuf(url, accessToken, databaseId, body, fetcher, maxRetries, version, stream) {
const headers = {
authorization: `Bearer ${accessToken}`,
...(version === 1 ? { "x-transaction-domain-id": databaseId } : {
"x-denokv-version": version.toString(),
"x-denokv-database-id": databaseId,
}),
};
return await (0, sleep_js_1.executeWithRetries)("fetchProtobuf", async () => {
const res = await fetcher(url, { method: "POST", body, headers });
if (res.status !== 200) {
throw new (res.status >= 500 && res.status < 600
? sleep_js_1.RetryableError
: Error)(`Unexpected response status: ${res.status} ${await res.text()}`);
}
const contentType = res.headers.get("content-type") ?? undefined;
const expectedContentTypes = stream
? ["application/octet-stream", "" /* TODO remove once fixed upstream */]
: [
"application/x-protobuf",
"application/protobuf", /* allow nonspec, was returned by denokv release 0.2.0 */
];
if (!expectedContentTypes.includes(contentType ?? "")) {
throw new Error(`Unexpected response content-type: ${contentType} ${await res.text()}`);
}
if (stream) {
if (res.body === null) {
throw new Error(`No response body for stream request`);
}
return res.body;
}
else {
return new Uint8Array(await res.arrayBuffer());
}
}, { maxRetries });
}
function isValidEndpointUrl(url) {
try {
const { protocol, pathname, search, hash } = new URL(url, "https://example.com");
return /^https?:$/.test(protocol) &&
(pathname === "/" ||
!pathname.endsWith("/") && search === "" && hash === ""); // must not end in "/" (except no path), no qp/hash implied since the spec simply appends "/action"
}
catch {
return false;
}
}
function isEndpointInfo(obj) {
if (!(0, check_js_1.isRecord)(obj))
return false;
const { url, consistency, ...rest } = obj;
return typeof url === "string" && isValidEndpointUrl(url) &&
(consistency === "strong" || consistency === "eventual") &&
Object.keys(rest).length === 0;
}
function isDatabaseMetadata(obj) {
if (!(0, check_js_1.isRecord)(obj))
return false;
const { version, databaseId, endpoints, token, expiresAt, ...rest } = obj;
return (version === 1 || version === 2 || version === 3) &&
typeof databaseId === "string" &&
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(databaseId) &&
Array.isArray(endpoints) && endpoints.every(isEndpointInfo) &&
typeof token === "string" &&
typeof expiresAt === "string" && (0, check_js_1.isDateTime)(expiresAt) &&
Object.keys(rest).length === 0;
}