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";...@@ -10,8 +10,8 @@ import { AuthEnvironmentScopes } from "@t3tools/contracts";
1010
11import {11import {
12 type AuthPairingLinkRepositoryError,12 type AuthPairingLinkRepositoryError,
13 toPersistenceDecodeError,13 PersistenceDecodeError,
14 toPersistenceSqlError,14 PersistenceSqlError,
15} from "./Errors.ts";15} from "./Errors.ts";
1616
17export const AuthPairingLinkRecord = Schema.Struct({17export const AuthPairingLinkRecord = Schema.Struct({
...@@ -90,8 +90,8 @@ export class AuthPairingLinkRepository extends Context.Service<...@@ -90,8 +90,8 @@ export class AuthPairingLinkRepository extends Context.Service<
90function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {90function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
91 return (cause: unknown): AuthPairingLinkRepositoryError =>91 return (cause: unknown): AuthPairingLinkRepositoryError =>
92 Schema.isSchemaError(cause)92 Schema.isSchemaError(cause)
93 ? toPersistenceDecodeError(decodeOperation)(cause)93 ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause)
94 : toPersistenceSqlError(sqlOperation)(cause);94 : new PersistenceSqlError({ operation: sqlOperation, cause });
95}95}
9696
97export const make = Effect.gen(function* () {97export const make = Effect.gen(function* () {
apps/server/src/persistence/AuthSessions.ts+4-4
...@@ -15,8 +15,8 @@ import {...@@ -15,8 +15,8 @@ import {
1515
16import {16import {
17 type AuthSessionRepositoryError,17 type AuthSessionRepositoryError,
18 toPersistenceDecodeError,18 PersistenceDecodeError,
19 toPersistenceSqlError,19 PersistenceSqlError,
20} from "./Errors.ts";20} from "./Errors.ts";
2121
22export const AuthSessionClientMetadataRecord = Schema.Struct({22export const AuthSessionClientMetadataRecord = Schema.Struct({
...@@ -146,8 +146,8 @@ function toAuthSessionRecord(row: typeof AuthSessionDbRow.Type): AuthSessionReco...@@ -146,8 +146,8 @@ function toAuthSessionRecord(row: typeof AuthSessionDbRow.Type): AuthSessionReco
146function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {146function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
147 return (cause: unknown): AuthSessionRepositoryError =>147 return (cause: unknown): AuthSessionRepositoryError =>
148 Schema.isSchemaError(cause)148 Schema.isSchemaError(cause)
149 ? toPersistenceDecodeError(decodeOperation)(cause)149 ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause)
150 : toPersistenceSqlError(sqlOperation)(cause);150 : new PersistenceSqlError({ operation: sqlOperation, cause });
151}151}
152152
153export const make = Effect.gen(function* () {153export 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 @@...@@ -1,6 +1,20 @@
1import * as Schema from "effect/Schema";1import * as Schema from "effect/Schema";
2import * as SchemaIssue from "effect/SchemaIssue";2import * 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
4// ===============================18// ===============================
5// Core Persistence Errors19// Core Persistence Errors
6// ===============================20// ===============================
...@@ -9,12 +23,14 @@ export class PersistenceSqlError extends Schema.TaggedErrorClass<PersistenceSqlE...@@ -9,12 +23,14 @@ export class PersistenceSqlError extends Schema.TaggedErrorClass<PersistenceSqlE
9 "PersistenceSqlError",23 "PersistenceSqlError",
10 {24 {
11 operation: Schema.String,25 operation: Schema.String,
12 detail: Schema.String,26 detail: Schema.optional(Schema.String),
13 cause: Schema.optional(Schema.Defect()),27 cause: Schema.optional(Schema.Defect()),
14 },28 },
15) {29) {
16 override get message(): string {30 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}`;
18 }34 }
19}35}
2036
...@@ -26,6 +42,14 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD...@@ -26,6 +42,14 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD
26 cause: Schema.optional(Schema.Defect()),42 cause: Schema.optional(Schema.Defect()),
27 },43 },
28) {44) {
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
29 override get message(): string {53 override get message(): string {
30 return `Decode error in ${this.operation}: ${this.issue}`;54 return `Decode error in ${this.operation}: ${this.issue}`;
31 }55 }
...@@ -33,6 +57,7 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD...@@ -33,6 +57,7 @@ export class PersistenceDecodeError extends Schema.TaggedErrorClass<PersistenceD
33const isPersistenceSqlError = Schema.is(PersistenceSqlError);57const isPersistenceSqlError = Schema.is(PersistenceSqlError);
34const isPersistenceDecodeError = Schema.is(PersistenceDecodeError);58const isPersistenceDecodeError = Schema.is(PersistenceDecodeError);
3559
60// Kept for orchestration/projection call sites, which are being revamped separately.
36export function toPersistenceSqlError(operation: string) {61export function toPersistenceSqlError(operation: string) {
37 return (cause: unknown): PersistenceSqlError =>62 return (cause: unknown): PersistenceSqlError =>
38 new PersistenceSqlError({63 new PersistenceSqlError({
...@@ -42,22 +67,10 @@ export function toPersistenceSqlError(operation: string) {...@@ -42,22 +67,10 @@ export function toPersistenceSqlError(operation: string) {
42 });67 });
43}68}
4469
70// Kept for orchestration/projection call sites, which are being revamped separately.
45export function toPersistenceDecodeError(operation: string) {71export function toPersistenceDecodeError(operation: string) {
46 return (error: Schema.SchemaError): PersistenceDecodeError =>72 return (cause: Schema.SchemaError): PersistenceDecodeError =>
47 new PersistenceDecodeError({73 PersistenceDecodeError.fromSchemaError(operation, cause);
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 });
61}74}
6275
63export const isPersistenceError = (u: unknown) =>76export const isPersistenceError = (u: unknown) =>
apps/server/src/persistence/ProviderSessionRuntime.ts+17-9
...@@ -16,9 +16,9 @@ import {...@@ -16,9 +16,9 @@ import {
16} from "@t3tools/contracts";16} from "@t3tools/contracts";
1717
18import {18import {
19 PersistenceDecodeError,
20 PersistenceSqlError,
19 type ProviderSessionRuntimeRepositoryError,21 type ProviderSessionRuntimeRepositoryError,
20 toPersistenceDecodeError,
21 toPersistenceSqlError,
22} from "./Errors.ts";22} from "./Errors.ts";
2323
24/**24/**
...@@ -117,8 +117,8 @@ const DeleteRuntimeRequestSchema = GetRuntimeRequestSchema;...@@ -117,8 +117,8 @@ const DeleteRuntimeRequestSchema = GetRuntimeRequestSchema;
117function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {117function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
118 return (cause: unknown): ProviderSessionRuntimeRepositoryError =>118 return (cause: unknown): ProviderSessionRuntimeRepositoryError =>
119 Schema.isSchemaError(cause)119 Schema.isSchemaError(cause)
120 ? toPersistenceDecodeError(decodeOperation)(cause)120 ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause)
121 : toPersistenceSqlError(sqlOperation)(cause);121 : new PersistenceSqlError({ operation: sqlOperation, cause });
122}122}
123123
124export const make = Effect.gen(function* () {124export const make = Effect.gen(function* () {
...@@ -235,9 +235,10 @@ export const make = Effect.gen(function* () {...@@ -235,9 +235,10 @@ export const make = Effect.gen(function* () {
235 onNone: () => Effect.succeed(Option.none()),235 onNone: () => Effect.succeed(Option.none()),
236 onSome: (row) =>236 onSome: (row) =>
237 decodeRuntime(row).pipe(237 decodeRuntime(row).pipe(
238 Effect.mapError(238 Effect.mapError((cause) =>
239 toPersistenceDecodeError(239 PersistenceDecodeError.fromSchemaError(
240 "ProviderSessionRuntimeRepository.getByThreadId:rowToRuntime",240 "ProviderSessionRuntimeRepository.getByThreadId:rowToRuntime",
241 cause,
241 ),242 ),
242 ),243 ),
243 Effect.map((runtime) => Option.some(runtime)),244 Effect.map((runtime) => Option.some(runtime)),
...@@ -259,8 +260,11 @@ export const make = Effect.gen(function* () {...@@ -259,8 +260,11 @@ export const make = Effect.gen(function* () {
259 rows,260 rows,
260 (row) =>261 (row) =>
261 decodeRuntime(row).pipe(262 decodeRuntime(row).pipe(
262 Effect.mapError(263 Effect.mapError((cause) =>
263 toPersistenceDecodeError("ProviderSessionRuntimeRepository.list:rowToRuntime"),264 PersistenceDecodeError.fromSchemaError(
265 "ProviderSessionRuntimeRepository.list:rowToRuntime",
266 cause,
267 ),
264 ),268 ),
265 ),269 ),
266 { concurrency: "unbounded" },270 { concurrency: "unbounded" },
...@@ -273,7 +277,11 @@ export const make = Effect.gen(function* () {...@@ -273,7 +277,11 @@ export const make = Effect.gen(function* () {
273 ) =>277 ) =>
274 deleteRuntimeByThreadId(input).pipe(278 deleteRuntimeByThreadId(input).pipe(
275 Effect.mapError(279 Effect.mapError(
276 toPersistenceSqlError("ProviderSessionRuntimeRepository.deleteByThreadId:query"),280 (cause) =>
281 new PersistenceSqlError({
282 operation: "ProviderSessionRuntimeRepository.deleteByThreadId:query",
283 cause,
284 }),
277 ),285 ),
278 );286 );
279287