UNPKG

@aradox/multi-orm

Version:

Type-safe ORM with multi-datasource support, row-level security, and Prisma-like API for PostgreSQL, SQL Server, and HTTP APIs

310 lines 9.76 kB
"use strict"; /** * Example Middleware for Row-Level Security (RLS) * * These middleware examples demonstrate common patterns for: * - Tenant isolation (multi-tenancy) * - User-based data filtering * - Audit logging * - Performance monitoring */ Object.defineProperty(exports, "__esModule", { value: true }); exports.tenantIsolationMiddleware = tenantIsolationMiddleware; exports.userOwnershipMiddleware = userOwnershipMiddleware; exports.auditLoggingMiddleware = auditLoggingMiddleware; exports.rbacMiddleware = rbacMiddleware; exports.performanceMonitoringMiddleware = performanceMonitoringMiddleware; exports.softDeleteMiddleware = softDeleteMiddleware; exports.dataSanitizationMiddleware = dataSanitizationMiddleware; /** * Tenant Isolation Middleware * * Automatically injects tenant_id filter on all queries. * Ensures users can only access data from their own tenant. * * Usage: * ```typescript * const client = await orm.generate(); * client.use(tenantIsolationMiddleware(['Customers', 'Orders', 'Products'])); * client.setContext({ user: { tenantId: 123 } }); * * // All queries automatically filtered by tenant_id * const customers = await client.Customers.findMany({}); * // WHERE tenant_id = 123 * ``` */ function tenantIsolationMiddleware(models) { return { name: 'tenant-isolation', beforeQuery: ({ model, args, context }) => { // Only apply to specified models if (!models.includes(model)) { return args; } // Check if user has tenantId if (!context.user?.tenantId) { throw new Error(`Tenant isolation: No tenantId found in context for model ${model}`); } // Inject tenant_id filter const tenantFilter = { tenant_id: context.user.tenantId }; if (args.where) { // Merge with existing where clause args.where = { AND: [ args.where, tenantFilter ] }; } else { // Set new where clause args.where = tenantFilter; } return args; } }; } /** * User Ownership Middleware * * Ensures users can only access their own data. * Injects user_id filter on specified models. * * Usage: * ```typescript * client.use(userOwnershipMiddleware(['Orders', 'Payments'])); * client.setContext({ user: { id: 456 } }); * * const orders = await client.Orders.findMany({}); * // WHERE user_id = 456 * ``` */ function userOwnershipMiddleware(models) { return { name: 'user-ownership', beforeQuery: ({ model, args, context, operation }) => { // Only apply to specified models and read operations if (!models.includes(model)) { return args; } // Skip for create operations (user_id will be set in data) if (operation === 'create' || operation === 'createMany') { return args; } if (!context.user?.id) { throw new Error(`User ownership: No user id found in context for model ${model}`); } // Inject user_id filter const ownerFilter = { user_id: context.user.id }; if (args.where) { args.where = { AND: [ args.where, ownerFilter ] }; } else { args.where = ownerFilter; } return args; } }; } /** * Audit Logging Middleware * * Logs all mutations (create, update, delete) for compliance. * * Usage: * ```typescript * client.use(auditLoggingMiddleware({ * async log(entry) { * await db.auditLog.create({ data: entry }); * } * })); * ``` */ function auditLoggingMiddleware(options) { return { name: 'audit-logging', afterQuery: async ({ model, operation, args, result, context }) => { // Only log mutations if (['create', 'createMany', 'update', 'updateMany', 'delete', 'deleteMany', 'upsert'].includes(operation)) { await options.log({ model, operation, userId: context.user?.id, timestamp: new Date(), args, result }); } return result; } }; } /** * Role-Based Access Control (RBAC) Middleware * * Restricts access based on user roles. * * Usage: * ```typescript * client.use(rbacMiddleware({ * Employees: ['admin', 'hr'], * Payments: ['admin', 'finance'], * Invoices: ['admin', 'finance'] * })); * * client.setContext({ user: { role: 'hr' } }); * await client.Employees.findMany({}); // ✅ Allowed * await client.Payments.findMany({}); // ❌ Forbidden * ``` */ function rbacMiddleware(modelRoles) { return { name: 'rbac', beforeQuery: ({ model, context }) => { const allowedRoles = modelRoles[model]; // No restrictions for this model if (!allowedRoles) { return {}; } const userRole = context.user?.role; if (!userRole) { throw new Error(`RBAC: No role found in context for model ${model}`); } if (!allowedRoles.includes(userRole)) { throw new Error(`RBAC: Role "${userRole}" is not allowed to access model ${model}. Required roles: ${allowedRoles.join(', ')}`); } return {}; } }; } /** * Performance Monitoring Middleware * * Tracks query execution time and logs slow queries. * * Usage: * ```typescript * client.use(performanceMonitoringMiddleware({ * slowQueryThreshold: 1000 // ms * })); * ``` */ function performanceMonitoringMiddleware(options) { const timings = new Map(); return { name: 'performance-monitoring', beforeQuery: ({ model, operation }) => { const key = `${model}.${operation}.${Date.now()}`; timings.set(key, Date.now()); return {}; }, afterQuery: ({ model, operation, result }) => { // Find the timing entry (last one for this model.operation) const entries = Array.from(timings.entries()) .filter(([k]) => k.startsWith(`${model}.${operation}`)); if (entries.length > 0) { const [key, startTime] = entries[entries.length - 1]; const duration = Date.now() - startTime; timings.delete(key); if (duration > options.slowQueryThreshold) { console.warn(`⚠️ Slow query detected: ${model}.${operation} took ${duration}ms`); } } return result; } }; } /** * Soft Delete Middleware * * Automatically filters out deleted records and converts delete to update. * * Usage: * ```typescript * client.use(softDeleteMiddleware(['Customers', 'Orders'])); * * // findMany automatically filters deleted_at IS NULL * const customers = await client.Customers.findMany({}); * * // delete() converts to update with deleted_at = NOW() * await client.Customers.delete({ where: { Id: 1 } }); * ``` */ function softDeleteMiddleware(models) { return { name: 'soft-delete', beforeQuery: ({ model, operation, args }) => { if (!models.includes(model)) { return args; } // For read operations, filter out soft-deleted records if (['findMany', 'findUnique', 'count'].includes(operation)) { const notDeletedFilter = { deleted_at: null }; if (args.where) { args.where = { AND: [ args.where, notDeletedFilter ] }; } else { args.where = notDeletedFilter; } } // For delete operations, convert to update if (operation === 'delete' || operation === 'deleteMany') { // Note: This requires special handling in the adapter // For now, just add a flag args._softDelete = true; args.data = { deleted_at: new Date() }; } return args; } }; } /** * Data Sanitization Middleware * * Removes sensitive fields from query results. * * Usage: * ```typescript * client.use(dataSanitizationMiddleware({ * Customers: ['ssn', 'credit_card'], * Employees: ['salary', 'ssn'] * })); * ``` */ function dataSanitizationMiddleware(modelFields) { return { name: 'data-sanitization', afterQuery: ({ model, result }) => { const fieldsToRemove = modelFields[model]; if (!fieldsToRemove) { return result; } const sanitize = (obj) => { if (!obj) return obj; const sanitized = { ...obj }; for (const field of fieldsToRemove) { delete sanitized[field]; } return sanitized; }; // Handle arrays if (Array.isArray(result)) { return result.map(sanitize); } // Handle single object return sanitize(result); } }; } //# sourceMappingURL=examples.js.map