Skip to content

fix: accept postgres:// in PrismaClientConfigSchema + Neon-aware health probe#1406

Merged
jaypatrick merged 2 commits intomainfrom
copilot/fix-prisma-client-config
Mar 25, 2026
Merged

fix: accept postgres:// in PrismaClientConfigSchema + Neon-aware health probe#1406
jaypatrick merged 2 commits intomainfrom
copilot/fix-prisma-client-config

Conversation

Copy link
Contributor

Copilot AI commented Mar 25, 2026

PrismaClientConfigSchema rejected postgres:// — the exact scheme Cloudflare Hyperdrive's .connectionString emits — causing an instant ZodError before any connection attempt. This is why database.latency_ms: 0 appeared in health responses and Hyperdrive showed zero activity.

Changes

worker/lib/prisma-config.ts

  • Replaced .startsWith('postgresql://') with .refine(s => s.startsWith('postgresql://') || s.startsWith('postgres://')) — both are valid PG DSNs accepted by @prisma/adapter-pg

worker/handlers/health.ts

  • Replaced SELECT 1 probe with SELECT current_database() AS db_name
  • Returns degraded (not healthy) if connected to a database other than adblock-compiler — catches misconfigured Hyperdrive endpoints silently pointing at neondb
  • Surfaces db_name and hyperdrive_host in the services.database response object for observability
"database": {
  "status": "healthy",
  "latency_ms": 42,
  "db_name": "adblock-compiler",
  "hyperdrive_host": "ep-winter-term-a8rxh2a9-pooler.eastus2.azure.neon.tech"
}

