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 }; }