1import * as Predicate from "effect/Predicate";
2import * as Schema from "effect/Schema";
3import * as SchemaIssue from "effect/SchemaIssue";
4
5function summarizeSchemaIssue(issue: SchemaIssue.Issue): string {
6 switch (issue._tag) {
7 case "Filter":
8 case "Encoding":
9 case "Pointer":
10 return `${issue._tag}(${summarizeSchemaIssue(issue.issue)})`;
11 case "Composite":
12 case "AnyOf":
13 return `${issue._tag}(${issue.issues.map(summarizeSchemaIssue).join(",")})`;
14 default:
15 return issue._tag;
16 }
17}
18
19// ===============================
20// Core Persistence Errors
21// ===============================
22
23export const PersistenceErrorCorrelation = Schema.Union([
24 Schema.Struct({ sessionId: Schema.String }),
25 Schema.Struct({ currentSessionId: Schema.String }),
26 Schema.Struct({ pairingLinkId: Schema.String }),
27 Schema.Struct({ threadId: Schema.String }),
28]);
29export type PersistenceErrorCorrelation = typeof PersistenceErrorCorrelation.Type;
30
31export class PersistenceSqlError extends Schema.TaggedError<PersistenceSqlError>()(
32 "PersistenceSqlError",
33 {
34 operation: Schema.String,
35 detail: Schema.optional(Schema.String),
36 correlation: Schema.optional(PersistenceErrorCorrelation),
37 cause: Schema.optional(Schema.Defect()),
38 },
39) {
40 override get message(): string {
41 return this.detail === undefined
42 ? `SQL error in ${this.operation}`
43 : `SQL error in ${this.operation}: ${this.detail}`;
44 }
45}
46
47export class PersistenceDecodeError extends Schema.TaggedError<PersistenceDecodeError>()(
48 "PersistenceDecodeError",
49 {
50 operation: Schema.String,
51 issue: Schema.String,
52 correlation: Schema.optional(PersistenceErrorCorrelation),
53 cause: Schema.optional(Schema.Defect()),
54 },
55) {
56 static fromSchemaError(
57 operation: string,
58 cause: Schema.SchemaError,
59 correlation?: PersistenceErrorCorrelation,
60 ): PersistenceDecodeError {
61 return new PersistenceDecodeError({
62 operation,
63 issue: summarizeSchemaIssue(cause.issue),
64 ...(correlation === undefined ? {} : { correlation }),
65 cause,
66 });
67 }
68
69 override get message(): string {
70 return `Decode error in ${this.operation}: ${this.issue}`;
71 }
72}
73
74/**
75 * Read a SQLite condition through SQL error wrappers.
76 * Use node:sqlite's fixed description, never the driver message.
77 */
78function sqliteCondition(cause: unknown): string | undefined {
79 let value = cause;
80 for (let depth = 0; depth < 4 && Predicate.isObject(value); depth += 1) {
81 if (
82 "errcode" in value &&
83 typeof value.errcode === "number" &&
84 "errstr" in value &&
85 typeof value.errstr === "string"
86 ) {
87 return `SQLITE(${value.errcode}) ${value.errstr}`;
88 }
89 value = "cause" in value ? value.cause : undefined;
90 }
91 return undefined;
92}
93
94/**
95 * A rejected payload must never reach diagnostics, so a schema failure
96 * contributes only its issue tags, and a driver failure only its normalized
97 * condition. Anything the mapper cannot categorize leaves the detail unset.
98 */
99function describeSqlCause(cause: unknown): string | undefined {
100 if (Schema.isSchemaError(cause)) return summarizeSchemaIssue(cause.issue);
101 return sqliteCondition(cause);
102}
103
104// Kept for orchestration/projection call sites, which are being revamped separately.
105export function toPersistenceSqlError(operation: string) {
106 return (cause: unknown): PersistenceSqlError => {
107 const detail = describeSqlCause(cause);
108 return new PersistenceSqlError({
109 operation,
110 ...(detail === undefined ? {} : { detail }),
111 cause,
112 });
113 };
114}
115
116// Kept for orchestration/projection call sites, which are being revamped separately.
117export function toPersistenceDecodeError(operation: string) {
118 return (cause: Schema.SchemaError): PersistenceDecodeError =>
119 PersistenceDecodeError.fromSchemaError(operation, cause);
120}
121
122export type OrchestrationEventStoreError = PersistenceSqlError | PersistenceDecodeError;
123
124export type OrchestrationCommandReceiptRepositoryError =
125 | PersistenceSqlError
126 | PersistenceDecodeError;
127
128export type ProviderSessionRuntimeRepositoryError = PersistenceSqlError | PersistenceDecodeError;
129export type AuthPairingLinkRepositoryError = PersistenceSqlError | PersistenceDecodeError;
130export type AuthSessionRepositoryError = PersistenceSqlError | PersistenceDecodeError;
131export type PullRequestFilesViewedRepositoryError = PersistenceSqlError | PersistenceDecodeError;
132export type PullRequestReviewGuideRepositoryError = PersistenceSqlError | PersistenceDecodeError;
133
134export type ProjectionRepositoryError = PersistenceSqlError | PersistenceDecodeError;