@deno/kv
Version:
A Deno KV client library optimized for Node.js.
159 lines (158 loc) • 6.99 kB
JavaScript
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { checkEnd, computeBigintMinimumNumberOfBytes, flipBytes, } from "./bytes.js";
// https://github.com/apple/foundationdb/blob/main/design/tuple.md
// limited to Uint8Array | string | number | bigint | boolean
// https://github.com/denoland/deno/blob/main/ext/kv/codec.rs
export function packKey(kvKey) {
return new Uint8Array(kvKey.flatMap((v) => [...packKeyPart(v)]));
}
export function packKeyPart(kvKeyPart) {
if (kvKeyPart instanceof Uint8Array) {
return new Uint8Array([
Typecode.ByteString,
...encodeZeroWithZeroFF(kvKeyPart),
0,
]);
}
if (typeof kvKeyPart === "string") {
return new Uint8Array([
Typecode.UnicodeString,
...encodeZeroWithZeroFF(new TextEncoder().encode(kvKeyPart)),
0,
]);
}
if (kvKeyPart === false)
return new Uint8Array([Typecode.False]);
if (kvKeyPart === true)
return new Uint8Array([Typecode.True]);
if (typeof kvKeyPart === "bigint") {
const neg = kvKeyPart < 0;
const abs = neg ? -kvKeyPart : kvKeyPart;
const numBytes = BigInt(computeBigintMinimumNumberOfBytes(abs));
const typecode = neg
? (numBytes <= 8n
? (Typecode.IntegerOneByteNegative - Number(numBytes) + 1)
: Typecode.IntegerArbitraryByteNegative)
: (numBytes <= 8n
? (Typecode.IntegerOneBytePositive + Number(numBytes) - 1)
: Typecode.IntegerArbitraryBytePositive);
const bytes = [typecode];
if (numBytes > 8n)
bytes.push(Number(numBytes));
for (let i = 0n; i < numBytes; i++) {
const mask = 0xffn << 8n * (numBytes - i - 1n);
const byte = Number((abs & mask) >> (8n * (numBytes - i - 1n)));
bytes.push(byte);
}
if (neg)
flipBytes(bytes, 1);
return new Uint8Array(bytes);
}
if (typeof kvKeyPart === "number") {
const sub = new Uint8Array(8);
new DataView(sub.buffer).setFloat64(0, -Math.abs(kvKeyPart), false);
if (kvKeyPart < 0)
flipBytes(sub);
return new Uint8Array([Typecode.FloatingPointDouble, ...sub]);
}
throw new Error(`Unsupported keyPart: ${typeof kvKeyPart} ${kvKeyPart}`);
}
export function unpackKey(bytes) {
const rt = [];
let pos = 0;
while (pos < bytes.length) {
const typecode = bytes[pos++];
if (typecode === Typecode.ByteString || typecode === Typecode.UnicodeString) {
// Uint8Array or string
const newBytes = [];
while (pos < bytes.length) {
const byte = bytes[pos++];
if (byte === 0 && bytes[pos] === 0xff) {
pos++;
}
else if (byte === 0) {
break;
}
newBytes.push(byte);
}
rt.push(typecode === Typecode.UnicodeString
? decoder.decode(new Uint8Array(newBytes))
: new Uint8Array(newBytes));
}
else if (typecode >= Typecode.IntegerArbitraryByteNegative &&
typecode <= Typecode.IntegerArbitraryBytePositive) {
// bigint
const neg = typecode < Typecode.IntegerZero;
const numBytes = BigInt((typecode === Typecode.IntegerArbitraryBytePositive ||
typecode === Typecode.IntegerArbitraryByteNegative)
? (neg ? (0xff - bytes[pos++]) : bytes[pos++])
: Math.abs(typecode - Typecode.IntegerZero));
let val = 0n;
for (let i = 0n; i < numBytes; i++) {
let byte = bytes[pos++];
if (neg)
byte = 0xff - byte;
val += BigInt(byte) << ((numBytes - i - 1n) * 8n);
}
rt.push(neg ? -val : val);
}
else if (typecode === Typecode.FloatingPointDouble) {
// number
const sub = new Uint8Array(bytes.subarray(pos, pos + 8));
const neg = sub[0] < 128;
if (neg)
flipBytes(sub);
const num = -new DataView(sub.buffer).getFloat64(0, false);
pos += 8;
rt.push(neg ? -num : num);
}
else if (typecode === Typecode.False) {
// boolean false
rt.push(false);
}
else if (typecode === Typecode.True) {
// boolean true
rt.push(true);
}
else {
throw new Error(`Unsupported typecode: ${typecode} in key: [${bytes.join(", ")}] after ${rt.join(", ")}`);
}
}
checkEnd(bytes, pos);
return rt;
}
//
const decoder = new TextDecoder();
var Typecode;
(function (Typecode) {
Typecode[Typecode["ByteString"] = 1] = "ByteString";
Typecode[Typecode["UnicodeString"] = 2] = "UnicodeString";
Typecode[Typecode["IntegerArbitraryByteNegative"] = 11] = "IntegerArbitraryByteNegative";
Typecode[Typecode["IntegerEightByteNegative"] = 12] = "IntegerEightByteNegative";
Typecode[Typecode["IntegerSevenByteNegative"] = 13] = "IntegerSevenByteNegative";
Typecode[Typecode["IntegerSixByteNegative"] = 14] = "IntegerSixByteNegative";
Typecode[Typecode["IntegerFiveByteNegative"] = 15] = "IntegerFiveByteNegative";
Typecode[Typecode["IntegerFourByteNegative"] = 16] = "IntegerFourByteNegative";
Typecode[Typecode["IntegerThreeByteNegative"] = 17] = "IntegerThreeByteNegative";
Typecode[Typecode["IntegerTwoByteNegative"] = 18] = "IntegerTwoByteNegative";
Typecode[Typecode["IntegerOneByteNegative"] = 19] = "IntegerOneByteNegative";
Typecode[Typecode["IntegerZero"] = 20] = "IntegerZero";
Typecode[Typecode["IntegerOneBytePositive"] = 21] = "IntegerOneBytePositive";
Typecode[Typecode["IntegerTwoBytePositive"] = 22] = "IntegerTwoBytePositive";
Typecode[Typecode["IntegerThreeBytePositive"] = 23] = "IntegerThreeBytePositive";
Typecode[Typecode["IntegerFourBytePositive"] = 24] = "IntegerFourBytePositive";
Typecode[Typecode["IntegerFiveBytePositive"] = 25] = "IntegerFiveBytePositive";
Typecode[Typecode["IntegerSixBytePositive"] = 26] = "IntegerSixBytePositive";
Typecode[Typecode["IntegerSevenBytePositive"] = 27] = "IntegerSevenBytePositive";
Typecode[Typecode["IntegerEightBytePositive"] = 28] = "IntegerEightBytePositive";
Typecode[Typecode["IntegerArbitraryBytePositive"] = 29] = "IntegerArbitraryBytePositive";
Typecode[Typecode["FloatingPointDouble"] = 33] = "FloatingPointDouble";
Typecode[Typecode["False"] = 38] = "False";
Typecode[Typecode["True"] = 39] = "True";
})(Typecode || (Typecode = {}));
function encodeZeroWithZeroFF(bytes) {
const index = bytes.indexOf(0);
return index < 0
? bytes
: new Uint8Array([...bytes].flatMap((v) => v === 0 ? [0, 0xff] : [v]));
}