/* oxlint-disable */ import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Exit from "effect/Exit" import * as Scope from "effect/Scope" import type { SqlClient } from "effect/unstable/sql/SqlClient" import type { SqlError } from "effect/unstable/sql/SqlError" import type { EffectCacheShape } from "drizzle-orm/cache/core/cache-effect" import type { WithCacheConfig } from "drizzle-orm/cache/core/types" import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core/errors" import type { EffectLoggerShape } from "drizzle-orm/effect-core/logger" import type { QueryEffectHKTBase } from "drizzle-orm/effect-core/query-effect" import { entityKind } from "drizzle-orm/entity" import type { AnyRelations } from "drizzle-orm/relations" import type { RelationalQueryMapperConfig } from "drizzle-orm/relations" import type { Query } from "drizzle-orm/sql/sql" import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" import { SQLiteEffectPreparedQuery, SQLiteEffectSession, SQLiteEffectTransaction } from "../sqlite-core/effect/session" import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" import type { PreparedQueryConfig, SQLiteExecuteMethod, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core/session" export interface EffectSQLiteQueryEffectHKT extends QueryEffectHKTBase { readonly error: EffectDrizzleQueryError readonly context: never } export type EffectSQLiteRunResult = readonly never[] export interface EffectSQLiteSessionOptions { logger: EffectLoggerShape cache: EffectCacheShape useJitMappers?: boolean } export class EffectSQLiteSession extends SQLiteEffectSession< EffectSQLiteQueryEffectHKT, EffectSQLiteRunResult, TRelations > { static override readonly [entityKind]: string = "EffectSQLiteSession" constructor( private client: SqlClient, dialect: SQLiteAsyncDialect, protected relations: TRelations, private options: EffectSQLiteSessionOptions, ) { super(dialect) } override prepareQuery( query: Query, fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown, queryMetadata?: { type: "select" | "update" | "delete" | "insert" tables: string[] }, cacheConfig?: WithCacheConfig, ): SQLiteEffectPreparedQuery { return new SQLiteEffectPreparedQuery( (params, method) => this.execute(query, params, method), query, this.options.logger, this.options.cache, queryMetadata, cacheConfig, fields, executeMethod, this.options.useJitMappers, customResultMapper, undefined, undefined, this.isInTransaction(), ) } override prepareRelationalQuery( query: Query, fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, customResultMapper: (rows: Record[], mapColumnValue?: (value: unknown) => unknown) => unknown, config: RelationalQueryMapperConfig, ): SQLiteEffectPreparedQuery { return new SQLiteEffectPreparedQuery( (params, method) => this.execute(query, params, method), query, this.options.logger, this.options.cache, undefined, undefined, fields, executeMethod, this.options.useJitMappers, customResultMapper, true, config, this.isInTransaction(), ) } private execute(query: Query, params: unknown[], method: SQLiteExecuteMethod | "values") { const statement = this.client.unsafe(query.sql, params) if (method === "values") return statement.values if (method === "get") return statement.withoutTransform.pipe(Effect.map((rows) => rows[0])) return statement.withoutTransform } private isInTransaction() { return Effect.serviceOption(this.client.transactionService).pipe(Effect.map((option) => option._tag === "Some")) } private executeTransactionStatement(connection: Effect.Success, query: string) { return connection.executeUnprepared(query, [], undefined).pipe(Effect.asVoid) } private withTransaction(effect: Effect.Effect, config: SQLiteTransactionConfig | undefined) { return Effect.uninterruptibleMask((restore) => Effect.withFiber((fiber) => { const services = fiber.context const connectionOption = Context.getOption(services, this.client.transactionService) const connection: Effect.Effect< readonly [Scope.Closeable | undefined, Effect.Success], SqlError > = connectionOption._tag === "Some" ? Effect.succeed([undefined, connectionOption.value[0]] as const) : Scope.make().pipe( Effect.flatMap((scope) => Scope.provide(this.client.reserve, scope).pipe( Effect.map((connection) => [scope, connection] as const), Effect.catch((error) => Scope.close(scope, Exit.fail(error)).pipe(Effect.andThen(Effect.fail(error))), ), ), ), ) const id = connectionOption._tag === "Some" ? connectionOption.value[1] + 1 : 0 return connection.pipe( Effect.flatMap(([scope, connection]) => { const transaction = this.executeTransactionStatement( connection, id === 0 ? `begin ${config?.behavior ?? "deferred"}` : `savepoint effect_sql_${id}`, ).pipe( Effect.flatMap(() => Effect.provideContext( restore(effect), Context.add(services, this.client.transactionService, [connection, id]), ).pipe( Effect.exit, Effect.flatMap((exit) => { const finalize = Exit.isSuccess(exit) ? id === 0 ? this.executeTransactionStatement(connection, "commit").pipe( // SQLite keeps the transaction open after deferred constraint commit failures. Effect.catch((error) => this.executeTransactionStatement(connection, "rollback").pipe( Effect.catch(() => Effect.void), Effect.andThen(Effect.fail(error)), ), ), ) : this.executeTransactionStatement(connection, `release savepoint effect_sql_${id}`) : id === 0 ? this.executeTransactionStatement(connection, "rollback") : this.executeTransactionStatement(connection, `rollback to savepoint effect_sql_${id}`).pipe( Effect.andThen( this.executeTransactionStatement(connection, `release savepoint effect_sql_${id}`), ), ) return finalize.pipe(Effect.flatMap(() => exit)) }), ), ), ) return scope === undefined ? transaction : transaction.pipe(Effect.onExit((exit) => Scope.close(scope, exit))) }), ) }), ) } override transaction( transaction: (tx: EffectSQLiteTransaction) => Effect.Effect, config?: SQLiteTransactionConfig, ): Effect.Effect { const { dialect, relations } = this return this.withTransaction( Effect.gen({ self: this }, function* () { const tx = new EffectSQLiteTransaction(dialect, this, relations) return yield* transaction(tx) }), config, ) } } export class EffectSQLiteTransaction extends SQLiteEffectTransaction< EffectSQLiteQueryEffectHKT, EffectSQLiteRunResult, TRelations > { static override readonly [entityKind]: string = "EffectSQLiteTransaction" override transaction: ( transaction: ( tx: SQLiteEffectTransaction, ) => Effect.Effect, ) => Effect.Effect = (tx) => this.session.transaction(tx) }