authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 11:26:51-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 11:26:51-07:00
log9c98cd60e866fb576e7029cd1152f75aca06452c
treee76fdea5e4abd9b8b89d5570aec6cd0edd46cd1f
parent48f88d522047198a660cacebbde854a617068720
signature Signed by PGP key B5690EEEBB952194

Remove persistence error constructor wrappers (#3398)

Co-authored-by: codex <codex@users.noreply.github.com>

5 files changed, 104 insertions(+), 34 deletions(-)

apps/server/src/persistence/AuthPairingLinks.ts+4-4
......@@ -10,8 +10,8 @@ import { AuthEnvironmentScopes } from "@t3tools/contracts";
1010
1111import {
1212 type AuthPairingLinkRepositoryError,
13 toPersistenceDecodeError,
14 toPersistenceSqlError,
13 PersistenceDecodeError,
14 PersistenceSqlError,
1515} from "./Errors.ts";
1616
1717export const AuthPairingLinkRecord = Schema.Struct({
......@@ -90,8 +90,8 @@ export class AuthPairingLinkRepository extends Context.Service<
9090function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
9191 return (cause: unknown): AuthPairingLinkRepositoryError =>
9292 Schema.isSchemaError(cause)
93 ? toPersistenceDecodeError(decodeOperation)(cause)
94 : toPersistenceSqlError(sqlOperation)(cause);
93 ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause)
94 : new PersistenceSqlError({ operation: sqlOperation, cause });
9595}
9696
9797export const make = Effect.gen(function* () {
apps/server/src/persistence/AuthSessions.ts+4-4
......@@ -15,8 +15,8 @@ import {
1515
1616import {
1717 type AuthSessionRepositoryError,
18 toPersistenceDecodeError,
19 toPersistenceSqlError,
18 PersistenceDecodeError,
19 PersistenceSqlError,
2020} from "./Errors.ts";
2121
2222export const AuthSessionClientMetadataRecord = Schema.Struct({
......@@ -146,8 +146,8 @@ function toAuthSessionRecord(row: typeof AuthSessionDbRow.Type): AuthSessionReco
146146function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
147147 return (cause: unknown): AuthSessionRepositoryError =>
148148 Schema.isSchemaError(cause)
149 ? toPersistenceDecodeError(decodeOperation)(cause)
150 : toPersistenceSqlError(sqlOperation)(cause);
149 ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause)
150 : new PersistenceSqlError({ operation: sqlOperation, cause });
151151}
152152
153153export const make = Effect.gen(function* () {
apps/server/src/persistence/Errors.test.ts created+49
......@@ -0,0 +1,49 @@
1import { assert, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4
5import { PersistenceDecodeError, PersistenceSqlError } from "./Errors.ts";
6
7const decodeRuntimePayload = Schema.decodeUnknownEffect(
8 Schema.Struct({
9 runtimePayload: Schema.Struct({
10 attempt: Schema.Number,
11 }),
12 }),
13);
14
15it("keeps SQL operation context without a tautological detail", () => {
16 const cause = new Error("database unavailable");
17 const error = new PersistenceSqlError({
18 operation: "AuthSessionRepository.list:query",
19 cause,
20 });
21
22 assert.equal(error.operation, "AuthSessionRepository.list:query");
23 assert.equal(error.detail, undefined);
24 assert.equal(error.cause, cause);
25 assert.equal(error.message, "SQL error in AuthSessionRepository.list:query");
26});
27
28it.effect("maps schema errors without copying rejected payloads into diagnostics", () =>
29 Effect.gen(function* () {
30 const rejectedPayload = "runtime-payload-secret-sentinel";
31 const cause = yield* Effect.flip(
32 decodeRuntimePayload({
33 runtimePayload: {
34 attempt: rejectedPayload,
35 },
36 }),
37 );
38 const error = PersistenceDecodeError.fromSchemaError(
39 "ProviderSessionRuntimeRepository.list:decodeRows",
40 cause,
41 );
42
43 assert.equal(error.operation, "ProviderSessionRuntimeRepository.list:decodeRows");
44 assert.equal(error.cause, cause);
45 assert.notInclude(error.issue, rejectedPayload);
46 assert.notInclude(error.message, rejectedPayload);
47 assert.include(error.issue, "InvalidType");
48 }),
49);
apps/server/src/persistence/Errors.ts+30-17
......@@ -1,6 +1,20 @@
11import * as Schema from "effect/Schema";
22import * as SchemaIssue from "effect/SchemaIssue";
33
4function summarizeSchemaIssue(issue: SchemaIssue.Issue): string {
5 switch (issue._tag) {
6 case "Filter":
7 case "Encoding":
8 case "Pointer":
9 return `${issue._tag}(${summarizeSchemaIssue(issue.issue)})`;
10 case "Composite":
11 case "AnyOf":
12 return `${issue._tag}(${issue.issues.map(summarizeSchemaIssue).join(",")})`;
13 default:
14 return issue._tag;
15 }
16}
17
418// ===============================
519// Core Persistence Errors
620// ===============================
......@@ -9,12 +23,14 @@ export class PersistenceSqlError extends Schema.TaggedErrorClass<PersistenceSqlE
923 "PersistenceSqlError",
1024 {
1125 operation: Schema.String,
12 detail: Schema.String,
26 detail: Schema.optional(Schema.String),
1327 cause: Schema.optional(Schema.Defect()),
1428 },
1529) {
1630 override get message(): string {
17 return `SQL error in ${this.operation}: ${this.detail}`;
31 return this.detail === undefined
32 ? `SQL error in ${this.operation}`
33 : `SQL error in ${this.operation}: ${this.detail}`;
1834 }
1935}
2036
......@@ -26,6 +42,14 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD
2642 cause: Schema.optional(Schema.Defect()),
2743 },
2844) {
45 static fromSchemaError(operation: string, cause: Schema.SchemaError): PersistenceDecodeError {
46 return new PersistenceDecodeError({
47 operation,
48 issue: summarizeSchemaIssue(cause.issue),
49 cause,
50 });
51 }
52
2953 override get message(): string {
3054 return `Decode error in ${this.operation}: ${this.issue}`;
3155 }
......@@ -33,6 +57,7 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD
3357const isPersistenceSqlError = Schema.is(PersistenceSqlError);
3458const isPersistenceDecodeError = Schema.is(PersistenceDecodeError);
3559
60// Kept for orchestration/projection call sites, which are being revamped separately.
3661export function toPersistenceSqlError(operation: string) {
3762 return (cause: unknown): PersistenceSqlError =>
3863 new PersistenceSqlError({
......@@ -42,22 +67,10 @@ export function toPersistenceSqlError(operation: string) {
4267 });
4368}
4469
70// Kept for orchestration/projection call sites, which are being revamped separately.
4571export function toPersistenceDecodeError(operation: string) {
46 return (error: Schema.SchemaError): PersistenceDecodeError =>
47 new PersistenceDecodeError({
48 operation,
49 issue: SchemaIssue.makeFormatterDefault()(error.issue),
50 cause: error,
51 });
52}
53
54export function toPersistenceDecodeCauseError(operation: string) {
55 return (cause: unknown): PersistenceDecodeError =>
56 new PersistenceDecodeError({
57 operation,
58 issue: `Failed to execute ${operation}`,
59 cause,
60 });
72 return (cause: Schema.SchemaError): PersistenceDecodeError =>
73 PersistenceDecodeError.fromSchemaError(operation, cause);
6174}
6275
6376export const isPersistenceError = (u: unknown) =>
apps/server/src/persistence/ProviderSessionRuntime.ts+17-9
......@@ -16,9 +16,9 @@ import {
1616} from "@t3tools/contracts";
1717
1818import {
19 PersistenceDecodeError,
20 PersistenceSqlError,
1921 type ProviderSessionRuntimeRepositoryError,
20 toPersistenceDecodeError,
21 toPersistenceSqlError,
2222} from "./Errors.ts";
2323
2424/**
......@@ -117,8 +117,8 @@ const DeleteRuntimeRequestSchema = GetRuntimeRequestSchema;
117117function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
118118 return (cause: unknown): ProviderSessionRuntimeRepositoryError =>
119119 Schema.isSchemaError(cause)
120 ? toPersistenceDecodeError(decodeOperation)(cause)
121 : toPersistenceSqlError(sqlOperation)(cause);
120 ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause)
121 : new PersistenceSqlError({ operation: sqlOperation, cause });
122122}
123123
124124export const make = Effect.gen(function* () {
......@@ -235,9 +235,10 @@ export const make = Effect.gen(function* () {
235235 onNone: () => Effect.succeed(Option.none()),
236236 onSome: (row) =>
237237 decodeRuntime(row).pipe(
238 Effect.mapError(
239 toPersistenceDecodeError(
238 Effect.mapError((cause) =>
239 PersistenceDecodeError.fromSchemaError(
240240 "ProviderSessionRuntimeRepository.getByThreadId:rowToRuntime",
241 cause,
241242 ),
242243 ),
243244 Effect.map((runtime) => Option.some(runtime)),
......@@ -259,8 +260,11 @@ export const make = Effect.gen(function* () {
259260 rows,
260261 (row) =>
261262 decodeRuntime(row).pipe(
262 Effect.mapError(
263 toPersistenceDecodeError("ProviderSessionRuntimeRepository.list:rowToRuntime"),
263 Effect.mapError((cause) =>
264 PersistenceDecodeError.fromSchemaError(
265 "ProviderSessionRuntimeRepository.list:rowToRuntime",
266 cause,
267 ),
264268 ),
265269 ),
266270 { concurrency: "unbounded" },
......@@ -273,7 +277,11 @@ export const make = Effect.gen(function* () {
273277 ) =>
274278 deleteRuntimeByThreadId(input).pipe(
275279 Effect.mapError(
276 toPersistenceSqlError("ProviderSessionRuntimeRepository.deleteByThreadId:query"),
280 (cause) =>
281 new PersistenceSqlError({
282 operation: "ProviderSessionRuntimeRepository.deleteByThreadId:query",
283 cause,
284 }),
277285 ),
278286 );
279287