UNPKG

@deno/kv

Version:

A Deno KV client library optimized for Node.js.

255 lines (254 loc) 10.3 kB
"use strict"; // Copyright 2023 the Deno authors. All rights reserved. MIT license. Object.defineProperty(exports, "__esModule", { value: true }); exports.WatchCache = exports.ProtoBasedKv = exports.UnknownV8 = void 0; const bytes_js_1 = require("./bytes.js"); const kv_key_js_1 = require("./kv_key.js"); const kv_util_js_1 = require("./kv_util.js"); var v8_js_1 = require("./v8.js"); Object.defineProperty(exports, "UnknownV8", { enumerable: true, get: function () { return v8_js_1.UnknownV8; } }); class ProtoBasedKv extends kv_util_js_1.BaseKv { constructor(debug, decodeV8, encodeV8) { super({ debug }); Object.defineProperty(this, "decodeV8", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "encodeV8", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.decodeV8 = decodeV8; this.encodeV8 = encodeV8; } async get_(key, consistency) { const { decodeV8 } = this; const packedKey = (0, kv_key_js_1.packKey)(key); const req = { ranges: [computeReadRangeForKey(packedKey)], }; const res = await this.snapshotRead(req, consistency); for (const range of res.ranges) { for (const item of range.values) { if ((0, bytes_js_1.equalBytes)(item.key, packedKey)) { return { key, value: (0, kv_util_js_1.readValue)(item.value, item.encoding, decodeV8), versionstamp: (0, bytes_js_1.encodeHex)(item.versionstamp), }; } } } return { key, value: null, versionstamp: null }; } async getMany_(keys, consistency) { const { decodeV8 } = this; const packedKeys = keys.map(kv_key_js_1.packKey); const packedKeysHex = packedKeys.map(bytes_js_1.encodeHex); const req = { ranges: packedKeys.map(computeReadRangeForKey), }; const res = await this.snapshotRead(req, consistency); const rowMap = new Map(); for (const range of res.ranges) { for (const { key, value, encoding, versionstamp } of range.values) { rowMap.set((0, bytes_js_1.encodeHex)(key), { key: (0, kv_key_js_1.unpackKey)(key), value: (0, kv_util_js_1.readValue)(value, encoding, decodeV8), versionstamp: (0, bytes_js_1.encodeHex)(versionstamp), }); } } return keys.map((key, i) => { const row = rowMap.get(packedKeysHex[i]); return row ? { key, value: row.value, versionstamp: row.versionstamp } : { key, value: null, versionstamp: null }; }); } async commit(checks, mutations, enqueues) { const write = { checks: checks.map(computeKvCheckMessage), mutations: mutations.map((v) => computeKvMutationMessage(v, this.encodeV8)), enqueues: enqueues.map(({ value, opts }) => computeEnqueueMessage(value, this.encodeV8, opts)), }; const { status, versionstamp } = await this.atomicWrite(write); if (status === "AW_CHECK_FAILURE") return { ok: false }; if (status !== "AW_SUCCESS") { throw new Error(`commit failed with status: ${status}`); } return { ok: true, versionstamp: (0, bytes_js_1.encodeHex)(versionstamp) }; } async *listStream(outCursor, selector, { batchSize, consistency, cursor: cursorOpt, limit, reverse = false } = {}) { const { decodeV8 } = this; let yielded = 0; if (typeof limit === "number" && yielded >= limit) return; const cursor = typeof cursorOpt === "string" ? (0, kv_util_js_1.unpackCursor)(cursorOpt) : undefined; let lastYieldedKeyBytes = cursor?.lastYieldedKeyBytes; let pass = 0; const prefixBytes = "prefix" in selector ? (0, kv_key_js_1.packKey)(selector.prefix) : undefined; while (true) { pass++; // console.log({ pass }); const req = { ranges: [] }; let start; let end; if ("prefix" in selector) { start = "start" in selector ? (0, kv_key_js_1.packKey)(selector.start) : prefixBytes; end = "end" in selector ? (0, kv_key_js_1.packKey)(selector.end) : new Uint8Array([...prefixBytes, 0xff]); } else { start = (0, kv_key_js_1.packKey)(selector.start); end = (0, kv_key_js_1.packKey)(selector.end); } if (reverse) { end = lastYieldedKeyBytes ?? end; } else { start = lastYieldedKeyBytes ?? start; } if (start === undefined || end === undefined) throw new Error(); const batchLimit = Math.min(batchSize ?? 100, 500, limit ?? Number.MAX_SAFE_INTEGER) + (lastYieldedKeyBytes ? 1 : 0); req.ranges.push({ start, end, limit: batchLimit, reverse }); const res = await this.snapshotRead(req, consistency); let entries = 0; for (const range of res.ranges) { for (const entry of range.values) { if (entries++ === 0 && (lastYieldedKeyBytes && (0, bytes_js_1.equalBytes)(lastYieldedKeyBytes, entry.key) || prefixBytes && (0, bytes_js_1.equalBytes)(prefixBytes, entry.key))) continue; const key = (0, kv_key_js_1.unpackKey)(entry.key); const value = (0, kv_util_js_1.readValue)(entry.value, entry.encoding, decodeV8); const versionstamp = (0, bytes_js_1.encodeHex)(entry.versionstamp); lastYieldedKeyBytes = entry.key; outCursor.set((0, kv_util_js_1.packCursor)({ lastYieldedKeyBytes })); // cursor needs to be set before yield yield { key, value, versionstamp }; yielded++; // console.log({ yielded, entries, limit }); if (typeof limit === "number" && yielded >= limit) return; } } if (entries < batchLimit) return; } } } exports.ProtoBasedKv = ProtoBasedKv; class WatchCache { constructor(decodeV8, keys) { Object.defineProperty(this, "decodeV8", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "keys", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "lastValues", { enumerable: true, configurable: true, writable: true, value: [] }); this.decodeV8 = decodeV8; this.keys = keys; } processOutputKeys(outputKeys) { const { lastValues, decodeV8, keys } = this; const initial = lastValues.length === 0; outputKeys.forEach((v, i) => { const { changed, entryIfChanged } = v; if (initial && !changed) { throw new Error(`watch: Expect all values in first message: ${JSON.stringify(outputKeys)}`); } if (!changed) return; if (entryIfChanged) { const { value: bytes, encoding } = entryIfChanged; const value = (0, kv_util_js_1.readValue)(bytes, encoding, decodeV8); const versionstamp = (0, bytes_js_1.encodeHex)(entryIfChanged.versionstamp); lastValues[i] = [value, versionstamp]; } else { lastValues[i] = undefined; // deleted } }); return keys.map((key, i) => { const lastValue = lastValues[i]; if (lastValue === undefined) { return { key, value: null, versionstamp: null }; } const [value, versionstamp] = lastValue; return { key, value, versionstamp }; }); } } exports.WatchCache = WatchCache; function computeReadRangeForKey(packedKey) { return { start: packedKey, end: new Uint8Array([0xff]), limit: 1, reverse: false, }; } function computeKvCheckMessage({ key, versionstamp }) { return { key: (0, kv_key_js_1.packKey)(key), versionstamp: (versionstamp === null || versionstamp === undefined) /* in proto3 all fields are optional, but the generated types don't * like it, so we have to cast it manually */ ? undefined : (0, bytes_js_1.decodeHex)(versionstamp), }; } function computeKvMutationMessage(mut, encodeV8) { const { key, type } = mut; return { key: (0, kv_key_js_1.packKey)(key), mutationType: type === "delete" ? "M_DELETE" : type === "max" ? "M_MAX" : type === "min" ? "M_MIN" : type == "set" ? "M_SET" : type === "sum" ? "M_SUM" : "M_UNSPECIFIED", value: mut.type === "delete" ? undefined : (0, kv_util_js_1.packKvValue)(mut.value, encodeV8), expireAtMs: mut.type === "set" && typeof mut.expireIn === "number" ? (Date.now() + mut.expireIn).toString() : "0", }; } function computeEnqueueMessage(value, encodeV8, { delay = 0, keysIfUndelivered = [] } = {}) { return { backoffSchedule: [100, 200, 400, 800], deadlineMs: `${Date.now() + delay}`, keysIfUndelivered: keysIfUndelivered.map(kv_key_js_1.packKey), payload: encodeV8(value), }; }