Table Utilities & Database Helpers
Overview
PadawanForge implements a comprehensive set of table utilities and database helper functions that provide efficient data manipulation, pagination, filtering, and query building capabilities. These utilities streamline database operations and provide consistent data handling patterns.
Architecture
Core Components
- Table Utilities: Pagination, filtering, and sorting helpers
- Query Builders: Dynamic SQL query construction
- Data Transformers: Result formatting and transformation
- Connection Management: Database connection utilities
- Performance Optimizers: Query optimization helpers
Utility Features
- Pagination Support: Offset and cursor-based pagination
- Dynamic Filtering: Flexible filter application
- Sorting: Multi-column sorting with direction
- Query Optimization: Efficient query construction
- Data Validation: Input validation and sanitization
Implementation
Table Utilities
import {
createPaginationQuery,
applyFilters,
applySorting,
buildWhereClause,
sanitizeInput,
transformResults
} from '@/lib/table-utils';
// Create paginated query
const paginationQuery = createPaginationQuery({
table: 'players',
select: ['uuid', 'username', 'display_name', 'created_at'],
page: 1,
limit: 20,
filters: { status: 'active', level: { min: 5 } },
sort: { field: 'created_at', direction: 'desc' }
});
// Apply filters to query
const filteredQuery = applyFilters(baseQuery, {
username: { like: 'john%' },
level: { gte: 10, lte: 50 },
status: 'active',
created_at: { gte: '2024-01-01' }
});
// Apply sorting
const sortedQuery = applySorting(filteredQuery, [
{ field: 'level', direction: 'desc' },
{ field: 'username', direction: 'asc' }
]);
Pagination Interface
interface PaginationOptions {
page?: number; // Page number (1-based)
limit?: number; // Items per page
cursor?: string; // Cursor for cursor-based pagination
offset?: number; // Offset for offset-based pagination
}
interface PaginationResult<T> {
data: T[]; // Paginated data
pagination: {
page: number; // Current page
limit: number; // Items per page
total: number; // Total items
totalPages: number; // Total pages
hasNext: boolean; // Has next page
hasPrev: boolean; // Has previous page
nextCursor?: string; // Next cursor
prevCursor?: string; // Previous cursor
};
}
Usage Examples
Basic Pagination
import { createPaginationQuery, executePagination } from '@/lib/table-utils';
// Simple pagination
async function getPlayers(db: D1Database, page: number = 1, limit: number = 20) {
const query = createPaginationQuery({
table: 'players',
select: ['uuid', 'username', 'display_name', 'level', 'created_at'],
page,
limit,
sort: { field: 'created_at', direction: 'desc' }
});
return await executePagination(db, query);
}
// Usage
const result = await getPlayers(db, 1, 20);
console.log(result);
// {
// data: [...],
// pagination: {
// page: 1,
// limit: 20,
// total: 150,
// totalPages: 8,
// hasNext: true,
// hasPrev: false
// }
// }
Advanced Filtering
// Complex filtering
async function getFilteredPlayers(db: D1Database, filters: any) {
const baseQuery = {
table: 'players',
select: ['uuid', 'username', 'display_name', 'level', 'status', 'created_at'],
joins: [
{ table: 'player_profiles', on: 'players.uuid = player_profiles.player_uuid' }
]
};
// Apply filters
const filteredQuery = applyFilters(baseQuery, {
username: { like: filters.username },
level: {
gte: filters.minLevel,
lte: filters.maxLevel
},
status: filters.status,
created_at: {
gte: filters.startDate,
lte: filters.endDate
},
'player_profiles.location': filters.location
});
// Apply sorting
const sortedQuery = applySorting(filteredQuery, [
{ field: 'level', direction: 'desc' },
{ field: 'username', direction: 'asc' }
]);
// Execute with pagination
return await executePagination(db, sortedQuery, {
page: filters.page || 1,
limit: filters.limit || 20
});
}
// Usage
const filters = {
username: 'john',
minLevel: 10,
maxLevel: 50,
status: 'active',
startDate: '2024-01-01',
endDate: '2024-12-31',
location: 'US',
page: 1,
limit: 25
};
const result = await getFilteredPlayers(db, filters);
Cursor-Based Pagination
// Cursor-based pagination for better performance
async function getPlayersCursor(db: D1Database, cursor?: string, limit: number = 20) {
const query = createPaginationQuery({
table: 'players',
select: ['uuid', 'username', 'display_name', 'created_at'],
cursor: cursor,
limit: limit,
sort: { field: 'created_at', direction: 'desc' }
});
return await executePagination(db, query);
}
// Usage
const firstPage = await getPlayersCursor(db, undefined, 20);
const nextPage = await getPlayersCursor(db, firstPage.pagination.nextCursor, 20);
Dynamic Query Building
// Build dynamic queries
class QueryBuilder {
private query: any = {
table: '',
select: [],
joins: [],
where: [],
orderBy: [],
groupBy: [],
having: []
};
from(table: string): QueryBuilder {
this.query.table = table;
return this;
}
select(...columns: string[]): QueryBuilder {
this.query.select.push(...columns);
return this;
}
join(table: string, on: string): QueryBuilder {
this.query.joins.push({ table, on });
return this;
}
where(condition: string, params: any[] = []): QueryBuilder {
this.query.where.push({ condition, params });
return this;
}
orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): QueryBuilder {
this.query.orderBy.push({ field, direction });
return this;
}
groupBy(...fields: string[]): QueryBuilder {
this.query.groupBy.push(...fields);
return this;
}
having(condition: string, params: any[] = []): QueryBuilder {
this.query.having.push({ condition, params });
return this;
}
build(): { sql: string; params: any[] } {
let sql = `SELECT ${this.query.select.join(', ')} FROM ${this.query.table}`;
const params: any[] = [];
// Add joins
for (const join of this.query.joins) {
sql += ` JOIN ${join.table} ON ${join.on}`;
}
// Add where clauses
if (this.query.where.length > 0) {
sql += ' WHERE ' + this.query.where.map(w => w.condition).join(' AND ');
this.query.where.forEach(w => params.push(...w.params));
}
// Add group by
if (this.query.groupBy.length > 0) {
sql += ` GROUP BY ${this.query.groupBy.join(', ')}`;
}
// Add having
if (this.query.having.length > 0) {
sql += ' HAVING ' + this.query.having.map(h => h.condition).join(' AND ');
this.query.having.forEach(h => params.push(...h.params));
}
// Add order by
if (this.query.orderBy.length > 0) {
const orderClauses = this.query.orderBy.map(o => `${o.field} ${o.direction}`);
sql += ` ORDER BY ${orderClauses.join(', ')}`;
}
return { sql, params };
}
}
// Usage
const query = new QueryBuilder()
.from('players')
.select('uuid', 'username', 'level')
.join('player_profiles', 'players.uuid = player_profiles.player_uuid')
.where('level >= ?', [10])
.where('status = ?', ['active'])
.orderBy('level', 'desc')
.build();
const result = await db.prepare(query.sql).bind(...query.params).all();
Data Transformation
// Transform database results
function transformPlayerData(rawData: any[]): Player[] {
return rawData.map(row => ({
uuid: row.uuid,
username: row.username,
displayName: row.display_name,
level: row.level,
status: row.status,
createdAt: new Date(row.created_at),
profile: {
bio: row.bio,
location: row.location,
avatarUrl: row.avatar_url
}
}));
}
// Transform with pagination
async function getTransformedPlayers(db: D1Database, options: PaginationOptions) {
const query = createPaginationQuery({
table: 'players',
select: [
'players.uuid',
'players.username',
'players.level',
'players.status',
'players.created_at',
'profiles.bio',
'profiles.location',
'profiles.avatar_url'
],
joins: [
{ table: 'player_profiles profiles', on: 'players.uuid = profiles.player_uuid' }
],
page: options.page,
limit: options.limit
});
const result = await executePagination(db, query);
return {
...result,
data: transformPlayerData(result.data)
};
}
Filter Application
// Apply complex filters
function applyAdvancedFilters(query: any, filters: any) {
const whereConditions: string[] = [];
const params: any[] = [];
// Text search
if (filters.search) {
whereConditions.push('(username LIKE ? OR display_name LIKE ?)');
params.push(`%${filters.search}%`, `%${filters.search}%`);
}
// Range filters
if (filters.levelRange) {
whereConditions.push('level BETWEEN ? AND ?');
params.push(filters.levelRange.min, filters.levelRange.max);
}
// Date filters
if (filters.dateRange) {
whereConditions.push('created_at BETWEEN ? AND ?');
params.push(filters.dateRange.start, filters.dateRange.end);
}
// Status filters
if (filters.statuses && filters.statuses.length > 0) {
const placeholders = filters.statuses.map(() => '?').join(',');
whereConditions.push(`status IN (${placeholders})`);
params.push(...filters.statuses);
}
// Boolean filters
if (filters.hasProfile !== undefined) {
if (filters.hasProfile) {
whereConditions.push('profiles.player_uuid IS NOT NULL');
} else {
whereConditions.push('profiles.player_uuid IS NULL');
}
}
if (whereConditions.length > 0) {
query.where = whereConditions.join(' AND ');
query.params = params;
}
return query;
}
// Usage
const filters = {
search: 'john',
levelRange: { min: 10, max: 50 },
dateRange: { start: '2024-01-01', end: '2024-12-31' },
statuses: ['active', 'verified'],
hasProfile: true
};
const query = applyAdvancedFilters(baseQuery, filters);
Performance Optimization
// Optimize queries for performance
class QueryOptimizer {
// Add indexes hint
static addIndexHint(query: string, table: string, index: string): string {
return query.replace(
`FROM ${table}`,
`FROM ${table} INDEXED BY ${index}`
);
}
// Limit result set
static addLimit(query: string, limit: number): string {
if (!query.includes('LIMIT')) {
return `${query} LIMIT ${limit}`;
}
return query;
}
// Add query timeout
static addTimeout(query: string, timeoutMs: number): string {
return `${query} /* timeout: ${timeoutMs}ms */`;
}
// Optimize for specific use case
static optimizeForRead(query: string): string {
return query.replace('SELECT', 'SELECT /* read optimization */');
}
static optimizeForCount(query: string): string {
return query.replace('SELECT *', 'SELECT COUNT(*) as count');
}
}
// Usage
let query = 'SELECT * FROM players WHERE level > 10';
query = QueryOptimizer.addIndexHint(query, 'players', 'idx_level');
query = QueryOptimizer.addLimit(query, 100);
query = QueryOptimizer.addTimeout(query, 5000);
Connection Management
// Database connection utilities
class DatabaseManager {
private db: D1Database;
private connectionPool: Map<string, any> = new Map();
constructor(db: D1Database) {
this.db = db;
}
// Execute query with retry logic
async executeWithRetry<T>(
queryFn: () => Promise<T>,
maxRetries: number = 3,
delayMs: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await queryFn();
} catch (error) {
lastError = error as Error;
if (attempt === maxRetries) {
throw lastError;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, delayMs * attempt));
}
}
throw lastError!;
}
// Execute transaction
async executeTransaction<T>(operations: (db: D1Database) => Promise<T>): Promise<T> {
return await this.db.batch([
this.db.prepare('BEGIN TRANSACTION'),
// Execute operations
this.db.prepare('COMMIT')
]);
}
// Health check
async healthCheck(): Promise<boolean> {
try {
await this.db.prepare('SELECT 1').first();
return true;
} catch {
return false;
}
}
}
Integration Examples
API Route Integration
import { createPaginationQuery, executePagination } from '@/lib/table-utils';
export async function GET(request: Request, locals: any) {
const url = new URL(request.url);
const page = parseInt(url.searchParams.get('page') || '1');
const limit = parseInt(url.searchParams.get('limit') || '20');
const search = url.searchParams.get('search') || '';
const status = url.searchParams.get('status') || '';
try {
const query = createPaginationQuery({
table: 'players',
select: ['uuid', 'username', 'display_name', 'level', 'status', 'created_at'],
page,
limit,
filters: {
username: search ? { like: `%${search}%` } : undefined,
status: status || undefined
},
sort: { field: 'created_at', direction: 'desc' }
});
const result = await executePagination(locals.runtime.env.DB, query);
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({ error: 'Failed to fetch players' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
Component Integration
import { useState, useEffect } from 'react';
import { createPaginationQuery, executePagination } from '@/lib/table-utils';
function PlayersTable() {
const [players, setPlayers] = useState<any[]>([]);
const [pagination, setPagination] = useState<any>({});
const [loading, setLoading] = useState(false);
const [filters, setFilters] = useState({
search: '',
status: '',
page: 1,
limit: 20
});
const loadPlayers = async () => {
setLoading(true);
try {
const query = createPaginationQuery({
table: 'players',
select: ['uuid', 'username', 'display_name', 'level', 'status'],
page: filters.page,
limit: filters.limit,
filters: {
username: filters.search ? { like: `%${filters.search}%` } : undefined,
status: filters.status || undefined
}
});
const result = await executePagination(db, query);
setPlayers(result.data);
setPagination(result.pagination);
} catch (error) {
console.error('Failed to load players:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadPlayers();
}, [filters]);
return (
<div className="players-table">
<div className="filters">
<input
type="text"
placeholder="Search players..."
value={filters.search}
onChange={(e) => setFilters({ ...filters, search: e.target.value, page: 1 })}
/>
<select
value={filters.status}
onChange={(e) => setFilters({ ...filters, status: e.target.value, page: 1 })}
>
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
{loading ? (
<div>Loading...</div>
) : (
<>
<table>
<thead>
<tr>
<th>Username</th>
<th>Display Name</th>
<th>Level</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{players.map(player => (
<tr key={player.uuid}>
<td>{player.username}</td>
<td>{player.display_name}</td>
<td>{player.level}</td>
<td>{player.status}</td>
</tr>
))}
</tbody>
</table>
<div className="pagination">
<button
disabled={!pagination.hasPrev}
onClick={() => setFilters({ ...filters, page: filters.page - 1 })}
>
Previous
</button>
<span>Page {pagination.page} of {pagination.totalPages}</span>
<button
disabled={!pagination.hasNext}
onClick={() => setFilters({ ...filters, page: filters.page + 1 })}
>
Next
</button>
</div>
</>
)}
</div>
);
}
Testing
Table Utilities Testing
describe('Table Utilities', () => {
it('should create pagination query correctly', () => {
const query = createPaginationQuery({
table: 'players',
select: ['uuid', 'username'],
page: 2,
limit: 10
});
expect(query.sql).toContain('SELECT uuid, username FROM players');
expect(query.sql).toContain('LIMIT 10 OFFSET 10');
});
it('should apply filters correctly', () => {
const baseQuery = { table: 'players', select: ['*'] };
const filters = { status: 'active', level: { gte: 10 } };
const filteredQuery = applyFilters(baseQuery, filters);
expect(filteredQuery.where).toContain('status = ?');
expect(filteredQuery.where).toContain('level >= ?');
});
it('should apply sorting correctly', () => {
const baseQuery = { table: 'players', select: ['*'] };
const sort = { field: 'username', direction: 'desc' };
const sortedQuery = applySorting(baseQuery, [sort]);
expect(sortedQuery.orderBy).toContain('username DESC');
});
});
This comprehensive table utilities system provides efficient database operations with pagination, filtering, and optimization capabilities.