UNPKG

@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.

332 lines 12.7 kB
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _QueryResult_row_description; import { encodeArgument } from "./encode.js"; import { decode } from "./decode.js"; const commandTagRegexp = /^([A-Za-z]+)(?: (\d+))?(?: (\d+))?/; export var ResultType; (function (ResultType) { ResultType[ResultType["ARRAY"] = 0] = "ARRAY"; ResultType[ResultType["OBJECT"] = 1] = "OBJECT"; })(ResultType || (ResultType = {})); export class RowDescription { constructor(columnCount, columns) { Object.defineProperty(this, "columnCount", { enumerable: true, configurable: true, writable: true, value: columnCount }); Object.defineProperty(this, "columns", { enumerable: true, configurable: true, writable: true, value: columns }); } } /** * This function transforms template string arguments into a query * * ```ts * ["SELECT NAME FROM TABLE WHERE ID = ", " AND DATE < "] * // "SELECT NAME FROM TABLE WHERE ID = $1 AND DATE < $2" * ``` */ export function templateStringToQuery(template, args, result_type) { const text = template.reduce((curr, next, index) => { return `${curr}$${index}${next}`; }); return new Query(text, result_type, args); } function objectQueryToQueryArgs(query, args) { args = normalizeObjectQueryArgs(args); let counter = 0; const clean_args = []; const clean_query = query.replaceAll(/(?<=\$)\w+/g, (match) => { match = match.toLowerCase(); if (match in args) { clean_args.push(args[match]); } else { throw new Error(`No value was provided for the query argument "${match}"`); } return String(++counter); }); return [clean_query, clean_args]; } /** This function lowercases all the keys of the object passed to it and checks for collission names */ function normalizeObjectQueryArgs(args) { const normalized_args = Object.fromEntries(Object.entries(args).map(([key, value]) => [key.toLowerCase(), value])); if (Object.keys(normalized_args).length !== Object.keys(args).length) { throw new Error("The arguments provided for the query must be unique (insensitive)"); } return normalized_args; } export class QueryResult { constructor(query) { Object.defineProperty(this, "query", { enumerable: true, configurable: true, writable: true, value: query }); Object.defineProperty(this, "command", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "rowCount", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * This variable will be set after the class initialization, however it's required to be set * in order to handle result rows coming in */ _QueryResult_row_description.set(this, void 0); Object.defineProperty(this, "warnings", { enumerable: true, configurable: true, writable: true, value: [] }); } get rowDescription() { return __classPrivateFieldGet(this, _QueryResult_row_description, "f"); } set rowDescription(row_description) { // Prevent #row_description from being changed once set if (row_description && !__classPrivateFieldGet(this, _QueryResult_row_description, "f")) { __classPrivateFieldSet(this, _QueryResult_row_description, row_description, "f"); } } /** * This function is required to parse each column * of the results */ loadColumnDescriptions(description) { this.rowDescription = description; } handleCommandComplete(commandTag) { const match = commandTagRegexp.exec(commandTag); if (match) { this.command = match[1]; if (match[3]) { // COMMAND OID ROWS this.rowCount = parseInt(match[3], 10); } else { // COMMAND ROWS this.rowCount = parseInt(match[2], 10); } } } /** * Add a row to the result based on metadata provided by `rowDescription` * This implementation depends on row description not being modified after initialization * * This function can throw on validation, so any errors must be handled in the message loop accordingly */ insertRow(_row) { throw new Error("No implementation for insertRow is defined"); } } _QueryResult_row_description = new WeakMap(); export class QueryArrayResult extends QueryResult { constructor() { super(...arguments); Object.defineProperty(this, "rows", { enumerable: true, configurable: true, writable: true, value: [] }); } insertRow(row_data) { if (!this.rowDescription) { throw new Error("The row descriptions required to parse the result data weren't initialized"); } // Row description won't be modified after initialization const row = row_data.map((raw_value, index) => { const column = this.rowDescription.columns[index]; if (raw_value === null) { return null; } return decode(raw_value, column); }); this.rows.push(row); } } function findDuplicatesInArray(array) { return array.reduce((duplicates, item, index) => { const is_duplicate = array.indexOf(item) !== index; if (is_duplicate && !duplicates.includes(item)) { duplicates.push(item); } return duplicates; }, []); } function snakecaseToCamelcase(input) { return input .split("_") .reduce((res, word, i) => { if (i !== 0) { word = word[0].toUpperCase() + word.slice(1); } res += word; return res; }, ""); } export class QueryObjectResult extends QueryResult { constructor() { super(...arguments); /** * The column names will be undefined on the first run of insertRow, since */ Object.defineProperty(this, "columns", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "rows", { enumerable: true, configurable: true, writable: true, value: [] }); } insertRow(row_data) { if (!this.rowDescription) { throw new Error("The row description required to parse the result data wasn't initialized"); } // This will only run on the first iteration after row descriptions have been set if (!this.columns) { if (this.query.fields) { if (this.rowDescription.columns.length !== this.query.fields.length) { throw new RangeError("The fields provided for the query don't match the ones returned as a result " + `(${this.rowDescription.columns.length} expected, ${this.query.fields.length} received)`); } this.columns = this.query.fields; } else { let column_names; if (this.query.camelcase) { column_names = this.rowDescription.columns.map((column) => snakecaseToCamelcase(column.name)); } else { column_names = this.rowDescription.columns.map((column) => column.name); } // Check field names returned by the database are not duplicated const duplicates = findDuplicatesInArray(column_names); if (duplicates.length) { throw new Error(`Field names ${duplicates.map((str) => `"${str}"`).join(", ")} are duplicated in the result of the query`); } this.columns = column_names; } } // It's safe to assert columns as defined from now on const columns = this.columns; if (columns.length !== row_data.length) { throw new RangeError("The result fields returned by the database don't match the defined structure of the result"); } const row = row_data.reduce((row, raw_value, index) => { const current_column = this.rowDescription.columns[index]; if (raw_value === null) { row[columns[index]] = null; } else { row[columns[index]] = decode(raw_value, current_column); } return row; }, {}); this.rows.push(row); } } export class Query { constructor(config_or_text, result_type, args = []) { Object.defineProperty(this, "args", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "camelcase", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * The explicitly set fields for the query result, they have been validated beforehand * for duplicates and invalid names */ Object.defineProperty(this, "fields", { enumerable: true, configurable: true, writable: true, value: void 0 }); // TODO // Should be private Object.defineProperty(this, "result_type", { enumerable: true, configurable: true, writable: true, value: void 0 }); // TODO // Document that this text is the one sent to the database, not the original one Object.defineProperty(this, "text", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.result_type = result_type; if (typeof config_or_text === "string") { if (!Array.isArray(args)) { [config_or_text, args] = objectQueryToQueryArgs(config_or_text, args); } this.text = config_or_text; this.args = args.map(encodeArgument); } else { let { args = [], camelcase, encoder = encodeArgument, fields, // deno-lint-ignore no-unused-vars name, text, } = config_or_text; // Check that the fields passed are valid and can be used to map // the result of the query if (fields) { const fields_are_clean = fields.every((field) => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)); if (!fields_are_clean) { throw new TypeError("The fields provided for the query must contain only letters and underscores"); } if (new Set(fields).size !== fields.length) { throw new TypeError("The fields provided for the query must be unique"); } this.fields = fields; } this.camelcase = camelcase; if (!Array.isArray(args)) { [text, args] = objectQueryToQueryArgs(text, args); } this.args = args.map(encoder); this.text = text; } } } //# sourceMappingURL=query.js.map