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
51 lines
1.6 KiB
TypeScript
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;
|
|
}
|