Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:16 +08:00
commit cfc6c441b5
22 changed files with 5629 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
/**
* Drizzle ORM with MySQL
*
* Type-safe ORM for MySQL via Hyperdrive.
*
* Install: npm install drizzle-orm mysql2
* Install (dev): npm install -D drizzle-kit
*
* Minimum version: mysql2@3.13.0
*/
import { drizzle } from "drizzle-orm/mysql2";
import { createConnection } from "mysql2";
import { mysqlTable, int, varchar, timestamp } from "drizzle-orm/mysql-core";
import { eq } from "drizzle-orm";
// Define schema
export const users = mysqlTable("users", {
id: int("id").primaryKey().autoincrement(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull(),
createdAt: timestamp("created_at").defaultNow(),
});
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create mysql2 connection
const connection = 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
});
// Create Drizzle client
const db = drizzle(connection);
try {
// Example: Select all users
const allUsers = await db.select().from(users);
// Example: Select with where clause
const user = await db
.select()
.from(users)
.where(eq(users.id, 1));
// Example: Insert
await db.insert(users).values({
name: "John Doe",
email: `john.${Date.now()}@example.com`
});
// Example: Update
await db
.update(users)
.set({ name: "Jane Doe" })
.where(eq(users.id, 1));
return Response.json({
success: true,
data: {
allUsers,
user
}
});
} catch (error: any) {
console.error("Database error:", error);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
// CRITICAL: Clean up connection
ctx.waitUntil(
new Promise<void>((resolve) => {
connection.end(() => resolve());
})
);
}
}
};

View File

@@ -0,0 +1,96 @@
/**
* Drizzle ORM with PostgreSQL
*
* Type-safe ORM for PostgreSQL via Hyperdrive.
*
* Install: npm install drizzle-orm postgres
* Install (dev): npm install -D drizzle-kit
*
* Minimum version: postgres@3.4.5
*/
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { pgTable, serial, varchar, timestamp } from "drizzle-orm/pg-core";
import { eq } from "drizzle-orm";
// Define schema
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create postgres.js connection
const sql = postgres(env.HYPERDRIVE.connectionString, {
max: 5,
prepare: true,
fetch_types: false
});
// Create Drizzle client
const db = drizzle(sql);
try {
// Example: Select all users
const allUsers = await db.select().from(users);
// Example: Select with where clause
const recentUsers = await db
.select()
.from(users)
.where(eq(users.createdAt, new Date('2024-01-01')));
// Example: Insert
const [newUser] = await db
.insert(users)
.values({
name: "John Doe",
email: `john.${Date.now()}@example.com`
})
.returning();
// Example: Update
await db
.update(users)
.set({ name: "Jane Doe" })
.where(eq(users.id, newUser.id));
// Example: Delete
// await db.delete(users).where(eq(users.id, 123));
return Response.json({
success: true,
data: {
allUsers,
recentUsers,
newUser
}
});
} catch (error: any) {
console.error("Database error:", error);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
ctx.waitUntil(sql.end());
}
}
};

94
templates/local-dev-setup.sh Executable file
View File

