feat: add Ekklesia doctor command
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Client, type QueryResultRow } from "pg";
|
||||
import { Client, type ClientConfig, type QueryResultRow } from "pg";
|
||||
import type {
|
||||
AppliedMigrationRecord,
|
||||
MigrationDatabase,
|
||||
@@ -12,6 +12,7 @@ const historyTableName = "ekklesia_core_migrations";
|
||||
const advisoryLockNamespace = 1_162_562_380;
|
||||
const advisoryLockKey = 1_296_648_018;
|
||||
const advisoryLockPollIntervalMs = 50;
|
||||
const maximumPostgresTimeoutMs = 2_147_483_647;
|
||||
|
||||
const createHistoryTableSql = `
|
||||
CREATE TABLE IF NOT EXISTS "public"."ekklesia_core_migrations" (
|
||||
@@ -72,6 +73,11 @@ export class PostgresMigrationDatabaseError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export interface PostgresMigrationDatabaseOptions {
|
||||
readonly connectionTimeoutMs?: number;
|
||||
readonly operationTimeoutMs?: number;
|
||||
}
|
||||
|
||||
interface HistoryExistenceRow extends QueryResultRow {
|
||||
readonly relation_kind: string;
|
||||
}
|
||||
@@ -124,6 +130,68 @@ function validateConnectionString(connectionString: string | undefined): string
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
function invalidConfiguration(message: string): PostgresMigrationDatabaseError {
|
||||
return databaseError("POSTGRES_MIGRATION_CONFIGURATION_INVALID", message);
|
||||
}
|
||||
|
||||
function validateTimeout(value: unknown, message: string): number | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
typeof value !== "number" ||
|
||||
!Number.isSafeInteger(value) ||
|
||||
value <= 0 ||
|
||||
value > maximumPostgresTimeoutMs
|
||||
) {
|
||||
throw invalidConfiguration(message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function createClientConfig(
|
||||
connectionString: string,
|
||||
options: PostgresMigrationDatabaseOptions | undefined,
|
||||
): ClientConfig {
|
||||
if (
|
||||
options !== undefined &&
|
||||
(typeof options !== "object" || options === null || Array.isArray(options))
|
||||
) {
|
||||
throw invalidConfiguration("The PostgreSQL migration database options are invalid.");
|
||||
}
|
||||
|
||||
let connectionTimeoutMs: unknown;
|
||||
let operationTimeoutMs: unknown;
|
||||
try {
|
||||
connectionTimeoutMs = options?.connectionTimeoutMs;
|
||||
operationTimeoutMs = options?.operationTimeoutMs;
|
||||
} catch {
|
||||
throw invalidConfiguration("The PostgreSQL migration database options are invalid.");
|
||||
}
|
||||
|
||||
const validatedConnectionTimeoutMs = validateTimeout(
|
||||
connectionTimeoutMs,
|
||||
"The PostgreSQL connection timeout is invalid.",
|
||||
);
|
||||
const validatedOperationTimeoutMs = validateTimeout(
|
||||
operationTimeoutMs,
|
||||
"The PostgreSQL operation timeout is invalid.",
|
||||
);
|
||||
|
||||
return {
|
||||
connectionString,
|
||||
...(validatedConnectionTimeoutMs === undefined
|
||||
? {}
|
||||
: { connectionTimeoutMillis: validatedConnectionTimeoutMs }),
|
||||
...(validatedOperationTimeoutMs === undefined
|
||||
? {}
|
||||
: {
|
||||
query_timeout: validatedOperationTimeoutMs,
|
||||
statement_timeout: validatedOperationTimeoutMs,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function isPostgresMigrationDatabaseError(error: unknown): error is PostgresMigrationDatabaseError {
|
||||
return error instanceof PostgresMigrationDatabaseError;
|
||||
}
|
||||
@@ -440,12 +508,14 @@ class PostgresMigrationDatabaseSession implements MigrationDatabaseSession {
|
||||
|
||||
export function createPostgresMigrationDatabase(
|
||||
connectionString: string | undefined,
|
||||
options?: PostgresMigrationDatabaseOptions,
|
||||
): MigrationDatabase {
|
||||
const validatedConnectionString = validateConnectionString(connectionString);
|
||||
const clientConfig = createClientConfig(validatedConnectionString, options);
|
||||
|
||||
return Object.freeze({
|
||||
async openSession(): Promise<MigrationDatabaseSession> {
|
||||
const client = new Client({ connectionString: validatedConnectionString });
|
||||
const client = new Client(clientConfig);
|
||||
client.on("error", () => undefined);
|
||||
try {
|
||||
await client.connect();
|
||||
|
||||
Reference in New Issue
Block a user