Skip to content

Commit e7a7f13

Browse files
committed
🔧 fix: recursive macro with conflict value per status
1 parent a8c405a commit e7a7f13

File tree

6 files changed

+106
-91
lines changed

6 files changed

+106
-91
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# 1.4.25 - 12 Feb 2025
2+
Feature:
3+
- export ElysiaStatus
4+
25
Bug fix:
36
- macro with conflict literal value per status
47
- recursive macro with conflict value per status

example/a.ts

Lines changed: 53 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,67 @@
1-
import { Elysia, t } from '../src'
2-
import { req } from '../test/utils'
1+
import { Elysia, ElysiaStatus, t, type UnwrapSchema } from '../src'
32

4-
const challengeModel = t.Object({
5-
nonce: t.String(),
6-
issued: t.Number(),
7-
bits: t.Number()
8-
})
3+
const Models = {
4+
'user.update': t.Object({
5+
id: t.String(),
6+
name: t.Optional(t.String())
7+
})
8+
}
99

10-
const app = new Elysia({
11-
cookie: {
12-
secrets: ['a', null],
13-
sign: 'challenge'
14-
}
15-
})
16-
.get(
17-
'/set',
18-
({ cookie: { challenge } }) => {
19-
challenge.value = {
20-
nonce: 'hello',
21-
bits: 19,
22-
issued: Date.now()
23-
}
10+
type Models = {
11+
[k in keyof typeof Models]: UnwrapSchema<(typeof Models)[k]>
12+
}
13+
14+
const app = new Elysia()
15+
.macro('isAuth', {
16+
// headers: t.Object({
17+
// authorization: t.TemplateLiteral('Authorization ${string}')
18+
// }),
19+
async resolve({ headers, status }) {
20+
// Mock authentication logic
21+
if (Math.random() > 0.5) return status(401, 'Not signed in')
22+
if (Math.random() > 0.5) return status(401, 'Deactivated account')
2423

25-
return challenge.value
26-
},
27-
{
28-
cookie: t.Cookie({
29-
challenge: t.Optional(challengeModel)
30-
})
31-
}
32-
)
33-
.get(
34-
'/get',
35-
({ cookie: { challenge } }) => {
3624
return {
37-
type: typeof challenge,
38-
value: challenge.value
25+
user: 'saltyaom'
3926
}
40-
},
41-
{
42-
cookie: t.Cookie({
43-
challenge: challengeModel
44-
})
45-
}
46-
)
47-
48-
const first = await app.handle(
49-
req('/set', {
50-
headers: {
51-
cookie: `challenge=${JSON.stringify({
52-
nonce: 'hello',
53-
bits: 19,
54-
issued: 1770750432990
55-
})}`
5627
}
5728
})
58-
)
29+
.model(Models)
30+
.macro('isAdmin', {
31+
isAuth: true,
32+
async resolve({ headers, status }) {
33+
// Mock admin check logic
34+
if (Math.random() > 0.5) return status(403, 'Not allowed')
35+
5936

60-
console.log(first.status)
61-
console.log(await first.json())
6237

63-
const cookie = first.headers.get('set-cookie')
64-
const challenge = cookie!.match(/challenge=([^;]*)/)![1]
38+
headers.authorization
6539

66-
console.log(challenge)
40+
return {
41+
admin: {
42+
async updateUser({ id, ...rest }: Models['user.update']) {
43+
if (Math.random() > 0.5) return status(404, 'No user')
6744

68-
const second = await app
69-
.handle(
70-
req('/get', {
71-
headers: {
72-
cookie: `challenge=${challenge}`
45+
return { id }
46+
}
47+
}
7348
}
74-
})
49+
}
50+
})
51+
.post(
52+
'/',
53+
async ({ user, admin, body, headers }) => {
54+
return 'ok'
55+
// const updated = await admin.updateUser(body)
56+
57+
// if (updated instanceof ElysiaStatus) return updated
58+
59+
// return `User ${user} updated user ${updated.id}` as const
60+
},
61+
{
62+
isAuth: true,
63+
// body: 'user.update'
64+
}
7565
)
7666

77-
console.log(second.status)
67+
type Result = (typeof app)['~Routes']['post']['response']

src/error.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ export class ElysiaCustomStatusResponse<
105105
}
106106
}
107107

108+
export const ElysiaStatus = ElysiaCustomStatusResponse
109+
108110
export const status = <
109111
const Code extends number | keyof StatusMap,
110112
const T = Code extends keyof InvertedStatusMap

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8235,6 +8235,7 @@ export {
82358235
InternalServerError,
82368236
InvalidCookieSignature,
82378237
ERROR_CODE,
8238+
ElysiaStatus,
82388239
ElysiaCustomStatusResponse,
82398240
type SelectiveStatus
82408241
} from './error'

src/types.ts

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -967,26 +967,9 @@ type ExtractOnlyResponseFromMacro<A> =
967967
? {}
968968
: {} extends A
969969
? {}
970-
: Extract<A, AnyElysiaCustomStatusResponse> extends infer A
971-
? IsNever<A> extends true
972-
? {}
973-
: {
974-
return: UnionToIntersect<
975-
A extends ElysiaCustomStatusResponse<
976-
any,
977-
infer Value,
978-
infer Status
979-
>
980-
? {
981-
[status in Status]: IsAny<Value> extends true
982-
? // @ts-ignore status is always in Status Map
983-
InvertedStatusMap[Status]
984-
: Value
985-
}
986-
: {}
987-
>
988-
}
989-
: {}
970+
: {
971+
return: MergeResponseStatus<A>
972+
}
990973

991974
type MergeResponseStatus<A> = {
992975
[status in keyof UnionToIntersect<
@@ -1007,10 +990,11 @@ type MergeAllStatus<T> = {
1007990
type ExtractAllResponseFromMacro<A> =
1008991
IsNever<A> extends true
1009992
? {}
1010-
: {
1011-
// Merge all status to single object first
1012-
return: Prettify<
1013-
MergeResponseStatus<A> &
993+
: {} extends A
994+
? {}
995+
: {
996+
// Merge all status to single object first
997+
return: MergeResponseStatus<A> &
1014998
(Exclude<
1015999
A,
10161000
AnyElysiaCustomStatusResponse
@@ -1028,8 +1012,7 @@ type ExtractAllResponseFromMacro<A> =
10281012
200: A
10291013
}
10301014
: {})
1031-
>
1032-
}
1015+
}
10331016

10341017
type FlattenMacroResponse<T> = T extends object
10351018
? '_' extends keyof T

test/types/lifecycle/soundness.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2305,7 +2305,7 @@ import { Prettify } from '../../../src/types'
23052305
}
23062306
},
23072307
b: {
2308-
resolve({ status }) {
2308+
beforeHandle({ status }) {
23092309
if (Math.random()) return status(401, 'b')
23102310
},
23112311
a: true
@@ -2315,7 +2315,6 @@ import { Prettify } from '../../../src/types'
23152315
b: true
23162316
})
23172317

2318-
23192318
type Routes = (typeof app)['~Routes']['get']['response']
23202319

23212320
expectTypeOf<Routes>().toEqualTypeOf<{
@@ -2325,6 +2324,43 @@ import { Prettify } from '../../../src/types'
23252324
}>
23262325
}
23272326

2327+
// Recursive macro with conflict value per status
2328+
{
2329+
const app = new Elysia()
2330+
.macro({
2331+
a: {
2332+
resolve({ status }) {
2333+
if (Math.random()) return status(400, 'a')
2334+
if (Math.random()) return status(401, 'b')
2335+
if (Math.random()) return status(401, 'c')
2336+
2337+
return { a: 'a' }
2338+
}
2339+
},
2340+
b: {
2341+
resolve({ status }) {
2342+
if (Math.random()) return status(400, 'x')
2343+
if (Math.random()) return status(401, 'y')
2344+
if (Math.random()) return status(401, 'z')
2345+
2346+
return { b: 'b' }
2347+
},
2348+
a: true
2349+
}
2350+
})
2351+
.get('/', () => 'handler' as const, {
2352+
b: true
2353+
})
2354+
2355+
type Routes = (typeof app)['~Routes']['get']['response']
2356+
2357+
expectTypeOf<Routes>().toEqualTypeOf<{
2358+
200: 'handler'
2359+
400: 'a' | 'x'
2360+
401: 'b' | 'c' | 'y' | 'z'
2361+
}>
2362+
}
2363+
23282364
// Separate Box from literal status-like response in macro
23292365
{
23302366
const app = new Elysia()

0 commit comments

Comments
 (0)