update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -0,0 +1,82 @@
# Hyperdrive
Accelerates database queries from Workers via connection pooling, edge setup, query caching.
## Key Features
- **Connection Pooling**: Persistent connections eliminate TCP/TLS/auth handshakes (~7 round-trips)
- **Edge Setup**: Connection negotiation at edge, pooling near origin
- **Query Caching**: Auto-cache non-mutating queries (default 60s TTL)
- **Support**: PostgreSQL, MySQL + compatibles (CockroachDB, Timescale, PlanetScale, Neon, Supabase)
## Architecture
```
Worker → Edge (setup) → Pool (near DB) → Origin
↓ cached reads
Cache
```
## Quick Start
```bash
# Create config
npx wrangler hyperdrive create my-db \
--connection-string="postgres://user:pass@host:5432/db"
# wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat"],
"hyperdrive": [{"binding": "HYPERDRIVE", "id": "<ID>"}]
}
```
```typescript
import { Client } from "pg";
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString,
});
await client.connect();
const result = await client.query("SELECT * FROM users WHERE id = $1", [123]);
await client.end();
return Response.json(result.rows);
},
};
```
## When to Use
✅ Global access to single-region DBs, high read ratios, popular queries, connection-heavy loads
❌ Write-heavy, real-time data (<1s), single-region apps close to DB
**💡 Pair with Smart Placement** for Workers making multiple queries - executes near DB to minimize latency.
## Driver Choice
| Driver | Use When | Notes |
|--------|----------|-------|
| **pg** (recommended) | General use, TypeScript, ecosystem compatibility | Stable, widely used, works with most ORMs |
| **postgres.js** | Advanced features, template literals, streaming | Lighter than pg, `prepare: true` is default |
| **mysql2** | MySQL/MariaDB/PlanetScale | MySQL only, less mature support |
## Reading Order
| New to Hyperdrive | Implementing | Troubleshooting |
|-------------------|--------------|-----------------|
| 1. README (this) | 1. [configuration.md](./configuration.md) | 1. [gotchas.md](./gotchas.md) |
| 2. [configuration.md](./configuration.md) | 2. [api.md](./api.md) | 2. [patterns.md](./patterns.md) |
| 3. [api.md](./api.md) | 3. [patterns.md](./patterns.md) | 3. [api.md](./api.md) |
## In This Reference
- [configuration.md](./configuration.md) - Setup, wrangler config, Smart Placement
- [api.md](./api.md) - Binding APIs, query patterns, driver usage
- [patterns.md](./patterns.md) - Use cases, ORMs, multi-query optimization
- [gotchas.md](./gotchas.md) - Limits, troubleshooting, connection management
## See Also
- [smart-placement](../smart-placement/) - Optimize multi-query Workers near databases
- [d1](../d1/) - Serverless SQLite alternative for edge-native apps
- [workers](../workers/) - Worker runtime with database bindings

View File

@@ -0,0 +1,143 @@
# API Reference
See [README.md](./README.md) for overview, [configuration.md](./configuration.md) for setup.
## Binding Interface
```typescript
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)
## PostgreSQL (node-postgres) - RECOMMENDED
```typescript
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)
```typescript
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)
```typescript
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:**
```sql
SELECT * FROM posts WHERE published = true;
SELECT COUNT(*) FROM users;
```
**NOT cacheable:**
```sql
-- 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:**
```typescript
// 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:**
```typescript
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:**
```typescript
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](./patterns.md) for use cases, [gotchas.md](./gotchas.md) for limits.

View File

