typeorm-query-hooks
v5.0.7
Published
π Powerful plugin-based hooks for TypeORM: Performance monitoring, result validation, query modification, transaction tracking & more. Works with JS, TS & NestJS!
Maintainers
Readme
π TypeORM Query Hooks
The ultimate TypeORM companion - 20 powerful plugins to prevent N+1 queries, detect connection leaks, block dangerous operations, auto-run EXPLAIN, trace query sources & more. Works seamlessly with JavaScript, TypeScript, and NestJS.
β¨ Why Use This Library?
Prevents Production Disasters:
- π΅οΈ N+1 Query Detection - Catches the #1 performance killer automatically
- π‘οΈ Safety Guards - Blocks
DELETE/UPDATEwithoutWHERE, prevents DDL in production - π§ Connection Leak Detection - Finds leaks before they crash your app
- π§ Zombie Transaction Monitoring - Prevents deadlocks from idle transactions
Automatic Debugging:
- π Source Code Tracing - Shows exact file:line where queries originate (no more guessing!)
- π¬ Auto-EXPLAIN - Runs query plan analysis on slow queries automatically
- β οΈ Lazy Loading Detection - Catches hidden N+1 problems
Enterprise Features:
- π Audit Logging - GDPR/HIPAA compliance ready
- ποΈ Cache Invalidation - Auto-invalidate on data changes
- π Result Transformation - Auto-convert to DTOs, remove sensitive data
Extensible:
- π¨ Create Custom Plugins - Build your own hooks for specific needs
- π 20+ Built-in Plugins - Ready-to-use solutions for common problems
- π― Full TypeScript Support - Type-safe plugin development
ποΈ 20 Powerful Plugins Included
π₯ Critical Performance & Safety
| Plugin | Purpose | Use Case | |--------|---------|----------| | π΅οΈ NPlusOneDetector | Detect N+1 query problems | #1 performance killer - catches 80% of issues | | π‘οΈ SafetyGuard | Block dangerous operations | Prevents DELETE/UPDATE without WHERE, blocks DDL | | π§ ConnectionLeakDetector | Find connection leaks | Prevents pool exhaustion and app crashes | | β±οΈ QueryTimeout | Automatic query timeouts | Prevents queries from hanging forever | | π§ IdleTransactionMonitor | Detect zombie transactions | Prevents deadlocks from idle transactions |
π¬ Analysis & Debugging
| Plugin | Purpose | Use Case | |--------|---------|----------| | π QuerySourceTracer | Show where queries originate | CSI: Database - find exact file:line in your code | | π¬ SlowQueryAnalyzer | Auto-run EXPLAIN on slow queries | Automatic query plan analysis | | β οΈ LazyLoadingDetector | Detect lazy-loaded relations | Catches hidden N+1 problems | | β‘ PerformanceMonitor | Track query execution time | Monitor and optimize performance |
ποΈ Data Management
| Plugin | Purpose | Use Case | |--------|---------|----------| | ποΈ CacheInvalidation | Auto-invalidate cache on writes | Maintain cache consistency | | π AuditLogging | Track all database operations | Compliance (GDPR, HIPAA), security | | π BulkOperations | Detect bulk operations | Prevent accidental mass updates | | π QueryResultTransformer | Transform query results | Auto-convert to DTOs, remove sensitive data |
π οΈ Utilities
| Plugin | Purpose | Use Case | |--------|---------|----------| | π·οΈ TableExtractor | Extract table names from queries | Logging, caching, access control | | β ResultValidator | Validate query results | Alert on empty results, pagination issues | | βοΈ QueryModifier | Modify queries before execution | Multi-tenancy, query hints, safety | | π QueryComplexity | Warn on complex queries | Identify queries needing optimization | | πΎ QueryMetadataRegistry | Store query metadata | Analytics, cross-cutting concerns | | πͺ΅ QueryLogger | Custom query logging | Flexible logging with filters |
π¦ Installation
npm install typeorm-query-hooks
# or
yarn add typeorm-query-hooksβ‘ Quick Start
1. Enable Hooks
import { enableQueryHooks } from 'typeorm-query-hooks';
// Enable at application startup (before any TypeORM queries)
enableQueryHooks({
verbose: false // Set to true for debugging
});Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| verbose | boolean | false | Enable detailed debug logging for the core hook system. Shows when hooks fire, plugins execute, queries are captured, etc. |
When to use verbose: true:
- Debugging why a plugin isn't working
- Understanding the hook execution flow
- Development/testing only (too noisy for production)
2. Register Plugins
import { registerPlugin } from 'typeorm-query-hooks';
import { NPlusOneDetectorPlugin } from 'typeorm-query-hooks/plugins/n-plus-one-detector';
// Register any plugins you need
registerPlugin(NPlusOneDetectorPlugin({
threshold: 5,
enableLogging: true
}));ποΈ Built-in Plugins (20 Total!)
π₯ Critical Performance & Safety
π΅οΈ NPlusOneDetector - Detect N+1 query problems (THE #1 performance killer)
What it does: Detects when the same query runs repeatedly in a short time window - the classic N+1 problem.
The Problem:
// β BAD - Causes N+1 problem
const users = await userRepository.find(); // 1 query
for (const user of users) { // Loop
const posts = await postRepository.find({ where: { userId: user.id } }); // N queries!
}
// Total: 101 queries for 100 users!Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| threshold | number | 5 | Maximum number of identical queries allowed within time window |
| window | number | 100 | Time window in milliseconds to track query patterns |
| includeStackTrace | boolean | true | Capture stack trace to show where N+1 originated |
| ignorePatterns | RegExp[] | [] | Regex patterns to ignore (e.g., /migrations$/i) |
| onNPlusOneDetected | function | undefined | Callback when N+1 is detected |
| enableLogging | boolean | false | Auto-log N+1 warnings to console |
Usage:
import { NPlusOneDetectorPlugin } from 'typeorm-query-hooks/plugins/n-plus-one-detector';
registerPlugin(NPlusOneDetectorPlugin({
threshold: 5, // Flag if same query runs > 5 times
window: 100, // Within 100ms window
includeStackTrace: true,
enableLogging: true,
onNPlusOneDetected: (context, count, fingerprint) => {
logger.error(`π¨ N+1 DETECTED! Query ran ${count} times`, {
fingerprint: fingerprint.substring(0, 100),
suggestion: 'Use .leftJoinAndSelect() or relations: []'
});
// Send to monitoring
datadog.increment('n_plus_one_detected', { count });
}
}));π‘οΈ SafetyGuard - Block dangerous database operations
What it does:
Prevents catastrophic mistakes like UPDATE users SET role='admin' (no WHERE = ALL users become admin!)
Real disasters this prevents:
- Junior dev ran
UPDATE users SET email='[email protected]'without WHERE β 1M users had same email - Migration with
DROP TABLEran in production DELETE FROM orderswithout WHERE β Lost 6 months of data
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| blockDDL | boolean | false | Block CREATE, ALTER, DROP, TRUNCATE operations |
| requireWhereClause | boolean | true | Require WHERE clause for UPDATE/DELETE β οΈ CRITICAL |
| blockTruncate | boolean | true | Block TRUNCATE operations |
| blockDrop | boolean | true | Block DROP TABLE/DATABASE operations |
| allowedEnvironments | string[] | ['development','test'] | Environments where destructive ops are allowed |
| protectedTables | string[] | [] | Tables with extra protection (e.g., ['users', 'payments']) |
| allowForce | boolean | false | Allow /* FORCE_ALLOW */ comment to bypass |
| throwOnBlock | boolean | true | Throw error when operation is blocked |
| onBlocked | function | undefined | Callback when operation is blocked |
| enableLogging | boolean | false | Auto-log blocked operations |
Usage:
import { SafetyGuardPlugin } from 'typeorm-query-hooks/plugins/safety-guard';
// Recommended for production
registerPlugin(SafetyGuardPlugin({
blockDDL: process.env.NODE_ENV === 'production',
requireWhereClause: true, // ALWAYS require WHERE
protectedTables: ['users', 'payments', 'transactions'],
throwOnBlock: true,
onBlocked: (context, blocked) => {
// Send critical alert
pagerduty.trigger({
severity: 'critical',
summary: `Dangerous operation blocked: ${blocked.operation}`,
tables: blocked.tables
});
},
enableLogging: true
}));π§ ConnectionLeakDetector - Find connection leaks before they crash your app
What it does: Detects connections that are acquired but never released - leads to pool exhaustion.
The Problem:
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.query('SELECT ...');
// β FORGOT queryRunner.release() - connection leaked!Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| maxConnectionAge | number | 30000 | Max connection age in ms before considered a leak |
| warnThreshold | number | 0.8 | Warn when pool usage exceeds this % (0.8 = 80%) |
| captureStackTrace | boolean | true | Capture where connection was acquired |
| onLeak | function | undefined | Callback when leak is detected |
| onPoolWarning | function | undefined | Callback when pool capacity warning |
| enableLogging | boolean | false | Auto-log leak warnings |
Usage:
import { ConnectionLeakDetectorPlugin } from 'typeorm-query-hooks/plugins/connection-leak-detector';
registerPlugin(ConnectionLeakDetectorPlugin({
maxConnectionAge: 30000, // 30 seconds
warnThreshold: 0.8, // Warn at 80% pool capacity
captureStackTrace: true,
enableLogging: true,
onLeak: (leak) => {
logger.error('π§ CONNECTION LEAK:', {
age: `${leak.age}ms`,
stackTrace: leak.stackTrace
});
monitoring.alert({ type: 'connection_leak', severity: 'critical' });
}
}));β±οΈ QueryTimeout - Automatic query timeouts
What it does: Prevents queries from hanging forever and blocking the connection pool.
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| defaultTimeout | number | 5000 | Default timeout for all queries (ms) |
| timeoutByType | Record<string, number> | {} | Override timeout by query type (e.g., { 'SELECT': 3000 }) |
| timeoutByTablePattern | Record<string, number> | {} | Override by table pattern (e.g., { 'report_.*': 30000 }) |
| throwOnTimeout | boolean | true | Throw error on timeout |
| onTimeout | function | undefined | Callback when timeout occurs |
| enableLogging | boolean | false | Auto-log timeouts |
Usage:
import { QueryTimeoutPlugin } from 'typeorm-query-hooks/plugins/query-timeout';
registerPlugin(QueryTimeoutPlugin({
defaultTimeout: 5000,
timeoutByType: {
'SELECT': 3000, // Reads should be fast
'INSERT': 10000, // Writes can be slower
'UPDATE': 10000
},
timeoutByTablePattern: {
'report_.*': 30000, // Reports can take 30s
'analytics_.*': 60000 // Analytics 60s
},
throwOnTimeout: true,
enableLogging: true
}));π§ IdleTransactionMonitor - Detect zombie transactions
What it does: Detects transactions that sit idle (no queries running) - causes deadlocks.
The Problem:
await queryRunner.startTransaction();
await queryRunner.manager.save(user);
// β Transaction is OPEN while doing HTTP call!
await fetch('https://api.slow-service.com'); // 5 seconds
// Meanwhile: DB locks held, other queries waiting, deadlock risk
await queryRunner.commitTransaction();Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| maxTransactionDuration | number | 5000 | Max transaction duration in ms |
| maxIdleTime | number | 1000 | Max idle time (no queries) in ms |
| autoRollback | boolean | false | Auto-rollback zombie transactions β οΈ Use carefully |
| onZombieDetected | function | undefined | Callback when zombie detected |
| enableLogging | boolean | false | Auto-log zombie warnings |
Usage:
import { IdleTransactionMonitorPlugin } from 'typeorm-query-hooks/plugins/idle-transaction-monitor';
registerPlugin(IdleTransactionMonitorPlugin({
maxTransactionDuration: 5000,
maxIdleTime: 1000,
autoRollback: false, // Don't auto-rollback in production
enableLogging: true,
onZombieDetected: (context, zombie) => {
logger.error('π§ ZOMBIE TRANSACTION:', {
duration: `${zombie.duration}ms`,
idleTime: `${zombie.idleTime}ms`,
queries: zombie.queriesExecuted
});
}
}));π¬ Analysis & Debugging
π QuerySourceTracer - CSI: Database (find exact file:line in your code)
What it does: Shows you EXACTLY where in your code each query originated.
The Problem:
You see a slow query: SELECT * FROM users WHERE email = '...'
You have 50 places that query users. Which one is slow? You don't know!
The Solution:
Shows: Query from: src/services/UserService.ts:45:12 in UserService.findByEmail
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| basePath | string | process.cwd() | Base path to filter stack traces |
| attachToQueryContext | boolean | true | Make source location available to other plugins |
| includeFullStackTrace | boolean | false | Include complete stack trace |
| ignorePaths | string[] | ['node_modules'] | Paths to ignore in stack traces |
| onQueryLogged | function | undefined | Callback with source location |
| enableLogging | boolean | false | Auto-log source location |
Usage:
import { QuerySourceTracerPlugin } from 'typeorm-query-hooks/plugins/query-source-tracer';
registerPlugin(QuerySourceTracerPlugin({
basePath: process.cwd() + '/src',
attachToQueryContext: true, // Other plugins can use context.sourceLocation
enableLogging: true
}));
// Logs show:
// [QuerySourceTracer] π Query Source:
// File: src/services/UserService.ts
// Line: 45:12
// Function: UserService.findByEmail
// SQL: SELECT "user"."id", "user"."email" FROM "users"...π¬ SlowQueryAnalyzer - Auto-run EXPLAIN on slow queries
What it does:
Automatically runs EXPLAIN (or EXPLAIN ANALYZE) on slow queries to show you WHY they're slow.
The Manual Way (painful):
- Slow query alert fires
- Copy the SQL
- Open pgAdmin/DBeaver
- Paste and run
EXPLAIN ANALYZE - Look for issues
The Automatic Way: Plugin does it all automatically and logs the execution plan immediately!
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| threshold | number | 1000 | Run EXPLAIN on queries slower than this (ms) |
| runAnalyze | boolean | false | Run EXPLAIN ANALYZE (actually executes query) β οΈ |
| databaseType | string | 'postgres' | Database type: 'postgres', 'mysql', 'mariadb', 'sqlite', 'mssql' |
| onAnalysis | function | undefined | Callback with execution plan |
| enableLogging | boolean | false | Auto-log execution plans |
Usage:
import { SlowQueryAnalyzerPlugin } from 'typeorm-query-hooks/plugins/slow-query-analyzer';
registerPlugin(SlowQueryAnalyzerPlugin({
threshold: 1000,
databaseType: 'postgres',
enableLogging: true,
onAnalysis: (context, plan) => {
if (plan.hasSeqScan) {
logger.error('π MISSING INDEX DETECTED:', {
sql: context.sql.substring(0, 200),
executionTime: context.executionTime,
plan: plan.raw,
suggestion: 'Add an index to improve performance'
});
}
}
}));β οΈ LazyLoadingDetector - Detect lazy-loaded relations (hidden N+1)
What it does: Warns when lazy-loaded relations are accessed (often causes hidden N+1 queries).
The Problem:
@Entity()
class User {
@OneToMany(() => Post, post => post.user)
posts: Promise<Post[]>; // Lazy loaded!
}
// Usage - looks innocent but causes N+1
const users = await userRepo.find();
for (const user of users) {
const posts = await user.posts; // Separate query per user!
}Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| warnOnLazyLoad | boolean | true | Warn when lazy loading is detected |
| suggestEagerLoading | boolean | true | Show code suggestion to fix |
| threshold | number | 1 | Warn after N lazy loads of same relation |
| onLazyLoadDetected | function | undefined | Callback when lazy load detected |
| enableLogging | boolean | false | Auto-log lazy loading warnings |
Usage:
import { LazyLoadingDetectorPlugin } from 'typeorm-query-hooks/plugins/lazy-loading-detector';
registerPlugin(LazyLoadingDetectorPlugin({
warnOnLazyLoad: true,
suggestEagerLoading: true,
threshold: 3,
enableLogging: true
}));
// Shows suggestions like:
// β οΈ Potential lazy loading detected
// π‘ Suggestion: Use eager loading:
// - Option 1: find({ relations: ['posts'] })
// - Option 2: .leftJoinAndSelect('user.posts', 'posts')β‘ PerformanceMonitor - Track query execution time
What it does: Monitors query performance and detects slow queries.
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| slowQueryThreshold | number | 500 | Threshold in ms for slow query detection |
| onSlowQuery | function | undefined | Callback when slow query detected |
| onMetric | function | undefined | Callback for all query completions |
| enableLogging | boolean | false | Auto-log performance metrics |
Usage:
import { PerformanceMonitorPlugin } from 'typeorm-query-hooks/plugins/performance-monitor';
registerPlugin(PerformanceMonitorPlugin({
slowQueryThreshold: 300,
enableLogging: true,
onSlowQuery: (context) => {
datadog.histogram('db.query.duration', context.executionTime);
},
onMetric: (context) => {
prometheus.histogram('query_duration', context.executionTime);
}
}));ποΈ Data Management
ποΈ CacheInvalidation - Auto-invalidate cache on data changes
What it does:
Automatically invalidates cache when INSERT, UPDATE, or DELETE operations occur.
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| onInvalidate | function | REQUIRED | Callback to clear your cache (Redis, memory, etc.) |
| invalidateOnTypes | string[] | ['INSERT','UPDATE','DELETE'] | Query types that trigger invalidation |
| monitorTables | string[] | [] (all) | Specific tables to monitor. Empty = all tables |
| enableLogging | boolean | false | Auto-log cache invalidations |
Usage:
import { CacheInvalidationPlugin } from 'typeorm-query-hooks/plugins/cache-invalidation';
registerPlugin(CacheInvalidationPlugin({
onInvalidate: async (tables) => {
for (const table of tables) {
await redis.del(`cache:${table}:*`);
}
},
monitorTables: ['users', 'products'], // Only these tables
enableLogging: true
}));π AuditLogging - Track all database operations (GDPR/HIPAA ready)
What it does: Comprehensive audit trail of who did what, when, and on which tables.
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| onAudit | function | REQUIRED | Callback to persist audit logs |
| getUserId | function | undefined | Function to get current user ID |
| auditTypes | string[] | ['INSERT','UPDATE','DELETE'] | Query types to audit |
| auditTables | string[] | [] (all) | Tables to audit. Empty = all |
| includeSql | boolean | true | Include SQL in audit logs |
| includeParameters | boolean | false | Include parameters (may contain sensitive data) |
| metadata | object\|function | undefined | Additional metadata to include |
| enableLogging | boolean | false | Auto-log audit entries |
Usage:
import { AuditLoggingPlugin } from 'typeorm-query-hooks/plugins/audit-logging';
registerPlugin(AuditLoggingPlugin({
getUserId: () => getCurrentUser()?.id,
onAudit: async (entry) => {
await auditLogRepository.save({
userId: entry.userId,
action: entry.action,
tables: entry.tables,
timestamp: entry.timestamp,
success: entry.success
});
},
auditTypes: ['INSERT', 'UPDATE', 'DELETE'],
auditTables: ['users', 'financial_records'], // Sensitive tables only
includeSql: true,
includeParameters: false, // Don't log sensitive data
metadata: () => ({
environment: process.env.NODE_ENV,
requestId: getRequestId()
}),
enableLogging: true
}));π BulkOperations - Prevent accidental mass updates/deletes
What it does: Detects operations affecting many rows - prevents accidents like deleting 100,000 records.
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| bulkThreshold | number | 100 | Threshold for considering operation "bulk" (rows) |
| monitorTypes | string[] | ['INSERT','UPDATE','DELETE'] | Query types to monitor |
| monitorTables | string[] | [] (all) | Tables to monitor. Empty = all |
| warnOnBulk | boolean | true | Warn when bulk operation detected |
| onBulkOperation | function | undefined | Callback when bulk operation detected |
| enableLogging | boolean | false | Auto-log bulk operations |
Usage:
import { BulkOperationsPlugin } from 'typeorm-query-hooks/plugins/bulk-operations';
registerPlugin(BulkOperationsPlugin({
bulkThreshold: 50,
warnOnBulk: true,
enableLogging: true,
onBulkOperation: (context, affectedRows) => {
if (process.env.NODE_ENV === 'production') {
throw new Error(`Bulk operation blocked: ${affectedRows} rows`);
}
}
}));π QueryResultTransformer - Auto-transform results to DTOs
What it does: Automatically transforms database results to DTOs, removes sensitive data, adds computed fields.
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| transformers | Record<string, Function> | {} | Transformers by entity name |
| globalTransformer | function | undefined | Applied to all results |
| enableLogging | boolean | false | Auto-log transformations |
Usage:
import { QueryResultTransformerPlugin } from 'typeorm-query-hooks/plugins/query-result-transformer';
registerPlugin(QueryResultTransformerPlugin({
transformers: {
User: (user) => ({
id: user.id,
fullName: `${user.firstName} ${user.lastName}`,
email: user.email,
// Remove sensitive data
password: undefined,
resetToken: undefined
}),
Product: (product) => ({
...product,
price: `$${product.price.toFixed(2)}`,
inStock: product.quantity > 0
})
},
enableLogging: true
}));π οΈ Utilities
π·οΈ TableExtractor - Extract table names from queries
What it does: Extracts all table names from any TypeORM query (SELECT, INSERT, UPDATE, DELETE, CTEs, subqueries, joins).
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| warnOnEmptyTables | boolean | false | Warn when no tables are extracted |
| enableLogging | boolean | false | Log extracted tables |
Usage:
import { createTableExtractorPlugin, extractTablesFromBuilder } from 'typeorm-query-hooks/plugins/table-extractor';
registerPlugin(createTableExtractorPlugin({
warnOnEmptyTables: true,
enableLogging: true
}));
// Use directly in code:
const query = userRepo.createQueryBuilder('user')
.leftJoin('user.posts', 'posts');
const tables = extractTablesFromBuilder(query);
// Or:
const tables2 = query.getInvolvedTables();β ResultValidator - Validate query results
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| largeResultThreshold | number | 1000 | Threshold for large result set (rows) |
| monitorTables | string[] | [] (all) | Tables to monitor. Empty = all |
| onEmptyResult | function | undefined | Callback when query returns no results |
| onLargeResult | function | undefined | Callback when result exceeds threshold |
| enableLogging | boolean | false | Auto-log validation warnings |
Usage:
import { ResultValidatorPlugin } from 'typeorm-query-hooks/plugins/result-validator';
registerPlugin(ResultValidatorPlugin({
largeResultThreshold: 5000,
monitorTables: ['users', 'orders'],
enableLogging: true,
onLargeResult: (context) => {
logger.warn(`Large result: ${context.rowCount} rows - consider pagination`);
}
}));βοΈ QueryModifier - Modify queries before execution
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| modifySql | function | undefined | Modify SQL before execution |
| modifyParameters | function | undefined | Modify parameters before execution |
| shouldExecute | function | undefined | Return false to cancel query |
| enableLogging | boolean | false | Auto-log modifications |
Usage:
import { QueryModifierPlugin, TenantFilterModifier, SafetyModifier } from 'typeorm-query-hooks/plugins/query-modifier';
// Multi-tenancy
registerPlugin(TenantFilterModifier({
getTenantId: () => getCurrentUser().tenantId,
tables: ['orders', 'products'],
tenantColumn: 'tenant_id'
}));
// Block queries during maintenance
registerPlugin(QueryModifierPlugin({
shouldExecute: (context) => {
if (isMaintenanceMode()) {
console.error('Database in maintenance');
return false;
}
return true;
}
}));π QueryComplexity - Warn on complex queries
Configuration:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| maxJoins | number | 5 | Max joins before warning |
| maxTables | number | 10 | Max tables before warning |
| warnOnSubqueries | boolean | false | Warn on subqueries |
| warnOnCTEs | boolean | false | Warn on Common Table Expressions |
| onComplexQuery | function | undefined | Callback when complex query detected |
| enableLogging | boolean | false | Auto-log complexity warnings |
Usage:
import { QueryComplexityPlugin } from 'typeorm-query-hooks/plugins/query-complexity';
registerPlugin(QueryComplexityPlugin({
maxJoins: 3,
maxTables: 5,
warnOnSubqueries: true,
enableLogging: true
}));πͺ΅ QueryLogger - Custom query logging with filters
See plugin documentation for details.
πΎ QueryMetadataRegistry - Store query metadata
Automatically registered when using NestJS integration. See NestJS section below.
π¨ Creating Custom Plugins
import { QueryHookPlugin } from 'typeorm-query-hooks';
const MyCustomPlugin: QueryHookPlugin = {
name: 'MyCustomPlugin',
onQueryBuild: (context) => {
console.log('Query built:', context.sql);
},
onQueryComplete: (context) => {
console.log(`Query took ${context.executionTime}ms`);
},
onSlowQuery: (context) => {
console.warn('Slow query detected!');
}
};
registerPlugin(MyCustomPlugin);π§ NestJS Integration
// main.ts or app.module.ts
import { enableQueryHooks, registerPlugin } from 'typeorm-query-hooks';
import { NPlusOneDetectorPlugin } from 'typeorm-query-hooks/plugins/n-plus-one-detector';
import { SafetyGuardPlugin } from 'typeorm-query-hooks/plugins/safety-guard';
// Enable before TypeORM connection
enableQueryHooks({ verbose: false });
// Register essential plugins
registerPlugin(NPlusOneDetectorPlugin({ threshold: 5, enableLogging: true }));
registerPlugin(SafetyGuardPlugin({ requireWhereClause: true }));
@Module({
imports: [TypeOrmModule.forRoot({ /* ... */ })],
})
export class AppModule {}π Debugging
Enable verbose mode to see detailed hook execution:
enableQueryHooks({ verbose: true });What it logs:
- When hooks are enabled
- When plugins are registered
- When each hook fires
- SQL queries being captured
- Tables extracted from queries
- Execution timing
π Real-World Impact
Before typeorm-query-hooks:
- β N+1 queries slow down production (found after users complain)
- β Accidental
DELETE FROM userswithout WHERE - β Connection pool exhausted β app crash
- β Slow query β manually copy SQL β run EXPLAIN β find issue
- β Don't know which file:line caused the query
After typeorm-query-hooks:
- β N+1 detected in development automatically
- β Dangerous queries blocked before execution
- β Connection leaks caught immediately
- β EXPLAIN runs automatically on slow queries
- β Exact source location shown for every query
π Show Your Support
If this library helps you, please give it a βοΈ on GitHub!
π License
MIT Β© Roy Leibovitz
Built with β€οΈ to make TypeORM better for everyone
