Interface StorageHooks

Lifecycle hooks for extending adapter behavior.

All hooks are optional and async. Hooks can:

  • Observe operations (logging, analytics)
  • Transform inputs (queries, parameters)
  • Transform outputs (results, errors)
  • Abort operations (return undefined to skip)

Hook Execution Order:

  1. onBeforeQuery/onBeforeWrite - Can modify context
  2. Operation executes
  3. onAfterQuery/onAfterWrite - Can modify result
  4. onError - Only on failure, can modify/suppress error

Thread Safety: Hooks execute sequentially per operation but may run concurrently across different operations.

const hooks: StorageHooks = {
// Log all queries
onBeforeQuery: async (ctx) => {
logger.debug('Query', { sql: ctx.statement, op: ctx.operationId });
return ctx;
},

// Track query duration
onAfterQuery: async (ctx, result) => {
const duration = Date.now() - ctx.startTime;
metrics.recordHistogram('query_duration', duration);
return result;
},

// Audit write operations
onBeforeWrite: async (ctx) => {
console.log(`[AUDIT] ${ctx.operation}: ${ctx.statement}`);
ctx.metadata = { ...ctx.metadata, auditedAt: Date.now() };
return ctx;
},

// Track writes
onAfterWrite: async (ctx, result) => {
if (result.changes > 0) {
metrics.increment('db.writes', result.changes);
}
},

// Log and transform errors
onError: async (error, ctx) => {
logger.error('Database error', { error, operation: ctx.operationId });
return new DatabaseError(error.message, { cause: error });
}
};
interface StorageHooks {
    onBeforeQuery?: ((context: QueryContext) => Promise<void | QueryContext>);
    onAfterQuery?: (<T>(context: QueryContext, result: T) => Promise<QueryHookResult<T>>);
    onBeforeWrite?: ((context: WriteContext) => Promise<WriteHookResult>);
    onAfterWrite?: ((context: WriteContext, result: StorageRunResult) => Promise<void>);
    onBeforeTransaction?: ((context: TransactionContext) => Promise<void | TransactionContext>);
    onAfterTransaction?: ((context: TransactionContext) => Promise<void>);
    onError?: ((error: Error, context: OperationContext) => Promise<ErrorHookResult>);
}

Properties

onBeforeQuery?: ((context: QueryContext) => Promise<void | QueryContext>)

Called before any query execution (get, all, exec).

Use for:

  • Query transformation/rewriting
  • Logging/tracing
  • Cache checks
  • Permission validation

Type declaration

    • (context): Promise<void | QueryContext>
    • Parameters

      Returns Promise<void | QueryContext>

      Modified context, original context, or undefined to skip

onBeforeQuery: async (ctx) => {
// Add tenant filter to all queries
if (!ctx.statement.includes('tenant_id')) {
ctx.statement = ctx.statement.replace(
'WHERE',
`WHERE tenant_id = '${tenantId}' AND`
);
}
return ctx;
}
onAfterQuery?: (<T>(context: QueryContext, result: T) => Promise<QueryHookResult<T>>)

Called after successful query execution.

Use for:

  • Result transformation
  • Caching
  • Metrics recording
  • Post-processing (e.g., decryption)

Type declaration

    • <T>(context, result): Promise<QueryHookResult<T>>
    • Type Parameters

      • T = unknown

      Parameters

      • context: QueryContext

        Query context (read-only)

      • result: T

        Query result

      Returns Promise<QueryHookResult<T>>

      Modified result, original result, or undefined

onAfterQuery: async (ctx, result) => {
if (Array.isArray(result)) {
return result.map(row => ({
...row,
fullName: `${row.firstName} ${row.lastName}`
}));
}
return result;
}
onBeforeWrite?: ((context: WriteContext) => Promise<WriteHookResult>)

Called before any write operation (run, batch).

Use for:

  • Validation
  • Audit logging
  • Timestamp injection
  • Encryption
  • Custom transformations

Type declaration

onBeforeWrite: async (ctx) => {
if (ctx.statement.includes('INSERT INTO')) {
ctx.metadata = {
...ctx.metadata,
insertedAt: Date.now()
};
}
return ctx;
}
onAfterWrite?: ((context: WriteContext, result: StorageRunResult) => Promise<void>)

Called after successful write operation.

Use for:

  • Cache invalidation
  • Sync triggers
  • Notification dispatch
  • Audit completion
  • External service updates

Type declaration

    • (context, result): Promise<void>
    • Parameters

      Returns Promise<void>

onAfterWrite: async (ctx, result) => {
if (result.changes > 0) {
await syncService.markDirty(ctx.affectedTables);
console.log(`Updated ${result.changes} rows`);
}
}
onBeforeTransaction?: ((context: TransactionContext) => Promise<void | TransactionContext>)

Called before transaction starts.

Use for:

  • Transaction logging
  • Distributed transaction coordination
  • Savepoint creation

Type declaration

onAfterTransaction?: ((context: TransactionContext) => Promise<void>)

Called after transaction completes (commit or rollback).

Use for:

  • Transaction logging
  • Cleanup
  • Cache invalidation

Type declaration

    • (context): Promise<void>
    • Parameters

      Returns Promise<void>

onError?: ((error: Error, context: OperationContext) => Promise<ErrorHookResult>)

Called when any operation fails.

Use for:

  • Error logging
  • Error transformation
  • Error suppression (return undefined)
  • Alerting
  • Retry decision

Type declaration

onError: async (error, ctx) => {
// Log with full context
logger.error('Database operation failed', {
error: error.message,
operation: ctx.operation,
operationId: ctx.operationId,
duration: Date.now() - ctx.startTime,
});

// Transform to application-specific error
if (error.message.includes('unique constraint')) {
return new DuplicateEntryError('Record already exists');
}

return error;
}