-
Notifications
You must be signed in to change notification settings - Fork 0
IStatementAdapter
IAmKirbki edited this page Jan 11, 2026
·
2 revisions
Interface for executing prepared SQL statements with parameter binding.
interface IStatementAdapter {
run(parameters?: Record<string, any>): Promise<RunResult>;
all(parameters?: Record<string, any>): Promise<any[]>;
get(parameters?: Record<string, any>): Promise<any>;
runSync?(parameters?: Record<string, any>): RunResult; // Optional
}Execute a query that modifies data (INSERT, UPDATE, DELETE).
Parameters:
-
parameters(Record<string, any>) - Optional parameter bindings
Returns: Promise<RunResult>
type RunResult = {
changes: number; // Number of rows affected
lastInsertRowid?: number | bigint; // Last inserted row ID
};Example:
const result = await statement.run({
name: 'John',
email: 'john@example.com'
});
console.log(`Inserted ${result.changes} row(s)`);
console.log(`New ID: ${result.lastInsertRowid}`);Execute a query and return all matching rows.
Parameters:
-
parameters(Record<string, any>) - Optional parameter bindings
Returns: Promise<any[]>
Example:
const users = await statement.all({ role: 'admin' });
users.forEach(user => {
console.log(user.name, user.email);
});Execute a query and return the first matching row.
Parameters:
-
parameters(Record<string, any>) - Optional parameter bindings
Returns: Promise<any> - Single row or undefined
Example:
const user = await statement.get({ id: 1 });
if (user) {
console.log(user.name);
}Execute a query synchronously. Only implement for databases that support synchronous operations.
Parameters:
-
parameters(Record<string, any>) - Optional parameter bindings
Returns: RunResult
When to implement:
- Database library supports synchronous operations (better-sqlite3)
- Needed for transaction support
- Performance optimization for batch operations
Example:
// Inside a transaction
const result = statement.runSync({ status: 'active' });
console.log(`Updated ${result.changes} rows`);import { IStatementAdapter, RunResult } from '@iamkirbki/database-handler-core';
export default class MyDatabaseStatement implements IStatementAdapter {
private statement: any;
private client: any;
constructor(query: string, client: any) {
this.client = client;
this.statement = query;
}
async run(parameters?: Record<string, any>): Promise<RunResult> {
const [query, params] = this.convertParameters(this.statement, parameters || {});
const result = await this.client.query(query, params);
return {
changes: result.rowCount || 0,
lastInsertRowid: result.rows[0]?.id
};
}
async all(parameters?: Record<string, any>): Promise<any[]> {
const [query, params] = this.convertParameters(this.statement, parameters || {});
const result = await this.client.query(query, params);
return result.rows;
}
async get(parameters?: Record<string, any>): Promise<any> {
const [query, params] = this.convertParameters(this.statement, parameters || {});
const result = await this.client.query(query, params);
return result.rows[0];
}
private convertParameters(query: string, params: Record<string, any>): [string, any[]] {
const paramList: any[] = [];
let index = 1;
const convertedQuery = query.replace(/@(\w+)/g, (match, paramName) => {
paramList.push(params[paramName]);
return `$${index++}`;
});
return [convertedQuery, paramList];
}
}import { IStatementAdapter, RunResult } from '@iamkirbki/database-handler-core';
import Database from 'better-sqlite3';
export default class MyDatabaseStatement implements IStatementAdapter {
private statement: Database.Statement;
constructor(statement: Database.Statement) {
this.statement = statement;
}
async run(parameters?: Record<string, any>): Promise<RunResult> {
const result = this.statement.run(parameters || {});
return {
changes: result.changes,
lastInsertRowid: result.lastInsertRowid
};
}
async all(parameters?: Record<string, any>): Promise<any[]> {
return this.statement.all(parameters || {});
}
async get(parameters?: Record<string, any>): Promise<any> {
return this.statement.get(parameters || {});
}
// Optional: for transaction support
runSync(parameters?: Record<string, any>): RunResult {
const result = this.statement.run(parameters || {});
return {
changes: result.changes,
lastInsertRowid: result.lastInsertRowid
};
}
}export default class PostgresStatement implements IStatementAdapter {
private query: string;
private client: any;
constructor(query: string, client: any) {
this.client = client;
this.query = query;
}
async run(parameters?: Record<string, any>): Promise<RunResult> {
const [query, params] = this.parameterReplacer(this.query, parameters || {});
const result = await this.client.query(query, params);
return {
changes: result.rowCount || 0,
lastInsertRowid: result.rows[0]?.id
};
}
async all(parameters?: Record<string, any>): Promise<any[]> {
const [query, params] = this.parameterReplacer(this.query, parameters || {});
const result = await this.client.query(query, params);
return result.rows;
}
async get(parameters?: Record<string, any>): Promise<any> {
const [query, params] = this.parameterReplacer(this.query, parameters || {});
const result = await this.client.query(query, params);
return result.rows[0];
}
private parameterReplacer(query: string, parameters: Record<string, any>): [string, any[]] {
const parameterKeys = Object.keys(parameters);
const parameterValues: any[] = [];
const result = query.replace(/@(\w+)/g, (match, key) => {
const index = parameterKeys.indexOf(key);
if (index !== -1) {
parameterValues.push(parameters[key]);
return `$${parameterValues.length}`;
}
return match;
});
return [result, parameterValues];
}
}export default class BetterSqlite3Statement implements IStatementAdapter {
private statement: Database.Statement;
constructor(statement: Database.Statement) {
this.statement = statement;
}
async run(parameters?: Record<string, any>): Promise<RunResult> {
const result = this.statement.run(parameters || {});
return {
changes: result.changes,
lastInsertRowid: result.lastInsertRowid
};
}
async all(parameters?: Record<string, any>): Promise<any[]> {
return this.statement.all(parameters || {});
}
async get(parameters?: Record<string, any>): Promise<any> {
return this.statement.get(parameters || {});
}
runSync(parameters?: Record<string, any>): RunResult {
const result = this.statement.run(parameters || {});
return {
changes: result.changes,
lastInsertRowid: result.lastInsertRowid
};
}
}private convertParameters(query: string, params: Record<string, any>): [string, any[]] {
const paramList: any[] = [];
let index = 1;
const converted = query.replace(/@(\w+)/g, (match, paramName) => {
paramList.push(params[paramName]);
return `$${index++}`;
});
return [converted, paramList];
}private convertParameters(query: string, params: Record<string, any>): [string, any[]] {
const paramList: any[] = [];
const converted = query.replace(/@(\w+)/g, (match, paramName) => {
paramList.push(params[paramName]);
return '?';
});
return [converted, paramList];
}// No conversion needed - SQLite supports @paramName natively
// Just pass parameters directly
const result = this.statement.run(parameters);For databases that support synchronous operations, implement runSync() to enable transaction batching:
// With runSync support
const commit = await adapter.transaction((items) => {
items.forEach(item => {
statement.runSync(item); // Synchronous execution
});
});
await commit();Without runSync:
// Must use async operations
const commit = await adapter.transaction(async (items) => {
for (const item of items) {
await statement.run(item); // Async execution
}
});
await commit();import { describe, it, expect } from 'vitest';
describe('MyDatabaseStatement', () => {
it('should execute run and return changes', async () => {
const result = await statement.run({ name: 'Test' });
expect(result.changes).toBeGreaterThan(0);
});
it('should fetch all rows', async () => {
const rows = await statement.all({ status: 'active' });
expect(Array.isArray(rows)).toBe(true);
});
it('should fetch single row', async () => {
const row = await statement.get({ id: 1 });
expect(row).toBeDefined();
});
it('should handle empty parameters', async () => {
const rows = await statement.all();
expect(Array.isArray(rows)).toBe(true);
});
});- Custom Adapter Guide - Full implementation guide
- IDatabaseAdapter - Database adapter interface
- Query - Using prepared statements