📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
# SQLite Patterns with bun:sqlite
|
||||
|
||||
Advanced patterns for database operations.
|
||||
|
||||
## Migrations
|
||||
|
||||
```typescript
|
||||
const migrations = [
|
||||
`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY)`,
|
||||
`CREATE TABLE users (id TEXT PRIMARY KEY, email TEXT UNIQUE)`,
|
||||
`ALTER TABLE users ADD COLUMN name TEXT`,
|
||||
`CREATE INDEX idx_users_email ON users(email)`
|
||||
];
|
||||
|
||||
function getCurrentVersion(db: Database): number {
|
||||
try {
|
||||
const result = db.query('SELECT version FROM schema_version').get() as { version: number } | undefined;
|
||||
return result?.version || 0;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function runMigrations(db: Database) {
|
||||
const currentVersion = getCurrentVersion(db);
|
||||
|
||||
db.transaction(() => {
|
||||
for (let i = currentVersion; i < migrations.length; i++) {
|
||||
console.log(`Running migration ${i + 1}...`);
|
||||
db.run(migrations[i]);
|
||||
}
|
||||
|
||||
db.run('DELETE FROM schema_version');
|
||||
db.run('INSERT INTO schema_version (version) VALUES (?)', [migrations.length]);
|
||||
})();
|
||||
|
||||
console.log(`Migrated to version ${migrations.length}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Connection Pool Pattern
|
||||
|
||||
```typescript
|
||||
class DatabasePool {
|
||||
private pools = new Map<string, Database>();
|
||||
|
||||
get(name: string = 'default'): Database {
|
||||
if (!this.pools.has(name)) {
|
||||
this.pools.set(name, new Database(`${name}.db`));
|
||||
}
|
||||
return this.pools.get(name)!;
|
||||
}
|
||||
|
||||
close(name?: string) {
|
||||
if (name) {
|
||||
this.pools.get(name)?.close();
|
||||
this.pools.delete(name);
|
||||
} else {
|
||||
for (const db of this.pools.values()) {
|
||||
db.close();
|
||||
}
|
||||
this.pools.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const dbPool = new DatabasePool();
|
||||
```
|
||||
|
||||
## Middleware Pattern (Hono)
|
||||
|
||||
```typescript
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { createFactory } from 'hono/factory';
|
||||
|
||||
type Env = {
|
||||
Variables: {
|
||||
db: Database;
|
||||
};
|
||||
};
|
||||
|
||||
const factory = createFactory<Env>();
|
||||
|
||||
// Option 1: Per-request connection
|
||||
const dbMiddleware = factory.createMiddleware(async (c, next) => {
|
||||
const db = new Database('app.db');
|
||||
c.set('db', db);
|
||||
|
||||
try {
|
||||
await next();
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Option 2: Pooled connection (preferred for performance)
|
||||
const dbPoolMiddleware = factory.createMiddleware(async (c, next) => {
|
||||
const db = dbPool.get();
|
||||
c.set('db', db);
|
||||
await next();
|
||||
// Don't close — reuse connection
|
||||
});
|
||||
```
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
```typescript
|
||||
type User = {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
class UserRepository {
|
||||
constructor(private db: Database) {}
|
||||
|
||||
private stmt = {
|
||||
findById: this.db.prepare('SELECT * FROM users WHERE id = ?'),
|
||||
findByEmail: this.db.prepare('SELECT * FROM users WHERE email = ?'),
|
||||
findAll: this.db.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT ?'),
|
||||
create: this.db.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?) RETURNING *'),
|
||||
update: this.db.prepare('UPDATE users SET email = ?, name = ? WHERE id = ? RETURNING *'),
|
||||
delete: this.db.prepare('DELETE FROM users WHERE id = ? RETURNING *')
|
||||
};
|
||||
|
||||
findById(id: string): User | null {
|
||||
const row = this.stmt.findById.get(id);
|
||||
return row ? this.mapRow(row) : null;
|
||||
}
|
||||
|
||||
findByEmail(email: string): User | null {
|
||||
const row = this.stmt.findByEmail.get(email);
|
||||
return row ? this.mapRow(row) : null;
|
||||
}
|
||||
|
||||
findAll(limit = 100): User[] {
|
||||
const rows = this.stmt.findAll.all(limit);
|
||||
return rows.map(this.mapRow);
|
||||
}
|
||||
|
||||
create(data: { email: string; name: string }): User {
|
||||
const id = crypto.randomUUID();
|
||||
const row = this.stmt.create.get(id, data.email, data.name);
|
||||
return this.mapRow(row);
|
||||
}
|
||||
|
||||
update(id: string, data: { email?: string; name?: string }): User | null {
|
||||
const existing = this.findById(id);
|
||||
if (!existing) return null;
|
||||
|
||||
const row = this.stmt.update.get(
|
||||
data.email ?? existing.email,
|
||||
data.name ?? existing.name,
|
||||
id
|
||||
);
|
||||
return this.mapRow(row);
|
||||
}
|
||||
|
||||
delete(id: string): boolean {
|
||||
const row = this.stmt.delete.get(id);
|
||||
return !!row;
|
||||
}
|
||||
|
||||
private mapRow(row: any): User {
|
||||
return {
|
||||
id: row.id,
|
||||
email: row.email,
|
||||
name: row.name,
|
||||
createdAt: new Date(row.created_at)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Patterns
|
||||
|
||||
```typescript
|
||||
// Simple transaction
|
||||
const transferFunds = db.transaction((fromId: string, toId: string, amount: number) => {
|
||||
const from = db.prepare('SELECT balance FROM accounts WHERE id = ?').get(fromId);
|
||||
if (!from || from.balance < amount) {
|
||||
throw new Error('Insufficient funds');
|
||||
}
|
||||
|
||||
db.run('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
|
||||
db.run('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
|
||||
|
||||
return { fromId, toId, amount };
|
||||
});
|
||||
|
||||
// Nested transaction (savepoint)
|
||||
const complexOperation = db.transaction(() => {
|
||||
db.run('INSERT INTO orders (id) VALUES (?)', [orderId]);
|
||||
|
||||
const addItem = db.transaction((itemId: string) => {
|
||||
db.run('INSERT INTO order_items (order_id, item_id) VALUES (?, ?)', [orderId, itemId]);
|
||||
});
|
||||
|
||||
for (const item of items) {
|
||||
addItem(item.id); // Each runs in savepoint
|
||||
}
|
||||
});
|
||||
|
||||
// Deferred vs immediate
|
||||
const deferredTx = db.transaction(() => {
|
||||
// Locks acquired on first write
|
||||
}).deferred();
|
||||
|
||||
const immediateTx = db.transaction(() => {
|
||||
// Locks acquired immediately
|
||||
}).immediate();
|
||||
|
||||
const exclusiveTx = db.transaction(() => {
|
||||
// Exclusive write lock
|
||||
}).exclusive();
|
||||
```
|
||||
|
||||
## Query Helpers
|
||||
|
||||
```typescript
|
||||
// Reusable query object
|
||||
const getUserQuery = db.query('SELECT * FROM users WHERE id = ?');
|
||||
const user1 = getUserQuery.get('1');
|
||||
const user2 = getUserQuery.get('2');
|
||||
|
||||
// Values (array results)
|
||||
const allEmails = db.query('SELECT email FROM users').values();
|
||||
// [['alice@example.com'], ['bob@example.com'], ...]
|
||||
|
||||
// Named columns
|
||||
const users = db.query('SELECT id, email FROM users').all();
|
||||
// [{ id: '1', email: 'alice@example.com' }, ...]
|
||||
```
|
||||
|
||||
## Full-Text Search
|
||||
|
||||
```typescript
|
||||
// Create FTS table
|
||||
db.run(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
|
||||
title,
|
||||
content,
|
||||
content='posts',
|
||||
content_rowid='id'
|
||||
)
|
||||
`);
|
||||
|
||||
// Triggers to keep FTS in sync
|
||||
db.run(`
|
||||
CREATE TRIGGER IF NOT EXISTS posts_ai AFTER INSERT ON posts BEGIN
|
||||
INSERT INTO posts_fts(rowid, title, content)
|
||||
VALUES (new.id, new.title, new.content);
|
||||
END
|
||||
`);
|
||||
|
||||
// Search
|
||||
function searchPosts(query: string) {
|
||||
return db.prepare(`
|
||||
SELECT posts.*
|
||||
FROM posts
|
||||
JOIN posts_fts ON posts.id = posts_fts.rowid
|
||||
WHERE posts_fts MATCH ?
|
||||
ORDER BY rank
|
||||
LIMIT 20
|
||||
`).all(query);
|
||||
}
|
||||
```
|
||||
|
||||
## JSON Support
|
||||
|
||||
```typescript
|
||||
// Store JSON
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
preferences TEXT -- JSON stored as text
|
||||
)
|
||||
`);
|
||||
|
||||
// Insert JSON
|
||||
db.prepare('INSERT INTO settings VALUES (?, ?)').run(
|
||||
userId,
|
||||
JSON.stringify({ theme: 'dark', notifications: true })
|
||||
);
|
||||
|
||||
// Query JSON (SQLite JSON functions)
|
||||
const darkUsers = db.prepare(`
|
||||
SELECT user_id FROM settings
|
||||
WHERE json_extract(preferences, '$.theme') = 'dark'
|
||||
`).all();
|
||||
|
||||
// Extract JSON field
|
||||
const theme = db.prepare(`
|
||||
SELECT json_extract(preferences, '$.theme') as theme
|
||||
FROM settings
|
||||
WHERE user_id = ?
|
||||
`).get(userId);
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
```typescript
|
||||
// WAL mode for better concurrency
|
||||
db.run('PRAGMA journal_mode = WAL');
|
||||
|
||||
// Increase cache size
|
||||
db.run('PRAGMA cache_size = -64000'); // 64MB
|
||||
|
||||
// Batch inserts
|
||||
const insertMany = db.transaction((users: User[]) => {
|
||||
const stmt = db.prepare('INSERT INTO users VALUES (?, ?, ?)');
|
||||
for (const user of users) {
|
||||
stmt.run(user.id, user.email, user.name);
|
||||
}
|
||||
});
|
||||
|
||||
insertMany(thousandsOfUsers); // Much faster than individual inserts
|
||||
|
||||
// Index for common queries
|
||||
db.run('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)');
|
||||
db.run('CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id)');
|
||||
```
|
||||
Reference in New Issue
Block a user