@rodrigopsasaki/vision-typeorm
v0.5.0
Published
TypeORM integration for seamless Vision database operation observability
Maintainers
Readme
@rodrigopsasaki/vision-typeorm
TypeORM integration for seamless Vision database operation observability.
Overview
Vision TypeORM provides comprehensive, zero-configuration observability for your TypeORM database operations. It automatically instruments DataSources, Repositories, EntityManagers, and transactions to give you structured insights into your database layer.
Features
- 🚀 Zero Configuration: Works out of the box with sensible defaults
- 🔒 Security First: Automatic sensitive data redaction
- ⚡ Performance Optimized: Minimal overhead with selective instrumentation
- 🏗️ Comprehensive Coverage: DataSource, Repository, EntityManager, QueryRunner, and Transaction instrumentation
- 🎯 Decorator Support: Advanced instrumentation with decorators
- 🔍 Rich Context: Detailed operation metadata, timing, and error information
- 🌐 Microservice Ready: Correlation ID support for distributed tracing
Installation
npm install @rodrigopsasaki/vision-typeormPeer Dependencies:
@rodrigopsasaki/vision: ^0.3.0typeorm: ^0.3.0
Quick Start
import { DataSource } from "typeorm";
import { vision } from "@rodrigopsasaki/vision";
import { instrumentDataSource } from "@rodrigopsasaki/vision-typeorm";
// 1. Initialize your DataSource as usual
const dataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "user",
password: "password",
database: "myapp",
entities: [User, Post],
synchronize: true,
});
await dataSource.initialize();
// 2. Instrument with Vision (zero configuration!)
const instrumentedDataSource = instrumentDataSource(dataSource);
// 3. Use normally - all operations are automatically observed
await vision.observe("user.create", async () => {
const userRepository = instrumentedDataSource.getRepository(User);
const user = await userRepository.save({
name: "John Doe",
email: "[email protected]",
});
vision.set("user_id", user.id);
return user;
});Output:
{
"name": "user.create",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"user_id": 1
}
}
{
"name": "db.user.save",
"data": {
"database.operation": "save",
"database.target": "typeorm",
"database.entity": "User",
"database.success": true,
"database.duration_ms": 15
}
}Core API
instrumentDataSource(dataSource, config?)
Instruments a TypeORM DataSource with Vision observability.
import { instrumentDataSource } from "@rodrigopsasaki/vision-typeorm";
const instrumentedDataSource = instrumentDataSource(dataSource, {
enabled: true,
logParams: false,
logQuery: true,
logResultCount: true,
redactFields: ["password", "token", "secret"],
});instrumentRepository(repository, config?)
Instruments a specific Repository instance.
import { instrumentRepository } from "@rodrigopsasaki/vision-typeorm";
const userRepository = dataSource.getRepository(User);
const instrumentedRepository = instrumentRepository(userRepository);visionTransaction(dataSourceOrManager, transactionFn, config?)
Enhanced transaction wrapper with observability.
import { visionTransaction } from "@rodrigopsasaki/vision-typeorm";
const result = await visionTransaction(dataSource, async (manager) => {
const userRepository = manager.getRepository(User);
const user = await userRepository.save(userData);
const postRepository = manager.getRepository(Post);
const post = await postRepository.save({ ...postData, userId: user.id });
return { user, post };
});Configuration Options
interface VisionTypeOrmConfig {
// Core settings
enabled?: boolean; // Default: true
// Logging control
logParams?: boolean; // Default: false (security)
logQuery?: boolean; // Default: true
logResultCount?: boolean; // Default: true
logConnectionInfo?: boolean; // Default: false
// Performance settings
maxQueryLength?: number; // Default: 1000
// Operation naming
includeEntityInName?: boolean; // Default: true
operationPrefix?: string; // Default: "db"
// Security
redactFields?: string[]; // Default: ["password", "token", "secret", "key", "hash"]
// Selective instrumentation
instrumentTransactions?: boolean; // Default: true
instrumentRepositories?: boolean; // Default: true
instrumentEntityManager?: boolean; // Default: true
}Advanced Usage
Decorator-Based Instrumentation
import { VisionInstrumented, VisionObserve, VisionParam } from "@rodrigopsasaki/vision-typeorm";
@VisionInstrumented({ logParams: true })
class UserService {
constructor(private dataSource: DataSource) {}
@VisionObserve()
async createUser(@VisionParam("userData") userData: CreateUserDto) {
const userRepository = this.dataSource.getRepository(User);
return await userRepository.save(userData);
}
async findUserByEmail(email: string) {
// This method is automatically instrumented by @VisionInstrumented
const userRepository = this.dataSource.getRepository(User);
return await userRepository.findOne({ where: { email } });
}
}Transaction with Isolation Level
import { visionTransactionWithIsolation } from "@rodrigopsasaki/vision-typeorm";
await visionTransactionWithIsolation(dataSource, "SERIALIZABLE", async (manager) => {
// Critical operations requiring serializable isolation
const account = await manager.getRepository(Account).findOne({
where: { id: accountId },
lock: { mode: "pessimistic_write" },
});
account.balance -= withdrawAmount;
await manager.getRepository(Account).save(account);
return account;
});Manual QueryRunner Management
import { visionQueryRunner } from "@rodrigopsasaki/vision-typeorm";
await visionQueryRunner(dataSource, async (queryRunner) => {
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const result = await queryRunner.query(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
["John Doe", "[email protected]"],
);
await queryRunner.commitTransaction();
return result;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
}
});Security Features
Automatic Data Redaction
Sensitive fields are automatically redacted from logs:
const config = {
logParams: true,
redactFields: ["password", "token", "creditCard", "ssn"],
};
// Input: { name: "John", password: "secret123", email: "[email protected]" }
// Logged: { name: "John", password: "[REDACTED]", email: "[email protected]" }Query Truncation
Long queries are automatically truncated to prevent log bloat:
const config = {
maxQueryLength: 200, // Queries longer than 200 chars are truncated
};Performance Optimization
Production Configuration
const productionConfig = {
enabled: true,
logParams: false, // Disable to reduce serialization overhead
logQuery: false, // Disable in high-frequency scenarios
logResultCount: true, // Keep for business metrics
maxQueryLength: 200, // Shorter truncation
logConnectionInfo: false, // Static information
instrumentEntityManager: false, // If not using directly
};Selective Instrumentation
// Only instrument specific components
const config = {
instrumentRepositories: true,
instrumentEntityManager: false, // Disable if not used
instrumentTransactions: true,
};Error Handling
Vision TypeORM automatically captures and structures database errors:
{
"name": "db.user.save",
"data": {
"database.operation": "save",
"database.success": false,
"database.error": "duplicate key value violates unique constraint",
"database.error_details": {
"code": "23505",
"constraint": "users_email_unique",
"table": "users",
"column": "email"
},
"database.duration_ms": 25
}
}Microservice Integration
For distributed systems, use correlation IDs to trace operations across services:
class CorrelationMiddleware {
static withCorrelationId<T>(id: string, fn: () => Promise<T>): Promise<T> {
return vision.observe("service.operation", async () => {
vision.set("correlation_id", id);
vision.set("service", "user-service");
return fn();
});
}
}
// Usage
await CorrelationMiddleware.withCorrelationId("req_123", async () => {
const user = await userService.createUser(userData);
const profile = await profileService.createProfile(user.id);
return { user, profile };
});Examples
See the examples/ directory for comprehensive examples:
- Basic Usage: Simple setup and usage
- Advanced Usage: Full feature demonstration
- Performance Optimized: Production-ready configuration
- Microservice Example: Distributed system patterns
Integration with Vision Exporters
Vision TypeORM works seamlessly with all Vision exporters:
import { vision } from "@rodrigopsasaki/vision";
vision.init({
exporters: [
{
name: "datadog",
success: (ctx) => {
// Send to Datadog APM
sendToDatadog(ctx);
},
error: (ctx, err) => {
// Send errors to error tracking
sendErrorToDatadog(ctx, err);
},
},
],
});TypeScript Support
Full TypeScript support with enhanced types:
import type {
VisionDataSource,
VisionRepository,
VisionTypeOrmConfig,
} from "@rodrigopsasaki/vision-typeorm";
// Enhanced types provide full IntelliSense support
const repository: VisionRepository<User> = instrumentRepository(userRepository);Best Practices
- Start Simple: Begin with default configuration and customize as needed
- Security First: Always configure
redactFieldsfor sensitive data - Performance Aware: Disable parameter/query logging in high-frequency operations
- Correlation IDs: Implement request tracing for distributed systems
- Error Context: Let Vision capture errors while implementing proper business error handling
- Selective Instrumentation: Only instrument what you need in performance-critical scenarios
Troubleshooting
Performance Impact
- Disable
logParamsandlogQueryfor high-frequency operations - Use shorter
maxQueryLengthvalues - Consider selective instrumentation
Memory Usage
- Set appropriate
maxQueryLengthlimits - Disable unused instrumentation features
TypeScript Issues
- Ensure TypeORM peer dependency version compatibility
- Use proper type imports from the package
Contributing
This is part of the Vision observability framework. For issues, feature requests, or contributions, please visit the main Vision repository.
License
MIT © Rodrigo Sasaki
