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
TypeScript
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