Files
dotfiles/.agents/skills/cloudflare-deploy/references/hyperdrive/api.md
2026-03-17 16:53:22 -07:00

3.8 KiB

API Reference

See README.md for overview, configuration.md for setup.

Binding Interface

interface Hyperdrive {
  connectionString: string;  // PostgreSQL
  // MySQL properties:
  host: string;
  port: number;
  user: string;
  password: string;
  database: string;
}

interface Env {
  HYPERDRIVE: Hyperdrive;
}

Generate types: npx wrangler types (auto-creates worker-configuration.d.ts from wrangler.jsonc)

import { Client } from "pg";  // pg@^8.17.2

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    const client = new Client({connectionString: env.HYPERDRIVE.connectionString});
    try {
      await client.connect();
      const result = await client.query("SELECT * FROM users WHERE id = $1", [123]);
      return Response.json(result.rows);
    } finally {
      await client.end();
    }
  },
};

⚠️ Workers connection limit: 6 per Worker invocation - use connection pooling wisely.

PostgreSQL (postgres.js)

import postgres from "postgres";  // postgres@^3.4.8

const sql = postgres(env.HYPERDRIVE.connectionString, {
  max: 5,             // Limit per Worker (Workers max: 6)
  prepare: true,      // Enabled by default, required for caching
  fetch_types: false, // Reduce latency if not using arrays
});

const users = await sql`SELECT * FROM users WHERE active = ${true} LIMIT 10`;

⚠️ prepare: true is enabled by default and required for Hyperdrive caching. Setting to false disables prepared statements + cache.

MySQL (mysql2)

import { createConnection } from "mysql2/promise";  // mysql2@^3.16.2

const conn = await createConnection({
  host: env.HYPERDRIVE.host,
  user: env.HYPERDRIVE.user,
  password: env.HYPERDRIVE.password,
  database: env.HYPERDRIVE.database,
  port: env.HYPERDRIVE.port,
  disableEval: true,  // ⚠️ REQUIRED for Workers
});

const [results] = await conn.query("SELECT * FROM users WHERE active = ? LIMIT ?", [true, 10]);
ctx.waitUntil(conn.end());

⚠️ MySQL support is less mature than PostgreSQL - expect fewer optimizations and potential edge cases.

Query Caching

Cacheable:

SELECT * FROM posts WHERE published = true;
SELECT COUNT(*) FROM users;

NOT cacheable:

-- Writes
INSERT/UPDATE/DELETE

-- Volatile functions
SELECT NOW();
SELECT random();
SELECT LASTVAL();  -- PostgreSQL
SELECT UUID();     -- MySQL

Cache config:

  • Default: max_age=60s, swr=15s
  • Max max_age: 3600s
  • Disable: --caching-disabled=true

Multiple configs pattern:

// Reads: cached
const sqlCached = postgres(env.HYPERDRIVE_CACHED.connectionString);
const posts = await sqlCached`SELECT * FROM posts ORDER BY views DESC LIMIT 10`;

// Writes/time-sensitive: no cache
const sqlNoCache = postgres(env.HYPERDRIVE_NO_CACHE.connectionString);
const orders = await sqlNoCache`SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 5 MINUTE`;

ORMs

Drizzle:

import { drizzle } from "drizzle-orm/postgres-js";  // drizzle-orm@^0.45.1
import postgres from "postgres";

const client = postgres(env.HYPERDRIVE.connectionString, {max: 5, prepare: true});
const db = drizzle(client);
const users = await db.select().from(users).where(eq(users.active, true)).limit(10);

Kysely:

import { Kysely, PostgresDialect } from "kysely";  // kysely@^0.27+
import postgres from "postgres";

const db = new Kysely({
  dialect: new PostgresDialect({
    postgres: postgres(env.HYPERDRIVE.connectionString, {max: 5, prepare: true}),
  }),
});
const users = await db.selectFrom("users").selectAll().where("active", "=", true).execute();

See patterns.md for use cases, gotchas.md for limits.