Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,4 @@ const app = new Elysia()
})
.listen(8080)

export type Server = typeof app
export type Server = typeof app
77 changes: 39 additions & 38 deletions src/fetch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import type {
IsUnknown,
IsNever,
Prettify,
TreatyToPath
TreatyToPath,
JSONSerialized
} from '../types'

export namespace EdenFetch {
export type Create<App extends Elysia<any, any, any, any, any, any, any>> =
App extends {
'~Routes': infer Schema extends Record<string, unknown>
}
? EdenFetch.Fn<
// @ts-expect-error
TreatyToPath<Schema>
>
: 'Please install Elysia before using Eden'
? EdenFetch.Fn<
// @ts-expect-error
TreatyToPath<Schema>
>
: 'Please install Elysia before using Eden'

export interface Config extends RequestInit {
fetcher?: typeof globalThis.fetch
Expand All @@ -32,55 +33,55 @@ export namespace EdenFetch {
options: Omit<RequestInit, 'body' | 'method' | 'headers'> &
('GET' extends Method
? {
method?: Method
}
method?: Method
}
: {
method: Method
}) &
method: Method
}) &
(IsNever<keyof Route['params']> extends true
? {
params?: Record<never, string>
}
params?: Record<never, string>
}
: {
params: Route['params']
}) &
params: Route['params']
}) &
(IsNever<keyof Route['query']> extends true
? {
query?: Record<never, string>
}
query?: Record<never, string>
}
: {
query: Route['query']
}) &
query: Route['query']
}) &
(undefined extends Route['headers']
? {
headers?: Record<string, string>
}
headers?: Record<string, string>
}
: {
headers: Route['headers']
}) &
headers: Route['headers']
}) &
(IsUnknown<Route['body']> extends false
? { body: Route['body'] }
: { body?: unknown })
) => Promise<
Prettify<
| {
data: Awaited<Route['response'][200]>
error: null
status: number
headers: Record<string, unknown>
retry(): Promise<void>
}
data: JSONSerialized<Awaited<Route['response'][200]>>
error: null
status: number
headers: Record<string, unknown>
retry(): Promise<void>
}
| {
data: null
error: MapError<Route['response']> extends infer Errors
? IsNever<Errors> extends true
? EdenFetchError<number, string>
: Errors
: EdenFetchError<number, string>
status: number
headers: Record<string, unknown>
retry(): Promise<void>
}
data: null
error: MapError<Route['response']> extends infer Errors
? IsNever<Errors> extends true
? EdenFetchError<number, string>
: Errors
: EdenFetchError<number, string>
status: number
headers: Record<string, unknown>
retry(): Promise<void>
}
>
>
}
202 changes: 102 additions & 100 deletions src/treaty/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/// <reference lib="dom" />
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'
Comment on lines +4 to 5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Treaty HTTP success data now correctly reflects JSON‑serialized shape; ensure errors/rawData are aligned

  • The change to data: JSONSerialized<...Awaited<ReturnedType>...> in EdenTreaty.Sign’s HTTP branch aligns Treaty’s success typing with EdenFetch and with the actual JSON on the wire (honoring .toJSON(), Date, function stripping, etc.). This is a welcome fix.

  • As with EdenFetch, the error side still relies on MapError<Response>, whose EdenFetchError.value currently uses the raw route response type. If you adopt the MapError change suggested in src/types.ts (wrapping T[K] in JSONSerialized), Treaty’s error typings will automatically become consistent with these new success semantics.

  • The addition of rawData: MessageEvent['data'] to OnMessage<Data> is useful, but it requires the runtime EdenWS implementation (in src/treaty/index.ts) to actually populate rawData when adapting MessageEvent (e.g., by passing both parsed data and the original ws.data). It’s worth double‑checking that implementation so type and runtime stay in sync.

Also applies to: 26-127, 131-143, 162-165


🏁 Script executed:

#!/bin/bash
# Verify that Treaty WS message handling sets `rawData` when constructing the event.
rg -n "parseMessageEvent" src/treaty -C5
rg -n "rawData" src/treaty -C5

Length of output: 1661


I need to check the parseMessageEvent implementation to confirm whether rawData is being properly set at runtime.Let me search the repository directly for the parseMessageEvent implementation.

Hmm, I need to check the actual code. Let me revise:

Actually, let me just look at the file directly:


🏁 Script executed:

#!/bin/bash
# Find the parseMessageEvent function definition in parsingUtils
rg -n "parseMessageEvent" src/utils -A 10

Length of output: 411


🏁 Script executed:

#!/bin/bash
# Find the parseMessageEvent function definition in parsingUtils
rg -n "function parseMessageEvent|export.*parseMessageEvent" src/utils -A 10

Length of output: 411


🏁 Script executed:

#!/bin/bash
# Find parsingUtils file and show parseMessageEvent implementation
fd "parsingUtils" src/utils --exec cat {}

Length of output: 2289


