From a451bfa79423fb50a2bf34325ef8922c832e055b Mon Sep 17 00:00:00 2001 From: Dinh Le Date: Mon, 28 Sep 2026 14:45:20 +0700 Subject: [PATCH] fix(contract): stop the handler errors map from being thenable The errors map passed to handlers returned a constructor for every string key, including `then`, so awaiting it or returning it from an async function never settled. It now unwraps the same keys as createORPCClient (then, toString, valueOf, toJSON, ...) instead of treating them as error codes. --- packages/contract/src/error-factory.test.ts | 19 ++++++++++++------- packages/contract/src/error-factory.ts | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/contract/src/error-factory.test.ts b/packages/contract/src/error-factory.test.ts index 77ae7470c..370a2efcd 100644 --- a/packages/contract/src/error-factory.test.ts +++ b/packages/contract/src/error-factory.test.ts @@ -212,20 +212,25 @@ describe('createORPCErrorConstructorMap', () => { expect(e.defined).toBe(true) }) - it('not proxy when access with symbol', () => { - // @ts-expect-error - invalid access - expect(constructors[Symbol('something')]).toBeUndefined() - }) - it('in operator works', () => { expect('BAD_GATEWAY' in constructors).toBe(true) expect('ANY_THING' in constructors).toBe(false) }) + it('not proxy on symbol and unwrap keys', async () => { + const map = constructors as any + expect(map[Symbol('something')]).toBeUndefined() + expect(map.then).toBeUndefined() + expect(await map).toBe(map) + expect(map.toString).toBe(Object.prototype.toString) + expect(map.valueOf).toBe(Object.prototype.valueOf) + expect(map.toJSON).toBeUndefined() + }) + it('does not resolve error codes through Object.prototype', () => { - const e = (constructors as any).toString() + const e = (constructors as any).constructor() - expect(e.code).toEqual('toString') + expect(e.code).toEqual('constructor') expect(e.defined).toEqual(false) }) }) diff --git a/packages/contract/src/error-factory.ts b/packages/contract/src/error-factory.ts index f9d33733a..01d59a618 100644 --- a/packages/contract/src/error-factory.ts +++ b/packages/contract/src/error-factory.ts @@ -3,7 +3,7 @@ import type { MaybeOptionalOptions, Writable } from '@orpc/shared' import type { ErrorMap, ErrorMapItem } from './error' import type { AnySchema, InferSchemaInput, Schema } from './schema' -import { ORPCError } from '@orpc/client' +import { ORPCError, RECURSIVE_CLIENT_UNWRAP_KEYS } from '@orpc/client' import { getOwn, resolveMaybeOptionalOptions } from '@orpc/shared' import { ValidationError } from './error' import { type } from './schema-utils' @@ -156,7 +156,7 @@ export type ORPCErrorConstructorMap = { export function createORPCErrorConstructorMap(errorMap: T): ORPCErrorConstructorMap { const proxy = new Proxy(errorMap, { get(target, code) { - if (typeof code !== 'string') { + if (typeof code !== 'string' || RECURSIVE_CLIENT_UNWRAP_KEYS.has(code)) { return Reflect.get(target, code) }