Last active
September 4, 2025 09:41
-
-
Save APZelos/66429ea7c0928f073ebaf6fb1800cf99 to your computer and use it in GitHub Desktop.
Surfacing the `PostgresError` from Drizzle's `DrizzleQueryError` when working with Drizzle in an Effect project.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type {SqlError} from "@effect/sql" | |
| import * as D from "drizzle-orm" | |
| import {Cause, Chunk, Data, Effect as E, Option, pipe, Runtime} from "effect" | |
| import {PostgresError} from "postgres" | |
| interface KnownPostgresErrorPayload { | |
| cause: PostgresError | |
| sqlError: SqlError.SqlError | |
| detail?: string | |
| schemaName?: string | |
| tableName?: string | |
| columnName?: string | |
| constraintName?: string | |
| } | |
| export class UnknownDatabaseException extends Data.TaggedError( | |
| "database/UnknownDatabaseException", | |
| )<{ | |
| cause: unknown | |
| }> {} | |
| export class DrizzleQueryError extends Data.TaggedError("database/DrizzleQueryError")<{ | |
| cause: D.DrizzleQueryError | |
| }> {} | |
| export class ConnectionError extends Data.TaggedError("database/ConnectionError")<{ | |
| cause: PostgresError | |
| sqlError: SqlError.SqlError | |
| }> {} | |
| export class ForeignKeyViolationError extends Data.TaggedError( | |
| "database/ForeignKeyViolationError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class UniqueViolationError extends Data.TaggedError( | |
| "database/UniqueViolationError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class DataError extends Data.TaggedError("database/DataError")<KnownPostgresErrorPayload> {} | |
| export class NotNullViolationError extends Data.TaggedError( | |
| "database/NotNullViolationError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class CheckViolationError extends Data.TaggedError( | |
| "database/CheckViolationError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class ExclusionViolationError extends Data.TaggedError( | |
| "database/ExclusionViolationError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class RestrictViolationError extends Data.TaggedError( | |
| "database/RestrictViolationError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class DeadlockDetectedError extends Data.TaggedError( | |
| "database/DeadlockDetectedError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class SerializationFailureError extends Data.TaggedError( | |
| "database/SerializationFailureError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class OutOfMemoryError extends Data.TaggedError( | |
| "database/OutOfMemoryError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class TooManyConnectionsError extends Data.TaggedError( | |
| "database/TooManyConnectionsError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class InvalidTransactionStateError extends Data.TaggedError( | |
| "database/InvalidTransactionStateError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class SyntaxOrAccessError extends Data.TaggedError( | |
| "database/SyntaxOrAccessError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class InsufficientResourcesError extends Data.TaggedError( | |
| "database/InsufficientResourcesError", | |
| )<KnownPostgresErrorPayload> {} | |
| export class UnknownPostgresException extends Data.TaggedError( | |
| "database/UnknownPostgresException", | |
| )<{ | |
| cause: PostgresError | |
| sqlError: SqlError.SqlError | |
| }> {} | |
| export function mapSqlError(sqlError: SqlError.SqlError) { | |
| const drizzleQueryError = pipe( | |
| Cause.failures(Cause.fail(sqlError.cause)), | |
| Chunk.findFirst((error) => error instanceof D.DrizzleQueryError), | |
| ) | |
| if (Option.isNone(drizzleQueryError)) { | |
| return new UnknownDatabaseException({cause: sqlError}) | |
| } | |
| return pipe( | |
| drizzleQueryError, | |
| Option.flatMap((error) => { | |
| if (Runtime.isFiberFailure(error.cause)) { | |
| return Cause.failureOption(error.cause[Runtime.FiberFailureCauseId]) | |
| } | |
| return Option.none() | |
| }), | |
| Option.flatMap((error) => { | |
| if (hasPostgresErrorCause(error)) { | |
| return Option.some(error.cause) | |
| } | |
| return Option.none() | |
| }), | |
| Option.map((error) => { | |
| const payload = { | |
| cause: error, | |
| sqlError, | |
| detail: error.detail, | |
| schemaName: error.schema_name, | |
| tableName: error.table_name, | |
| columnName: error.column_name, | |
| constraintName: error.constraint_name, | |
| } | |
| if (error.code === "23503") return new ForeignKeyViolationError(payload) | |
| if (error.code === "23505") return new UniqueViolationError(payload) | |
| if (error.code === "23502") return new NotNullViolationError(payload) | |
| if (error.code === "23514") return new CheckViolationError(payload) | |
| if (error.code === "23P01") return new ExclusionViolationError(payload) | |
| if (error.code === "23001") return new RestrictViolationError(payload) | |
| if (error.code === "40001") return new SerializationFailureError(payload) | |
| if (error.code === "40P01") return new DeadlockDetectedError(payload) | |
| if (error.code === "53200") return new OutOfMemoryError(payload) | |
| if (error.code === "53300") return new TooManyConnectionsError(payload) | |
| if (error.code.startsWith("08")) return new ConnectionError(payload) | |
| if (error.code.startsWith("22")) return new DataError(payload) | |
| if (error.code.startsWith("25")) return new InvalidTransactionStateError(payload) | |
| if (error.code.startsWith("42")) return new SyntaxOrAccessError(payload) | |
| if (error.code.startsWith("53")) return new InsufficientResourcesError(payload) | |
| return new UnknownPostgresException(payload) | |
| }), | |
| Option.match({ | |
| onSome: (error) => error, | |
| onNone: () => new DrizzleQueryError({cause: drizzleQueryError.value}), | |
| }), | |
| ) | |
| } | |
| export const eMapSqlError = E.mapError(mapSqlError) | |
| function hasPostgresErrorCause(error: unknown): error is {cause: PostgresError} { | |
| return ( | |
| !!error && typeof error === "object" && "cause" in error && error.cause instanceof PostgresError | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment