UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

134 lines (111 loc) 3.92 kB
import { AlephaError } from "alepha"; import { sql } from "drizzle-orm"; import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; import { PostgresProvider } from "./PostgresProvider.ts"; export class NodePostgresProvider extends PostgresProvider { static readonly SSL_MODES = [ "require", "allow", "prefer", "verify-full", ] as const; protected client?: postgres.Sql; protected pg?: PostgresJsDatabase; /** * Get the Drizzle Postgres database instance. */ public override get db(): PostgresJsDatabase { if (!this.pg) { throw new AlephaError("Database not initialized"); } return this.pg; } protected override async executeMigrations( migrationsFolder: string, ): Promise<void> { // Set search_path so schema-free migration SQL resolves to the correct schema. // postgres.js doesn't support the `connection` startup parameter with pooled // connections (e.g. Neon), so we SET it explicitly before running migrations. if (this.schema !== "public") { await this.db.execute( sql.raw(`SET search_path TO ${this.schema}, public`), ); } await migrate(this.db, { migrationsFolder, migrationsTable: this.migrationsTable, }); } // ------------------------------------------------------------------------------------------------------------------- public async connect(): Promise<void> { const options = this.getClientOptions(); this.log.debug("Connect ..", { ...options, password: options.password ? "****" : undefined, // hide password }); const client = postgres(options); await client`SELECT 1`; // test connection this.client = client; this.pg = drizzle(client, { logger: { // forward logs logQuery: (query: string, params: unknown[]) => { this.log.trace(query, { params }); }, }, }); this.log.info("Connection OK"); } public async close(): Promise<void> { if (this.client) { this.log.debug("Close..."); await this.client.end(); this.client = undefined; this.pg = undefined; this.log.info("Connection closed"); } } // ------------------------------------------------------------------------------------------------------------------- /** * Map the DATABASE_URL to postgres client options. */ protected getClientOptions(): postgres.Options<any> { const url = new URL(this.url); const options: postgres.Options<any> = { host: url.hostname, user: decodeURIComponent(url.username), database: decodeURIComponent(url.pathname.replace("/", "")), password: decodeURIComponent(url.password), port: Number(url.port || 5432), ssl: this.ssl(url), onnotice: () => { // let drizzle handle logs }, }; // Pool options — only set when explicitly configured via env vars if (this.pgEnv.POOL_MAX != null) options.max = this.pgEnv.POOL_MAX; if (this.pgEnv.POOL_IDLE_TIMEOUT != null) options.idle_timeout = this.pgEnv.POOL_IDLE_TIMEOUT; if (this.pgEnv.POOL_CONNECT_TIMEOUT != null) options.connect_timeout = this.pgEnv.POOL_CONNECT_TIMEOUT; // Set search_path at connection level so schema-free migration SQL // resolves to the correct PostgreSQL schema across all pool connections. if (this.schema !== "public") { options.connection = { search_path: `${this.schema}, public` }; } return options; } protected ssl( url: URL, ): "require" | "allow" | "prefer" | "verify-full" | undefined { const mode = url.searchParams.get("sslmode"); for (const it of NodePostgresProvider.SSL_MODES) { if (mode === it) { return it; } } } }