diff --git a/packages/client-runtime/src/errors/errorTrace.test.ts b/packages/client-runtime/src/errors/errorTrace.test.ts index 075049bd55ef7cb3c90c20bb3c5a28ff5c7397ac..255098999952bbe619c6bdf7dde87ac17424d3b1 100644 --- a/packages/client-runtime/src/errors/errorTrace.test.ts +++ b/packages/client-runtime/src/errors/errorTrace.test.ts @@ -1,3 +1,4 @@ +import * as Cause from "effect/Cause"; import { describe, expect, it } from "vite-plus/test"; import { findErrorTraceId } from "./errorTrace.ts"; @@ -22,4 +23,22 @@ describe("findErrorTraceId", () => { expect(findErrorTraceId(error)).toBeNull(); }); + + it("finds trace metadata in Effect cause branches", () => { + const cause = Cause.fromReasons([ + Cause.makeFailReason(new Error("first failure")), + Cause.makeFailReason({ traceId: "trace-secondary" }), + ]); + + expect(findErrorTraceId(cause)).toBe("trace-secondary"); + }); + + it("finds trace metadata in aggregate error branches", () => { + const error = new AggregateError( + [new Error("first failure"), { traceId: "trace-aggregate" }], + "request failed", + ); + + expect(findErrorTraceId(error)).toBe("trace-aggregate"); + }); }); diff --git a/packages/client-runtime/src/errors/errorTrace.ts b/packages/client-runtime/src/errors/errorTrace.ts index ec1b2a6b2cd944ca10a96e2369cd855b76e48999..74deb37c4f3fb0daed3d4428e810fc7657b60877 100644 --- a/packages/client-runtime/src/errors/errorTrace.ts +++ b/packages/client-runtime/src/errors/errorTrace.ts @@ -1,17 +1,49 @@ +import * as Cause from "effect/Cause"; + +const MAX_ERROR_TRACE_NODES = 128; + export function findErrorTraceId(error: unknown): string | null { const seen = new Set(); - let current: unknown = error; + const pending: Array = [error]; + let inspectedNodeCount = 0; - while (typeof current === "object" && current !== null && !seen.has(current)) { + while (pending.length > 0 && inspectedNodeCount < MAX_ERROR_TRACE_NODES) { + const current = pending.pop(); + inspectedNodeCount += 1; + if (typeof current !== "object" || current === null || seen.has(current)) { + continue; + } seen.add(current); const record = current as { readonly cause?: unknown; + readonly errors?: unknown; readonly traceId?: unknown; }; if (typeof record.traceId === "string" && record.traceId.trim().length > 0) { return record.traceId; } - current = record.cause; + + if (Array.isArray(record.errors)) { + for (let index = record.errors.length - 1; index >= 0; index -= 1) { + pending.push(record.errors[index]); + } + } + if (Cause.isCause(current)) { + for (let index = current.reasons.length - 1; index >= 0; index -= 1) { + const reason = current.reasons[index]; + switch (reason?._tag) { + case "Fail": + pending.push(reason.error); + break; + case "Die": + pending.push(reason.defect); + break; + } + } + } + if ("cause" in record) { + pending.push(record.cause); + } } return null;