npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, πŸ‘‹, I’m Ryan HefnerΒ  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you πŸ™

Β© 2025 – Pkg Stats / Ryan Hefner

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!

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.

npm version License: MIT TypeScript


✨ Why Use This Library?

Prevents Production Disasters:

  • πŸ•΅οΈ N+1 Query Detection - Catches the #1 performance killer automatically
  • πŸ›‘οΈ Safety Guards - Blocks DELETE/UPDATE without WHERE, 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 TABLE ran in production
  • DELETE FROM orders without 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):

  1. Slow query alert fires
  2. Copy the SQL
  3. Open pgAdmin/DBeaver
  4. Paste and run EXPLAIN ANALYZE
  5. 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 users without 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