Runtime does not populate rawData field—MessageEvent.data is overwritten with parsed data

The type contract at src/treaty/types.ts:164 declares rawData: MessageEvent['data'] as required, but the implementation does not provide it:

  • At src/treaty/index.ts:118, parseMessageEvent(ws) returns only the parsed value (see src/utils/parsingUtils.ts:66-72)
  • The spread { ...ws, data } at lines 120–123 overwrites the original ws.data with the parsed result
  • The original untouched message data is lost; rawData is never assigned

This breaks the type contract—calling code cannot safely access onMessage.rawData (it will be undefined). Either remove rawData from the type or update the runtime at src/treaty/index.ts:118–123 to preserve the original data:

const data = parseMessageEvent(ws as MessageEvent)
listener({
    ...ws,
    data,
    rawData: ws.data
} as any)


type Files = File | FileList

type Replace<RecordType, TargetType, GenericType> = {
[K in keyof RecordType]: RecordType[K] extends TargetType
? GenericType
: RecordType[K]
? GenericType
: RecordType[K]
}

type MaybeArray<T> = T | T[]
Expand All @@ -25,103 +25,105 @@ export namespace EdenTreaty {

export type Sign<Route extends Record<string, any>> = {
[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<string, string>
}) => EdenWS<Route>
: (params: { $query: Route['query'] }) => EdenWS<Route['subscribe']>
: // ? HTTP route
((
params: Prettify<
{
$fetch?: RequestInit
getRaw?: boolean
$transform?: Transform
} & (IsUnknown<Body> extends false
? Replace<Body, Blob | Blob[], Files>
: {}) &
(undefined extends Query
? {
$query?: Record<string, string>
}
: {
$query: Query
}) &
(undefined extends Headers
? {
$headers?: Record<string, unknown>
}
: {
$headers: Headers
})
>
) => Promise<
(
| {
data: Response extends {
200: infer ReturnedType
}
? Awaited<ReturnedType>
: unknown
error: null
}
| {
data: null
error: Response extends Record<number, unknown>
? MapError<Response> extends infer Errors
? IsNever<Errors> extends true
? EdenFetchError<number, string>
: Errors
: EdenFetchError<number, string>
: EdenFetchError<number, unknown>
}
) & {
status: number
response: Response
headers: Record<string, string>
}
>) extends (params: infer Params) => infer Response
? {
$params: undefined
$headers: undefined
$query: undefined
} extends Params
? (
params?: Params,
options?: {
fetch?: RequestInit
transform?: EdenTreaty.Transform<Response>
// @ts-ignore
query?: Params['query']
// @ts-ignore
headers?: Params['headers']
}
) => Response
: (
params: Params,
options?: {
fetch?: RequestInit
transform?: EdenTreaty.Transform<Response>
// @ts-ignore
query?: Params['query']
// @ts-ignore
headers?: Params['headers']
}
) => Response
: never
: Prettify<Sign<Route[K]>>
? K extends 'subscribe'
? // ? Websocket route
undefined extends Route['query']
? (params?: {
$query?: Record<string, string>
}) => EdenWS<Route>
: (params: { $query: Route['query'] }) => EdenWS<Route['subscribe']>
: // ? HTTP route
((
params: Prettify<
{
$fetch?: RequestInit
getRaw?: boolean
$transform?: Transform
} & (IsUnknown<Body> extends false
? Replace<Body, Blob | Blob[], Files>
: {}) &
(undefined extends Query
? {
$query?: Record<string, string>
}
: {
$query: Query
}) &
(undefined extends Headers
? {
$headers?: Record<string, unknown>
}
: {
$headers: Headers
})
>
) => Promise<
(
| {
data: JSONSerialized<
Response extends {
200: infer ReturnedType
}
? Awaited<ReturnedType>
: unknown
>
error: null
}
| {
data: null
error: Response extends Record<number, unknown>
? MapError<Response> extends infer Errors
? IsNever<Errors> extends true
? EdenFetchError<number, string>
: Errors
: EdenFetchError<number, string>
: EdenFetchError<number, unknown>
}
) & {
status: number
response: Response
headers: Record<string, string>
}
>) extends (params: infer Params) => infer Response
? {
$params: undefined
$headers: undefined
$query: undefined
} extends Params
? (
params?: Params,
options?: {
fetch?: RequestInit
transform?: EdenTreaty.Transform<Response>
// @ts-ignore
query?: Params['query']
// @ts-ignore
headers?: Params['headers']
}
) => Response
: (
params: Params,
options?: {
fetch?: RequestInit
transform?: EdenTreaty.Transform<Response>
// @ts-ignore
query?: Params['query']
// @ts-ignore
headers?: Params['headers']
}
) => Response
: never
: Prettify<Sign<Route[K]>>
}

type UnwrapPromise<T> = T extends Promise<infer A> ? A : T
Expand All @@ -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<T>
) => UnwrapPromise<T> | void
>
Expand Down
Loading