@@ -0,0 +1,159 @@
# Configuration
See [README.md](./README.md) for overview.
## Create Config
**PostgreSQL:**
```bash
# Basic
npx wrangler hyperdrive create my-db \
--connection-string="postgres://user:pass@host:5432/db"
# Custom cache
npx wrangler hyperdrive create my-db \
--connection-string="postgres://..." \
--max-age=120 --swr=30
# No cache
npx wrangler hyperdrive create my-db \
--connection-string="postgres://..." \
--caching-disabled=true
```
**MySQL:**
```bash
npx wrangler hyperdrive create my-db \
--connection-string="mysql://user:pass@host:3306/db"
```
## wrangler.jsonc
```jsonc
{
"compatibility_date": "2025-01-01", // Use latest for new projects
"compatibility_flags": ["nodejs_compat"],
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<HYPERDRIVE_ID>",
"localConnectionString": "postgres://user:pass@localhost:5432/dev"
}
]
}
```
**Generate TypeScript types:** Run `npx wrangler types` to auto-generate `worker-configuration.d.ts` from your wrangler.jsonc.
**Multiple configs:**
```jsonc
{
"hyperdrive": [
{"binding": "HYPERDRIVE_CACHED", "id": "<ID1>"},
{"binding": "HYPERDRIVE_NO_CACHE", "id": "<ID2>"}
]
}
```
## Management
```bash
npx wrangler hyperdrive list
npx wrangler hyperdrive get <ID>
npx wrangler hyperdrive update <ID> --max-age=180
npx wrangler hyperdrive delete <ID>
```
## Config Options
Hyperdrive create/update CLI flags:
| Option | Default | Notes |
|--------|---------|-------|
| `--caching-disabled` | `false` | Disable caching |
| `--max-age` | `60` | Cache TTL (max 3600s) |
| `--swr` | `15` | Stale-while-revalidate |
| `--origin-connection-limit` | 20/100 | Free/paid |
| `--access-client-id` | - | Tunnel auth |
| `--access-client-secret` | - | Tunnel auth |
| `--sslmode` | `require` | PostgreSQL only |
## Smart Placement Integration
For Workers making **multiple queries** per request, enable Smart Placement to execute near your database:
```jsonc
{
"compatibility_date": "2025-01-01",
"compatibility_flags": ["nodejs_compat"],
"placement": {
"mode": "smart"
},
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<HYPERDRIVE_ID>"
}
]
}
```
**Benefits:** Multi-query Workers run closer to DB, reducing round-trip latency. See [patterns.md](./patterns.md) for examples.
## Private DB via Tunnel
```
Worker → Hyperdrive → Access → Tunnel → Private Network → DB
```
**Setup:**
```bash
# 1. Create tunnel
cloudflared tunnel create my-db-tunnel
# 2. Configure hostname in Zero Trust dashboard
# Domain: db-tunnel.example.com
# Service: TCP -> localhost:5432
# 3. Create service token (Zero Trust > Service Auth)
# Save Client ID/Secret
# 4. Create Access app (db-tunnel.example.com)
# Policy: Service Auth token from step 3
# 5. Create Hyperdrive
npx wrangler hyperdrive create my-private-db \
--host=db-tunnel.example.com \
--user=dbuser --password=dbpass --database=prod \
--access-client-id=<ID> --access-client-secret=<SECRET>
```
**⚠️ Don't specify `--port` with Tunnel** - port configured in tunnel service settings.
## Local Dev
**Option 1: Local (RECOMMENDED):**
```bash
# Env var (takes precedence)
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgres://user:pass@localhost:5432/dev"
npx wrangler dev
# wrangler.jsonc
{"hyperdrive": [{"binding": "HYPERDRIVE", "localConnectionString": "postgres://..."}]}
```
**Remote DB locally:**
```bash
# PostgreSQL
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgres://user:pass@remote:5432/db?sslmode=require"
# MySQL
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="mysql://user:pass@remote:3306/db?sslMode=REQUIRED"
```
**Option 2: Remote execution:**
```bash
npx wrangler dev --remote # Uses deployed config, affects production
```
See [api.md](./api.md), [patterns.md](./patterns.md), [gotchas.md](./gotchas.md).

View File

