Files
gh-jezweb-claude-skills-ski…/templates/prepared-statements.ts
2025-11-30 08:24:43 +08:00

306 lines
7.3 KiB
TypeScript

/**
* Prepared Statements with Drizzle ORM and D1
*
* Prepared statements allow you to define queries once and execute them
* multiple times with different parameters for better performance.
*
* IMPORTANT: D1 doesn't cache prepared statements between requests like
* traditional SQLite. They're still useful for code reusability and type safety.
*/
import { drizzle } from 'drizzle-orm/d1';
import { users, posts, comments } from './schema';
import { eq, and, gte, sql } from 'drizzle-orm';
/**
* Basic Prepared Statements
*/
// Get user by ID (prepared)
export function prepareGetUserById(db: ReturnType<typeof drizzle>) {
return db
.select()
.from(users)
.where(eq(users.id, sql.placeholder('id')))
.prepare();
}
// Usage:
// const getUserById = prepareGetUserById(db);
// const user1 = await getUserById.get({ id: 1 });
// const user2 = await getUserById.get({ id: 2 });
// Get user by email (prepared)
export function prepareGetUserByEmail(db: ReturnType<typeof drizzle>) {
return db
.select()
.from(users)
.where(eq(users.email, sql.placeholder('email')))
.prepare();
}
// Get posts by author (prepared)
export function prepareGetPostsByAuthor(db: ReturnType<typeof drizzle>) {
return db
.select()
.from(posts)
.where(eq(posts.authorId, sql.placeholder('authorId')))
.prepare();
}
/**
* Prepared Statements with Multiple Parameters
*/
// Get published posts after a date
export function prepareGetPublishedPostsAfterDate(db: ReturnType<typeof drizzle>) {
return db
.select()
.from(posts)
.where(
and(
eq(posts.published, sql.placeholder('published')),
gte(posts.createdAt, sql.placeholder('afterDate'))
)
)
.prepare();
}
// Usage:
// const getPublishedPosts = prepareGetPublishedPostsAfterDate(db);
// const recentPosts = await getPublishedPosts.all({
// published: true,
// afterDate: new Date('2024-01-01'),
// });
// Search users by partial email/name match
export function prepareSearchUsers(db: ReturnType<typeof drizzle>) {
return db
.select()
.from(users)
.where(
sql`${users.email} LIKE ${sql.placeholder('searchTerm')} OR ${users.name} LIKE ${sql.placeholder('searchTerm')}`
)
.prepare();
}
// Usage:
// const searchUsers = prepareSearchUsers(db);
// const results = await searchUsers.all({ searchTerm: '%john%' });
/**
* Prepared Statements for INSERT
*/
// Insert user (prepared)
export function prepareInsertUser(db: ReturnType<typeof drizzle>) {
return db
.insert(users)
.values({
email: sql.placeholder('email'),
name: sql.placeholder('name'),
bio: sql.placeholder('bio'),
})
.returning()
.prepare();
}
// Usage:
// const insertUser = prepareInsertUser(db);
// const [newUser] = await insertUser.get({
// email: 'test@example.com',
// name: 'Test User',
// bio: null,
// });
// Insert post (prepared)
export function prepareInsertPost(db: ReturnType<typeof drizzle>) {
return db
.insert(posts)
.values({
title: sql.placeholder('title'),
slug: sql.placeholder('slug'),
content: sql.placeholder('content'),
authorId: sql.placeholder('authorId'),
published: sql.placeholder('published'),
})
.returning()
.prepare();
}
/**
* Prepared Statements for UPDATE
*/
// Update user name (prepared)
export function prepareUpdateUserName(db: ReturnType<typeof drizzle>) {
return db
.update(users)
.set({
name: sql.placeholder('name'),
updatedAt: sql.placeholder('updatedAt'),
})
.where(eq(users.id, sql.placeholder('id')))
.returning()
.prepare();
}
// Usage:
// const updateUserName = prepareUpdateUserName(db);
// const [updated] = await updateUserName.get({
// id: 1,
// name: 'New Name',
// updatedAt: new Date(),
// });
// Publish post (prepared)
export function preparePublishPost(db: ReturnType<typeof drizzle>) {
return db
.update(posts)
.set({
published: true,
updatedAt: sql.placeholder('updatedAt'),
})
.where(eq(posts.id, sql.placeholder('id')))
.returning()
.prepare();
}
/**
* Prepared Statements for DELETE
*/
// Delete user (prepared)
export function prepareDeleteUser(db: ReturnType<typeof drizzle>) {
return db
.delete(users)
.where(eq(users.id, sql.placeholder('id')))
.returning()
.prepare();
}
// Delete posts by author (prepared)
export function prepareDeletePostsByAuthor(db: ReturnType<typeof drizzle>) {
return db
.delete(posts)
.where(eq(posts.authorId, sql.placeholder('authorId')))
.returning()
.prepare();
}
/**
* Best Practices
*/
// Create a class to encapsulate all prepared statements
export class PreparedQueries {
private db: ReturnType<typeof drizzle>;
// Prepared statements
private getUserByIdStmt;
private getUserByEmailStmt;
private insertUserStmt;
private updateUserNameStmt;
private deleteUserStmt;
constructor(db: ReturnType<typeof drizzle>) {
this.db = db;
// Initialize all prepared statements once
this.getUserByIdStmt = prepareGetUserById(db);
this.getUserByEmailStmt = prepareGetUserByEmail(db);
this.insertUserStmt = prepareInsertUser(db);
this.updateUserNameStmt = prepareUpdateUserName(db);
this.deleteUserStmt = prepareDeleteUser(db);
}
// Convenient methods that use prepared statements
async getUserById(id: number) {
return await this.getUserByIdStmt.get({ id });
}
async getUserByEmail(email: string) {
return await this.getUserByEmailStmt.get({ email });
}
async insertUser(data: { email: string; name: string; bio?: string | null }) {
const [user] = await this.insertUserStmt.get({
email: data.email,
name: data.name,
bio: data.bio ?? null,
});
return user;
}
async updateUserName(id: number, name: string) {
const [user] = await this.updateUserNameStmt.get({
id,
name,
updatedAt: new Date(),
});
return user;
}
async deleteUser(id: number) {
const [user] = await this.deleteUserStmt.get({ id });
return user;
}
}
// Usage:
// const queries = new PreparedQueries(db);
// const user = await queries.getUserById(1);
/**
* Performance Considerations for D1
*
* Unlike traditional SQLite:
* - D1 doesn't cache prepared statements between requests
* - Each request starts fresh
* - Prepared statements are still useful for:
* 1. Code reusability
* 2. Type safety
* 3. Preventing SQL injection
* 4. Cleaner code organization
*
* But don't expect:
* - Performance improvements from statement caching
* - Faster execution on repeated calls
* - Shared state between requests
*/
/**
* When to Use Prepared Statements:
*
* ✅ Good for:
* - Queries you'll execute multiple times in the same request
* - Complex queries with dynamic parameters
* - Code organization and reusability
* - Type-safe parameter passing
*
* ❌ Not necessary for:
* - One-off queries
* - Simple CRUD operations
* - Static queries without parameters
*/
/**
* Execution Methods:
*
* - .all() - Returns all results as array
* - .get() - Returns first result or undefined
* - .run() - Executes query, returns metadata only
*/
/**
* TypeScript Types
*/
import type { InferSelectModel } from 'drizzle-orm';
export type PreparedQuery<T> = {
all: (params: T) => Promise<any[]>;
get: (params: T) => Promise<any | undefined>;
run: (params: T) => Promise<any>;
};