Files
Ekklesia/packages/module-database/src/index.ts
T
ekklesia 859675c3c7
CI / verify (pull_request) Successful in 58s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 29s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / deploy-smoke (split) (pull_request) Successful in 1m31s
feat: add module database ABI v1 adapters
2026-10-07 16:44:28 -05:00

51 lines
1.6 KiB
TypeScript

import {
isModuleDatabaseError,
type ModuleDatabaseError,
type ModuleDatabaseParameter,
type ModuleDatabaseQuery,
} from "@ekklesia/contracts";
/** SQL text and values ready to pass directly to a Module Database ABI v1 executor. */
export type ModuleSqlQuery = Readonly<Required<Pick<ModuleDatabaseQuery, "text" | "values">>>;
/**
* Builds one PostgreSQL statement without ever interpolating a value into its text.
*
* Every interpolation becomes the next positional parameter. Dynamic identifiers and trusted SQL
* fragments are deliberately unsupported in v1: keep identifiers in the static template text.
*/
export function sql(
strings: TemplateStringsArray,
...values: readonly ModuleDatabaseParameter[]
): ModuleSqlQuery {
let text = strings[0] ?? "";
for (let index = 0; index < values.length; index += 1) {
text += `$${index + 1}${strings[index + 1] ?? ""}`;
}
return Object.freeze({ text, values: Object.freeze([...values]) });
}
export type EkklesiaDatabaseError = Pick<
ModuleDatabaseError,
"code" | "message" | "name" | "sqlState"
>;
/**
* Finds a stable Ekklesia database error through wrappers such as DrizzleQueryError.
*
* Callers inspect the returned `code` and `sqlState`; they never need a PostgreSQL driver error,
* `.cause.code`, or message parsing.
*/
export function findEkklesiaDatabaseError(value: unknown): EkklesiaDatabaseError | undefined {
const visited = new Set<unknown>();
let current = value;
while (current instanceof Error && !visited.has(current)) {
if (isModuleDatabaseError(current)) {
return current;
}
visited.add(current);
current = current.cause;
}
return undefined;
}