@@ -0,0 +1,94 @@
#!/bin/bash
#
# Hyperdrive Local Development Setup Helper
#
# This script helps set up environment variables for local development
# with Hyperdrive. Use this to avoid committing credentials to wrangler.jsonc.
#
# Usage:
# ./local-dev-setup.sh
# source .env.local # Load variables into current shell
# npm run dev # Start wrangler dev
#
set -e
echo "🚀 Hyperdrive Local Development Setup"
echo ""
# Get binding name from wrangler.jsonc
BINDING_NAME=$(grep -A 2 '"hyperdrive"' wrangler.jsonc | grep '"binding"' | cut -d'"' -f4 | head -1)
if [ -z "$BINDING_NAME" ]; then
echo "❌ Could not find Hyperdrive binding in wrangler.jsonc"
echo " Please ensure you have a hyperdrive configuration with a binding."
exit 1
fi
echo "Found Hyperdrive binding: $BINDING_NAME"
echo ""
# Ask for database details
echo "Enter your local database connection details:"
echo ""
read -p "Database type (postgres/mysql): " DB_TYPE
read -p "Host (default: localhost): " DB_HOST
DB_HOST=${DB_HOST:-localhost}
if [ "$DB_TYPE" = "postgres" ]; then
read -p "Port (default: 5432): " DB_PORT
DB_PORT=${DB_PORT:-5432}
else
read -p "Port (default: 3306): " DB_PORT
DB_PORT=${DB_PORT:-3306}
fi
read -p "Database name: " DB_NAME
read -p "Username: " DB_USER
read -sp "Password: " DB_PASSWORD
echo ""
# Build connection string
if [ "$DB_TYPE" = "postgres" ]; then
CONNECTION_STRING="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
else
CONNECTION_STRING="mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
fi
# Create .env.local file
ENV_VAR_NAME="CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_${BINDING_NAME}"
cat > .env.local <<EOF
# Hyperdrive Local Development Environment Variables
# Generated on: $(date)
#
# Load these variables before running wrangler dev:
# source .env.local
# npm run dev
#
# DO NOT commit this file to version control!
export ${ENV_VAR_NAME}="${CONNECTION_STRING}"
EOF
# Add to .gitignore
if ! grep -q ".env.local" .gitignore 2>/dev/null; then
echo ".env.local" >> .gitignore
echo "✅ Added .env.local to .gitignore"
fi
echo ""
echo "✅ Created .env.local with environment variable:"
echo " ${ENV_VAR_NAME}"
echo ""
echo "Next steps:"
echo "1. Load the environment variable:"
echo " source .env.local"
echo ""
echo "2. Start local development server:"
echo " npx wrangler dev"
echo ""
echo "3. Your Worker will connect to:"
echo " ${DB_TYPE}://${DB_HOST}:${DB_PORT}/${DB_NAME}"
echo ""

80
templates/mysql2-basic.ts Normal file
View File

@@ -0,0 +1,80 @@
/**
* MySQL with mysql2
*
* MySQL driver for Cloudflare Workers via Hyperdrive.
*
* CRITICAL: Must set disableEval: true (eval() not supported in Workers)
* Minimum version: mysql2@3.13.0
*/
import { createConnection } from "mysql2/promise";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create MySQL connection via Hyperdrive
const connection = await createConnection({
host: env.HYPERDRIVE.host,
user: env.HYPERDRIVE.user,
password: env.HYPERDRIVE.password,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port,
// CRITICAL: Required for Workers (eval() not supported)
disableEval: true
});
try {
// Example: Simple query
const [rows] = await connection.query('SELECT NOW() as current_time');
console.log("Query executed successfully");
// Example: Parameterized query (prevents SQL injection)
const [users] = await connection.query(
'SELECT id, name, email FROM users WHERE created_at > ? LIMIT ?',
['2024-01-01', 10]
);
// Example: Execute multiple statements
const [results] = await connection.query(
'SELECT COUNT(*) as total FROM users'
);
return Response.json({
success: true,
data: {
currentTime: (rows as any[])[0].current_time,
users: users,
totalUsers: (results as any[])[0].total
},
// Hyperdrive metadata
hyperdriveInfo: {
host: env.HYPERDRIVE.host,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port
}
});
} catch (error: any) {
console.error("Database error:", error.message);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
// CRITICAL: Clean up connection AFTER response is sent
ctx.waitUntil(connection.end());
}
}
};

View File

@@ -0,0 +1,68 @@
/**
* PostgreSQL with node-postgres (pg) - Basic Usage
*
* Simple pattern using pg.Client for straightforward queries.
* Good for: Single query per request, simple operations
*/
import { Client } from "pg";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create a new client for this request
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
try {
// Connect to the database
await client.connect();
console.log("Connected to PostgreSQL via Hyperdrive");
// Example: Simple query
const result = await client.query('SELECT NOW() as current_time');
console.log("Query executed successfully");
// Example: Parameterized query (prevents SQL injection)
const users = await client.query(
'SELECT id, name, email FROM users WHERE created_at > $1 LIMIT $2',
['2024-01-01', 10]
);
return Response.json({
success: true,
currentTime: result.rows[0].current_time,
users: users.rows,
// Hyperdrive metadata
hyperdriveInfo: {
host: env.HYPERDRIVE.host,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port
}
});
} catch (error: any) {
console.error("Database error:", error.message);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
// CRITICAL: Clean up connection AFTER response is sent
// ctx.waitUntil() runs in background (non-blocking)
ctx.waitUntil(client.end());
}
}
};

