Skip to content

Commit bcbe823

Browse files
authored
Merge branch 'main' into feat/parse-dates-option
2 parents 39055bb + b4faf0d commit bcbe823

File tree

12 files changed

+453
-100
lines changed

12 files changed

+453
-100
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 1.4.7
2+
Bug fix:
3+
- [#247](https://github.com/elysiajs/eden/pull/247), [#246](https://github.com/elysiajs/eden/issues/246) handle network error
4+
- [#244](https://github.com/elysiajs/eden/issues/244) don't skip index path name
5+
- [#241](https://github.com/elysiajs/eden/issues/241) prevent freeze from thenable
6+
- [#236](https://github.com/elysiajs/eden/issues/236) reconcile default headers in type-level
7+
- [#232](https://github.com/elysiajs/eden/issues/232) accept query array
8+
19
# 1.4.6 - 23 Dec 2025
210
Bug fix:
311
- [#235](https://github.com/elysiajs/eden/pull/235) SSE stream parser with partial chunks

bun.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/a.ts

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,26 @@
1-
import { Elysia, t } from 'elysia'
1+
import { Elysia, sse, t } from 'elysia'
22
import { treaty } from '../src'
33

4+
const model = t.Object({
5+
value: t.Number()
6+
})
7+
48
const app = new Elysia()
5-
.post('/files', ({ body: { files } }) => files.map((file) => file.name), {
6-
body: t.Object({
7-
files: t.Files()
8-
})
9-
})
10-
.post('/any/file', ({ body: { file } }) => file.name, {
11-
body: t.Object({
12-
file: t.File({ type: 'image/*' })
9+
.get('/', ({ query }) => query, {
10+
query: t.Object({
11+
minDate: t.Date(),
12+
maxDate: t.Date()
1313
})
1414
})
15-
.post('/png/file', ({ body: { file } }) => file.name, {
16-
body: t.Object({
17-
file: t.File({ type: 'image/png' })
18-
})
19-
})
20-
21-
const client = treaty(app)
22-
type client = typeof client
23-
24-
const filePath1 = `test/public/aris-yuzu.jpg`
25-
const filePath2 = `test/public/midori.png`
26-
const filePath3 = `test/public/kyuukurarin.mp4`
15+
.listen(3000)
2716

28-
const bunFile1 = Bun.file(filePath1)
29-
const bunFile2 = Bun.file(filePath2)
30-
const bunFile3 = Bun.file(filePath3)
17+
const api = treaty(app)
3118

32-
const { data: files } = await client.files.post({
33-
files: bunFile1
19+
const { data, error } = await api.get({
20+
query: {
21+
minDate: new Date(),
22+
maxDate: new Date()
23+
}
3424
})
3525

36-
console.log(files)
26+
console.log(data, error)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elysiajs/eden",
3-
"version": "1.4.6",
3+
"version": "1.4.7",
44
"description": "Fully type-safe Elysia client",
55
"author": {
66
"name": "saltyAom",

src/fetch/index.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Elysia } from 'elysia'
33
import { EdenFetchError } from '../errors'
44
import type { EdenFetch } from './types'
55
import { parseStringifiedValue } from '../utils/parsingUtils'
6+
import { ThrowHttpErrors } from '../types'
67

78
export type { EdenFetch } from './types'
89

@@ -30,17 +31,32 @@ const parseResponse = async (response: Response) => {
3031
return response.text().then(parseStringifiedValue)
3132
}
3233

33-
const handleResponse = async (response: Response, retry: () => any) => {
34+
const shouldThrow = (
35+
error: EdenFetchError<number, unknown>,
36+
throwHttpErrors?: ThrowHttpErrors
37+
): boolean => {
38+
if (typeof throwHttpErrors === 'function') return throwHttpErrors(error)
39+
return throwHttpErrors === true
40+
}
41+
42+
const handleResponse = async (
43+
response: Response,
44+
retry: () => any,
45+
throwHttpErrors?: ThrowHttpErrors
46+
) => {
3447
const data = await parseResponse(response)
3548

36-
if (response.status > 300)
49+
if (response.status >= 300 || response.status < 200) {
50+
const error = new EdenFetchError(response.status, data)
51+
if (shouldThrow(error, throwHttpErrors)) throw error
3752
return {
3853
data: null,
3954
status: response.status,
4055
headers: response.headers,
4156
retry,
42-
error: new EdenFetchError(response.status, data)
57+
error
4358
}
59+
}
4460

4561
return {
4662
data,
@@ -56,13 +72,15 @@ export const edenFetch = <App extends Elysia<any, any, any, any, any, any, any>>
5672
config?: EdenFetch.Config
5773
): EdenFetch.Create<App> =>
5874
// @ts-ignore
59-
(endpoint: string, { query, params, body, ...options } = {}) => {
75+
(endpoint: string, { query, params, body, throwHttpErrors: requestThrowHttpErrors, ...options } = {}) => {
6076
if (params)
6177
Object.entries(params).forEach(([key, value]) => {
6278
endpoint = endpoint.replace(`:${key}`, value as string)
6379
})
6480

6581
const fetch = config?.fetcher || globalThis.fetch
82+
// Per-request throwHttpErrors overrides config
83+
const resolvedThrowHttpErrors = requestThrowHttpErrors ?? config?.throwHttpErrors
6684

6785
const nonNullishQuery = query
6886
? Object.fromEntries(
@@ -100,14 +118,19 @@ export const edenFetch = <App extends Elysia<any, any, any, any, any, any, any>>
100118

101119
const execute = () =>
102120
fetch(requestUrl, init)
103-
.then((response) => handleResponse(response, execute))
104-
.catch((err) => ({
105-
data: null,
106-
error: new EdenFetchError(503, err),
107-
status: 503,
108-
headers: undefined,
109-
retry: execute
110-
}))
121+
.then((response) => handleResponse(response, execute, resolvedThrowHttpErrors))
122+
.catch((err) => {
123+
if (err instanceof EdenFetchError) throw err
124+
const error = new EdenFetchError(503, err)
125+
if (shouldThrow(error, resolvedThrowHttpErrors)) throw error
126+
return {
127+
data: null,
128+
error,
129+
status: 503,
130+
headers: undefined,
131+
retry: execute
132+
}
133+
})
111134

112135
return execute()
113136
}

src/fetch/types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import type {
55
IsUnknown,
66
IsNever,
77
Prettify,
8-
TreatyToPath
8+
TreatyToPath,
9+
ThrowHttpErrors
910
} from '../types'
1011

1112
export namespace EdenFetch {
@@ -21,6 +22,7 @@ export namespace EdenFetch {
2122

2223
export interface Config extends RequestInit {
2324
fetcher?: typeof globalThis.fetch
25+
throwHttpErrors?: ThrowHttpErrors
2426
}
2527

2628
export type Fn<Schema extends Record<string, any>> = <
@@ -60,7 +62,9 @@ export namespace EdenFetch {
6062
}) &
6163
(IsUnknown<Route['body']> extends false
6264
? { body: Route['body'] }
63-
: { body?: unknown })
65+
: { body?: unknown }) & {
66+
throwHttpErrors?: ThrowHttpErrors
67+
}
6468
) => Promise<
6569
Prettify<
6670
| {

src/treaty2/index.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
parseStringifiedDate,
1111
parseStringifiedValue
1212
} from '../utils/parsingUtils'
13+
import { ThrowHttpErrors } from '../types'
1314

1415
const method = [
1516
'get',
@@ -23,6 +24,14 @@ const method = [
2324
'subscribe'
2425
] as const
2526

27+
const shouldThrow = (
28+
error: EdenFetchError<number, unknown>,
29+
throwHttpErrors?: ThrowHttpErrors
30+
): boolean => {
31+
if (typeof throwHttpErrors === 'function') return throwHttpErrors(error)
32+
return throwHttpErrors === true
33+
}
34+
2635
const locals = ['localhost', '127.0.0.1', '0.0.0.0']
2736

2837
const isServer = typeof FileList === 'undefined'
@@ -213,12 +222,13 @@ const createProxy = (
213222
): any =>
214223
new Proxy(() => {}, {
215224
get(_, param: string): any {
216-
return createProxy(
217-
domain,
218-
config,
219-
param === 'index' ? paths : [...paths, param],
220-
elysia
225+
if (
226+
paths.length === 0 &&
227+
(param === 'then' || param === 'catch' || param === 'finally')
221228
)
229+
return undefined
230+
231+
return createProxy(domain, config, [...paths, param], elysia)
222232
},
223233
apply(_, __, [body, options]) {
224234
if (
@@ -251,29 +261,30 @@ const createProxy = (
251261

252262
let q = ''
253263
if (query) {
254-
const append = (key: string, value: string) => {
255-
q +=
264+
const append = (key: string, value: unknown) => {
265+
// Explicitly exclude null and undefined values from url encoding
266+
// to prevent parsing string "null" / string "undefined"
267+
if (value === undefined || value === null) return
268+
269+
if (value instanceof Date) value = value.toISOString()
270+
271+
q +=
256272
(q ? '&' : '?') +
257273
`${encodeURIComponent(key)}=${encodeURIComponent(
258-
value
274+
typeof value === 'object'
275+
? JSON.stringify(value)
276+
: value + ''
259277
)}`
260278
}
261279

262280
for (const [key, value] of Object.entries(query)) {
263281
if (Array.isArray(value)) {
264282
for (const v of value) append(key, v)
265-
continue
266-
}
267283

268-
// Explicitly exclude null and undefined values from url encoding
269-
// to prevent parsing string "null" / string "undefined"
270-
if (value === undefined || value === null) continue
271-
272-
if (typeof value === 'object') {
273-
append(key, JSON.stringify(value))
274284
continue
275285
}
276-
append(key, `${value}`)
286+
287+
append(key, value)
277288
}
278289
}
279290

@@ -322,6 +333,14 @@ const createProxy = (
322333
? body.fetch
323334
: options?.fetch
324335

336+
// Per-request throwHttpErrors overrides config
337+
const requestThrowHttpErrors =
338+
isGetOrHead && typeof body === 'object'
339+
? body.throwHttpErrors
340+
: options?.throwHttpErrors
341+
const resolvedThrowHttpErrors =
342+
requestThrowHttpErrors ?? config.throwHttpErrors
343+
325344
fetchInit = {
326345
...fetchInit,
327346
...fetchOpts
@@ -498,7 +517,8 @@ const createProxy = (
498517
}
499518
}
500519

501-
if (options?.headers?.['content-type'])
520+
if (options?.headers?.['content-type'])
521+
// @ts-ignore
502522
fetchInit.headers['content-type'] =
503523
options?.headers['content-type']
504524

@@ -511,9 +531,12 @@ const createProxy = (
511531
new Request(url, fetchInit)
512532
) ?? fetcher!(url, fetchInit))
513533
} catch (err) {
534+
const error = new EdenFetchError(503, err)
535+
if (shouldThrow(error, resolvedThrowHttpErrors))
536+
throw error
514537
return {
515538
data: null,
516-
error: new EdenFetchError(503, err),
539+
error,
517540
response: undefined,
518541
status: 503,
519542
headers: undefined
@@ -574,7 +597,8 @@ const createProxy = (
574597

575598
return v
576599
})
577-
break
600+
break
601+
578602
case 'application/octet-stream':
579603
data = await response.arrayBuffer()
580604
break
@@ -600,6 +624,8 @@ const createProxy = (
600624

601625
if (response.status >= 300 || response.status < 200) {
602626
error = new EdenFetchError(response.status, data)
627+
if (shouldThrow(error, resolvedThrowHttpErrors))
628+
throw error
603629
data = null
604630
}
605631

@@ -626,11 +652,12 @@ const createProxy = (
626652
}) as any
627653

628654
export const treaty = <
629-
const App extends Elysia<any, any, any, any, any, any, any>
655+
const App extends Elysia<any, any, any, any, any, any, any>,
656+
const Head extends {} = {}
630657
>(
631658
domain: string | App,
632-
config: Treaty.Config = {}
633-
): Treaty.Create<App> => {
659+
config: Treaty.Config<Head> = {}
660+
): Treaty.Create<App, Head> => {
634661
if (typeof domain === 'string') {
635662
if (!config.keepDomain) {
636663
if (!domain.includes('://'))

0 commit comments

Comments
 (0)