diff --git a/example/server.ts b/example/server.ts index f40701d..bc1d9d6 100644 --- a/example/server.ts +++ b/example/server.ts @@ -209,4 +209,4 @@ const app = new Elysia() }) .listen(8080) -export type Server = typeof app +export type Server = typeof app \ No newline at end of file diff --git a/src/fetch/types.ts b/src/fetch/types.ts index 58e6e2d..0531736 100644 --- a/src/fetch/types.ts +++ b/src/fetch/types.ts @@ -5,7 +5,8 @@ import type { IsUnknown, IsNever, Prettify, - TreatyToPath + TreatyToPath, + JSONSerialized } from '../types' export namespace EdenFetch { @@ -13,11 +14,11 @@ export namespace EdenFetch { App extends { '~Routes': infer Schema extends Record } - ? EdenFetch.Fn< - // @ts-expect-error - TreatyToPath - > - : 'Please install Elysia before using Eden' + ? EdenFetch.Fn< + // @ts-expect-error + TreatyToPath + > + : 'Please install Elysia before using Eden' export interface Config extends RequestInit { fetcher?: typeof globalThis.fetch @@ -32,55 +33,55 @@ export namespace EdenFetch { options: Omit & ('GET' extends Method ? { - method?: Method - } + method?: Method + } : { - method: Method - }) & + method: Method + }) & (IsNever extends true ? { - params?: Record - } + params?: Record + } : { - params: Route['params'] - }) & + params: Route['params'] + }) & (IsNever extends true ? { - query?: Record - } + query?: Record + } : { - query: Route['query'] - }) & + query: Route['query'] + }) & (undefined extends Route['headers'] ? { - headers?: Record - } + headers?: Record + } : { - headers: Route['headers'] - }) & + headers: Route['headers'] + }) & (IsUnknown extends false ? { body: Route['body'] } : { body?: unknown }) ) => Promise< Prettify< | { - data: Awaited - error: null - status: number - headers: Record - retry(): Promise - } + data: JSONSerialized> + error: null + status: number + headers: Record + retry(): Promise + } | { - data: null - error: MapError extends infer Errors - ? IsNever extends true - ? EdenFetchError - : Errors - : EdenFetchError - status: number - headers: Record - retry(): Promise - } + data: null + error: MapError extends infer Errors + ? IsNever extends true + ? EdenFetchError + : Errors + : EdenFetchError + status: number + headers: Record + retry(): Promise + } > > } diff --git a/src/treaty/types.ts b/src/treaty/types.ts index 678c3c3..7821f68 100644 --- a/src/treaty/types.ts +++ b/src/treaty/types.ts @@ -1,15 +1,15 @@ /// import { Elysia } from 'elysia' import type { EdenWS } from './index' -import type { IsUnknown, IsNever, MapError, Prettify } from '../types' +import type { IsUnknown, IsNever, MapError, Prettify, JSONSerialized } from '../types' import type { EdenFetchError } from '../errors' type Files = File | FileList type Replace = { [K in keyof RecordType]: RecordType[K] extends TargetType - ? GenericType - : RecordType[K] + ? GenericType + : RecordType[K] } type MaybeArray = T | T[] @@ -25,103 +25,105 @@ export namespace EdenTreaty { export type Sign> = { [K in keyof Route as K extends `:${string}` - ? (string & {}) | number | K - : K extends '' | '/' - ? 'index' - : K]: Route[K] extends { + ? (string & {}) | number | K + : K extends '' | '/' + ? 'index' + : K]: Route[K] extends { body: infer Body headers: infer Headers query: infer Query params: unknown response: infer Response } - ? K extends 'subscribe' - ? // ? Websocket route - undefined extends Route['query'] - ? (params?: { - $query?: Record - }) => EdenWS - : (params: { $query: Route['query'] }) => EdenWS - : // ? HTTP route - (( - params: Prettify< - { - $fetch?: RequestInit - getRaw?: boolean - $transform?: Transform - } & (IsUnknown extends false - ? Replace - : {}) & - (undefined extends Query - ? { - $query?: Record - } - : { - $query: Query - }) & - (undefined extends Headers - ? { - $headers?: Record - } - : { - $headers: Headers - }) - > - ) => Promise< - ( - | { - data: Response extends { - 200: infer ReturnedType - } - ? Awaited - : unknown - error: null - } - | { - data: null - error: Response extends Record - ? MapError extends infer Errors - ? IsNever extends true - ? EdenFetchError - : Errors - : EdenFetchError - : EdenFetchError - } - ) & { - status: number - response: Response - headers: Record - } - >) extends (params: infer Params) => infer Response - ? { - $params: undefined - $headers: undefined - $query: undefined - } extends Params - ? ( - params?: Params, - options?: { - fetch?: RequestInit - transform?: EdenTreaty.Transform - // @ts-ignore - query?: Params['query'] - // @ts-ignore - headers?: Params['headers'] - } - ) => Response - : ( - params: Params, - options?: { - fetch?: RequestInit - transform?: EdenTreaty.Transform - // @ts-ignore - query?: Params['query'] - // @ts-ignore - headers?: Params['headers'] - } - ) => Response - : never - : Prettify> + ? K extends 'subscribe' + ? // ? Websocket route + undefined extends Route['query'] + ? (params?: { + $query?: Record + }) => EdenWS + : (params: { $query: Route['query'] }) => EdenWS + : // ? HTTP route + (( + params: Prettify< + { + $fetch?: RequestInit + getRaw?: boolean + $transform?: Transform + } & (IsUnknown extends false + ? Replace + : {}) & + (undefined extends Query + ? { + $query?: Record + } + : { + $query: Query + }) & + (undefined extends Headers + ? { + $headers?: Record + } + : { + $headers: Headers + }) + > + ) => Promise< + ( + | { + data: JSONSerialized< + Response extends { + 200: infer ReturnedType + } + ? Awaited + : unknown + > + error: null + } + | { + data: null + error: Response extends Record + ? MapError extends infer Errors + ? IsNever extends true + ? EdenFetchError + : Errors + : EdenFetchError + : EdenFetchError + } + ) & { + status: number + response: Response + headers: Record + } + >) extends (params: infer Params) => infer Response + ? { + $params: undefined + $headers: undefined + $query: undefined + } extends Params + ? ( + params?: Params, + options?: { + fetch?: RequestInit + transform?: EdenTreaty.Transform + // @ts-ignore + query?: Params['query'] + // @ts-ignore + headers?: Params['headers'] + } + ) => Response + : ( + params: Params, + options?: { + fetch?: RequestInit + transform?: EdenTreaty.Transform + // @ts-ignore + query?: Params['query'] + // @ts-ignore + headers?: Params['headers'] + } + ) => Response + : never + : Prettify> } type UnwrapPromise = T extends Promise ? A : T @@ -130,12 +132,12 @@ export namespace EdenTreaty { ( response: unknown extends T ? { - data: any - error: any - response: Response - status: number - headers: Headers - } + data: any + error: any + response: Response + status: number + headers: Headers + } : UnwrapPromise ) => UnwrapPromise | void > diff --git a/src/treaty2/types.ts b/src/treaty2/types.ts index bd21178..38e1287 100644 --- a/src/treaty2/types.ts +++ b/src/treaty2/types.ts @@ -2,7 +2,7 @@ import type { Elysia, ELYSIA_FORM_DATA } from 'elysia' import { EdenWS } from './ws' -import type { IsNever, MaybeEmptyObject, Not, Prettify } from '../types' +import type { IsNever, MaybeEmptyObject, Not, Prettify, JSONSerialized } from '../types' // type Files = File | FileList @@ -14,30 +14,30 @@ import type { IsNever, MaybeEmptyObject, Not, Prettify } from '../types' type And = A extends true ? B extends true - ? true - : false + ? true + : false : false type ReplaceGeneratorWithAsyncGenerator< in out RecordType extends Record > = { [K in keyof RecordType]: IsNever extends true - ? RecordType[K] - : RecordType[K] extends Generator - ? void extends B - ? AsyncGenerator - : And, void extends B ? false : true> extends true - ? B - : AsyncGenerator | B - : RecordType[K] extends AsyncGenerator - ? And>, void extends B ? true : false> extends true - ? AsyncGenerator - : And, void extends B ? false : true> extends true - ? B - : AsyncGenerator | B - : RecordType[K] extends ReadableStream - ? AsyncGenerator - : RecordType[K] + ? RecordType[K] + : RecordType[K] extends Generator + ? void extends B + ? AsyncGenerator + : And, void extends B ? false : true> extends true + ? B + : AsyncGenerator | B + : RecordType[K] extends AsyncGenerator + ? And>, void extends B ? true : false> extends true + ? AsyncGenerator + : And, void extends B ? false : true> extends true + ? B + : AsyncGenerator | B + : RecordType[K] extends ReadableStream + ? AsyncGenerator + : RecordType[K] } & {} type Enumerate< @@ -67,110 +67,110 @@ export namespace Treaty { App extends { '~Routes': infer Schema extends Record } - ? Prettify> & CreateParams - : 'Please install Elysia before using Eden' + ? Prettify> & CreateParams + : 'Please install Elysia before using Eden' export type Sign> = { [K in keyof Route as K extends `:${string}` - ? never - : K]: K extends 'subscribe' // ? Websocket route - ? MaybeEmptyObject & - MaybeEmptyObject< - Route['subscribe']['query'], - 'query' - > extends infer Param - ? (options?: Param) => EdenWS - : never - : Route[K] extends { - body: infer Body - headers: infer Headers - params: any - query: infer Query - response: infer Res extends Record - } - ? MaybeEmptyObject & - MaybeEmptyObject extends infer Param - ? {} extends Param - ? undefined extends Body - ? K extends 'get' | 'head' - ? ( - options?: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : ( - body?: Body, - options?: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : K extends 'get' | 'head' - ? ( - options?: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : {} extends Body - ? ( - body?: Body, - options?: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : ( - body: Body, - options?: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : K extends 'get' | 'head' - ? ( - options: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : ( - body: Body, - options: Prettify - ) => Promise< - TreatyResponse< - ReplaceGeneratorWithAsyncGenerator - > - > - : never - : CreateParams + ? never + : K]: K extends 'subscribe' // ? Websocket route + ? MaybeEmptyObject & + MaybeEmptyObject< + Route['subscribe']['query'], + 'query' + > extends infer Param + ? (options?: Param) => EdenWS + : never + : Route[K] extends { + body: infer Body + headers: infer Headers + params: any + query: infer Query + response: infer Res extends Record + } + ? MaybeEmptyObject & + MaybeEmptyObject extends infer Param + ? {} extends Param + ? undefined extends Body + ? K extends 'get' | 'head' + ? ( + options?: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : ( + body?: Body, + options?: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : K extends 'get' | 'head' + ? ( + options?: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : {} extends Body + ? ( + body?: Body, + options?: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : ( + body: Body, + options?: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : K extends 'get' | 'head' + ? ( + options: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : ( + body: Body, + options: Prettify + ) => Promise< + TreatyResponse< + ReplaceGeneratorWithAsyncGenerator + > + > + : never + : CreateParams } type CreateParams> = Extract extends infer Path extends string - ? IsNever extends true - ? Prettify> - : // ! DO NOT USE PRETTIFY ON THIS LINE, OTHERWISE FUNCTION CALLING WILL BE OMITTED - (((params: { - [param in Path extends `:${infer Param}` - ? Param extends `${infer Param}?` - ? Param - : Param - : never]: string | number - }) => Prettify> & - CreateParams) & - Prettify>) & - (Path extends `:${string}?` - ? CreateParams - : {}) - : never + ? IsNever extends true + ? Prettify> + : // ! DO NOT USE PRETTIFY ON THIS LINE, OTHERWISE FUNCTION CALLING WILL BE OMITTED + (((params: { + [param in Path extends `:${infer Param}` + ? Param extends `${infer Param}?` + ? Param + : Param + : never]: string | number + }) => Prettify> & + CreateParams) & + Prettify>) & + (Path extends `:${string}?` + ? CreateParams + : {}) + : never export interface Config { fetch?: Omit @@ -178,9 +178,9 @@ export namespace Treaty { headers?: MaybeArray< | RequestInit['headers'] | (( - path: string, - options: RequestInit - ) => RequestInit['headers'] | void) + path: string, + options: RequestInit + ) => RequestInit['headers'] | void) > onRequest?: MaybeArray< ( @@ -198,37 +198,41 @@ export namespace Treaty { export type TreatyResponse> = | { - data: Res[Extract] extends { - [ELYSIA_FORM_DATA]: infer Data - } - ? Data - : Res[Extract] - error: null - response: Response - status: number - headers: ResponseInit['headers'] - } + data: JSONSerialized< + Res[Extract] extends { + [ELYSIA_FORM_DATA]: infer Data + } + ? Data + : Res[Extract] + > + error: null + response: Response + status: number + headers: ResponseInit['headers'] + } | { - data: null - error: Exclude extends never - ? { - status: unknown - value: unknown - } - : { - [Status in keyof Res]: { - status: Status - value: Res[Status] extends { - [ELYSIA_FORM_DATA]: infer Data - } - ? Data - : Res[Status] + data: null + error: Exclude extends never + ? { + status: unknown + value: unknown + } + : { + [Status in keyof Res]: { + status: Status + value: JSONSerialized< + Res[Status] extends { + [ELYSIA_FORM_DATA]: infer Data } - }[Exclude] - response: Response - status: number - headers: ResponseInit['headers'] - } + ? Data + : Res[Status] + > + } + }[Exclude] + response: Response + status: number + headers: ResponseInit['headers'] + } export interface OnMessage extends MessageEvent { data: Data diff --git a/src/types.ts b/src/types.ts index 2011a9b..bae7605 100644 --- a/src/types.ts +++ b/src/types.ts @@ -21,8 +21,8 @@ export type MapError> = [ }[keyof T] ] extends [infer A extends number] ? { - [K in A]: EdenFetchError - }[A] + [K in A]: EdenFetchError + }[A] : false export type UnionToIntersect = ( @@ -49,16 +49,16 @@ export type IsUnknown = IsAny extends true type IsExactlyUnknown = [T] extends [unknown] ? [unknown] extends [T] - ? true - : false + ? true + : false : false; type IsUndefined = [T] extends [undefined] ? true : false type IsMatchingEmptyObject = [T] extends [{}] ? [{}] extends [T] - ? true - : false + ? true + : false : false export type MaybeEmptyObject< @@ -91,14 +91,50 @@ export type Prettify = { export type TreatyToPath = UnionToIntersect< T extends Record - ? { - [K in keyof T]: T[K] extends AnyTypedRoute - ? { [path in Path]: { [method in K]: T[K] } } - : unknown extends T[K] - ? { [path in Path]: { [method in K]: T[K] } } - : TreatyToPath - }[keyof T] - : {} + ? { + [K in keyof T]: T[K] extends AnyTypedRoute + ? { [path in Path]: { [method in K]: T[K] } } + : unknown extends T[K] + ? { [path in Path]: { [method in K]: T[K] } } + : TreatyToPath + }[keyof T] + : {} > export type Not = T extends true ? false : true + +/** + * Transforms a type to match its JSON.stringify() serialization behavior. + * This handles: + * - Objects with .toJSON() methods (e.g., DDD domain instances) + * - Date objects (converted to string) + * - Arrays (recursively serialized) + * - Objects (recursively serialized, excluding functions) + * - Primitives (unchanged) + * + * Special types like AsyncGenerator, Generator, ReadableStream, File, and Blob + * are preserved as-is since they're handled by other transformations or runtime. + */ +export type JSONSerialized = T extends { toJSON(): infer R } + ? R extends () => infer U + ? JSONSerialized + : JSONSerialized + : T extends Date + ? string + : T extends Function + ? never + : T extends AsyncGenerator + ? AsyncGenerator + : T extends Generator + ? Generator + : T extends ReadableStream + ? ReadableStream + : T extends File + ? File + : T extends Blob + ? Blob + : T extends Array + ? Array> + : T extends object + ? { [K in keyof T as T[K] extends Function ? never : K]: JSONSerialized } + : T \ No newline at end of file