111
templates/postgres-js.ts Normal file
View File

@@ -0,0 +1,111 @@
/**
* PostgreSQL with postgres.js
*
* Modern PostgreSQL driver with better performance and tagged template literals.
* Good for: Fast queries, streaming, modern API
*
* Minimum version: postgres@3.4.5
*/
import postgres from "postgres";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create postgres.js connection
const sql = postgres(env.HYPERDRIVE.connectionString, {
// CRITICAL: max 5 connections (Workers limit: 6)
max: 5,
// CRITICAL for caching: Enable prepared statements
prepare: true,
// Disable fetch_types if not using array types (reduces latency)
fetch_types: false,
// Connection timeout
connect_timeout: 10,
// Idle connection timeout
idle_timeout: 30
});
try {
// Example: Simple query with tagged template literal
const currentTime = await sql`SELECT NOW() as current_time`;
// Example: Parameterized query (auto-escaped)
const users = await sql`
SELECT id, name, email
FROM users
WHERE created_at > ${new Date('2024-01-01')}
LIMIT 10
`;
// Example: Dynamic columns (use sql() for identifiers)
const orderBy = 'created_at';
const sortedUsers = await sql`
SELECT * FROM users
ORDER BY ${sql(orderBy)} DESC
LIMIT 5
`;
// Example: Bulk insert
const newUsers = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' }
];
await sql`
INSERT INTO users ${sql(newUsers, 'name', 'email')}
`;
// Example: Transaction
const result = await sql.begin(async sql => {
const [user] = await sql`
INSERT INTO users (name, email)
VALUES ('Charlie', 'charlie@example.com')
RETURNING *
`;
await sql`
INSERT INTO audit_log (action, user_id)
VALUES ('User created', ${user.id})
`;
return user;
});
return Response.json({
success: true,
data: {
currentTime: currentTime[0].current_time,
users: users,
sortedUsers: sortedUsers,
newUser: result
}
});
} catch (error: any) {
console.error("Database error:", error);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
// CRITICAL: Close all connections
ctx.waitUntil(sql.end({ timeout: 5 }));
}
}
};

View File

@@ -0,0 +1,80 @@
/**
* PostgreSQL with node-postgres (pg) - Connection Pool
*
* Advanced pattern using pg.Pool for parallel queries.
* Good for: Multiple queries per request, better performance
*/
import { Pool } from "pg";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create a connection pool
// CRITICAL: max: 5 (Workers limit is 6 concurrent external connections)
const pool = new Pool({
connectionString: env.HYPERDRIVE.connectionString,
max: 5, // Max connections in pool (stay within Workers' limit)
idleTimeoutMillis: 30000, // Close idle connections after 30 seconds
connectionTimeoutMillis: 10000 // Timeout after 10 seconds if can't acquire connection
});
try {
// Example: Run multiple queries in parallel
const [usersResult, postsResult, statsResult] = await Promise.all([
pool.query('SELECT id, name, email FROM users ORDER BY created_at DESC LIMIT 10'),
pool.query('SELECT id, title, author_id FROM posts ORDER BY published_at DESC LIMIT 10'),
pool.query(`
SELECT
(SELECT COUNT(*) FROM users) as total_users,
(SELECT COUNT(*) FROM posts) as total_posts,
(SELECT COUNT(*) FROM comments) as total_comments
`)
]);
// Example: Transaction
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('INSERT INTO users (name, email) VALUES ($1, $2)', ['John Doe', 'john@example.com']);
await client.query('INSERT INTO audit_log (action) VALUES ($1)', ['User created']);
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release(); // Return connection to pool
}
return Response.json({
success: true,
data: {
users: usersResult.rows,
posts: postsResult.rows,
stats: statsResult.rows[0]
}
});
} catch (error: any) {
console.error("Database error:", error.message);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
// CRITICAL: Clean up all pool connections
ctx.waitUntil(pool.end());
}
}
};

