From af7019b9a7c895d32be90d814fe157ed0eefab7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 14 Apr 2026 10:31:11 +0100 Subject: [PATCH] feat(database): add `getConnectionString` method --- packages/database/prod/src/main.test.ts | 64 +++++++++++++++++-------- packages/database/prod/src/main.ts | 11 +++++ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/packages/database/prod/src/main.test.ts b/packages/database/prod/src/main.test.ts index 338d5a1b5..17c0f29fa 100644 --- a/packages/database/prod/src/main.test.ts +++ b/packages/database/prod/src/main.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { getDatabase, MissingDatabaseConnectionError } from './main.js' +import { getConnectionString, getDatabase, MissingDatabaseConnectionError } from './main.js' const { mockWaddlerNodePostgres, mockWaddlerNeonHttp, mockPgPool, mockNeonPool, mockNeon, mockNeonConfig } = vi.hoisted( () => ({ @@ -35,6 +35,9 @@ vi.mock('ws', () => ({ default: function MockWebSocket() {}, })) +const CONNECTION_STRING = 'postgres://user:pass@localhost:5432/mydb' +const OTHER_CONNECTION_STRING = 'postgres://other:pass@localhost:5432/otherdb' + describe('getDatabase', () => { const originalEnv = process.env @@ -57,11 +60,11 @@ describe('getDatabase', () => { }) it('returns an object with sql, pool, and connectionString using node-postgres by default', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING const result = getDatabase() - expect(mockPgPool).toHaveBeenCalledWith({ connectionString: 'postgres://user:pass@localhost:5432/mydb' }) + expect(mockPgPool).toHaveBeenCalledWith({ connectionString: CONNECTION_STRING }) expect(mockWaddlerNodePostgres).toHaveBeenCalledWith({ client: expect.any(Object) }) expect(mockWaddlerNeonHttp).not.toHaveBeenCalled() expect(mockNeonPool).not.toHaveBeenCalled() @@ -69,43 +72,43 @@ describe('getDatabase', () => { driver: 'server', sql: 'node-postgres-sql', pool: expect.any(Object), - connectionString: 'postgres://user:pass@localhost:5432/mydb', + connectionString: CONNECTION_STRING, }) }) it('uses node-postgres when NETLIFY_DB_DRIVER is "server"', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING process.env.NETLIFY_DB_DRIVER = 'server' const result = getDatabase() expect(mockWaddlerNodePostgres).toHaveBeenCalled() expect(mockWaddlerNeonHttp).not.toHaveBeenCalled() - expect(result.connectionString).toBe('postgres://user:pass@localhost:5432/mydb') + expect(result.connectionString).toBe(CONNECTION_STRING) }) it('uses neon-http for sql and neon Pool for pool when NETLIFY_DB_DRIVER is "serverless"', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING process.env.NETLIFY_DB_DRIVER = 'serverless' const result = getDatabase() expect(mockWaddlerNeonHttp).toHaveBeenCalledWith({ client: 'neon-http-client' }) - expect(mockNeonPool).toHaveBeenCalledWith({ connectionString: 'postgres://user:pass@localhost:5432/mydb' }) + expect(mockNeonPool).toHaveBeenCalledWith({ connectionString: CONNECTION_STRING }) expect(mockWaddlerNodePostgres).not.toHaveBeenCalled() expect(mockPgPool).not.toHaveBeenCalled() - expect(mockNeon).toHaveBeenCalledWith('postgres://user:pass@localhost:5432/mydb') + expect(mockNeon).toHaveBeenCalledWith(CONNECTION_STRING) expect(result).toEqual({ driver: 'serverless', sql: 'neon-http-sql', pool: expect.any(Object), httpClient: 'neon-http-client', - connectionString: 'postgres://user:pass@localhost:5432/mydb', + connectionString: CONNECTION_STRING, }) }) it('sets neonConfig.webSocketConstructor from ws when WebSocket is not globally available', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING process.env.NETLIFY_DB_DRIVER = 'serverless' delete mockNeonConfig.webSocketConstructor @@ -122,7 +125,7 @@ describe('getDatabase', () => { }) it('does not override neonConfig.webSocketConstructor if already set', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING process.env.NETLIFY_DB_DRIVER = 'serverless' const existingWs = function ExistingWebSocket() {} mockNeonConfig.webSocketConstructor = existingWs @@ -133,20 +136,43 @@ describe('getDatabase', () => { }) it('allows override via connectionString option', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING - const result = getDatabase({ connectionString: 'postgres://other:pass@localhost:5432/otherdb' }) + const result = getDatabase({ connectionString: OTHER_CONNECTION_STRING }) - expect(mockPgPool).toHaveBeenCalledWith({ connectionString: 'postgres://other:pass@localhost:5432/otherdb' }) - expect(result.connectionString).toBe('postgres://other:pass@localhost:5432/otherdb') + expect(mockPgPool).toHaveBeenCalledWith({ connectionString: OTHER_CONNECTION_STRING }) + expect(result.connectionString).toBe(OTHER_CONNECTION_STRING) }) it('uses NETLIFY_DB_URL environment variable', () => { - process.env.NETLIFY_DB_URL = 'postgres://user:pass@localhost:5432/mydb' + process.env.NETLIFY_DB_URL = CONNECTION_STRING const result = getDatabase() - expect(mockPgPool).toHaveBeenCalledWith({ connectionString: 'postgres://user:pass@localhost:5432/mydb' }) - expect(result.connectionString).toBe('postgres://user:pass@localhost:5432/mydb') + expect(mockPgPool).toHaveBeenCalledWith({ connectionString: CONNECTION_STRING }) + expect(result.connectionString).toBe(CONNECTION_STRING) + }) +}) + +describe('getConnectionString', () => { + const originalEnv = process.env + + beforeEach(() => { + process.env = { ...originalEnv } + delete process.env.NETLIFY_DB_URL + }) + + afterEach(() => { + process.env = originalEnv + }) + + it('returns the connection string from NETLIFY_DB_URL', () => { + process.env.NETLIFY_DB_URL = CONNECTION_STRING + + expect(getConnectionString()).toBe(CONNECTION_STRING) + }) + + it('throws MissingDatabaseConnectionError when NETLIFY_DB_URL is not set', () => { + expect(() => getConnectionString()).toThrow(MissingDatabaseConnectionError) }) }) diff --git a/packages/database/prod/src/main.ts b/packages/database/prod/src/main.ts index 58b4c7347..1911e845c 100644 --- a/packages/database/prod/src/main.ts +++ b/packages/database/prod/src/main.ts @@ -36,6 +36,17 @@ export interface ServerlessDatabaseConnection { export type DatabaseConnection = ServerDatabaseConnection | ServerlessDatabaseConnection +export function getConnectionString(): string { + const env = getEnvironment() + const connectionString = env.get('NETLIFY_DB_URL') + + if (!connectionString) { + throw new MissingDatabaseConnectionError() + } + + return connectionString +} + export function getDatabase(options: GetDatabaseOptions = {}): DatabaseConnection { const env = getEnvironment() const connectionString = options.connectionString ?? env.get('NETLIFY_DB_URL')