@@ -0,0 +1,77 @@
# Gotchas
See [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [patterns.md](./patterns.md).
## Common Errors
### "Too many open connections" / "Connection limit exceeded"
**Cause:** Workers have a hard limit of **6 concurrent connections per invocation**
**Solution:** Set `max: 5` in driver config, reuse connections, ensure proper cleanup with `client.end()` or `ctx.waitUntil(conn.end())`
### "Failed to acquire a connection (Pool exhausted)"
**Cause:** All connections in pool are in use, often due to long-running transactions
**Solution:** Reduce transaction duration, avoid queries >60s, don't hold connections during external calls, or upgrade to paid plan for more connections
### "connection_refused"
**Cause:** Database refusing connections due to firewall, connection limits, or service down
**Solution:** Check firewall allows Cloudflare IPs, verify DB listening on port, confirm service running, and validate credentials
### "Query timeout (deadline exceeded)"
**Cause:** Query execution exceeding 60s timeout limit
**Solution:** Optimize with indexes, reduce dataset with LIMIT, break into smaller queries, or use async processing
### "password authentication failed"
**Cause:** Invalid credentials in Hyperdrive configuration
**Solution:** Check username and password in Hyperdrive config match database credentials
### "SSL/TLS connection error"
**Cause:** SSL/TLS configuration mismatch between Hyperdrive and database
**Solution:** Add `sslmode=require` (Postgres) or `sslMode=REQUIRED` (MySQL), upload CA cert if self-signed, verify DB has SSL enabled, and check cert expiry
### "Queries not being cached"
**Cause:** Query is mutating (INSERT/UPDATE/DELETE), contains volatile functions (NOW(), RANDOM()), or caching disabled
**Solution:** Verify query is non-mutating SELECT, avoid volatile functions, confirm caching enabled, use `wrangler dev --remote` to test, and set `prepare=true` for postgres.js
### "Slow multi-query Workers despite Hyperdrive"
**Cause:** Worker executing at edge, each query round-trips to DB region
**Solution:** Enable Smart Placement (`"placement": {"mode": "smart"}` in wrangler.jsonc) to execute Worker near DB. See [patterns.md](./patterns.md) Multi-Query pattern.
### "Local database connection failed"
**Cause:** `localConnectionString` incorrect or database not running
**Solution:** Verify `localConnectionString` correct, check DB running, confirm env var name matches binding, and test with psql/mysql client
### "Environment variable not working"
**Cause:** Environment variable format incorrect or not exported
**Solution:** Use format `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>`, ensure binding matches wrangler.jsonc, export variable in shell, and restart wrangler dev
## Limits
| Limit | Free | Paid | Notes |
|-------|------|------|-------|
| Max configs | 10 | 25 | Hyperdrive configurations per account |
| Worker connections | 6 | 6 | Max concurrent connections per Worker invocation |
| Username/DB name | 63 bytes | 63 bytes | Maximum length |
| Connection timeout | 15s | 15s | Time to establish connection |
| Idle timeout | 10 min | 10 min | Connection idle timeout |
| Max origin connections | ~20 | ~100 | Connections to origin database |
| Query duration max | 60s | 60s | Queries >60s terminated |
| Cached response max | 50 MB | 50 MB | Responses >50MB returned but not cached |
## Resources
- [Docs](https://developers.cloudflare.com/hyperdrive/)
- [Getting Started](https://developers.cloudflare.com/hyperdrive/get-started/)
- [Wrangler Reference](https://developers.cloudflare.com/hyperdrive/reference/wrangler-commands/)
- [Supported DBs](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/)
- [Discord #hyperdrive](https://discord.cloudflare.com)
- [Limit Increase Form](https://forms.gle/ukpeZVLWLnKeixDu7)

View File

@@ -0,0 +1,190 @@
# Patterns
See [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md).
## High-Traffic Read-Heavy
```typescript
const sql = postgres(env.HYPERDRIVE.connectionString, {max: 5, prepare: true});
// Cacheable: popular content
const posts = await sql`SELECT * FROM posts WHERE published = true ORDER BY views DESC LIMIT 20`;
// Cacheable: user profiles
const [user] = await sql`SELECT id, username, bio FROM users WHERE id = ${userId}`;
```
**Benefits:** Trending/profiles cached (60s), connection pooling handles spikes.
## Mixed Read/Write
```typescript
interface Env {
HYPERDRIVE_CACHED: Hyperdrive; // max_age=120
HYPERDRIVE_REALTIME: Hyperdrive; // caching disabled
}
// Reads: cached
if (req.method === "GET") {
const sql = postgres(env.HYPERDRIVE_CACHED.connectionString, {prepare: true});
const products = await sql`SELECT * FROM products WHERE category = ${cat}`;
}
// Writes: no cache (immediate consistency)
if (req.method === "POST") {
const sql = postgres(env.HYPERDRIVE_REALTIME.connectionString, {prepare: true});
await sql`INSERT INTO orders ${sql(data)}`;
}
```
## Analytics Dashboard
```typescript
const client = new Client({connectionString: env.HYPERDRIVE.connectionString});
await client.connect();
// Aggregate queries cached (use fixed timestamps for caching)
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
const dailyStats = await client.query(`
SELECT DATE(created_at) as date, COUNT(*) as orders, SUM(amount) as revenue
FROM orders WHERE created_at >= $1
GROUP BY DATE(created_at) ORDER BY date DESC
`, [thirtyDaysAgo]);
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const topProducts = await client.query(`
SELECT p.name, COUNT(oi.id) as count, SUM(oi.quantity * oi.price) as revenue
FROM order_items oi JOIN products p ON oi.product_id = p.id
WHERE oi.created_at >= $1
GROUP BY p.id, p.name ORDER BY revenue DESC LIMIT 10
`, [sevenDaysAgo]);
```
**Benefits:** Expensive aggregations cached (avoid NOW() for cacheability), dashboard instant, reduced DB load.
## Multi-Tenant
```typescript
const tenantId = req.headers.get("X-Tenant-ID");
const sql = postgres(env.HYPERDRIVE.connectionString, {prepare: true});
// Tenant-scoped queries cached separately
const docs = await sql`
SELECT * FROM documents
WHERE tenant_id = ${tenantId} AND deleted_at IS NULL
ORDER BY updated_at DESC LIMIT 50
`;
```
**Benefits:** Per-tenant caching, shared connection pool, protects DB from multi-tenant load.
## Geographically Distributed
```typescript
// Worker runs at edge nearest user
// Connection setup at edge (fast), pooling near DB (efficient)
const sql = postgres(env.HYPERDRIVE.connectionString, {prepare: true});
const [user] = await sql`SELECT * FROM users WHERE id = ${userId}`;
return Response.json({
user,
serverRegion: req.cf?.colo, // Edge location
});
```
**Benefits:** Edge setup + DB pooling = global → single-region DB without replication.
## Multi-Query + Smart Placement
For Workers making **multiple queries** per request, enable Smart Placement to execute near DB:
```jsonc
// wrangler.jsonc
{
"placement": {"mode": "smart"},
"hyperdrive": [{"binding": "HYPERDRIVE", "id": "<ID>"}]
}
```
```typescript
const sql = postgres(env.HYPERDRIVE.connectionString, {prepare: true});
// Multiple queries benefit from Smart Placement
const [user] = await sql`SELECT * FROM users WHERE id = ${userId}`;
const orders = await sql`SELECT * FROM orders WHERE user_id = ${userId} ORDER BY created_at DESC LIMIT 10`;
const stats = await sql`SELECT COUNT(*) as total, SUM(amount) as spent FROM orders WHERE user_id = ${userId}`;
return Response.json({user, orders, stats});
```
**Benefits:** Worker executes near DB → reduces latency for each query. Without Smart Placement, each query round-trips from edge.
## Connection Pooling
Operates in **transaction mode**: connection acquired per transaction, `RESET` on return.
**SET statements:**
```typescript
// ✅ Within transaction
await client.query("BEGIN");
await client.query("SET work_mem = '256MB'");
await client.query("SELECT * FROM large_table"); // Uses SET
await client.query("COMMIT"); // RESET after
// ✅ Single statement
await client.query("SET work_mem = '256MB'; SELECT * FROM large_table");
// ❌ Across queries (may get different connection)
await client.query("SET work_mem = '256MB'");
await client.query("SELECT * FROM large_table"); // SET not applied
```
**Best practices:**
```typescript
// ❌ Long transactions block pooling
await client.query("BEGIN");
await processThousands(); // Connection held entire time
await client.query("COMMIT");
// ✅ Short transactions
await client.query("BEGIN");
await client.query("UPDATE users SET status = $1 WHERE id = $2", [status, id]);
await client.query("COMMIT");
// ✅ SET LOCAL within transaction
await client.query("BEGIN");
await client.query("SET LOCAL work_mem = '256MB'");
await client.query("SELECT * FROM large_table");
await client.query("COMMIT");
```
## Performance Tips
**Enable prepared statements (required for caching):**
```typescript
const sql = postgres(connectionString, {prepare: true}); // Default, enables caching
```
**Optimize connection settings:**
```typescript
const sql = postgres(connectionString, {
max: 5, // Stay under Workers' 6 connection limit
fetch_types: false, // Reduce latency if not using arrays
idle_timeout: 60, // Match Worker lifetime
});
```
**Write cache-friendly queries:**
```typescript
// ✅ Cacheable (deterministic)
await sql`SELECT * FROM products WHERE category = 'electronics' LIMIT 10`;
// ❌ Not cacheable (volatile NOW())
await sql`SELECT * FROM logs WHERE created_at > NOW()`;
// ✅ Cacheable (parameterized timestamp)
const ts = Date.now();
await sql`SELECT * FROM logs WHERE created_at > ${ts}`;
```
See [gotchas.md](./gotchas.md) for limits, troubleshooting.