import { DatabaseClient, QueryResult } from '../database/database-client.interface'; import { TableBuilder, TableSchema, InferTableType } from '../schema/table-builder'; import { UnwrapDbColumns, InsertData, UpdateData, UpsertData, ExtractDbColumns, ExtractDbColumnKeys } from './db-column'; import { DbEntity, EntityConstructor } from './entity-base'; import { DbModelConfig } from './model-config'; import { Condition, SqlFragment, UnwrapSelection, FieldRef } from '../query/conditions'; import { ResolveCollectionResults, SelectQueryBuilder, QueryBuilder } from '../query/query-builder'; import { PreparedQuery } from '../query/prepared-query'; import { InferRowType } from '../schema/row-type'; import { DbSchemaManager } from '../migration/db-schema-manager'; import { DbSequence, SequenceConfig } from '../schema/sequence-builder'; import type { DbCte } from '../query/cte-builder'; import { CteRootQueryBuilder } from '../query/cte-root-query'; import type { UnionQueryBuilder } from '../query/union-builder'; import type { FutureQuery, FutureSingleQuery, FutureCountQuery } from '../query/future-query'; /** * Collection aggregation strategy type */ export type CollectionStrategyType = 'cte' | 'temptable' | 'lateral'; /** * Column information returned by getColumns() * * @typeParam TEntity - The entity type, used to strongly type propertyName as one of the entity's column keys * * @example * ```typescript * // With typed entity * const columns: ColumnInfo[] = db.users.getColumns(); * columns[0].propertyName; // Type: 'id' | 'username' | 'email' | ... (only column keys) * * // Get just the property names as a typed array * const keys = db.users.getColumnKeys(); * // Type: Array<'id' | 'username' | 'email' | ...> * ``` */ export interface ColumnInfo { /** Property name in the entity class (TypeScript name) - typed as keyof entity columns */ propertyName: ExtractDbColumnKeys; /** Column name in the database */ columnName: string; /** SQL type (e.g., 'integer', 'varchar', 'timestamp') */ type: string; /** Whether the column is a primary key */ isPrimaryKey: boolean; /** Whether the column is auto-incremented (identity) */ isAutoIncrement: boolean; /** Whether the column is nullable */ isNullable: boolean; /** Whether the column has a unique constraint */ isUnique: boolean; /** Default value if any */ defaultValue?: any; /** Whether this is a navigation property (only present when includeNavigation is true) */ isNavigation?: boolean; /** Navigation type: 'one' for reference, 'many' for collection (only for navigation properties) */ navigationType?: 'one' | 'many'; /** Target table name (only for navigation properties) */ targetTable?: string; } /** * Order direction for orderBy clauses */ export type OrderDirection = 'ASC' | 'DESC'; /** * A single field that can be used in orderBy. * At runtime, this is analyzed to extract the column reference. * The type is intentionally broad to support various usage patterns. */ export type OrderableField = T; /** * Order by specification with direction - a tuple of [field, direction] */ export type OrderByTuple = [T, OrderDirection]; /** * Order by selector result - can be a single field, array of fields, or array of [field, direction] tuples */ export type OrderByResult = T | T[] | Array>; /** * The section a log message belongs to — *what* is being logged — so a custom * logger can route or filter by category (e.g. drop `'sql'`, keep `'error'`). * * - `'sql'` — the SQL query text * - `'params'` — query parameters * - `'timing'` — execution time / time-trace output * - `'slow'` — a slow-query notice (see `onQueryTakingTooLong`) * - `'info'` — general informational messages (migrations, etc.) * - `'warn'` — warnings * - `'error'` — errors */ export type LogSection = 'sql' | 'params' | 'timing' | 'slow' | 'info' | 'warn' | 'error'; /** * @deprecated Renamed to {@link LogSection}. The logger's second argument is now * the log *section* (what is being logged), not a severity level. Kept as an alias. */ export type LogLevel = LogSection; /** * Default logger used when no custom `logger` is provided. Routes by section to * the appropriate console method. */ export declare function defaultLogger(message: string, section?: LogSection): void; /** * Query execution options */ export interface QueryOptions { /** Enable SQL query logging */ logQueries?: boolean; /** * Custom logger function (defaults to {@link defaultLogger}). The second * argument is the {@link LogSection} — what is being logged ('sql', 'params', * 'timing', 'error', ...) — so the logger can filter or route by category. */ logger?: (message: string, section?: LogSection) => void; /** Log query execution time */ logExecutionTime?: boolean; /** Log query parameters */ logParameters?: boolean; /** Collection aggregation strategy (default: 'lateral') */ collectionStrategy?: CollectionStrategyType; /** * Disable automatic mapper transformations (fromDriver/toDriver). * When enabled, raw database values are returned without transformation. * Use this for performance-critical queries where you'll handle mapping manually. * Default: false */ disableMappers?: boolean; /** * Enable binary protocol for query execution (when supported by driver). * Binary protocol can improve performance by avoiding string conversions. * Currently supported by: pg library with rowMode='array' or binary format. * Default: false (uses text protocol) */ useBinaryProtocol?: boolean; /** * Return raw database result without any ORM processing. * When enabled, the raw rows from the database driver are returned as-is, * skipping all ORM transformations (mapping, result shaping, etc.). * Useful for debugging or when you need direct access to the database result. * Default: false */ rawResult?: boolean; /** * Enable detailed time tracing for query phases. * When enabled, logs timing information for: * - Query building (SQL generation, context setup) * - Query execution (database round-trip) * - Result processing (transformation, mapping, merging) * Useful for performance debugging and optimization. * Default: false */ traceTime?: boolean; /** * Callback invoked when a query's execution time exceeds its expected * threshold (the default {@link longRunningQueryThreshold}, or a per-query * `.expectedExecutionTime(ms)` override). Providing this callback enables * slow-query detection — which captures a call stack per query, so leave it * unset when you don't need it. * * The query still runs to completion (this is a diagnostic notice, not a * cancellation — use `.withTimeout()` to actually cancel). The callback * receives the SQL, params, duration, threshold, and the **user** call stack * that initiated the query (internal linkgress frames removed), so the top * frame is the code that called `.toList()` / `.firstOrDefault()` / etc. * * Errors thrown by the callback are swallowed so they cannot affect the query. */ onQueryTakingTooLong?: (info: SlowQueryInfo) => void; /** * Threshold in milliseconds above which a query is considered "too long" and * {@link onQueryTakingTooLong} fires. Overridable per query with * `.expectedExecutionTime(ms)`. Default: 10000 (10s). */ longRunningQueryThreshold?: number; } /** * Details passed to the {@link QueryOptions.onQueryTakingTooLong} callback when a * query runs longer than its expected execution time. */ export interface SlowQueryInfo { /** The SQL text that was executed. */ sql: string; /** The query parameters, if any. */ params?: any[]; /** Actual execution time in milliseconds. */ durationMs: number; /** The threshold that was exceeded, in milliseconds. */ thresholdMs: number; /** * Call stack of the user code that initiated the query, with linkgress * internal frames removed — the top frame is the call site of the terminal * method (`.toList()`, `.firstOrDefault()`, `.count()`, ...). Empty string if * no user frames could be resolved. */ stack: string; } /** * Options for {@link DataContext.transaction}. */ export interface TransactionOptions { /** * Statement timeout (ms) applied to EVERY statement in the transaction via * `SET LOCAL statement_timeout`, overriding the connection-level default for the * duration of the transaction (it auto-resets at COMMIT/ROLLBACK). Use this for * long-running units of work (batch jobs, exports) that would otherwise hit a * tight global default — it also covers bulk inserts/upserts that don't expose a * per-query `.withTimeout()`. `0` disables the timeout for the transaction. On * timeout a {@link QueryTimeoutError} is thrown. */ timeoutMs?: number; /** * Slow-query "expected execution time" (ms) for statements in this transaction; * a statement running longer fires `onQueryTakingTooLong` (diagnostic only, no * cancellation). Defaults to `timeoutMs` when omitted, so a transaction with a * deliberately raised timeout doesn't also trip the global slow-query threshold. */ expectedExecutionMs?: number; } /** * Time trace entry for a single operation */ export interface TimeTraceEntry { phase: string; operation: string; durationMs: number; startTime: number; endTime: number; details?: Record; } /** * Complete time trace for a query execution */ export interface QueryTimeTrace { totalMs: number; phases: { queryBuild?: number; queryExecution?: number; resultProcessing?: number; }; entries: TimeTraceEntry[]; rowCount?: number; } /** * Time tracer utility for measuring query phases */ export declare class TimeTracer { private enabled; private logger?; private entries; private startTime; private currentPhase; private phaseStartTime; private phases; constructor(enabled: boolean, logger?: ((message: string, section?: LogSection) => void) | undefined); /** * Start timing a phase */ startPhase(phase: string): void; /** * End timing a phase */ endPhase(): number; /** * Time a specific operation within a phase */ trace(operation: string, fn: () => T, details?: Record): T; /** * Time an async operation within a phase */ traceAsync(operation: string, fn: () => Promise, details?: Record): Promise; /** * Get the complete trace */ getTrace(rowCount?: number): QueryTimeTrace; /** * Log the trace summary */ logSummary(rowCount?: number): void; } /** * @deprecated Use QueryOptions instead */ export type LoggingOptions = QueryOptions; /** * Query executor with optional logging */ export declare class QueryExecutor { private client; private options; /** * Per-query timeout override (ms) set via `.withTimeout()`. Threaded down to * the driver as `QueryExecutionOptions.timeoutMs`. `undefined` means no * override (the connection-level default, if any, applies). */ private overrideTimeoutMs?; /** * Per-query "expected execution time" override (ms) set via * `.expectedExecutionTime()`. If the query runs longer than this, * `onQueryTakingTooLong` fires. `undefined` means use the context default. */ private overrideExpectedMs?; constructor(client: DatabaseClient, options?: QueryOptions, /** * Per-query timeout override (ms) set via `.withTimeout()`. Threaded down to * the driver as `QueryExecutionOptions.timeoutMs`. `undefined` means no * override (the connection-level default, if any, applies). */ overrideTimeoutMs?: number | undefined, /** * Per-query "expected execution time" override (ms) set via * `.expectedExecutionTime()`. If the query runs longer than this, * `onQueryTakingTooLong` fires. `undefined` means use the context default. */ overrideExpectedMs?: number | undefined); /** * Build the per-query execution options (binary protocol + timeout override), * or `undefined` when neither is set so the driver takes its fast path. */ private buildExecutionOptions; /** * Return a new executor sharing this one's client and options but applying the * given per-query timeout override (ms). Pass `0` to disable the timeout for * the derived executor. Used by `.withTimeout()` on the query builders. */ withTimeout(timeoutMs: number): QueryExecutor; /** * Return a new executor that flags this query as expected to finish within * `expectedMs` — if it runs longer, `onQueryTakingTooLong` fires. Used by * `.expectedExecutionTime()` on the query builders. */ withExpectedExecutionTime(expectedMs: number): QueryExecutor; /** Whether slow-query detection is active (a callback is configured). */ private get slowQueryEnabled(); /** Effective "too long" threshold for the current query (ms). */ private get expectedExecutionMs(); /** * Begin timing/stack capture for a query. Returns `undefined` when neither * execution-time logging nor slow-query detection is active (zero overhead). * The stack is captured here — synchronously, inside the caller's call chain — * so the slow-query callback can report the user's code, not an async frame. */ private beginTiming; /** * Finish timing: log execution time (if enabled) and fire the slow-query * callback (if enabled and the expected threshold was exceeded). */ private finishTiming; query(sql: string, params?: any[]): Promise; /** * Execute a multi-statement query using the simple protocol (no parameters) * Only available for clients that support it (e.g., PostgresClient) */ querySimple(sql: string): Promise; /** * Execute a multi-statement query and return ALL result sets * Only available for PostgresClient */ querySimpleMulti(sql: string): Promise; /** * Get the query options for this executor */ getOptions(): QueryOptions; } /** * Conflict target for upsert operations */ export interface ConflictTarget { columns?: string[]; constraint?: string; } /** * Insert configuration for bulk operations */ export interface InsertConfig { /** * Size of insert chunk. If not provided, auto-detected based on max PG query parameters limit */ chunkSize?: number; /** * Use OVERRIDING SYSTEM VALUE to allow inserting into identity/serial columns */ overridingSystemValue?: boolean; /** * Skip rows that would violate unique constraints (ON CONFLICT DO NOTHING) */ onConflictDoNothing?: boolean; } /** * Upsert configuration */ export interface UpsertConfig { /** * Size of insert chunk for bulk upserts */ chunkSize?: number; /** * Primary key columns for conflict detection. If not specified, table's primary keys are used */ primaryKey?: string | string[]; /** * Use OVERRIDING SYSTEM VALUE (auto-detected if not specified) */ overridingSystemValue?: boolean; /** * WHERE clause for the conflict target */ targetWhere?: string; /** * WHERE clause for the UPDATE SET */ setWhere?: string; /** * Reference item to detect columns. If not specified, first value from array is used */ referenceItem?: any; /** * List of column names that should be updated on conflict. If not specified, all non-PK columns are updated */ updateColumns?: string[]; /** * Filter function to determine if column should be updated on conflict */ updateColumnFilter?: (columnName: string) => boolean; } /** * Returning clause configuration * - undefined: no RETURNING clause (returns void) * - true: return all columns * - selector function: return selected columns */ export type ReturningConfig = undefined | true | ((entity: TEntity) => TResult); /** * Helper type to infer the result type based on ReturningConfig * Note: Uses conditional types to properly infer return types */ export type ReturningResult = TReturning extends undefined ? void : TReturning extends true ? TEntity : TReturning extends (entity: TEntity) => infer R ? R : TReturning extends (...args: any[]) => infer R ? R : never; /** * Base type for returning option - used in method signatures */ export type ReturningOption = undefined | true | ((entity: TEntity) => any); /** * Fluent insert operation that can be awaited directly or chained with .returning() * * @example * ```typescript * // No returning (default) - returns void * await db.users.insert({ username: 'alice' }); * * // With returning() - returns full entity * const user = await db.users.insert({ username: 'alice' }).returning(); * * // With returning(selector) - returns selected columns * const { id } = await db.users.insert({ username: 'alice' }).returning(u => ({ id: u.id })); * ``` */ export interface FluentInsert extends PromiseLike { /** Return all columns from the inserted row */ returning(): PromiseLike>; /** Return selected columns from the inserted row */ returning(selector: (entity: EntityQuery) => TResult): PromiseLike>; } /** * Fluent insert many operation */ export interface FluentInsertMany extends PromiseLike { /** Return all columns from the inserted rows */ returning(): PromiseLike[]>; /** Return selected columns from the inserted rows */ returning(selector: (entity: EntityQuery) => TResult): PromiseLike[]>; } /** * Fluent update operation */ export interface FluentUpdate extends PromiseLike { /** Return all columns from the updated rows */ returning(): PromiseLike[]>; /** Return selected columns from the updated rows */ returning(selector: (entity: EntityQuery) => TResult): PromiseLike[]>; } /** * Fluent bulk update operation */ export interface FluentBulkUpdate extends PromiseLike { /** Return all columns from the updated rows */ returning(): PromiseLike[]>; /** Return selected columns from the updated rows */ returning(selector: (entity: EntityQuery) => TResult): PromiseLike[]>; } /** * Fluent upsert operation */ export interface FluentUpsert extends PromiseLike { /** Return all columns from the upserted rows */ returning(): PromiseLike[]>; /** Return selected columns from the upserted rows */ returning(selector: (entity: EntityQuery) => TResult): PromiseLike[]>; } /** * Fluent delete operation for SelectQueryBuilder * Used with db.table.where(...).delete() */ export interface FluentDelete extends PromiseLike { /** Return the number of deleted rows */ affectedCount(): PromiseLike; /** Return all columns from the deleted rows */ returning(): PromiseLike; /** Return selected columns from the deleted rows */ returning(selector: (row: TSelection) => TResult): PromiseLike; } /** * Fluent update operation for SelectQueryBuilder * Used with db.table.where(...).update(data) */ export interface FluentQueryUpdate extends PromiseLike { /** Return the number of updated rows */ affectedCount(): PromiseLike; /** Return all columns from the updated rows */ returning(): PromiseLike; /** Return selected columns from the updated rows */ returning(selector: (row: TSelection) => TResult): PromiseLike; } /** * Insert builder for upsert operations */ export declare class InsertBuilder { private schema; private client; private executor?; private dataArray; private conflictTarget?; private conflictAction; private updateColumns?; private updateColumnFilter?; private targetWhereClause?; private setWhereClause?; private overridingSystemValue; constructor(schema: TSchema, client: DatabaseClient, executor?: QueryExecutor | undefined); /** * Set the values to insert (single row or multiple rows) */ values(data: Partial> | Partial>[]): this; /** * Specify conflict target (columns or constraint name) */ onConflict(target?: ConflictTarget | string[]): this; /** * Do nothing on conflict */ doNothing(): this; /** * Update on conflict (upsert) */ doUpdate(options?: { set?: Partial>; where?: string; updateColumns?: string[]; updateColumnFilter?: (columnName: string) => boolean; }): this; /** * Set target WHERE clause for ON CONFLICT */ targetWhere(where: string): this; /** * Enable OVERRIDING SYSTEM VALUE */ setOverridingSystemValue(value?: boolean): this; /** * Execute the insert/upsert */ execute(): Promise[]>; } /** * Table accessor with query methods */ export declare class TableAccessor> { private tableBuilder; private client; private schemaRegistry; private executor?; private collectionStrategy?; private schema; constructor(tableBuilder: TBuilder, client: DatabaseClient, schemaRegistry: Map, executor?: QueryExecutor | undefined, collectionStrategy?: CollectionStrategyType | undefined); /** * Configure query options for the current query chain * Returns a new TableAccessor instance with the specified options * * @example * ```typescript * const results = await db.users * .withQueryOptions({ logQueries: true, collectionStrategy: 'temptable' }) * .select(u => ({ id: u.id, name: u.username })) * .toList(); * ``` */ withQueryOptions(options: QueryOptions): TableAccessor; /** * Set a per-query timeout (ms) applied to every query and CRUD operation * started from the returned accessor. Each such query is wrapped individually * (`SET LOCAL statement_timeout`); pass `0` to disable. Overrides the * connection-level default. On timeout a `QueryTimeoutError` is thrown. * * @example * await db.users.withTimeout(5000).where(u => gt(u.id, 0)).toList(); */ withTimeout(timeoutMs: number): TableAccessor; /** * Mark queries started from the returned accessor as expected to finish within * `expectedMs` (ms). If a query runs longer, the context's * `onQueryTakingTooLong` callback fires — the query is NOT cancelled (use * `.withTimeout()` for that). Overrides the context's `longRunningQueryThreshold`. */ expectedExecutionTime(expectedMs: number): TableAccessor; /** * Start a select query with automatic type inference * UnwrapSelection extracts the value types from SqlFragment expressions */ select(selector: (row: InferRowType) => TSelection): SelectQueryBuilder>; /** * Add WHERE condition before select */ where(condition: (row: InferRowType) => Condition): QueryBuilder>; /** * Add CTEs (Common Table Expressions) to the query */ with(...ctes: DbCte[]): SelectQueryBuilder>; /** * Left join with another table and selector */ leftJoin(rightTable: { _getSchema: () => TableSchema; } | import('../query/subquery').Subquery, condition: (left: InferRowType, right: TRight) => Condition, selector: (left: InferRowType, right: TRight) => TSelection, alias?: string): SelectQueryBuilder>; /** * Inner join with another table or subquery and selector */ innerJoin(rightTable: { _getSchema: () => TableSchema; } | import('../query/subquery').Subquery, condition: (left: InferRowType, right: TRight) => Condition, selector: (left: InferRowType, right: TRight) => TSelection, alias?: string): SelectQueryBuilder>; /** * Get table schema (internal use for joins) */ _getSchema(): TableSchema; /** * Get table schema */ getSchema(): TableSchema; /** * Get table name */ getTableName(): string; /** * Insert a row */ insert(data: Partial>): Promise>; /** * Bulk insert with advanced configuration */ insertBulk(value: Partial> | Partial>[], insertConfig?: InsertConfig): Promise[]>; /** * Insert a single chunk (internal method) */ private insertBulkSingle; /** * Upsert with advanced configuration */ upsertBulk(values: Partial>[], config?: UpsertConfig): Promise[]>; /** * Upsert a single chunk (internal method) */ private upsertBulkSingle; /** * Insert with conflict resolution (upsert) */ onConflictDoNothing(): InsertBuilder; /** * Insert with conflict resolution (upsert) - start building the upsert query */ values(data: Partial> | Partial>[]): InsertBuilder; /** * Update rows */ update(id: any, data: Partial>): Promise | null>; /** * Delete a row by id */ delete(id: any): Promise; } /** * Schema definition for DataContext */ export type ContextSchema = { [tableName: string]: TableBuilder; }; /** * Infer table accessor types from schema with proper relation types */ export type InferContextSchema = { [K in keyof T]: T[K] extends TableBuilder ? TableAccessor : never; }; /** * DataContext - main entry point for database operations */ export declare class DataContext { protected client: DatabaseClient; private schemaRegistry; private tableAccessors; private executor?; private queryOptions?; constructor(client: DatabaseClient, schema: TSchema, queryOptions?: QueryOptions); /** * Initialize schema and create table accessors */ private initializeSchema; /** * Get table accessor by name * When in a transaction, creates a fresh accessor with the transactional client */ getTable(name: K): InferContextSchema[K]; /** * Execute raw SQL query with optional type parameter for results * * @example * ```typescript * // Untyped query * const result = await db.query('SELECT * FROM users'); * * // Typed query - returns T[] * const users = await db.query<{ id: number; name: string }>('SELECT id, name FROM users'); * * // With parameters * const user = await db.query<{ id: number }>('SELECT id FROM users WHERE name = $1', ['alice']); * ``` */ query(sql: string, params?: any[]): Promise; /** * Start a query whose FROM root is a {@link DbCte} (rather than an entity * table), enabling join shapes the entity-anchored * `db..with(...).leftJoin(cte, …)` path cannot express — notably * `FULL OUTER` / `RIGHT` / `CROSS` joins and `ON TRUE` predicates between two * CTEs. * * @example * ```typescript * const cteBuilder = new DbCteBuilder(); * const spend = cteBuilder.with('spend', * db.orders * .where(o => and(eq(o.userId, userId), eq(o.status, OrderState.PAID))) * .select(o => ({ currency: o.currency, totalPrice: o.totalPrice })) * .groupBy(o => ({ currency: o.currency })) * .select(g => ({ currency: g.key.currency, totalPrice: g.sum(o => o.totalPrice) })) * ); * const tier = cteBuilder.with('current_tier', * db.tierAssignments * .where(t => and(eq(t.userId, userId), eq(t.isCurrent, true))) * .select(t => ({ currentTierId: t.tierId })) * .limit(1) * ); * * const rows = await db * .selectFromCte(spend.cte) * .fullOuterJoin(tier.cte, onTrue()) * .select((s, t) => ({ * currency: s.currency, * totalPrice: s.totalPrice, * currentTierId: t.currentTierId, * })) * .toList(); * ``` */ selectFromCte>(rootCte: DbCte): CteRootQueryBuilder; /** * Get the underlying database client. * Useful for advanced operations like multi-statement queries in migrations. * * @example * ```typescript * // Execute multi-statement SQL * await db.getClient().querySimple(` * ALTER TABLE users ADD COLUMN new_field TEXT; * CREATE INDEX idx_users_new_field ON users(new_field); * `); * ``` */ getClient(): DatabaseClient; /** * Execute in transaction * Creates a scoped transactional context to avoid race conditions with concurrent transactions. * Each transaction gets its own isolated context instance with fresh table accessors. */ transaction(fn: (ctx: this) => Promise, options?: TransactionOptions): Promise; /** * Creates a scoped copy of this context with a different client. * Used internally for transaction isolation to prevent race conditions * when multiple transactions run concurrently. */ protected createTransactionalContext(txClient: DatabaseClient, queryOptionsOverride?: Partial): this; /** * Get schema manager for create/drop operations and automatic migrations. * * Pass `{ concurrentIndexes: true }` to force every index created during * `ensureCreated()` / `migrate()` to use `CREATE INDEX CONCURRENTLY` without * having to mark each index with `.concurrent()`. This must run outside a * transaction — PostgreSQL disallows `CONCURRENTLY` inside a transaction. * * `recreateChangedIndexes` (default `true`) makes `migrate()` drop + recreate * a same-named index whose definition no longer matches the model (operator * class, expressions, method, uniqueness, columns, partial predicate). Set it * to `false` for the legacy name-only behavior. */ getSchemaManager(options?: { concurrentIndexes?: boolean; recreateChangedIndexes?: boolean; }): DbSchemaManager; /** * Close database connection */ dispose(): Promise; } /** * Typed upsert configuration for entities */ export type EntityUpsertConfig = { /** * Size of insert chunk for bulk upserts */ chunkSize?: number; /** * Primary key columns for conflict detection. If not specified, table's primary keys are used * Can be specified as property names (strings) or using lambda selectors */ primaryKey?: keyof ExtractDbColumns | (keyof ExtractDbColumns)[] | ((entity: TEntity) => any); /** * Use OVERRIDING SYSTEM VALUE (auto-detected if not specified) */ overridingSystemValue?: boolean; /** * WHERE clause for the conflict target */ targetWhere?: string; /** * WHERE clause for the UPDATE SET */ setWhere?: string; /** * Reference item to detect columns. If not specified, first value from array is used */ referenceItem?: any; /** * List of columns that should be updated on conflict. Can be property names or lambda selectors */ updateColumns?: (keyof ExtractDbColumns)[] | ((entity: TEntity) => Partial>); /** * Filter function to determine if column should be updated on conflict */ updateColumnFilter?: (columnName: string) => boolean; }; /** * Type helper to detect if a type is a class instance (has prototype methods) * vs a plain data object. Used to prevent Date, Map, Set, etc. from being * treated as DbEntity. * Excludes DbColumn and SqlFragment which have valueOf but are not value types. */ type IsClassInstance = T extends { __isDbColumn: true; } ? false : T extends SqlFragment ? false : T extends { valueOf(): infer V; } ? V extends T ? true : V extends number | string | boolean | bigint | symbol ? true : false : false; /** * Check for types with known class method signatures */ type HasClassMethods = T extends { getTime(): number; } ? true : T extends { size: number; has(value: any): boolean; } ? true : T extends { byteLength: number; } ? true : T extends { then(onfulfilled?: any): any; } ? true : T extends { message: string; name: string; } ? true : T extends { exec(string: string): any; } ? true : false; /** * Combined check for value types that should not be treated as DbEntity */ type IsValueType = IsClassInstance extends true ? true : HasClassMethods extends true ? true : false; /** * Type helper to convert plain object values to FieldRefs for use in conditions * This is used when TSelection is not a DbEntity but needs to be used in where/join conditions */ type ToFieldRefs = T extends object ? IsValueType extends true ? FieldRef : { [K in keyof T]: FieldRef; } : FieldRef; /** * Type helper to build entity query type with navigation support * Preserves class instances (Date, Map, Set, etc.) as-is without recursively mapping them */ export type EntityQuery = { [K in keyof TEntity]: TEntity[K] extends (infer U)[] | undefined ? U extends DbEntity ? EntityCollectionQuery : TEntity[K] : IsValueType> extends true ? TEntity[K] : TEntity[K] extends DbEntity | undefined ? EntityQuery> : TEntity[K]; }; /** * Collection query builder type for navigation collections */ export interface EntityCollectionQuery { select(selector: (item: EntityQuery) => TSelection): EntityCollectionQueryWithSelect; selectDistinct(selector: (item: EntityQuery) => TSelection): EntityCollectionQueryWithSelect; where(condition: (item: EntityQuery) => Condition): this; orderBy(selector: (item: EntityQuery) => T): this; orderBy(selector: (item: EntityQuery) => T[]): this; orderBy(selector: (item: EntityQuery) => Array<[T, OrderDirection]>): this; limit(count: number): this; offset(count: number): this; min(selector: (item: EntityQuery) => TSelection): SqlFragment; max(selector: (item: EntityQuery) => TSelection): SqlFragment; sum(selector: (item: EntityQuery) => TSelection): SqlFragment; count(): SqlFragment; exists(): SqlFragment; selectMany(selector: (item: EntityQuery) => EntityCollectionQuery): EntityCollectionQuery; toNumberList(asName?: string): number[]; toStringList(asName?: string): string[]; toList(asName?: string): TEntity[]; firstOrDefault(asName?: string): TEntity | null; } export interface EntityCollectionQueryWithSelect { where(condition: (item: EntityQuery) => Condition): this; orderBy(selector: (item: TSelection) => T): this; orderBy(selector: (item: TSelection) => T[]): this; orderBy(selector: (item: TSelection) => Array<[T, OrderDirection]>): this; limit(count: number): this; offset(count: number): this; min(): Promise; max(): Promise; sum(): Promise; count(): Promise; exists(): Promise; toNumberList(asName?: string): number[]; toStringList(asName?: string): string[]; toList(asName?: string): TSelection[]; firstOrDefault(asName?: string): TSelection | null; } /** * Interface for queryable entity collections that can be filtered with .where() * Use this type when you need to store a query in a variable and add more .where() conditions. * * @example * ```typescript * let query: IEntityQueryable = db.users; * if (onlyActive) { * query = query.where(u => eq(u.isActive, true)); * } * if (minAge) { * query = query.where(u => gte(u.age, minAge)); * } * const results = await query.toList(); * ``` */ export interface IEntityQueryable { /** * Set a per-query timeout (ms) for this query, overriding the connection-level * default. Only this query is wrapped (`SET LOCAL statement_timeout`); pass `0` * to disable. On timeout a `QueryTimeoutError` is thrown. */ withTimeout(timeoutMs: number): IEntityQueryable; /** * Mark this query as expected to finish within `expectedMs` (ms). If it runs * longer, the context's `onQueryTakingTooLong` callback fires (the query is * NOT cancelled). Overrides the context's `longRunningQueryThreshold`. */ expectedExecutionTime(expectedMs: number): IEntityQueryable; /** * Add a WHERE condition. Multiple where() calls are chained with AND logic. */ where(condition: (entity: EntityQuery) => Condition): IEntityQueryable; /** * INNER JOIN used purely as a row FILTER — keeps the entity shape (no * selector), so scope-style predicates can hop across an N:1 FK in ONE * query level (e.g. order_item ⋈ invoicing_partner_data). The optional * third callback contributes an extra WHERE predicate with the same * (left, right) arguments. Only the right table's COLUMNS are addressable * (no navigations). Joining a 1:N side duplicates left rows — use * exists()/inSubquery for semi-join semantics there. */ joinFilter(rightTable: DbEntityTable, on: (left: EntityQuery, right: EntityQuery) => Condition, filter?: (left: EntityQuery, right: EntityQuery) => Condition): IEntityQueryable; /** * LEFT JOIN used purely as a row FILTER — see joinFilter. Combine with an * IS NULL predicate in `filter` for anti-join shapes ("no matching right * row"). */ leftJoinFilter(rightTable: DbEntityTable, on: (left: EntityQuery, right: EntityQuery) => Condition, filter?: (left: EntityQuery, right: EntityQuery) => Condition): IEntityQueryable; /** * Select specific fields from the entity */ select(selector: (entity: EntityQuery) => TSelection): EntitySelectQueryBuilder>; /** * Order by field(s) */ orderBy(selector: (row: EntityQuery) => T): IEntityQueryable; orderBy(selector: (row: EntityQuery) => T[]): IEntityQueryable; orderBy(selector: (row: EntityQuery) => Array<[T, OrderDirection]>): IEntityQueryable; /** * Limit results */ limit(count: number): IEntityQueryable; /** * Offset results */ offset(count: number): IEntityQueryable; /** * Add CTEs (Common Table Expressions) to the query */ with(...ctes: import('../query/cte-builder').DbCte[]): IEntityQueryable; /** * Left join with another table, CTE, or subquery */ leftJoin(rightTable: DbEntityTable, condition: (left: EntityQuery, right: EntityQuery) => Condition, selector: (left: EntityQuery, right: EntityQuery) => TSelection, alias?: string): EntitySelectQueryBuilder>; leftJoin, TSelection>(rightTable: import('../query/subquery').Subquery, condition: (left: EntityQuery, right: TRight) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection, alias: string): EntitySelectQueryBuilder>; leftJoin, TSelection>(rightTable: import('../query/cte-builder').DbCte, condition: (left: EntityQuery, right: ToFieldRefs) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection): EntitySelectQueryBuilder>; /** * Inner join with another table, CTE, or subquery */ innerJoin(rightTable: DbEntityTable, condition: (left: EntityQuery, right: EntityQuery) => Condition, selector: (left: EntityQuery, right: EntityQuery) => TSelection, alias?: string): EntitySelectQueryBuilder>; innerJoin, TSelection>(rightTable: import('../query/subquery').Subquery, condition: (left: EntityQuery, right: TRight) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection, alias: string): EntitySelectQueryBuilder>; innerJoin, TSelection>(rightTable: import('../query/cte-builder').DbCte, condition: (left: EntityQuery, right: ToFieldRefs) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection): EntitySelectQueryBuilder>; /** * Execute query and return all results */ toList(): Promise[]>; /** * Execute query and return first result */ first(): Promise>; /** * Execute query and return first result or null if not found */ firstOrDefault(): Promise | null>; /** * Count matching records */ count(): Promise; /** * Execute query and return results with total count using COUNT(*) OVER(). * Useful for pagination - gets data and total count in a single query. */ countOver(): Promise<{ data: UnwrapDbColumns[]; totalCount: number; }>; /** * Check if any rows match the query */ exists(): Promise; /** * Delete records matching the current WHERE condition * Returns a fluent builder that can be awaited directly or chained with .returning() */ delete(): FluentDelete>; /** * Update records matching the current WHERE condition * Returns a fluent builder that can be awaited directly or chained with .returning() * * Accepts either a partial data object, or a function that receives the entity's * column proxy (so SqlFragment values can reference table columns). For example: * ```ts * .update(p => ({ integrationInfo: jsonbMerge(p.integrationInfo, patch) })) * ``` */ update(data: UpdateData | ((row: EntityQuery) => UpdateData)): FluentQueryUpdate>; /** * Create a prepared query for efficient reusable parameterized execution */ prepare = Record>(name: string): PreparedQuery, TParams>; /** * Create a future query that will be executed later. * Use with FutureQueryRunner.runAsync() for batch execution in a single roundtrip. */ future(): FutureQuery>; /** * Create a future query that returns a single result or null. * Use with FutureQueryRunner.runAsync() for batch execution. */ futureFirstOrDefault(): FutureSingleQuery>; /** * Create a future query that returns a count. * Use with FutureQueryRunner.runAsync() for batch execution. */ futureCount(): FutureCountQuery; } /** * Strongly-typed query builder for entities * Results automatically unwrap DbColumn to T and SqlFragment to T */ export interface EntitySelectQueryBuilder { /** * Set a per-query timeout (ms) for this query, overriding the connection-level * default. Only this query is wrapped (`SET LOCAL statement_timeout`); pass `0` * to disable. On timeout a `QueryTimeoutError` is thrown. */ withTimeout(timeoutMs: number): EntitySelectQueryBuilder; /** * Mark this query as expected to finish within `expectedMs` (ms). If it runs * longer, the context's `onQueryTakingTooLong` callback fires (the query is * NOT cancelled). Overrides the context's `longRunningQueryThreshold`. */ expectedExecutionTime(expectedMs: number): EntitySelectQueryBuilder; select(selector: (entity: TSelection extends DbEntity ? EntityQuery : TSelection) => TNewSelection): EntitySelectQueryBuilder>; selectDistinct(selector: (entity: TSelection extends DbEntity ? EntityQuery : TSelection) => TNewSelection): EntitySelectQueryBuilder>; where(condition: (entity: TSelection extends DbEntity ? EntityQuery : ToFieldRefs) => Condition): EntitySelectQueryBuilder; /** INNER JOIN as a pure row filter — selection shape preserved; see {@link IEntityQueryable.joinFilter}. */ joinFilter(rightTable: DbEntityTable, on: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: EntityQuery) => Condition, filter?: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: EntityQuery) => Condition): EntitySelectQueryBuilder; /** LEFT JOIN as a pure row filter — selection shape preserved; see {@link IEntityQueryable.leftJoinFilter}. */ leftJoinFilter(rightTable: DbEntityTable, on: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: EntityQuery) => Condition, filter?: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: EntityQuery) => Condition): EntitySelectQueryBuilder; orderBy(selector: (row: TSelection extends DbEntity ? EntityQuery : TSelection) => T): EntitySelectQueryBuilder; orderBy(selector: (row: TSelection extends DbEntity ? EntityQuery : TSelection) => T[]): EntitySelectQueryBuilder; orderBy(selector: (row: TSelection extends DbEntity ? EntityQuery : TSelection) => Array<[T, OrderDirection]>): EntitySelectQueryBuilder; limit(count: number): EntitySelectQueryBuilder; offset(count: number): EntitySelectQueryBuilder; count(): Promise; countOver(): Promise<{ data: ResolveCollectionResults[]; totalCount: number; }>; exists(): Promise; first(): Promise>; firstOrDefault(): Promise | null>; firstOrThrow(): Promise>; leftJoin, TNewSelection>(rightTable: import('../query/cte-builder').DbCte, condition: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: ToFieldRefs) => Condition, selector: (left: TSelection extends DbEntity ? EntityQuery : TSelection, right: TRight) => TNewSelection): EntitySelectQueryBuilder>; leftJoin, TNewSelection>(rightTable: import('../query/subquery').Subquery, condition: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: ToFieldRefs) => Condition, selector: (left: TSelection extends DbEntity ? EntityQuery : TSelection, right: TRight) => TNewSelection, alias: string): EntitySelectQueryBuilder>; leftJoin(rightTable: DbEntityTable, condition: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: EntityQuery) => Condition, selector: (left: TSelection extends DbEntity ? EntityQuery : TSelection, right: EntityQuery) => TNewSelection, alias?: string): EntitySelectQueryBuilder>; innerJoin, TNewSelection>(rightTable: import('../query/cte-builder').DbCte, condition: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: ToFieldRefs) => Condition, selector: (left: TSelection extends DbEntity ? EntityQuery : TSelection, right: TRight) => TNewSelection): EntitySelectQueryBuilder>; innerJoin, TNewSelection>(rightTable: import('../query/subquery').Subquery, condition: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: ToFieldRefs) => Condition, selector: (left: TSelection extends DbEntity ? EntityQuery : TSelection, right: TRight) => TNewSelection, alias: string): EntitySelectQueryBuilder>; innerJoin(rightTable: DbEntityTable, condition: (left: TSelection extends DbEntity ? EntityQuery : ToFieldRefs, right: EntityQuery) => Condition, selector: (left: TSelection extends DbEntity ? EntityQuery : TSelection, right: EntityQuery) => TNewSelection, alias?: string): EntitySelectQueryBuilder>; groupBy(selector: (entity: TSelection extends DbEntity ? EntityQuery : TSelection) => TGroupingKey): import('../query/grouped-query').GroupedQueryBuilder : TSelection, TGroupingKey>; min(selector?: (entity: TSelection extends DbEntity ? EntityQuery : TSelection) => TResult): Promise; max(selector?: (entity: TSelection extends DbEntity ? EntityQuery : TSelection) => TResult): Promise; sum(selector?: (entity: TSelection extends DbEntity ? EntityQuery : TSelection) => TResult): Promise; count(): Promise; countOver(): Promise<{ data: ResolveCollectionResults[]; totalCount: number; }>; exists(): Promise; asSubquery(mode?: TMode): import('../query/subquery').Subquery : TMode extends 'array' ? UnwrapDbColumns[] : UnwrapDbColumns, TMode>; with(...ctes: import('../query/cte-builder').DbCte[]): this; /** * Combine this query with another using UNION (removes duplicates) * * @param query Another query with compatible selection type * @returns A UnionQueryBuilder for further chaining * * @example * ```typescript * const result = await db.users * .select(u => ({ id: u.id, name: u.username })) * .union(db.customers.select(c => ({ id: c.id, name: c.name }))) * .orderBy(r => r.name) * .toList(); * ``` */ union(query: EntitySelectQueryBuilder | SelectQueryBuilder): UnionQueryBuilder; /** * Combine this query with another using UNION ALL (keeps all rows including duplicates) * * @param query Another query with compatible selection type * @returns A UnionQueryBuilder for further chaining * * @example * ```typescript * // UNION ALL is faster than UNION as it doesn't need to remove duplicates * const allLogs = await db.errorLogs * .select(l => ({ timestamp: l.createdAt, message: l.message })) * .unionAll(db.infoLogs.select(l => ({ timestamp: l.createdAt, message: l.message }))) * .orderBy(r => r.timestamp) * .toList(); * ``` */ unionAll(query: EntitySelectQueryBuilder | SelectQueryBuilder): UnionQueryBuilder; /** * Build SQL for use in UNION queries (without ORDER BY, LIMIT, OFFSET) * @internal Used by UnionQueryBuilder */ buildUnionSql(context: import('../query/conditions').SqlBuildContext): string; /** * Update records matching the current WHERE condition. * * Accepts either a partial data object, or a function that receives the entity's * column proxy (so SqlFragment values can reference table columns). */ update(data: UpdateData | ((row: EntityQuery) => UpdateData)): FluentQueryUpdate; delete(): FluentDelete; toList(): Promise[]>; first(): Promise>; firstOrDefault(): Promise | null>; /** * Create a prepared query for efficient reusable parameterized execution */ prepare = Record>(name: string): PreparedQuery, TParams>; /** * Create a future query that will be executed later. * Use with FutureQueryRunner.runAsync() for batch execution in a single roundtrip. * * @returns A FutureQuery that can be executed individually or in a batch * * @example * ```typescript * const q1 = db.users.select(u => ({ id: u.id, name: u.username })).future(); * const q2 = db.posts.select(p => ({ title: p.title })).future(); * * // Execute in a single roundtrip * const [users, posts] = await FutureQueryRunner.runAsync([q1, q2]); * ``` */ future(): FutureQuery>; /** * Create a future query that returns a single result or null. * Use with FutureQueryRunner.runAsync() for batch execution. * * @returns A FutureSingleQuery that resolves to a single result or null * * @example * ```typescript * const q1 = db.users.where(u => eq(u.id, 1)).futureFirstOrDefault(); * const q2 = db.posts.where(p => eq(p.id, 5)).futureFirstOrDefault(); * * const [user, post] = await FutureQueryRunner.runAsync([q1, q2]); * // user: User | null, post: Post | null * ``` */ futureFirstOrDefault(): FutureSingleQuery>; /** * Create a future query that returns a count. * Use with FutureQueryRunner.runAsync() for batch execution. * * @returns A FutureCountQuery that resolves to a number * * @example * ```typescript * const q1 = db.users.futureCount(); * const q2 = db.posts.futureCount(); * * const [userCount, postCount] = await FutureQueryRunner.runAsync([q1, q2]); * ``` */ futureCount(): FutureCountQuery; } /** * DbEntity insert builder for upsert operations with proper typing */ export declare class EntityInsertBuilder { private builder; constructor(builder: InsertBuilder); /** * Specify conflict target (columns or constraint name) */ onConflict(target?: ConflictTarget | string[]): this; /** * Do nothing on conflict */ doNothing(): this; /** * Update on conflict (upsert) */ doUpdate(options?: { set?: InsertData; where?: string; }): this; /** * Execute the insert/upsert */ execute(): Promise[]>; } /** * Table accessor with entity typing */ export declare class DbEntityTable { private context; private tableName; private tableBuilder; constructor(context: DataContext, tableName: string, tableBuilder: TableBuilder); /** * Get the table schema for this entity * @internal */ _getSchema(): TableSchema; /** * Get the database client * @internal */ _getClient(): DatabaseClient; /** * Get the query executor for logging * @internal */ _getExecutor(): any; /** * Get the collection strategy * @internal */ _getCollectionStrategy(): CollectionStrategyType | undefined; /** * Get the schema registry * @internal */ _getSchemaRegistry(): Map; /** * Get information about all columns in this table. * By default returns metadata about database columns only, excluding navigation properties. * * @param options - Optional configuration * @param options.includeNavigation - If true, includes navigation properties in the result. * Defaults to false (only database columns). * * @returns Array of column information objects * * @example * ```typescript * // Get only database columns (default) * const columns = db.users.getColumns(); * // Returns: [ * // { propertyName: 'id', columnName: 'id', type: 'integer', isPrimaryKey: true, ... }, * // { propertyName: 'username', columnName: 'username', type: 'varchar', ... }, * // { propertyName: 'email', columnName: 'email', type: 'text', ... }, * // ] * * // Include navigation properties * const allColumns = db.users.getColumns({ includeNavigation: true }); * // Returns: [ * // { propertyName: 'id', columnName: 'id', type: 'integer', ... }, * // { propertyName: 'posts', isNavigation: true, navigationType: 'many', targetTable: 'posts' }, * // ] * * // Get column names only * const columnNames = db.users.getColumns().map(c => c.propertyName); * // Returns: ['id', 'username', 'email', ...] * * // Get database column names * const dbColumnNames = db.users.getColumns().map(c => c.columnName); * // Returns: ['id', 'username', 'email', ...] * ``` */ getColumns(options?: { includeNavigation?: boolean; }): ColumnInfo[]; /** * Get an array of all column property names (keys) for this entity. * Returns a strongly typed array where each element is a valid column key of TEntity. * * This is a convenience method equivalent to `getColumns().map(c => c.propertyName)` * but with better type inference. * * @param options - Optional configuration * @param options.includeNavigation - If true, includes navigation property names. * Defaults to false (only database columns). * * @returns Array of column property names typed as ExtractDbColumnKeys * * @example * ```typescript * // Get only database column keys (default) * const keys = db.users.getColumnKeys(); * // Type: ExtractDbColumnKeys[] which is ('id' | 'username' | 'email' | ...)[] * * // Include navigation property names * const allKeys = db.users.getColumnKeys({ includeNavigation: true }); * // Returns: ['id', 'username', 'email', 'posts', 'orders', ...] * * // Exclude primary key columns (useful for updates) * const updateableKeys = db.users.getColumnKeys({ includePrimaryKey: false }); * // Returns: ['username', 'email', ...] (without 'id') * * // Use for dynamic property access * const user = await db.users.findOne(u => eq(u.id, 1)); * for (const key of db.users.getColumnKeys()) { * console.log(`${key}: ${user[key]}`); // TypeScript knows key is valid * } * * // Use for building dynamic queries * const columnKeys = db.users.getColumnKeys(); * // columnKeys[0] is typed as 'id' | 'username' | 'email' | ... * ``` */ getColumnKeys(options?: { includeNavigation?: boolean; includePrimaryKey?: boolean; }): ExtractDbColumnKeys[]; /** * Get an object containing all entity properties as DbColumn references. * Useful for building dynamic queries or accessing column metadata. * * @param options - Optional configuration * @param options.excludeNavigation - If true (default), excludes navigation properties. * Set to false to include navigation properties. * * @returns Object with property names as keys and their DbColumn/navigation references as values * * @example * ```typescript * // Get all column properties (excludes navigation by default) * const cols = db.users.props(); * // Use in select: db.users.select(u => ({ id: cols.id, name: cols.username })) * * // Include navigation properties * const allProps = db.users.props({ excludeNavigation: false }); * ``` */ props(options?: { excludeNavigation?: boolean; }): EntityQuery; /** * Get qualified table name with schema prefix if specified * @internal */ private _getQualifiedTableName; /** * Configure query options for the current query chain * Returns a new DbEntityTable instance with a modified context that has the specified options * * @example * ```typescript * const results = await db.users * .withQueryOptions({ logQueries: true, collectionStrategy: 'temptable' }) * .select(u => ({ id: u.id, name: u.username })) * .toList(); * ``` */ withQueryOptions(options: QueryOptions): DbEntityTable; /** * Set a per-query timeout (ms) applied to every query and CRUD operation * started from the returned table. Each such query is wrapped individually * (`SET LOCAL statement_timeout`); pass `0` to disable. Overrides the * connection-level default. On timeout a `QueryTimeoutError` is thrown. * * @example * await db.users.withTimeout(5000).where(u => gt(u.id, 0)).toList(); */ withTimeout(timeoutMs: number): DbEntityTable; /** * Mark queries started from the returned table as expected to finish within * `expectedMs` (ms). If a query runs longer, the context's * `onQueryTakingTooLong` callback fires — the query is NOT cancelled (use * `.withTimeout()` for that). Overrides the context's `longRunningQueryThreshold`. * * @example * await db.users.expectedExecutionTime(2000).where(u => gt(u.id, 0)).toList(); */ expectedExecutionTime(expectedMs: number): DbEntityTable; /** * Build a derived table whose context surfaces a transformed executor (via a * proxy context, threaded into this table's accessor). Shared by * `.withTimeout()` and `.expectedExecutionTime()`. * @internal */ private _deriveWithExecutor; /** * Select all records - returns full entities with unwrapped DbColumns */ toList(): Promise[]>; /** * Get first record * @throws Error if no records exist */ first(): Promise>; /** * Get first record or null if none exist */ firstOrDefault(): Promise | null>; /** * Count all records */ count(): Promise; /** * Execute query and return results with total count using COUNT(*) OVER(). * Useful for pagination - gets data and total count in a single query. */ countOver(): Promise<{ data: UnwrapDbColumns[]; totalCount: number; }>; /** * Check if any records exist */ exists(): Promise; /** * Order by field(s) */ orderBy(selector: (row: EntityQuery) => T): IEntityQueryable; orderBy(selector: (row: EntityQuery) => T[]): IEntityQueryable; orderBy(selector: (row: EntityQuery) => Array<[T, OrderDirection]>): IEntityQueryable; /** * Limit results */ limit(count: number): IEntityQueryable; /** * Offset results */ offset(count: number): IEntityQueryable; /** * Select query * UnwrapSelection extracts the value types from SqlFragment expressions */ select(selector: (entity: EntityQuery) => TSelection): EntitySelectQueryBuilder>; /** * Select distinct */ selectDistinct(selector: (entity: EntityQuery) => TSelection): EntitySelectQueryBuilder; /** * Where query - returns all columns by default */ where(condition: (entity: EntityQuery) => Condition): IEntityQueryable; /** * INNER JOIN as a pure row filter directly off the table — see * {@link IEntityQueryable.joinFilter}. */ joinFilter(rightTable: DbEntityTable, on: (left: EntityQuery, right: EntityQuery) => Condition, filter?: (left: EntityQuery, right: EntityQuery) => Condition): IEntityQueryable; /** * LEFT JOIN as a pure row filter directly off the table — see * {@link IEntityQueryable.leftJoinFilter}. */ leftJoinFilter(rightTable: DbEntityTable, on: (left: EntityQuery, right: EntityQuery) => Condition, filter?: (left: EntityQuery, right: EntityQuery) => Condition): IEntityQueryable; /** * Entity queryable over all rows (select-all shape) — shared bootstrap for * filter-joins invoked directly on the table. */ private asEntityQueryable; /** * Create a prepared query for efficient reusable parameterized execution. * Since DbEntityTable represents all records (no WHERE clause), this returns * a prepared query that selects all records. Use where() first for filtering. */ prepare = Record>(name: string): PreparedQuery, TParams>; /** * Create a future query that will be executed later. * Use with FutureQueryRunner.runAsync() for batch execution in a single roundtrip. */ future(): FutureQuery>; /** * Create a future query that returns a single result or null. * Use with FutureQueryRunner.runAsync() for batch execution. */ futureFirstOrDefault(): FutureSingleQuery>; /** * Create a future query that returns a count. * Use with FutureQueryRunner.runAsync() for batch execution. */ futureCount(): FutureCountQuery; /** * Add CTEs (Common Table Expressions) to the query */ with(...ctes: DbCte[]): IEntityQueryable; /** * Left join with another table (DbEntity) */ leftJoin(rightTable: DbEntityTable, condition: (left: EntityQuery, right: EntityQuery) => Condition, selector: (left: EntityQuery, right: EntityQuery) => TSelection, alias?: string): EntitySelectQueryBuilder>; /** * Left join with a subquery (plain object result, not DbEntity) */ leftJoin, TSelection>(rightTable: import('../query/subquery').Subquery, condition: (left: EntityQuery, right: TRight) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection, alias: string): EntitySelectQueryBuilder>; /** * Left join with a CTE */ leftJoin, TSelection>(rightTable: import('../query/cte-builder').DbCte, condition: (left: EntityQuery, right: ToFieldRefs) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection): EntitySelectQueryBuilder>; /** * Inner join with another table (DbEntity) */ innerJoin(rightTable: DbEntityTable, condition: (left: EntityQuery, right: EntityQuery) => Condition, selector: (left: EntityQuery, right: EntityQuery) => TSelection, alias?: string): EntitySelectQueryBuilder>; /** * Inner join with a subquery (plain object result, not DbEntity) */ innerJoin, TSelection>(rightTable: import('../query/subquery').Subquery, condition: (left: EntityQuery, right: TRight) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection, alias: string): EntitySelectQueryBuilder>; /** * Inner join with a CTE */ innerJoin, TSelection>(rightTable: import('../query/cte-builder').DbCte, condition: (left: EntityQuery, right: ToFieldRefs) => Condition, selector: (left: EntityQuery, right: TRight) => TSelection): EntitySelectQueryBuilder>; /** * Insert - accepts only DbColumn properties (excludes navigation properties) * Returns a fluent builder that can be awaited directly or chained with .returning() * * @example * ```typescript * // No returning (default) - returns void * await db.users.insert({ username: 'alice', email: 'alice@test.com' }); * * // With returning() - returns full entity * const user = await db.users.insert({ username: 'alice' }).returning(); * * // With returning(selector) - returns selected columns * const { id } = await db.users.insert({ username: 'alice' }).returning(u => ({ id: u.id })); * ``` */ insert(data: InsertData): FluentInsert; /** * Upsert (insert or update on conflict) * Returns a fluent builder that can be awaited directly or chained with .returning() * * @example * ```typescript * // No returning (default) - returns void * await db.users.upsert([{ username: 'alice' }], { primaryKey: 'username' }); * * // With returning() - returns full entities * const users = await db.users.upsert([{ username: 'alice' }], { primaryKey: 'username' }).returning(); * ``` */ upsert(data: InsertData[], config?: EntityUpsertConfig): FluentUpsert; /** * Bulk insert with advanced configuration * Supports automatic chunking for large datasets * Returns a fluent builder that can be awaited directly or chained with .returning() * * @example * ```typescript * // No returning (default) - returns void * await db.users.insertBulk([{ username: 'alice' }]); * * // With returning() - returns full entities * const users = await db.users.insertBulk([{ username: 'alice' }]).returning(); * * // With returning(selector) - returns selected columns * const results = await db.users.insertBulk([{ username: 'alice' }]).returning(u => ({ id: u.id })); * * // With chunk size * await db.users.insertBulk([{ username: 'alice' }], { chunkSize: 100 }); * * // Skip duplicates (ON CONFLICT DO NOTHING) * await db.users.insertBulk([{ username: 'alice' }], { onConflictDoNothing: true }); * ``` */ insertBulk(value: InsertData | InsertData[], options?: InsertConfig): FluentInsertMany; /** * Execute a single bulk insert batch * @internal */ private insertBulkSingle; /** * Upsert with advanced configuration * Auto-detects primary keys and supports chunking * Returns a fluent builder that can be awaited directly or chained with .returning() * * @example * ```typescript * // No returning (default) - returns void * await db.users.upsertBulk([{ id: 1, username: 'alice' }], { primaryKey: 'id' }); * * // With returning() - returns full entities * const users = await db.users.upsertBulk([{ id: 1, username: 'alice' }]).returning(); * * // With returning(selector) - returns selected columns * const results = await db.users.upsertBulk([{ id: 1, username: 'alice' }]) * .returning(u => ({ id: u.id })); * * // Values may be SqlFragments (self-contained SQL expressions, e.g. a scalar * // subquery) — computed in the same statement and flowing into the DO UPDATE * // arm via EXCLUDED, so a fold + upsert stays ONE round trip: * const rows = await db.users.upsertBulk( * [{ username: 'alice', loginCount: sql`(SELECT COUNT(*) FROM "login" WHERE "username" = ${'alice'})` }], * { primaryKey: 'username', updateColumns: ['loginCount'] } * ).returning(u => ({ loginCount: u.loginCount })); * ``` */ upsertBulk(values: UpsertData[], config?: EntityUpsertConfig): FluentUpsert; /** * Execute a single upsert batch * @internal */ private upsertBulkSingle; /** * Map database column names back to property names. * * The property/column/mapper plan is derived from the schema ONCE and cached * per schema object (the registry returns stable instances): the previous * implementation ran Object.entries + ColumnBuilder.build() PER COLUMN PER * ROW — ~30k config constructions for a 3000-row × 10-column read, the * single largest ORM-side cost in the CPU profile (~4% of total samples). */ private mapResultToEntity; /** Lazily built + schema-cached column plan for {@link mapResultToEntity}. */ private getEntityMappingPlan; /** * Map array of database results to entities. * * All rows of one result set share the same column shape, so the * present-column subset of the mapping plan is derived from the first row * once, and the per-row loop runs without `in` checks. */ private mapResultsToEntities; /** * Extract property names from lambda selector */ private extractPropertyNames; /** * Start building an upsert query with values */ values(data: InsertData | InsertData[]): EntityInsertBuilder; /** * Update all records in the table * Returns a fluent builder that can be awaited directly or chained with .returning() * * Usage: * await db.users.update({ age: 30 }) // Update all * await db.users.where(u => eq(u.id, 1)).update({ age: 30 }) // Update with condition * const updated = await db.users.where(u => eq(u.id, 1)).update({ age: 30 }).returning() */ update(data: UpdateData | ((row: EntityQuery) => UpdateData)): FluentQueryUpdate>; /** * Bulk update multiple records efficiently using PostgreSQL VALUES clause * Updates records matching primary key(s) with provided data * Returns a fluent builder that can be awaited directly or chained with .returning() * * @param data Array of objects with primary key(s) and columns to update * @param config Optional configuration for the bulk update * * @example * ```typescript * // No returning (default) - returns void * await db.users.bulkUpdate([ * { id: 1, age: 30 }, * { id: 2, age: 25 }, * ]); * * // With returning() - returns full entities * const updated = await db.users.bulkUpdate([{ id: 1, age: 30 }]).returning(); * * // With returning(selector) - returns selected columns * const results = await db.users.bulkUpdate([{ id: 1, age: 30 }]) * .returning(u => ({ id: u.id, age: u.age })); * * // With custom primary key * await db.users.bulkUpdate( * [{ username: 'alice', age: 31 }], * { primaryKey: 'username' } * ); * ``` */ bulkUpdate(data: Array> & Record>, config?: { /** Primary key column(s) to match records. Auto-detected if not specified */ primaryKey?: string | string[]; /** Chunk size for large batches. Auto-calculated if not specified */ chunkSize?: number; }): FluentBulkUpdate; /** Static type map for PostgreSQL type casting - computed once */ private static readonly PG_TYPE_MAP; /** * Execute a single bulk update batch * @internal */ private bulkUpdateSingle; /** * Delete all records from the table * Returns a fluent builder that can be awaited directly or chained with .returning() * * Usage: * await db.users.delete() // Delete all * await db.users.where(u => eq(u.id, 1)).delete() // Delete with condition * const deleted = await db.users.where(u => eq(u.id, 1)).delete().returning() */ delete(): FluentDelete>; /** * Create a mock entity for type inference in lambdas */ private createMockEntity; /** * Build RETURNING clause SQL based on config * @internal */ private buildReturningClause; /** * Map row results applying custom mappers * @internal * @param rows - Raw database rows * @param aliasToProperty - Optional mapping from result aliases to entity property names. * If undefined, assumes full entity mapping (db column names -> property names) */ private mapReturningResults; /** * Detect if a RETURNING selector uses navigation properties * Returns navigation info if found, null otherwise * @internal */ private detectNavigationInReturning; /** * Resolve all navigation joins by finding the correct path through the schema graph * @internal */ private resolveJoinsForTableAliases; /** * Build RETURNING clause with navigation property support using CTE * @internal */ private buildReturningWithNavigation; /** * Rewrite table references in a collection subquery to use the correct aliases * from the main query's JOINs. This handles multi-level navigation where the * collection is accessed through intermediate joined tables. * * @param expression - The SQL expression (join clause or select expression) to rewrite * @param tableToAlias - Map of table names to their aliases in the main query * @returns The rewritten expression with all table references updated * @internal */ private rewriteCollectionTableReferences; /** * Map RETURNING results with navigation properties * Reconstructs nested objects from flat column paths * @internal */ private mapReturningResultsWithNavigation; } /** * Base database context with entity-first approach */ export declare abstract class DatabaseContext extends DataContext { private modelConfig; private entityTables; private sequenceRegistry; private sequenceInstances; private searchNormalizeRequired; constructor(client: DatabaseClient, queryOptions?: QueryOptions); /** * Override this method to configure your entities */ protected abstract setupModel(modelConfig: DbModelConfig): void; /** * Optional: Override this method to register sequences. * This is called during construction to ensure sequences are registered before schema creation. * * @example * ```typescript * protected setupSequences(): void { * // Access sequence getters to register them * this.mySeq; * this.anotherSeq; * } * ``` */ protected setupSequences?(): void; /** * Hook called before any database migrations/schema changes are applied. * Override this method to execute custom SQL scripts that must run first — * before the ORM analyzes or modifies the schema, and before any file-based * migrations run. * * Fires at the start of `getSchemaManager().migrate()` / `ensureCreated()`, * and (via `MigrationRunner.up()`) before file migrations on an existing * database. On a fresh database, the runner triggers auto-migration, so this * still runs first there too. * * @example * ```typescript * protected async onMigrationStart(client: DatabaseClient): Promise { * // Ensure a required extension exists before any tables are created * await client.query(`CREATE EXTENSION IF NOT EXISTS "pg_trgm"`); * } * ``` * * @param client - Database client for executing custom SQL */ protected onMigrationStart(client: DatabaseClient): Promise; /** * Hook called after database migrations/schema creation are complete. * Override this method to execute custom SQL scripts that are outside the scope of the ORM. * * @example * ```typescript * protected async onMigrationComplete(client: DatabaseClient): Promise { * // Create custom functions, views, triggers, etc. * await client.query(` * CREATE OR REPLACE FUNCTION custom_function() * RETURNS void AS $$ * BEGIN * -- Custom logic here * END; * $$ LANGUAGE plpgsql; * `); * } * ``` * * @param client - Database client for executing custom SQL */ protected onMigrationComplete(client: DatabaseClient): Promise; /** * Register a sequence in the schema * @param config - Sequence configuration */ protected registerSequence(config: SequenceConfig): void; /** * Get a sequence instance for interacting with the database * @param config - Sequence configuration * @returns DbSequence instance with nextValue() and resync() methods */ protected sequence(config: SequenceConfig): DbSequence; /** * Get all registered sequences * @internal */ getSequenceRegistry(): Map; /** * Get schema manager for create/drop operations with post-migration hook support */ getSchemaManager(options?: { concurrentIndexes?: boolean; recreateChangedIndexes?: boolean; }): DbSchemaManager; /** * Get strongly-typed table accessor for an entity * @internal - Use property accessors on derived class instead */ protected table(entityClass: EntityConstructor): DbEntityTable; } export {}; //# sourceMappingURL=db-context.d.ts.map