worker/lib/prisma-config.test.ts

  • Renamed and flipped the incorrect rejection test (postgres:// was wrongly expected to fail) to a happy-path assertion
  • Added 3 additional postgres:// happy-path tests: with SSL query param, without port, and localhost

worker/lib/prisma.test.ts

  • Added postgres:// happy-path test

worker/handlers/health.test.ts

  • Updated existing mock payloads from { '?column?': 1 }{ db_name: 'adblock-compiler' }
  • Added host field to HyperdriveBinding stubs
  • Added 4 new tests: wrong-db returns degraded, db_name field present on healthy, hyperdrive_host field present, $queryRaw throws returns down

Testing

  • Unit tests added/updated
  • Manual testing performed
  • CI passes

Zero Trust Architecture Checklist

Worker / Backend

  • Every handler verifies auth before executing business logic — N/A, health endpoint is intentionally unauthenticated (no sensitive data exposed)
  • CORS origin allowlist enforced (not *) on write/authenticated endpoints — N/A, read-only probe
  • All secrets accessed via Worker Secret bindings (not [vars]) — N/A, no secrets touched
  • All external inputs Zod-validated before use — PrismaClientConfigSchema validation is the subject of this fix
  • All D1 queries use parameterized .prepare().bind() (no string interpolation) — N/A, no D1 queries
  • Security events emitted to Analytics Engine on auth failures — N/A, no auth in health probe

Frontend / Angular

  • Protected routes have functional CanActivateFn auth guards — N/A
  • Auth tokens managed via Clerk SDK (not localStorage) — N/A
  • HTTP interceptor attaches ****** (no manual token passing) — N/A
  • API responses validated with Zod schemas before consumption — N/A
Original prompt

Problem

There are two issues to fix and one enhancement to add.


Bug: PrismaClientConfigSchema rejects postgres:// — the scheme Cloudflare Hyperdrive actually returns

Root cause: worker/lib/prisma-config.ts validates the connection string with .startsWith('postgresql://'). However, the Cloudflare Hyperdrive binding's .connectionString uses the postgres:// short alias (confirmed via wrangler hyperdrive get"scheme": "postgres"). This means PrismaClientConfigSchema.parse(...) throws a ZodError before any connection is ever opened, causing handleHealth to catch the error and return { status: 'down', latency_ms: 0 } for the database service. This is why Hyperdrive shows zero activity — the connection is never established.

Evidence from health endpoint:

{
  "services": {
    "database": { "status": "down", "latency_ms": 0 }
  }
}

latency_ms: 0 confirms the probe throws instantly (Zod validation) before any network round-trip.

There is also an existing test that explicitly asserts this wrong behaviour:

// worker/lib/prisma-config.test.ts line 79
Deno.test('PrismaClientConfigSchema rejects postgres:// shorthand protocol', () => {
    // Schema uses startsWith("postgresql://"), so the shorter alias is rejected.
    const result = PrismaClientConfigSchema.safeParse({
        connectionString: 'postgres://user:pass@host:5432/db',
    });
    assertEquals(result.success, false); // ← This assertion is WRONG
});

Fix 1 (Option A): Update PrismaClientConfigSchema to accept both schemes

File: worker/lib/prisma-config.ts

Replace:

export const PrismaClientConfigSchema = z.object({
    connectionString: z.string().url().startsWith('postgresql://'),
});

With:

/**
 * Schema for validating Hyperdrive connection string configuration.
 * Accepts both the canonical postgresql:// and the postgres:// alias.
 *
 * Cloudflare Hyperdrive returns postgres:// from its .connectionString property
 * (the scheme field in the Hyperdrive config is "postgres"), while local dev
 * and direct Neon URLs use postgresql://. Both are valid PostgreSQL DSNs and
 * are accepted by @prisma/adapter-pg.
 */
export const PrismaClientConfigSchema = z.object({
    connectionString: z
        .string()
        .url()
        .refine(
            (s) => s.startsWith('postgresql://') || s.startsWith('postgres://'),
            { message: 'Connection string must start with postgresql:// or postgres://' },
        ),
});

Fix 2: Update worker/lib/prisma-config.test.ts

  1. Change the existing test 'PrismaClientConfigSchema rejects postgres:// shorthand protocol' — it should now pass (assert result.success === true). Update the test name and comment accordingly.

  2. Add new happy-path tests for postgres://:

    • 'PrismaClientConfigSchema validates postgres:// shorthand (Hyperdrive scheme)' — basic postgres://user:pass@host:5432/db
    • 'PrismaClientConfigSchema validates postgres:// with SSL query param (Hyperdrive + Neon)'postgres://user:pass@ep-xxx-pooler.eastus2.azure.neon.tech:5432/adblock-compiler?sslmode=require
    • 'PrismaClientConfigSchema validates postgres:// without port'postgres://user:pass@host/db
    • 'PrismaClientConfigSchema validates postgres:// localhost URL'postgres://localhost:5432/mydb
  3. Keep all other rejection tests unchanged (mysql://, http://, mongodb://, empty, null, etc. should still fail).

Also update worker/lib/prisma.test.ts — it has a duplicate 'PrismaClientConfigSchema rejects non-postgresql URL' test using mysql:// which is fine. But add a new test there too:

  • 'PrismaClientConfigSchema validates postgres:// shorthand (Hyperdrive)'

Enhancement: Update worker/handlers/health.ts — Neon-aware database smoke test

Replace the simple SELECT 1 database probe with an extended probe that:

  1. Runs SELECT current_database() AS db_name via $queryRaw
  2. Asserts the result is 'adblock-compiler' (the production database name) — if the wrong database is connected, returns 'degraded' instead of 'healthy'
  3. Surfaces db_name and hyperdrive_host in the response JSON for the database service entry
  4. The ServiceResult type for database should be extended to include optional db_name?: string and hyperdrive_host?: string fields

Updated handleHealth database probe section (replace lines 40–50 in the current file):

export async function handleHealth(env: Env): Promise<Response> {
    type ServiceStatus = 'healthy' | 'degraded' | 'down';
    type ServiceResult = { status: ServiceStatus; latency_ms?: number };
    type DatabaseResult = ServiceResult & { db_name?: string; hyperdrive_host?: string };

    const probe = async (fn: () => Promise<void>): Promise<ServiceResult> => {
        const t0 = Date.now();
        try {
            await fn();
            return { status: 'healthy', late...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

 Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with [Raycast](https://gh.io/cca-raycast-docs).

@jaypatrick jaypatrick added bug Something isn't working database Neon Postgre labels Mar 25, 2026
@jaypatrick jaypatrick added this to the beta milestone Mar 25, 2026
… health database probe

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Agent-Logs-Url: https://github.com/jaypatrick/adblock-compiler/sessions/8cd0547b-71ff-4569-92ce-90e2a3b0fed3
Copilot AI changed the title [WIP] Fix PrismaClientConfigSchema to accept postgres:// scheme fix: accept postgres:// in PrismaClientConfigSchema + Neon-aware health probe Mar 25, 2026
Copilot AI requested a review from jaypatrick March 25, 2026 22:13
@jaypatrick jaypatrick requested a review from Copilot March 25, 2026 22:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Hyperdrive/Prisma configuration validation by allowing postgres:// connection strings (which Hyperdrive emits), and enhances the health check to validate the connected PostgreSQL database name for Neon/Hyperdrive misconfiguration detection.

Changes:

  • Update PrismaClientConfigSchema to accept both postgresql:// and postgres:// schemes.
  • Enhance /health database probing to query current_database() and mark status as degraded when connected to an unexpected DB.
  • Update and expand unit tests to cover postgres:// DSNs and the new health probe response shape.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
worker/lib/prisma-config.ts Accepts postgres:// in connection string validation with a clearer refinement message.
worker/lib/prisma-config.test.ts Fixes the previously incorrect rejection test and adds multiple postgres:// happy-path cases.
worker/lib/prisma.test.ts Adds a postgres:// happy-path validation test.
worker/handlers/health.ts Switches DB probe to current_database(), adds wrong-DB degradation logic, and includes extra DB metadata in the response.
worker/handlers/health.test.ts Updates mocks and adds tests for degraded/healthy/down database scenarios and new response fields.

Comment on lines +41 to +43
// Extended database probe: verify connectivity AND confirm we're on the correct
// production database. Returns db_name and hyperdrive_host for observability.
const databaseProbe = async (): Promise<DatabaseResult> => {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment describes this as a “production database” probe and says the endpoint exposes “no sensitive data”, but the handler now returns DB metadata and the file header still mentions a D1 SELECT 1 probe. Please update the documentation comments to match the current Hyperdrive/Prisma probe behavior and the data actually returned.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +75
const dbName = rows[0]?.db_name ?? 'unknown';
const latency_ms = Date.now() - t0;
// Fail-fast guard: warn if connected to wrong database
if (dbName !== 'adblock-compiler') {
return {
status: 'degraded',
latency_ms,
db_name: dbName,
hyperdrive_host: env.HYPERDRIVE.host,
};
}
return {
status: 'healthy',
latency_ms,
db_name: dbName,
hyperdrive_host: env.HYPERDRIVE.host,
};
} catch {
return {
status: 'down',
latency_ms: Date.now() - t0,
hyperdrive_host: env.HYPERDRIVE?.host,
};
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/health is a publicly accessible endpoint (Anonymous tier + public CORS) but the response now includes db_name and hyperdrive_host, which leaks internal infrastructure details cross-origin and contradicts the “no sensitive data” claim. Consider removing these fields from the public response or gating them behind an authenticated/admin-only endpoint (or a debug flag that’s only honored for trusted callers).

Copilot uses AI. Check for mistakes.
@jaypatrick jaypatrick marked this pull request as ready for review March 25, 2026 22:23
@jaypatrick jaypatrick merged commit 1e54396 into main Mar 25, 2026
30 checks passed
@jaypatrick jaypatrick deleted the copilot/fix-prisma-client-config branch March 25, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working database Neon Postgre

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants