/** * Drizzle ORM Session for postgres.do * * This module provides the session layer that connects Drizzle ORM * to the postgres.do client. It implements the PgSession interface * required by Drizzle's PostgreSQL dialect. */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { entityKind } from 'drizzle-orm/entity' import { NoopLogger, type Logger } from 'drizzle-orm/logger' import type { PgDialect } from 'drizzle-orm/pg-core/dialect' import { PgTransaction } from 'drizzle-orm/pg-core' import type { SelectedFieldsOrdered } from 'drizzle-orm/pg-core/query-builders/select.types' import type { PgTransactionConfig, PreparedQueryConfig, PgQueryResultHKT } from 'drizzle-orm/pg-core/session' import { PgPreparedQuery, PgSession } from 'drizzle-orm/pg-core/session' import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations' import type { Query } from 'drizzle-orm/sql/sql' import { fillPlaceholders, sql } from 'drizzle-orm/sql/sql' import type { Assume } from 'drizzle-orm/utils' import type { Sql, TransactionSql, Row } from '../types.js' // Access to dialect through base class - abstract transaction method is implemented in PostgresDoTransaction declare abstract class PgTransactionWithDialect< TQueryResult extends PgQueryResultHKT, TFullSchema extends Record, TSchema extends TablesRelationalConfig > extends PgTransaction { readonly dialect: PgDialect } /** * PostgresDO-compatible client interface * * This interface represents the client that Drizzle will use to execute queries. * It's compatible with both the main Sql client and transaction clients. */ export interface PostgresDoClient { /** * Execute raw SQL query with parameters */ unsafe(query: string, params?: unknown[]): Promise /** * Begin a transaction (only on main client) */ begin?( fn: (sql: TransactionSql) => Promise, options?: { isolationLevel?: string; readOnly?: boolean; deferrable?: boolean } ): Promise /** * Create a savepoint within a transaction (only on transaction client) */ savepoint?(fn: (sql: TransactionSql) => Promise): Promise /** * Client options (for type parser configuration) */ options?: { parsers?: Record unknown> serializers?: Record string> } } /** * Prepared query implementation for postgres.do * * This class handles the execution of prepared queries through * the postgres.do client's unsafe() method. */ export class PostgresDoPreparedQuery extends PgPreparedQuery { static override readonly [entityKind]: string = 'PostgresDoPreparedQuery' constructor( private client: PostgresDoClient, private queryString: string, private params: unknown[], private logger: Logger, private fields: SelectedFieldsOrdered | undefined, private _isResponseInArrayMode: boolean, private customResultMapper?: (rows: unknown[][]) => T['execute'] ) { super({ sql: queryString, params }) } /** * Execute the prepared query with placeholder values */ override async execute(placeholderValues: Record = {}): Promise { const params = fillPlaceholders(this.params, placeholderValues) this.logger.logQuery(this.queryString, params) const { fields, queryString, client, customResultMapper } = this if (!fields && !customResultMapper) { // Return raw results for non-select queries return client.unsafe(queryString, params) } // For select queries, we need array mode results for mapping // The postgres.do client returns objects, so we convert them to arrays const rows = await client.unsafe(queryString, params) if (customResultMapper) { // If we have field definitions, convert object rows to array rows if (fields) { const arrayRows = rows.map((row) => { return fields.map((field) => { const fieldWithPath = field as { path?: string[]; name?: string } const key = fieldWithPath.path?.join('.') ?? fieldWithPath.name ?? '' return (row as Record)[key] }) }) return customResultMapper(arrayRows as unknown[][]) } return customResultMapper(rows as unknown as unknown[][]) } // Return object rows directly - Drizzle will handle result mapping via the customResultMapper return rows } /** * Get all rows as objects */ async all(placeholderValues: Record = {}): Promise { const params = fillPlaceholders(this.params, placeholderValues) this.logger.logQuery(this.queryString, params) return this.client.unsafe(this.queryString, params) } /** @internal */ isResponseInArrayMode(): boolean { return this._isResponseInArrayMode } } /** * Session options for postgres.do */ export interface PostgresDoSessionOptions { logger?: Logger } /** * Drizzle ORM Session for postgres.do * * This class implements the PgSession interface and provides * the bridge between Drizzle's query builders and the postgres.do client. */ export class PostgresDoSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig > extends PgSession { static override readonly [entityKind]: string = 'PostgresDoSession' logger: Logger declare client: PostgresDoClient constructor( client: PostgresDoClient, dialect: PgDialect, private schema: RelationalSchemaConfig | undefined, public options: PostgresDoSessionOptions = {} ) { super(dialect) this.client = client this.logger = options.logger ?? new NoopLogger() } /** * Prepare a query for execution */ override prepareQuery( query: Query, fields: SelectedFieldsOrdered | undefined, _name: string | undefined, isResponseInArrayMode: boolean, customResultMapper?: (rows: unknown[][]) => T['execute'] ): PgPreparedQuery { return new PostgresDoPreparedQuery( this.client, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper ) } /** * Execute a raw query and return values */ query(query: string, params: unknown[]): Promise { this.logger.logQuery(query, params) // Convert object rows to array rows for Drizzle's internal use return this.client.unsafe(query, params).then((rows) => rows.map((row) => Object.values(row as Record)) ) } /** * Execute a raw query and return objects */ queryObjects(query: string, params: unknown[]): Promise { this.logger.logQuery(query, params) return this.client.unsafe(query, params) } /** * Execute a transaction */ override async transaction( transaction: (tx: PostgresDoTransaction) => Promise, config?: PgTransactionConfig ): Promise { const client = this.client as Sql if (!client.begin) { throw new Error('Transaction not supported: client does not have begin method') } return client.begin( async (txClient) => { const session = new PostgresDoSession( txClient as unknown as PostgresDoClient, this.dialect, this.schema, this.options ) const tx = new PostgresDoTransaction(this.dialect, session, this.schema) if (config) { await tx.setTransaction(config) } return transaction(tx) }, { ...(config?.isolationLevel !== undefined ? { isolationLevel: config.isolationLevel } : {}), readOnly: config?.accessMode === 'read only', ...(config?.deferrable !== undefined ? { deferrable: config.deferrable } : {}), } ) } } /** * Transaction implementation for postgres.do * * This class provides nested transaction support through savepoints. */ export class PostgresDoTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig > extends PgTransaction { static override readonly [entityKind]: string = 'PostgresDoTransaction' constructor( dialect: PgDialect, /** @internal */ readonly session: PostgresDoSession, schema: RelationalSchemaConfig | undefined, nestedIndex = 0 ) { super(dialect, session, schema, nestedIndex) } /** * Create a nested transaction using savepoints */ async transaction( transaction: (tx: PostgresDoTransaction) => Promise ): Promise { const savepointName = `sp${this.nestedIndex + 1}` // Access dialect from base class through type assertion const baseDialect = (this as unknown as PgTransactionWithDialect).dialect const tx = new PostgresDoTransaction( baseDialect, this.session, this.schema, this.nestedIndex + 1 ) await tx.execute(sql.raw(`SAVEPOINT ${savepointName}`)) try { const result = await transaction(tx) await tx.execute(sql.raw(`RELEASE SAVEPOINT ${savepointName}`)) return result } catch (err) { await tx.execute(sql.raw(`ROLLBACK TO SAVEPOINT ${savepointName}`)) throw err } } } /** * Query result HKT for postgres.do * * This type defines the shape of query results returned by the adapter. */ export interface PostgresDoQueryResultHKT extends PgQueryResultHKT { readonly $brand: 'PgQueryResultHKT' readonly row: unknown type: Assume[] }