UNPKG

prisma-entity-framework

Version:

TypeScript Entity Framework for Prisma ORM with Active Record pattern, fluent query builder, relation graphs, batch operations, advanced CRUD, search filters, and pagination utilities.

1,464 lines (1,449 loc) 55.2 kB
import { PrismaClient } from '@prisma/client'; /** * Rate Limiter for controlling query throughput * * Implements token bucket algorithm to prevent database overload * by limiting the number of queries per second. */ /** * Configuration options for rate limiter */ interface RateLimiterOptions { /** * Maximum number of queries allowed per second */ maxQueriesPerSecond: number; /** * Algorithm to use for rate limiting * - token-bucket: Allows bursts up to bucket capacity * - sliding-window: Strict limit over rolling time window */ algorithm: 'token-bucket' | 'sliding-window'; } /** * Status information about the rate limiter */ interface RateLimiterStatus { /** * Number of available tokens/queries */ available: number; /** * Total capacity */ total: number; /** * Current utilization (0-1) */ utilization: number; } /** * Abstract base class for rate limiters */ declare abstract class RateLimiter { protected options: RateLimiterOptions; constructor(options: RateLimiterOptions); /** * Acquire permission to execute a query * Will wait if rate limit is exceeded * * @returns Promise that resolves when permission is granted */ abstract acquire(): Promise<void>; /** * Get current status of the rate limiter * * @returns Status information */ abstract getStatus(): RateLimiterStatus; /** * Reset the rate limiter state */ abstract reset(): void; } /** * Token Bucket Rate Limiter * * Implements the token bucket algorithm: * - Tokens are added to the bucket at a constant rate * - Each query consumes one token * - If no tokens available, request waits until tokens are refilled * - Allows bursts up to bucket capacity */ declare class TokenBucketRateLimiter extends RateLimiter { private tokens; private lastRefill; private readonly refillRate; private readonly bucketCapacity; constructor(options: RateLimiterOptions); /** * Refill tokens based on elapsed time */ private refill; /** * Acquire permission to execute a query * Waits if no tokens are available */ acquire(): Promise<void>; /** * Get current status */ getStatus(): RateLimiterStatus; /** * Reset the rate limiter */ reset(): void; } /** * Create a rate limiter instance * * @param options - Rate limiter configuration * @returns RateLimiter instance * * @example * ```typescript * const limiter = createRateLimiter({ * maxQueriesPerSecond: 100, * algorithm: 'token-bucket' * }); * * // Acquire permission before query * await limiter.acquire(); * await prisma.user.findMany(); * ``` */ declare function createRateLimiter(options: RateLimiterOptions): RateLimiter; /** * Configuration options for Prisma Entity Framework */ interface PrismaConfig { /** * Maximum number of concurrent operations * If not set, will be auto-detected from connection pool */ maxConcurrency?: number; /** * Enable or disable parallel execution * Default: true (enabled when pool size > 1) */ enableParallel?: boolean; /** * Maximum queries per second (rate limiting) * Default: 100 */ maxQueriesPerSecond?: number; } /** * Configure the Prisma client instance to be used by all entities * This must be called once at application startup before using any entity operations * * @param prisma - PrismaClient instance * @param config - Optional configuration for parallel execution * @throws Error if prisma is null or undefined * @throws Error if configuration options are invalid * * @example * ```typescript * import { PrismaClient } from '@prisma/client'; * import { configurePrisma } from 'prisma-entity-framework'; * * const prisma = new PrismaClient(); * * // Basic configuration (auto-detect pool size) * configurePrisma(prisma); * * // Custom configuration * configurePrisma(prisma, { * maxConcurrency: 4, * enableParallel: true, * maxQueriesPerSecond: 50 * }); * ``` */ declare function configurePrisma(prisma: PrismaClient, config?: PrismaConfig): void; /** * Get the configured Prisma instance * * @returns PrismaClient instance * @throws Error if Prisma has not been configured * @internal */ declare function getPrismaInstance(): PrismaClient; /** * Check if Prisma has been configured * * @returns boolean indicating if Prisma is configured */ declare function isPrismaConfigured(): boolean; /** * Reset the Prisma configuration * Useful for testing scenarios where you need to reconfigure Prisma */ declare function resetPrismaConfiguration(): void; /** * Detect connection pool size from Prisma configuration * * Attempts to read pool size from: * 1. DATABASE_URL connection_limit parameter * 2. DATABASE_URL pool_size parameter (PostgreSQL) * 3. Prisma internal configuration * 4. Default values based on database provider * * @returns Detected connection pool size * * @example * ```typescript * // With DATABASE_URL=postgresql://user:pass@localhost:5432/db?connection_limit=20 * const poolSize = getConnectionPoolSize(); // Returns 20 * ``` */ declare function getConnectionPoolSize(): number; /** * Get the maximum concurrency level for parallel operations * * Returns the configured maxConcurrency if set, otherwise returns * the detected connection pool size * * @returns Maximum concurrency level * * @example * ```typescript * const concurrency = getMaxConcurrency(); * console.log(`Max concurrent operations: ${concurrency}`); * ``` */ declare function getMaxConcurrency(): number; /** * Check if parallel execution is enabled * * Parallel execution is enabled when: * - enableParallel config is true (default) * - Connection pool size > 1 * * @returns true if parallel execution is enabled * * @example * ```typescript * if (isParallelEnabled()) { * console.log('Parallel execution is enabled'); * } * ``` */ declare function isParallelEnabled(): boolean; /** * Parallel Execution Utilities * * Provides utilities for executing operations in parallel with controlled * concurrency, error handling, and performance metrics. */ /** * Options for parallel execution */ interface ParallelOptions { /** * Maximum number of concurrent operations * If not specified, uses configured maxConcurrency */ concurrency?: number; /** * Rate limit (queries per second) * If not specified, uses configured rate limiter */ rateLimit?: number; /** * Callback for progress updates */ onProgress?: (completed: number, total: number) => void; /** * Callback for individual operation errors */ onError?: (error: Error, index: number) => void; } /** * Result of parallel execution */ interface ParallelResult<T> { /** * Successful results */ results: T[]; /** * Errors that occurred during execution */ errors: Array<{ index: number; error: Error; }>; /** * Performance metrics */ metrics: ParallelMetrics; } /** * Performance metrics for parallel execution */ interface ParallelMetrics { /** * Total execution time in milliseconds */ totalTime: number; /** * Estimated time for sequential execution */ sequentialEstimate: number; /** * Speedup factor (sequential / parallel) */ speedupFactor: number; /** * Items processed per second */ itemsPerSecond: number; /** * Parallel efficiency (0-1) * 1.0 = perfect parallelization */ parallelEfficiency: number; /** * Connection pool utilization (0-1) */ connectionUtilization: number; } /** * Tracks performance metrics during parallel execution */ declare class ParallelMetricsTracker { private startTime; private endTime; private operationTimes; private totalOperations; private concurrency; constructor(totalOperations: number, concurrency: number); /** * Start tracking */ start(): void; /** * Record an operation completion */ recordOperation(duration: number): void; /** * Complete tracking and calculate metrics */ complete(): ParallelMetrics; } /** * Create a metrics object with default values * * @returns ParallelMetrics with default values */ declare function createParallelMetrics(): ParallelMetrics; /** * Execute operations in parallel with controlled concurrency * * Uses Promise.allSettled to ensure individual failures don't crash other operations. * Respects connection pool limits and applies rate limiting. * * @param operations - Array of async operations to execute * @param options - Parallel execution options * @returns Promise resolving to results, errors, and metrics * * @example * ```typescript * const operations = batches.map(batch => * () => prisma.user.createMany({ data: batch }) * ); * * const result = await executeInParallel(operations, { concurrency: 4 }); * * console.log(`Completed: ${result.results.length}`); * console.log(`Errors: ${result.errors.length}`); * console.log(`Speedup: ${result.metrics.speedupFactor}x`); * ``` */ declare function executeInParallel<T>(operations: Array<() => Promise<T>>, options?: ParallelOptions): Promise<ParallelResult<T>>; /** * Create optimal chunks for parallel execution * * Divides items into chunks that can be processed in parallel batches. * * @param items - Items to chunk * @param batchSize - Size of each batch * @param concurrency - Number of concurrent operations * @returns Array of chunked items * * @example * ```typescript * const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; * const chunks = chunkForParallel(items, 2, 3); * // Result: [[1,2], [3,4], [5,6], [7,8], [9,10]] * // Can process 3 chunks at a time * ``` */ declare function chunkForParallel<T>(items: T[], batchSize: number, concurrency: number): T[][]; /** * Get optimal concurrency level for an operation * * Recommends concurrency based on operation type and item count. * * @param operationType - Type of operation (read or write) * @param itemCount - Number of items to process * @returns Recommended concurrency level * * @example * ```typescript * const concurrency = getOptimalConcurrency('write', 5000); * console.log(`Recommended concurrency: ${concurrency}`); * ``` */ declare function getOptimalConcurrency(operationType: 'read' | 'write', itemCount: number): number; /** * Determine if parallel execution should be used * * Checks if parallel execution would provide benefits based on * dataset size and connection pool configuration. * * @param itemCount - Number of items to process * @param poolSize - Connection pool size * @returns true if parallel execution is recommended * * @example * ```typescript * if (shouldUseParallel(items.length, getConnectionPoolSize())) { * await executeInParallel(operations); * } else { * // Execute sequentially * } * ``` */ declare function shouldUseParallel(itemCount: number, poolSize: number): boolean; /** * Represents a base entity with generic CRUD operations. * @template TModel - The type of the data model. */ interface IBaseEntity<TModel> { /** * Unique identifier of the entity (optional). * Can be a number (for SQL databases) or string (for MongoDB ObjectId). */ id?: number | string; /** * Creates a new instance of the entity in the database. * @returns A promise that resolves with the created model instance. */ create(): Promise<TModel>; /** * Updates the entity in the database. * @returns A promise that resolves with the updated entity or `null` if the update fails. */ update(): Promise<TModel | null>; /** * Deletes the entity from the database. * @returns A promise that resolves with the id of the deleted record * (number for SQL databases, string for MongoDB, or 0 if deletion failed). */ delete(): Promise<number | string>; } declare namespace FindByFilterOptions { type StringSearch = { keys?: string[]; value: string; mode?: "EXACT" | "LIKE" | "STARTS_WITH" | "ENDS_WITH"; grouping?: "and" | "or"; }; type RangeSearch = { keys?: string[]; min?: number | Date; max?: number | Date; grouping?: "and" | "or"; }; type ListSearch = { keys?: string[]; values: any[]; mode?: "IN" | "NOT_IN" | "HAS_SOME" | "HAS_EVERY"; grouping?: "and" | "or"; }; type SearchOptions = { stringSearch?: StringSearch[]; rangeSearch?: RangeSearch[]; listSearch?: ListSearch[]; grouping?: "and" | "or"; }; type PaginationOptions = { page: number; pageSize: number; take: number; skip: number; }; type PaginatedResponse<T> = { total: number; page: number; pageSize: number; data: T[]; }; type NestedRelations = Array<{ [relation: string]: NestedRelations; }> | "*"; type Options = { onlyOne?: boolean; relationsToInclude?: NestedRelations; search?: SearchOptions; pagination?: PaginationOptions; orderBy?: Record<string, 'asc' | 'desc'>; parallel?: boolean; concurrency?: number; rateLimit?: number; }; const defaultOptions: Options; } declare abstract class BaseEntity<TModel extends Record<string, any>> implements IBaseEntity<TModel> { static readonly model: any; static readonly BATCH_SIZE = 1500; static readonly MONGODB_TRANSACTION_BATCH_SIZE = 100; readonly id?: number | string; /** * Get optimal batch size for current database and operation * @param operation - Type of operation (createMany, updateMany, transaction) * @returns Optimal batch size */ protected static getOptimalBatchSize(operation?: 'createMany' | 'updateMany' | 'transaction'): number; constructor(data?: Partial<TModel>); /** * Automatically initializes entity properties from data object * * Supports three property types: * 1. Decorated properties with @Property() - Uses the setter created by decorator * 2. Properties with manual getters/setters - Assigns to private _property * 3. Public properties - Assigns directly to the property * * @param data - Data object to initialize from * @protected */ protected initializeProperties(data?: Partial<TModel>): void; /** * Finds entities by applying filters, search criteria, pagination, and ordering. * Supports relation includes, complex searches, and automatic chunking for large list searches (>10k items). * * @template T - The entity type * @param filter - Base filter object with entity properties to match * @param options - Query options (search, pagination, relationsToInclude, orderBy, onlyOne) * @returns PaginatedResponse<T> if paginated, T if onlyOne, T[] otherwise, or null if no results * @throws Error if model is not defined * * @example * ```typescript * // Paginated results with relations and ordering * const result = await User.findByFilter( * { status: 'active' }, * { * pagination: { page: 1, pageSize: 10, take: 10, skip: 0 }, * relationsToInclude: ['posts', 'profile'], * orderBy: { createdAt: 'desc' } * } * ); * ``` */ static findByFilter<T extends Record<string, any>>(this: new (data: any) => BaseEntity<T>, filter: Partial<T>, options?: FindByFilterOptions.Options): Promise<FindByFilterOptions.PaginatedResponse<T> | T[] | T | null>; /** * Counts the number of records matching the given filter * @param filter - Filter criteria * @returns Promise<number> - The count of matching records */ static countByFilter<T extends Record<string, any>>(this: new (data: any) => BaseEntity<T>, filter: Partial<T>): Promise<number>; findByFilter(filter: Partial<TModel>, options?: FindByFilterOptions.Options): Promise<FindByFilterOptions.PaginatedResponse<TModel> | TModel[] | TModel | null>; static getModelInformation(this: new (data: any) => BaseEntity<any>, modelName?: string): any; private static sanitizeKeysRecursive; create(): Promise<TModel>; static createMany<T extends Record<string, any>>(this: new (data: any) => BaseEntity<T>, items: Partial<T>[], skipDuplicates?: boolean, keyTransformTemplate?: (relationName: string) => string, options?: { parallel?: boolean; concurrency?: number; }): Promise<number>; /** * Upsert a single entity (update if exists with same unique fields, create otherwise) * Verifies existence using unique constraints, checks for changes before updating * * @param data - The entity data to upsert * @param keyTransformTemplate - Optional function to transform relation names to FK field names * @returns The upserted entity * * @example * ```typescript * const user = await User.upsert({ email: 'john@example.com', name: 'John Doe' }); * // If user with email exists and has changes -> update * // If user with email exists but no changes -> return existing * // If user doesn't exist -> create * ``` */ static upsert<T extends Record<string, any>>(this: new (data: any) => BaseEntity<T>, data: Partial<T>, keyTransformTemplate?: (relationName: string) => string): Promise<T>; /** * Upsert multiple entities in batch (update if exists, create otherwise) * Optimized version that fetches all existing records in batch and compares changes efficiently * * @param items - Array of entity data to upsert * @param keyTransformTemplate - Optional function to transform relation names to FK field names * @returns Object with counts of created, updated, and unchanged records * * @example * ```typescript * const result = await User.upsertMany([ * { email: 'john@example.com', name: 'John Doe' }, * { email: 'jane@example.com', name: 'Jane Smith' } * ]); * // Returns: { created: 1, updated: 1, unchanged: 0, total: 2 } * ``` */ static upsertMany<T extends Record<string, any>>(this: new (data: any) => BaseEntity<T>, items: Partial<T>[], keyTransformTemplate?: (relationName: string) => string, options?: { parallel?: boolean; concurrency?: number; }): Promise<{ created: number; updated: number; unchanged: number; total: number; }>; /** * Checks if there are changes between new data and existing data. * * @param newData - New data to compare * @param existingData - Existing data to compare against * @param ignoreFields - Additional fields to ignore beyond defaults (id, createdAt, updatedAt) * @returns true if any changes detected, false otherwise */ protected static hasChanges<T extends Record<string, any>>(newData: T, existingData: T, ignoreFields?: string[]): boolean; /** * Checks if a field is a standard ignored field (id, createdAt, updatedAt) * @param key - Field name to check * @returns true if field should be ignored */ private static isStandardIgnoredField; /** * Normalizes a value for comparison. * - null, undefined, and empty string are treated as null * - Strings are trimmed * - Other values are returned as-is * * @param value - Value to normalize * @returns Normalized value */ private static normalizeValueForComparison; /** * Performs deep equality comparison between two values. * Optimized for performance - avoids JSON.stringify which is 5x slower. * * Handles: * - Primitives (string, number, boolean, null, undefined) * - Arrays (recursive comparison) * - Objects (recursive comparison) * * @param a - First value to compare * @param b - Second value to compare * @returns true if values are deeply equal, false otherwise */ private static deepEqual; /** * Compares two arrays for deep equality * @param a - First array * @param b - Second array * @returns true if arrays are deeply equal */ private static deepEqualArrays; /** * Compares two objects for deep equality * @param a - First object * @param b - Second object * @returns true if objects are deeply equal */ private static deepEqualObjects; /** * Deduplicates data based on known unique constraints for each model */ private static deduplicateByUniqueConstraints; update(): Promise<TModel>; private static pruneUpdatePayload; private static shouldSkipField; private static isEmptyObject; private static hasPrismaOperations; private static removeRelationObjectsWithFK; static updateManyById(this: new (data: any) => BaseEntity<any>, dataList: Array<Partial<any>>, options?: { parallel?: boolean; concurrency?: number; }): Promise<number>; /** * Optimized MongoDB batch update using transactions * Uses Prisma's transaction API for atomic batch operations * MongoDB has transaction size limits, so we use smaller batches * @private */ private static updateManyByIdMongoDB; private static prepareUpdateList; /** * Escapes a value for use in raw SQL queries * @param value - Value to escape * @param prisma - Prisma client instance (for database-specific escaping) * @param isJsonField - Whether this is a JSON field (requires special handling) * @returns Escaped SQL string */ private static escapeValue; /** * Escapes a JSON value for SQL insertion * Uses database-specific JSON handling to avoid escaping issues * * MySQL JSON escaping rules: * - JSON.stringify() produces: {"path":"C:\\test"} (backslash already escaped in JSON) * - For SQL string literal, we need: '{"path":"C:\\\\test"}' (escape backslashes for SQL) * - MySQL JSON parser then reads: {"path":"C:\\test"} (correct!) * * @param value - JSON value to escape * @param prisma - Prisma client instance * @returns Escaped JSON string for SQL */ private static escapeJsonValue; private static buildUpdateQuery; delete(): Promise<number | string>; static deleteByFilter<T extends Record<string, any>>(this: new (data: any) => BaseEntity<T>, filter: Partial<T>, options?: FindByFilterOptions.Options): Promise<number>; /** * Delete multiple entities by their IDs in parallel batches * * @param ids - Array of IDs to delete * @param options - Parallel execution options * @returns Number of deleted records * * @example * ```typescript * const deleted = await User.deleteByIds([1, 2, 3, 4, 5], { parallel: true }); * console.log(`Deleted ${deleted} users`); * ``` */ static deleteByIds(this: new (data: any) => BaseEntity<any>, ids: any[], options?: { parallel?: boolean; concurrency?: number; }): Promise<number>; toJson(): string; toObject(): TModel; private assignProperties; } declare class ModelUtils { private static readonly MAX_DEPTH; /** * Gets the dependency tree for models based on their relationships * Returns models in topological order (dependencies first) */ static getModelDependencyTree(modelNames: string[]): Array<{ name: string; dependencies: string[]; }>; /** * Sorts models in topological order based on their dependencies */ static sortModelsByDependencies(models: Array<{ name: string; dependencies: string[]; }>): string[]; /** * Finds the path from a child model to a parent model through relationships */ static findPathToParentModel(fromModel: string, toModel: string, maxDepth?: number): string | null; /** * Builds a nested filter object to search by a field in a parent model */ static buildNestedFilterToParent(fromModel: string, toModel: string, fieldName: string, value: any): Record<string, any>; /** * Builds include tree for nested relations based on provided configuration */ static getIncludesTree(modelName: string, relationsToInclude?: FindByFilterOptions.NestedRelations, currentDepth?: number): Promise<Record<string, any>>; /** * Gets all model names from Prisma runtime */ static getAllModelNames(): string[]; /** * Extracts unique constraints from a model using Prisma runtime * Returns an array of field name arrays that form unique constraints */ static getUniqueConstraints(modelName: string): string[][]; } /** * Utility class for processing relational data structures. */ declare class DataUtils { /** * Processes relational data by transforming nested objects and arrays into Prisma-compatible formats. * Converts objects into `connect` or `create` structures for relational integrity. * JSON fields and scalar arrays are preserved as-is without wrapping in connect/create. * @param data The original data object containing relations. * @param modelInfo Optional model information to detect JSON fields and scalar arrays * @returns A transformed object formatted for Prisma operations. */ static processRelations(data: Record<string, any>, modelInfo?: any): Record<string, any>; private static isObject; private static processRelationArray; private static processRelationObject; static normalizeRelationsToFK(data: Record<string, any>, keyTransformTemplate?: (relationName: string) => string): Record<string, any>; } /** * Supported database providers */ type DatabaseProvider = 'mysql' | 'postgresql' | 'sqlite' | 'sqlserver' | 'mongodb'; /** * Database dialect configuration for SQL generation */ interface DatabaseDialect { provider: DatabaseProvider; identifierQuote: string; booleanTrue: string; booleanFalse: string; supportsReturning: boolean; } /** * Detects the database provider from Prisma client configuration * * @param prisma - Optional PrismaClient instance (uses global if not provided) * @returns The detected database provider * @throws Error if provider cannot be detected * * @example * ```typescript * const provider = getDatabaseProvider(); * console.log(provider); // 'postgresql' or 'mysql' * ``` */ declare function getDatabaseProvider(prisma?: PrismaClient): DatabaseProvider; /** * Gets the database dialect configuration for the current provider * * @param prisma - Optional PrismaClient instance (uses global if not provided) * @returns Database dialect configuration * * @example * ```typescript * const dialect = getDatabaseDialect(); * console.log(dialect.identifierQuote); // '`' for MySQL, '"' for PostgreSQL * ``` */ declare function getDatabaseDialect(prisma?: PrismaClient): DatabaseDialect; /** * Quotes an identifier according to the database dialect * * @param identifier - The identifier to quote (table name, column name, etc.) * @param prisma - Optional PrismaClient instance * @returns Quoted identifier * * @example * ```typescript * // MySQL * quoteIdentifier('User') // '`User`' * * // PostgreSQL * quoteIdentifier('User') // '"User"' * ``` */ declare function quoteIdentifier(identifier: string, prisma?: PrismaClient): string; /** * Formats a boolean value according to the database dialect * * @param value - The boolean value * @param prisma - Optional PrismaClient instance * @returns Formatted boolean string * * @example * ```typescript * // MySQL * formatBoolean(true) // '1' * * // PostgreSQL * formatBoolean(true) // 'TRUE' * ``` */ declare function formatBoolean(value: boolean, prisma?: PrismaClient): string; /** * Performance utilities for optimizing batch operations */ /** * Database-specific batch size configurations */ declare const BATCH_SIZE_CONFIG: { readonly sqlite: { readonly createMany: 500; readonly updateMany: 500; readonly transaction: 100; }; readonly mysql: { readonly createMany: 1500; readonly updateMany: 1500; readonly transaction: 1000; }; readonly postgresql: { readonly createMany: 1500; readonly updateMany: 1500; readonly transaction: 1000; }; readonly sqlserver: { readonly createMany: 1000; readonly updateMany: 1000; readonly transaction: 1000; }; readonly mongodb: { readonly createMany: 1000; readonly updateMany: 100; readonly transaction: 100; }; }; /** * Get optimal batch size for a specific operation and database * * @param operation - The type of operation (createMany, updateMany, transaction) * @returns Optimal batch size for the current database * * @example * ```typescript * const batchSize = getOptimalBatchSize('createMany'); * for (let i = 0; i < items.length; i += batchSize) { * const batch = items.slice(i, i + batchSize); * await model.createMany({ data: batch }); * } * ``` */ declare function getOptimalBatchSize(operation: 'createMany' | 'updateMany' | 'transaction'): number; /** * Estimate memory usage for a batch operation * Helps prevent out-of-memory errors with large datasets * * @param itemCount - Number of items in the batch * @param avgItemSize - Average size of each item in bytes (default: 1KB) * @returns Estimated memory usage in MB */ declare function estimateBatchMemoryUsage(itemCount: number, avgItemSize?: number): number; /** * Check if a batch operation is safe to execute based on memory constraints * * @param itemCount - Number of items in the batch * @param maxMemoryMB - Maximum allowed memory in MB (default: 100MB) * @param avgItemSize - Average size of each item in bytes (default: 1KB) * @returns True if the operation is safe, false otherwise */ declare function isBatchSafe(itemCount: number, maxMemoryMB?: number, avgItemSize?: number): boolean; /** * Split an array into optimal batches based on database provider * * @param items - Array of items to batch * @param operation - The type of operation * @returns Array of batches * * @example * ```typescript * const batches = createOptimalBatches(users, 'createMany'); * for (const batch of batches) { * await User.createMany(batch); * } * ``` */ declare function createOptimalBatches<T>(items: T[], operation: 'createMany' | 'updateMany' | 'transaction'): T[][]; /** * Performance metrics for batch operations */ interface BatchMetrics { totalItems: number; batchCount: number; avgBatchSize: number; estimatedMemoryMB: number; startTime: number; endTime?: number; durationMs?: number; itemsPerSecond?: number; } /** * Create a batch metrics tracker * * @param totalItems - Total number of items to process * @param batchSize - Size of each batch * @returns Metrics object with tracking methods * * @example * ```typescript * const metrics = createBatchMetrics(1000, 100); * // ... perform operations * metrics.complete(); * console.log(`Processed ${metrics.itemsPerSecond} items/sec`); * ``` */ declare function createBatchMetrics(totalItems: number, batchSize: number): BatchMetrics; /** * Retry configuration for batch operations */ interface RetryConfig { maxRetries: number; initialDelayMs: number; maxDelayMs: number; backoffMultiplier: number; } /** * Default retry configuration */ declare const DEFAULT_RETRY_CONFIG: RetryConfig; /** * Execute a batch operation with exponential backoff retry * * @param operation - The operation to execute * @param config - Retry configuration * @returns Result of the operation * * @example * ```typescript * const result = await withRetry( * () => model.createMany({ data: batch }), * { maxRetries: 3, initialDelayMs: 100 } * ); * ``` */ declare function withRetry<T>(operation: () => Promise<T>, config?: Partial<RetryConfig>): Promise<T>; /** * Chunk an array into smaller arrays * More efficient than slice for large arrays * * @param array - Array to chunk * @param size - Size of each chunk * @returns Array of chunks */ declare function chunk<T>(array: T[], size: number): T[][]; /** * Calculate optimal batch size for OR conditions in WHERE clauses * Prevents "too many placeholders" errors in databases * * @param fieldsPerCondition - Number of fields in each OR condition (e.g., 2 for {email, username}) * @param safetyMargin - Safety margin as percentage (default: 0.8 = 80% of max) * @returns Optimal number of OR conditions per batch * * @example * ```typescript * // For upsert with email field (1 field per condition) * const batchSize = getOptimalOrBatchSize(1); * * // For composite unique constraint {email, tenantId} (2 fields) * const batchSize = getOptimalOrBatchSize(2); * * // Batch the OR conditions * for (let i = 0; i < orConditions.length; i += batchSize) { * const batch = orConditions.slice(i, i + batchSize); * await model.findMany({ where: { OR: batch } }); * } * ``` */ declare function getOptimalOrBatchSize(fieldsPerCondition?: number, safetyMargin?: number): number; /** * Calculate the number of placeholders needed for a set of OR conditions * Useful for validating if a query will exceed database limits * * @param orConditions - Array of OR condition objects * @returns Total number of placeholders needed * * @example * ```typescript * const orConditions = [ * { email: 'user1@example.com' }, * { email: 'user2@example.com', username: 'user2' } * ]; * * const placeholders = calculateOrPlaceholders(orConditions); * // Returns: 3 (1 field in first condition + 2 fields in second) * ``` */ declare function calculateOrPlaceholders(orConditions: Record<string, any>[]): number; /** * Check if a set of OR conditions is safe to execute without batching * * @param orConditions - Array of OR condition objects * @param safetyMargin - Safety margin as percentage (default: 0.8) * @returns True if safe to execute in single query, false if batching needed * * @example * ```typescript * const orConditions = [...]; // Large array of conditions * * if (isOrQuerySafe(orConditions)) { * // Execute in single query * await model.findMany({ where: { OR: orConditions } }); * } else { * // Need to batch * const batchSize = getOptimalOrBatchSize(1); * for (let i = 0; i < orConditions.length; i += batchSize) { * const batch = orConditions.slice(i, i + batchSize); * await model.findMany({ where: { OR: batch } }); * } * } * ``` */ declare function isOrQuerySafe(orConditions: Record<string, any>[], safetyMargin?: number): boolean; /** * @Property() Decorator * * Automatically creates private properties with getters and setters for entity fields. * This decorator uses the legacy decorator specification for maximum compatibility. * * @example * ```typescript * class User extends BaseEntity<IUser> { * @Property() declare name: string; * @Property() declare email: string; * } * * // Equivalent to: * class User extends BaseEntity<IUser> { * private _name: string; * get name() { return this._name; } * set name(value: string) { this._name = value; } * } * ``` * * @returns PropertyDecorator function */ declare function Property(): PropertyDecorator; /** * SearchUtils class for high-level search filter operations * Provides utilities for applying search filters and default filters to queries * * @class SearchUtils */ declare class SearchUtils { /** * Applies search filters to a base filter using SearchBuilder * * @param baseFilter - The base filter object to extend * @param searchOptions - Search options with string, range, and list searches * @param modelInfo - Optional Prisma model information for relation detection * @returns Combined filter with search conditions applied * * @remarks * This is a wrapper around SearchBuilder.build() for convenience * Combines the base filter with advanced search options * * @example * ```typescript * const filter = SearchUtils.applySearchFilter( * { isActive: true }, * { * stringSearch: [{ keys: ['name'], value: 'John', mode: 'LIKE' }] * } * ); * ``` */ static applySearchFilter(baseFilter: Record<string, any>, searchOptions: FindByFilterOptions.SearchOptions, modelInfo?: any): Record<string, any>; /** * Converts plain filter objects into Prisma-compatible query conditions * Automatically detects field types and applies appropriate conditions * * @param input - Plain object with field values to filter by * @param modelInfo - Optional Prisma model information for relation detection * @returns Prisma-compatible filter object * * @remarks * Automatic condition mapping: * - Strings/Numbers/Dates → { equals: value } * - Arrays → { hasEvery: value } * - Nested objects → { is: {...} } for single relations * - Nested objects → { some: {...} } for array relations (when modelInfo provided) * - Skips null, undefined, empty strings, and empty arrays * * @example * ```typescript * SearchUtils.applyDefaultFilters({ name: 'John', age: 30 }) * // Returns: { name: { equals: 'John' }, age: { equals: 30 } } * * SearchUtils.applyDefaultFilters({ author: { name: 'John' } }) * // Returns: { author: { is: { name: { equals: 'John' } } } } * ``` */ static applyDefaultFilters(input: Record<string, any>, modelInfo?: any): Record<string, any>; /** * Builds a default condition based on value type * * @param value - The value to create a condition for * @param fieldName - Optional field name for relation detection * @param modelInfo - Optional model information for relation type detection * @returns Prisma condition object or undefined for invalid values * @private * * @remarks * - Scalars (string/number/boolean/Date) → { equals: value } * - Arrays → { hasEvery: value } (or undefined if empty) * - Objects → { is: {...} } or { some: {...} } depending on relation type */ private static buildDefaultCondition; /** * Determines if a field represents an array relation in the Prisma model * * @param fieldName - The name of the field to check * @param modelInfo - Prisma model information containing field definitions * @returns True if the field is an array relation (isList: true), false otherwise * @private * * @remarks * Used to determine whether to use 'some' or 'is' for nested object filters * Array relations use 'some', single relations use 'is' */ private static isArrayRelation; /** * Gets the model info for a related field * * @param fieldName - The name of the relation field * @param modelInfo - Parent model information * @returns Model info for the related model, or undefined if not found * @private */ private static getRelationModelInfo; /** * Generates string search options for all string fields in a filter object * * @param filters - Object with field values to create search options from * @param mode - Search mode to apply (default: 'EXACT') * @param grouping - Whether to use 'and' or 'or' grouping (default: 'and') * @returns Array of string search options for all non-empty string fields * * @remarks * - Only processes string fields with non-empty values * - Useful for quickly creating search options from form data * - Each field gets its own search option * * @example * ```typescript * SearchUtils.getCustomSearchOptionsForAll( * { name: 'John', email: 'john@example.com' }, * 'LIKE', * 'or' * ); * // Returns: [ * // { keys: ['name'], value: 'John', mode: 'LIKE', grouping: 'or' }, * // { keys: ['email'], value: 'john@example.com', mode: 'LIKE', grouping: 'or' } * // ] * ``` */ static getCustomSearchOptionsForAll(filters: Record<string, any>, mode?: "LIKE" | "EXACT" | "STARTS_WITH" | "ENDS_WITH", grouping?: "and" | "or"): FindByFilterOptions.StringSearch[]; } /** * SearchBuilder class for constructing complex search filters * Combines multiple search conditions into a single filter object * * @class SearchBuilder */ declare class SearchBuilder { /** * Builds a complete search filter by applying string, range, and list searches * * @param baseFilter - The base filter object to extend * @param options - Search options containing string, range, and list searches * @param modelInfo - Optional Prisma model information for relation detection * @returns Combined filter object with all search conditions applied * * @example * ```typescript * const filter = SearchBuilder.build( * { isActive: true }, * { * stringSearch: [{ keys: ['name'], value: 'John', mode: 'LIKE' }], * rangeSearch: [{ keys: ['age'], min: 18, max: 65 }] * } * ); * // Returns: { isActive: true, name: { contains: 'John' }, age: { gte: 18, lte: 65 } } * ``` */ static build(baseFilter: Record<string, any>, options: FindByFilterOptions.SearchOptions, modelInfo?: any): Record<string, any>; /** * Applies a specific type of search condition to the filter * Handles both AND and OR grouping of conditions * * @template T - Type of search condition with optional keys and grouping * @param filter - The filter object to modify * @param conditions - Array of search conditions to apply * @param buildCondition - Function to build the condition object from the search option * @param modelInfo - Optional Prisma model information for relation detection * @private * * @remarks * - Skips invalid conditions using ConditionUtils.isValid() * - For OR grouping: adds conditions to filter.OR array and tracks paths for cleanup * - For AND grouping: assigns conditions directly to filter using ObjectUtils.assign() * - Cleans up duplicate paths that appear in OR conditions */ private static apply; } /** * ConditionUtils class for validating and building search conditions * Provides utilities for creating Prisma-compatible query conditions * * @class ConditionUtils */ declare class ConditionUtils { /** * Validates if a value is considered valid for filtering * * @param value - The value to validate * @returns True if the value is valid, false otherwise * * @remarks * - Returns false for: null, undefined, empty strings (including whitespace-only), empty arrays * - Returns false for objects where all nested values are invalid * - Returns true for: non-empty strings, numbers (including 0), booleans, non-empty arrays, valid objects * * @example * ```typescript * ConditionUtils.isValid('hello') // true * ConditionUtils.isValid('') // false * ConditionUtils.isValid(0) // true * ConditionUtils.isValid([]) // false * ConditionUtils.isValid({ key: 'val' }) // true * ``` */ static isValid(value: any): boolean; /** * Creates a Prisma string condition based on the search mode * * @param option - String search options with value and mode * @returns Prisma condition object for string matching * * @remarks * Supported modes: * - LIKE: Creates { contains: value } for substring matching * - STARTS_WITH: Creates { startsWith: value } * - ENDS_WITH: Creates { endsWith: value } * - EXACT (default): Creates { equals: value } for exact matching * * @example * ```typescript * ConditionUtils.string({ value: 'John', mode: 'LIKE' }) * // Returns: { contains: 'John' } * * ConditionUtils.string({ value: 'John', mode: 'STARTS_WITH' }) * // Returns: { startsWith: 'John' } * ``` */ static string(option: FindByFilterOptions.StringSearch): any; /** * Creates a Prisma range condition for numeric or date filtering * * @param option - Range search options with min and/or max values * @returns Prisma condition object with gte/lte operators * * @remarks * - If only min is provided: returns { gte: min } * - If only max is provided: returns { lte: max } * - If both provided: returns { gte: min, lte: max } * - If neither provided: returns empty object * * @example * ```typescript * ConditionUtils.range({ min: 18, max: 65 }) * // Returns: { gte: 18, lte: 65 } * * ConditionUtils.range({ min: new Date('2024-01-01') }) * // Returns: { gte: Date('2024-01-01') } * ``` */ static range(option: FindByFilterOptions.RangeSearch): any; /** * Creates a Prisma list condition for array matching * * @param option - List search options with array of values and mode * @returns Prisma condition object based on mode * * @remarks * Supported modes: * - IN (default): Creates { in: values } for matching any value in the list * - NOT_IN: Creates { notIn: values } for excluding values in the list * - HAS_SOME: Creates { hasSome: values } for array fields that contain some values * - HAS_EVERY: Creates { hasEvery: values } for array fields that contain all values * * @example * ```typescript * ConditionUtils.list({ values: ['active', 'pending'], mode: 'IN' }) * // Returns: { in: ['active', 'pending'] } * * ConditionUtils.list({ values: ['deleted'], mode: 'NOT_IN' }) * // Returns: { notIn: ['deleted'] } * * ConditionUtils.list({ values: ['tag1', 'tag2'], mode: 'HAS_SOME' }) * // Returns: { hasSome: ['tag1', 'tag2'] } * * ConditionUtils.list({ values: ['required1', 'required2'], mode: 'HAS_EVERY' }) * // Returns: { hasEvery: ['required1', 'required2'] } * ``` */ static list(option: FindByFilterOptions.ListSearch): any; } /** * ObjectUtils class for nested object manipulation * Provides utilities for working with deeply nested object structures * * @class ObjectUtils */ declare class ObjectUtils { /** * Assigns a value to a nested path in an object, creating intermediate objects as needed * * @param target - The object to modify * @param path - Dot-separated path to the property (e.g., 'user.profile.name') * @param value - The value to assign * @param modelInfo - Optional Prisma model information for relation-aware structure creation * * @remarks * - Creates intermediate objects if they don't exist * - With modelInfo: wraps new relations with 'is'/'some' based on field type * - Without modelInfo: creates plain nested objects * - Intelligently merges with existing Prisma filter structures (is/some) * - Preserves existing sibling properties * - Handles nested Prisma relation filters correctly * * @example * ```typescript * const obj = {}; * ObjectUtils.assign(obj, 'user.profile.name', 'John'); * // obj is now: { user: { profile: { name: 'John' } } } * * // With existing Prisma filter structure: * const filter = { group: { is: { id: 1 } } }; * ObjectUtils.assign(filter, 'group.course.name', 'Math'); * // filter is now: { group: { is: { id: 1, course: { name: 'Math' } } } } * * // With modelInfo (creates proper Prisma structures): * const filter = {}; * ObjectUtils.assign(filter, 'posts.author.name', { contains: 'John' }, modelInfo); * // filter is now: { posts: { some: { author: { is: { name: { contains: 'John' } } } } } } * ``` */ static assign(target: Record<string, any>, path: string, value: any, modelInfo?: any): void; /** * Navigates into an existing object structure, detecting Prisma wrappers * @private */ private static navigateIntoExisting; /** * Creates a new structure for a key, with Prisma awareness if modelInfo provided * @private */ private static createNewStructure; /** * Builds a nested object from a path and value * * @param path - Dot-separated path (e.g., 'user.profile.name') * @param value - The value to place at the end of the path * @returns A nested object with the value at the specified path * * @remarks * Constructs the object from the inside out, starting with the value * and wrapping it in nested objects according to the path * * @example * ```typescript * ObjectUtils.build('user.profile.name', 'John') * // Returns: { user: { profile: { name: 'John' } } } * * ObjectUtils.build('status', 'active') * // Returns: { status: 'active' } * ``` */ static build(path: string, value: any): Record<string, any>; /** * Builds a nested object from a path and value with Prisma relation awareness * Wraps array relations with 'some' and single relations with 'is' * * @param path - Dot-separated path (e.g., 'group.groupMembers.user.idNumber') * @param value - The value to place at the end of the path * @param modelInfo - Optional Prisma model information for relation detection * @returns A nested object with proper