View File

@@ -0,0 +1,151 @@
/**
* Prisma ORM with PostgreSQL
*
* Type-safe ORM for PostgreSQL via Hyperdrive using driver adapters.
*
* Install: npm install prisma @prisma/client pg @prisma/adapter-pg
*
* Setup:
* 1. npx prisma init
* 2. Define schema in prisma/schema.prisma
* 3. npx prisma generate --no-engine
* 4. npx prisma migrate dev (for migrations)
*
* CRITICAL: Must use driver adapters (@prisma/adapter-pg) for Hyperdrive
*/
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
import { Pool } from "pg";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
/**
* Example Prisma schema (prisma/schema.prisma):
*
* generator client {
* provider = "prisma-client-js"
* previewFeatures = ["driverAdapters"]
* }
*
* datasource db {
* provider = "postgresql"
* url = env("DATABASE_URL")
* }
*
* model User {
* id Int @id @default(autoincrement())
* name String
* email String @unique
* createdAt DateTime @default(now())
* posts Post[]
* }
*
* model Post {
* id Int @id @default(autoincrement())
* title String
* content String?
* published Boolean @default(false)
* authorId Int
* author User @relation(fields: [authorId], references: [id])
* }
*/
export default {
async fetch(
request: Request,
env: Bindings,
ctx: ExecutionContext
): Promise<Response> {
// Create pg.Pool for driver adapter
const pool = new Pool({
connectionString: env.HYPERDRIVE.connectionString,
max: 5
});
// Create Prisma driver adapter
const adapter = new PrismaPg(pool);
// Create Prisma client with adapter
const prisma = new PrismaClient({ adapter });
try {
// Example: Create user
const newUser = await prisma.user.create({
data: {
name: "John Doe",
email: `john.${Date.now()}@example.com`
}
});
// Example: Find all users
const allUsers = await prisma.user.findMany({
include: {
posts: true // Include related posts
}
});
// Example: Find user by email
const user = await prisma.user.findUnique({
where: {
email: "john@example.com"
}
});
// Example: Update user
await prisma.user.update({
where: { id: newUser.id },
data: { name: "Jane Doe" }
});
// Example: Create post with relation
await prisma.post.create({
data: {
title: "My First Post",
content: "Hello World!",
published: true,
authorId: newUser.id
}
});
// Example: Complex query with filters
const recentUsers = await prisma.user.findMany({
where: {
createdAt: {
gte: new Date('2024-01-01')
}
},
orderBy: {
createdAt: 'desc'
},
take: 10
});
return Response.json({
success: true,
data: {
newUser,
allUsers,
user,
recentUsers
}
});
} catch (error: any) {
console.error("Database error:", error);
return Response.json({
success: false,
error: error.message
}, {
status: 500
});
} finally {
// Clean up connections
ctx.waitUntil(pool.end());
}
}
};

View File

@@ -0,0 +1,38 @@
{
"name": "my-worker-with-hyperdrive",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"compatibility_flags": [
"nodejs_compat" // REQUIRED for database drivers (pg, postgres, mysql2)
],
"hyperdrive": [
{
// Binding name - access as env.HYPERDRIVE in your Worker
"binding": "HYPERDRIVE",
// Hyperdrive configuration ID from: wrangler hyperdrive create
"id": "<your-hyperdrive-id-here>",
// (Optional) Local development connection string
// Alternative: Use CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE env var
"localConnectionString": "postgres://user:password@localhost:5432/local_db"
}
],
// Example: Multiple Hyperdrive configurations
// "hyperdrive": [
// {
// "binding": "POSTGRES_DB",
// "id": "postgres-hyperdrive-id"
// },
// {
// "binding": "MYSQL_DB",
// "id": "mysql-hyperdrive-id"
// }
// ],
// Optional: Enable observability
"observability": {
"enabled": true
}
}