diff --git a/src/sql/util.ts b/src/sql/util.ts new file mode 100644 index 0000000..c8669c0 --- /dev/null +++ b/src/sql/util.ts @@ -0,0 +1,86 @@ +import snakeCase from "snake-case"; + +import { Row } from "./api"; + +interface ColMapper { + _read(str: string): F; + + _write(val: F): string; +} + +type Spec = { + [K in keyof R]: ColMapper; +}; + +/** + * Function objects describing the precise way in which our SQL driver + * transmits and receives values to the database as strings. Note that we could + * potentially have more than one SQL driver so this probably needs to be + * pushed down into `/sql/api.ts` somehow... + */ +export const T = { + bigint: { + _read: (str: string) => BigInt(str), + _write: (val: bigint) => val.toString(), + }, + boolean: { + _read: (str: string) => str === "true", + _write: (val: boolean) => val.toString(), + }, + number: { + _read: (str: string) => parseInt(str), + _write: (val: number) => val.toString(), + }, + string: { + _read: (str: string) => str, + _write: (val: string) => val, + }, + Date: { + _read: (str: string) => new Date(str), + _write: (val: Date) => val.toISOString(), + }, +}; + +/** + * Boilerplate-reducing SQL <-> JavaScript record mapper. The TypeScript + * compiler will ensure that the mapping is correct. + */ +export function createSqlMapper(spec: Spec) { + const snaked = new Map(); + const colNames = new Array(); + + for (const k in spec) { + const sk = snakeCase(k); + + snaked.set(k, sk); + colNames.push(sk); + } + + function readRow(row: Row): R { + const result = {} as R; + + for (const k in spec) { + const sk = snaked.get(k); + const v = spec[k]._read(row[sk!]); + + result[k] = v; + } + + return result; + } + + function writeRow(obj: R): Row { + const result = {}; + + for (const k in spec) { + const sk = snaked.get(k); + const v = spec[k]._write(obj[k]); + + result[sk!] = v; + } + + return result; + } + + return { readRow, writeRow, colNames }; +}