@worker-tools/deno-kv-storage
Version:
An implementation of the StorageArea (1,2,3) interface for Deno with an extensible system for supporting various database backends.
257 lines • 8 kB
JavaScript
import { date } from "../deps.js";
import { parseArray } from "./array_parser.js";
// Datetime parsing based on:
// https://github.com/bendrucker/postgres-date/blob/master/index.js
// Copyright (c) Ben Drucker <bvdrucker@gmail.com> (bendrucker.me). MIT License.
const BACKSLASH_BYTE_VALUE = 92;
const BC_RE = /BC$/;
const DATETIME_RE = /^(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?/;
const HEX = 16;
const HEX_PREFIX_REGEX = /^\\x/;
const TIMEZONE_RE = /([Z+-])(\d{2})?:?(\d{2})?:?(\d{2})?/;
export function decodeBigint(value) {
return BigInt(value);
}
export function decodeBigintArray(value) {
return parseArray(value, (x) => BigInt(x));
}
export function decodeBoolean(value) {
return value[0] === "t";
}
export function decodeBooleanArray(value) {
return parseArray(value, (x) => x[0] === "t");
}
export function decodeBox(value) {
const [a, b] = value.match(/\(.*?\)/g) || [];
return {
a: decodePoint(a),
b: decodePoint(b),
};
}
export function decodeBoxArray(value) {
return parseArray(value, decodeBox, ";");
}
export function decodeBytea(byteaStr) {
if (HEX_PREFIX_REGEX.test(byteaStr)) {
return decodeByteaHex(byteaStr);
}
else {
return decodeByteaEscape(byteaStr);
}
}
export function decodeByteaArray(value) {
return parseArray(value, decodeBytea);
}
function decodeByteaEscape(byteaStr) {
const bytes = [];
let i = 0;
let k = 0;
while (i < byteaStr.length) {
if (byteaStr[i] !== "\\") {
bytes.push(byteaStr.charCodeAt(i));
++i;
}
else {
if (/[0-7]{3}/.test(byteaStr.substr(i + 1, 3))) {
bytes.push(parseInt(byteaStr.substr(i + 1, 3), 8));
i += 4;
}
else {
let backslashes = 1;
while (i + backslashes < byteaStr.length &&
byteaStr[i + backslashes] === "\\") {
backslashes++;
}
for (k = 0; k < Math.floor(backslashes / 2); ++k) {
bytes.push(BACKSLASH_BYTE_VALUE);
}
i += Math.floor(backslashes / 2) * 2;
}
}
}
return new Uint8Array(bytes);
}
function decodeByteaHex(byteaStr) {
const bytesStr = byteaStr.slice(2);
const bytes = new Uint8Array(bytesStr.length / 2);
for (let i = 0, j = 0; i < bytesStr.length; i += 2, j++) {
bytes[j] = parseInt(bytesStr[i] + bytesStr[i + 1], HEX);
}
return bytes;
}
export function decodeCircle(value) {
const [point, radius] = value.substring(1, value.length - 1).split(/,(?![^(]*\))/);
return {
point: decodePoint(point),
radius: radius,
};
}
export function decodeCircleArray(value) {
return parseArray(value, decodeCircle);
}
export function decodeDate(dateStr) {
// there are special `infinity` and `-infinity`
// cases representing out-of-range dates
if (dateStr === "infinity") {
return Number(Infinity);
}
else if (dateStr === "-infinity") {
return Number(-Infinity);
}
return date.parse(dateStr, "yyyy-MM-dd");
}
export function decodeDateArray(value) {
return parseArray(value, decodeDate);
}
export function decodeDatetime(dateStr) {
/**
* Postgres uses ISO 8601 style date output by default:
* 1997-12-17 07:37:16-08
*/
const matches = DATETIME_RE.exec(dateStr);
if (!matches) {
return decodeDate(dateStr);
}
const isBC = BC_RE.test(dateStr);
const year = parseInt(matches[1], 10) * (isBC ? -1 : 1);
// remember JS dates are 0-based
const month = parseInt(matches[2], 10) - 1;
const day = parseInt(matches[3], 10);
const hour = parseInt(matches[4], 10);
const minute = parseInt(matches[5], 10);
const second = parseInt(matches[6], 10);
// ms are written as .007
const msMatch = matches[7];
const ms = msMatch ? 1000 * parseFloat(msMatch) : 0;
let date;
const offset = decodeTimezoneOffset(dateStr);
if (offset === null) {
date = new Date(year, month, day, hour, minute, second, ms);
}
else {
// This returns miliseconds from 1 January, 1970, 00:00:00,
// adding decoded timezone offset will construct proper date object.
const utc = Date.UTC(year, month, day, hour, minute, second, ms);
date = new Date(utc + offset);
}
// use `setUTCFullYear` because if date is from first
// century `Date`'s compatibility for millenium bug
// would set it as 19XX
date.setUTCFullYear(year);
return date;
}
export function decodeDatetimeArray(value) {
return parseArray(value, decodeDatetime);
}
export function decodeInt(value) {
return parseInt(value, 10);
}
// deno-lint-ignore no-explicit-any
export function decodeIntArray(value) {
if (!value)
return null;
return parseArray(value, decodeInt);
}
export function decodeJson(value) {
return JSON.parse(value);
}
export function decodeJsonArray(value) {
return parseArray(value, JSON.parse);
}
export function decodeLine(value) {
const [a, b, c] = value.substring(1, value.length - 1).split(",");
return {
a: a,
b: b,
c: c,
};
}
export function decodeLineArray(value) {
return parseArray(value, decodeLine);
}
export function decodeLineSegment(value) {
const [a, b] = value
.substring(1, value.length - 1)
.match(/\(.*?\)/g) || [];
return {
a: decodePoint(a),
b: decodePoint(b),
};
}
export function decodeLineSegmentArray(value) {
return parseArray(value, decodeLineSegment);
}
export function decodePath(value) {
// Split on commas that are not inside parantheses
// since encapsulated commas are separators for the point coordinates
const points = value.substring(1, value.length - 1).split(/,(?![^(]*\))/);
return points.map(decodePoint);
}
export function decodePathArray(value) {
return parseArray(value, decodePath);
}
export function decodePoint(value) {
const [x, y] = value.substring(1, value.length - 1).split(",");
if (Number.isNaN(parseFloat(x)) || Number.isNaN(parseFloat(y))) {
throw new Error(`Invalid point value: "${Number.isNaN(parseFloat(x)) ? x : y}"`);
}
return {
x: x,
y: y,
};
}
export function decodePointArray(value) {
return parseArray(value, decodePoint);
}
export function decodePolygon(value) {
return decodePath(value);
}
export function decodePolygonArray(value) {
return parseArray(value, decodePolygon);
}
export function decodeStringArray(value) {
if (!value)
return null;
return parseArray(value, (value) => value);
}
/**
* Decode numerical timezone offset from provided date string.
*
* Matched these kinds:
* - `Z (UTC)`
* - `-05`
* - `+06:30`
* - `+06:30:10`
*
* Returns offset in miliseconds.
*/
function decodeTimezoneOffset(dateStr) {
// get rid of date part as TIMEZONE_RE would match '-MM` part
const timeStr = dateStr.split(" ")[1];
const matches = TIMEZONE_RE.exec(timeStr);
if (!matches) {
return null;
}
const type = matches[1];
if (type === "Z") {
// Zulu timezone === UTC === 0
return 0;
}
// in JS timezone offsets are reversed, ie. timezones
// that are "positive" (+01:00) are represented as negative
// offsets and vice-versa
const sign = type === "-" ? 1 : -1;
const hours = parseInt(matches[2], 10);
const minutes = parseInt(matches[3] || "0", 10);
const seconds = parseInt(matches[4] || "0", 10);
const offset = hours * 3600 + minutes * 60 + seconds;
return sign * offset * 1000;
}
export function decodeTid(value) {
const [x, y] = value.substring(1, value.length - 1).split(",");
return [BigInt(x), BigInt(y)];
}
export function decodeTidArray(value) {
return parseArray(value, decodeTid);
}
//# sourceMappingURL=decoders.js.map