1import {
2 AuthenticationError,
3 AuthorizationError,
4 ConnectionError,
5 ConstraintError,
6 DeadlockError,
7 LockTimeoutError,
8 SerializationError,
9 SqlError,
10 type SqlErrorReason,
11 SqlSyntaxError,
12 StatementTimeoutError,
13 UniqueViolation,
14 UnknownError
15} from "effect/sql/SqlError"
16
17interface ErrorProps {
18 readonly cause: unknown
19 readonly message: string
20 readonly operation: string
21}
22
23const channelNameEncoder = new TextEncoder()
24
25export const validateChannelName = (channel: string, operation: string): SqlError | undefined => {
26 if (channelNameEncoder.encode(channel).byteLength <= 63) return undefined
27 const message = "PostgreSQL channel names must not exceed 63 UTF-8 bytes"
28 return new SqlError({ reason: new UnknownError({ cause: new Error(message), message, operation }) })
29}
30
31const normalizeConstraint = (constraint: unknown): string => {
32 if (typeof constraint !== "string") return "unknown"
33 const normalized = constraint.trim()
34 return normalized.length === 0 ? "unknown" : normalized
35}
36
37export const classifySqlState = (
38 code: string | undefined,
39 constraint: unknown,
40 props: ErrorProps
41): SqlErrorReason => {
42 if (code !== undefined) {
43 if (code.startsWith("08")) {
44 return new ConnectionError(props)
45 }
46 if (code.startsWith("28")) {
47 return new AuthenticationError(props)
48 }
49 if (code === "42501") {
50 return new AuthorizationError(props)
51 }
52 if (code.startsWith("42")) {
53 return new SqlSyntaxError(props)
54 }
55 if (code === "23505") {
56 return new UniqueViolation({ ...props, constraint: normalizeConstraint(constraint) })
57 }
58 if (code.startsWith("23")) {
59 return new ConstraintError(props)
60 }
61 if (code === "40P01") {
62 return new DeadlockError(props)
63 }
64 if (code === "40001") {
65 return new SerializationError(props)
66 }
67 if (code === "55P03") {
68 return new LockTimeoutError(props)
69 }
70 if (code === "57014") {
71 return new StatementTimeoutError(props)
72 }
73 }
74 return new UnknownError(props)
75}