74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
import { Client } from "postgres";
|
|
import config from "./config.ts";
|
|
import { JSONObject } from "./JSON.ts";
|
|
import { Result, StoccaTreError } from "./Result.ts";
|
|
|
|
type Interpolable = number | string | bigint | null | boolean;
|
|
type UnionToIntersection<Union> =
|
|
(Union extends any ? (x: Union) => any : never) extends (x: infer Intersection) => any
|
|
? Intersection
|
|
: never;
|
|
type SQLWithArg<Arg> = Arg extends string ? `${string}$${Arg}${string}` : never;
|
|
type SQLWithArgs<Args extends Record<string, Interpolable>> = UnionToIntersection<SQLWithArg<keyof Args>>;
|
|
type ArgNamesInSQL<SQL extends string> =
|
|
SQL extends `${string}$${infer Arg1} ${infer SQLAfterArg1}`
|
|
? Arg1 extends `${infer Arg1NoTrailing}${";" | "," | "'" | "\"" | "(" | ")"}`
|
|
? Arg1NoTrailing | ArgNamesInSQL<SQLAfterArg1>
|
|
: Arg1 | ArgNamesInSQL<SQLAfterArg1>
|
|
: SQL extends `${string}$${infer ArgN}`
|
|
? ArgN extends `${infer ArgNNoTrailing}${";" | "," | "'" | "\"" | "(" | ")"}`
|
|
? ArgNNoTrailing
|
|
: ArgN
|
|
: never;
|
|
|
|
type ParametrisedSQL = `${string}$${string}`;
|
|
type IsPlainSQL<T> = T extends ParametrisedSQL ? never : T;
|
|
type ArgsForSQL<SQL extends string> = SQL extends ParametrisedSQL
|
|
? Record<ArgNamesInSQL<SQL>, Interpolable>
|
|
: JSONObject | undefined;
|
|
|
|
// Helper for casting queries
|
|
export type Q<T extends JSONObject> = Result<T[]>;
|
|
|
|
export type WithoutId<T> = Omit<T, "id">;
|
|
|
|
export interface StoccaTreDbConn {
|
|
query<Q extends string>(query: Q, ...args: Q extends IsPlainSQL<Q> ? [(JSONObject | undefined)?] : [ArgsForSQL<Q>]): Promise<Result<JSONObject[]>>;
|
|
}
|
|
|
|
export default async function createNewDbConnection(): Promise<Result<StoccaTreDbConn>> {
|
|
let postgresClient: Client;
|
|
try {
|
|
postgresClient = new Client({
|
|
user: config.dbUsername,
|
|
database: "stocca_tre",
|
|
hostname: config.hostname,
|
|
port: config.dbPort,
|
|
password: config.dbPassword,
|
|
tls: {
|
|
enabled: false,
|
|
},
|
|
});
|
|
await postgresClient.connect();
|
|
} catch (e: unknown) {
|
|
const error = e as { message?: string };
|
|
if (error.message) {
|
|
return [new StoccaTreError(error.message).qualified("Error connecting to database: ")];
|
|
}
|
|
return [new StoccaTreError("Error connecting to database.")];
|
|
}
|
|
return [,
|
|
{
|
|
async query<Q extends string>(query: Q, ...args: Q extends IsPlainSQL<Q> ? [(JSONObject | undefined)?] : [ArgsForSQL<Q>]): Promise<Result<JSONObject[]>> {
|
|
try {
|
|
const result = <{rows: JSONObject[]}> await postgresClient.queryObject(query, args[0]);
|
|
return [, result.rows];
|
|
} catch (e: unknown) {
|
|
const error = e as { message?: string };
|
|
return [new StoccaTreError(error.message ?? "Internal database error.")];
|
|
}
|
|
},
|
|
},
|
|
];
|
|
}
|