@buntal/http 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/cookie.ts +84 -0
- package/app/index.ts +3 -0
- package/app/request.ts +8 -0
- package/app/response.ts +13 -0
- package/index.ts +180 -0
- package/package.json +2 -2
- package/router/index.ts +5 -0
package/app/cookie.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Req } from '../../handler/main/request'
|
|
2
|
+
import type { Res } from '../../handler/main/response'
|
|
3
|
+
|
|
4
|
+
export type CookieOptions = {
|
|
5
|
+
maxAge?: number
|
|
6
|
+
expires?: Date
|
|
7
|
+
path?: string
|
|
8
|
+
domain?: string
|
|
9
|
+
secure?: boolean
|
|
10
|
+
httpOnly?: boolean
|
|
11
|
+
sameSite?: 'Strict' | 'Lax' | 'None'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const cookie = {
|
|
15
|
+
get: (req: Req, name: string) => {
|
|
16
|
+
const cookies = req.headers.get('cookie')
|
|
17
|
+
if (!cookies) return null
|
|
18
|
+
const cookieArray = cookies.split('; ')
|
|
19
|
+
for (const cookie of cookieArray) {
|
|
20
|
+
const [key, value] = cookie.split('=')
|
|
21
|
+
if (key === name) {
|
|
22
|
+
return decodeURIComponent(value || '')
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
},
|
|
27
|
+
getAll: (req: Req) => {
|
|
28
|
+
const cookies = req.headers.get('cookie')
|
|
29
|
+
if (!cookies) return {}
|
|
30
|
+
const cookieArray = cookies.split('; ')
|
|
31
|
+
return cookieArray.reduce((acc, cookie) => {
|
|
32
|
+
const [key, value] = cookie.split('=') as [string, string?]
|
|
33
|
+
return { ...acc, [key]: decodeURIComponent(value || '') }
|
|
34
|
+
}, {})
|
|
35
|
+
},
|
|
36
|
+
set: (
|
|
37
|
+
res: Res,
|
|
38
|
+
name: string,
|
|
39
|
+
value: string,
|
|
40
|
+
{
|
|
41
|
+
maxAge,
|
|
42
|
+
expires,
|
|
43
|
+
path,
|
|
44
|
+
domain,
|
|
45
|
+
secure,
|
|
46
|
+
httpOnly,
|
|
47
|
+
sameSite
|
|
48
|
+
}: CookieOptions = {}
|
|
49
|
+
) => {
|
|
50
|
+
let cookieString = `${name}=${encodeURIComponent(value)}`
|
|
51
|
+
if (maxAge) {
|
|
52
|
+
cookieString += `; Max-Age=${maxAge}`
|
|
53
|
+
}
|
|
54
|
+
if (expires) {
|
|
55
|
+
cookieString += `; Expires=${expires.toUTCString()}`
|
|
56
|
+
}
|
|
57
|
+
if (path) {
|
|
58
|
+
cookieString += `; Path=${path}`
|
|
59
|
+
}
|
|
60
|
+
if (domain) {
|
|
61
|
+
cookieString += `; Domain=${domain}`
|
|
62
|
+
}
|
|
63
|
+
if (secure) {
|
|
64
|
+
cookieString += '; Secure'
|
|
65
|
+
}
|
|
66
|
+
if (httpOnly) {
|
|
67
|
+
cookieString += '; HttpOnly'
|
|
68
|
+
}
|
|
69
|
+
if (sameSite) {
|
|
70
|
+
cookieString += `; SameSite=${sameSite}`
|
|
71
|
+
}
|
|
72
|
+
res.headers({
|
|
73
|
+
'Set-Cookie': cookieString
|
|
74
|
+
})
|
|
75
|
+
return cookieString
|
|
76
|
+
},
|
|
77
|
+
delete: (res: Res, name: string) => {
|
|
78
|
+
const cookieString = `${name}=; Max-Age=0; path=/`
|
|
79
|
+
res.headers({
|
|
80
|
+
'Set-Cookie': cookieString
|
|
81
|
+
})
|
|
82
|
+
return cookieString
|
|
83
|
+
}
|
|
84
|
+
}
|
package/app/index.ts
CHANGED
package/app/request.ts
ADDED
package/app/response.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Res as BaseRes } from '@buntal/handler'
|
|
2
|
+
import { cookie, type CookieOptions } from './cookie'
|
|
3
|
+
|
|
4
|
+
export class Res extends BaseRes {
|
|
5
|
+
cookie(name: string, value?: string | null, options?: CookieOptions) {
|
|
6
|
+
if (value) {
|
|
7
|
+
cookie.set(this, name, value, options)
|
|
8
|
+
} else {
|
|
9
|
+
cookie.delete(this, name)
|
|
10
|
+
}
|
|
11
|
+
return this
|
|
12
|
+
}
|
|
13
|
+
}
|
package/index.ts
CHANGED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { ALLOWED_METHODS, h, Res, type AtomicHandler, type Req } from '@buntal/handler'
|
|
2
|
+
import type { WebSocketHandler } from 'bun'
|
|
3
|
+
import { buildRouter } from './router'
|
|
4
|
+
|
|
5
|
+
type Config = {
|
|
6
|
+
port: number
|
|
7
|
+
appDir?: string
|
|
8
|
+
websocket?: WebSocketHandler
|
|
9
|
+
injectHandler?: (payload: {
|
|
10
|
+
req: Req
|
|
11
|
+
match: Bun.MatchedRoute
|
|
12
|
+
handler: any
|
|
13
|
+
}) => Promise<Response | void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ExtractRouteParams<Path extends string> =
|
|
17
|
+
Path extends `${string}/:${infer Param}/${infer Rest}`
|
|
18
|
+
? { [K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string }
|
|
19
|
+
: Path extends `${string}/:${infer Param}`
|
|
20
|
+
? { [K in Param]: string }
|
|
21
|
+
: Path extends `/:${infer Param}`
|
|
22
|
+
? { [K in Param]: string }
|
|
23
|
+
: {}
|
|
24
|
+
|
|
25
|
+
export class Http {
|
|
26
|
+
private middlewares: AtomicHandler[] = []
|
|
27
|
+
private routes: Record<
|
|
28
|
+
string,
|
|
29
|
+
{ [K in (typeof ALLOWED_METHODS)[number] | string]?: AtomicHandler }
|
|
30
|
+
> = {}
|
|
31
|
+
private errorHandler:
|
|
32
|
+
| ((error: Error) => Response | Promise<Response>)
|
|
33
|
+
| null = null
|
|
34
|
+
private notFoundHandler: AtomicHandler = (_, res) =>
|
|
35
|
+
res.status(404).json({
|
|
36
|
+
error: 'Not found'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
constructor(private config: Config) {}
|
|
40
|
+
|
|
41
|
+
start(cb?: (server: Bun.Server) => void) {
|
|
42
|
+
const res = new Res()
|
|
43
|
+
const middlewares = this.middlewares
|
|
44
|
+
|
|
45
|
+
const server = Bun.serve({
|
|
46
|
+
port: this.config.port,
|
|
47
|
+
reusePort: true,
|
|
48
|
+
routes: this.routes,
|
|
49
|
+
websocket: this.config.websocket,
|
|
50
|
+
fetch: async (raw: Request, server): Promise<Response | any> => {
|
|
51
|
+
if (this.config.websocket && server.upgrade(raw)) return
|
|
52
|
+
|
|
53
|
+
if (raw.method === 'OPTIONS') {
|
|
54
|
+
return res.send('departed')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!this.config.appDir) {
|
|
58
|
+
return res.status(404).json({
|
|
59
|
+
error: 'Not found'
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const req = raw as Req
|
|
64
|
+
|
|
65
|
+
const router = buildRouter(this.config.appDir!)
|
|
66
|
+
const match = router.match(req)
|
|
67
|
+
|
|
68
|
+
if (match) {
|
|
69
|
+
req.params = match.params || {}
|
|
70
|
+
req.query = match.query || {}
|
|
71
|
+
|
|
72
|
+
const handler = await import(match.filePath)
|
|
73
|
+
const injected = await this.config.injectHandler?.({
|
|
74
|
+
req,
|
|
75
|
+
match,
|
|
76
|
+
handler
|
|
77
|
+
})
|
|
78
|
+
if (injected instanceof Response) {
|
|
79
|
+
return injected
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (req.method in handler) {
|
|
83
|
+
return h(...middlewares, handler[req.method])(req, res)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return h(...middlewares, this.notFoundHandler)(req, res)
|
|
88
|
+
},
|
|
89
|
+
error: async (error: Error) => {
|
|
90
|
+
if (this.errorHandler) {
|
|
91
|
+
return await this.errorHandler(error)
|
|
92
|
+
}
|
|
93
|
+
return res.status(500).json({
|
|
94
|
+
error: error.message,
|
|
95
|
+
details: error.stack
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
cb?.(server)
|
|
101
|
+
return server
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onError(handler: (error: Error) => Response | Promise<Response>) {
|
|
105
|
+
this.errorHandler = handler
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onNotFound(handler: AtomicHandler) {
|
|
109
|
+
this.notFoundHandler = handler
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
use(handler: AtomicHandler) {
|
|
113
|
+
this.middlewares.push(handler)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get<R extends string, P = ExtractRouteParams<R>>(
|
|
117
|
+
route: R,
|
|
118
|
+
...handlers: AtomicHandler<P>[]
|
|
119
|
+
) {
|
|
120
|
+
this.routes[route] = {
|
|
121
|
+
GET: (req) =>
|
|
122
|
+
h<P>(...(this.middlewares as AtomicHandler<P>[]), ...handlers)(
|
|
123
|
+
req as Req<P>,
|
|
124
|
+
new Res()
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
post<R extends string, P = ExtractRouteParams<R>>(
|
|
130
|
+
route: R,
|
|
131
|
+
...handlers: AtomicHandler<P>[]
|
|
132
|
+
) {
|
|
133
|
+
this.routes[route] = {
|
|
134
|
+
POST: (req) =>
|
|
135
|
+
h<P>(...(this.middlewares as AtomicHandler<P>[]), ...handlers)(
|
|
136
|
+
req as Req<P>,
|
|
137
|
+
new Res()
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
put<R extends string, P = ExtractRouteParams<R>>(
|
|
143
|
+
route: R,
|
|
144
|
+
...handlers: AtomicHandler<P>[]
|
|
145
|
+
) {
|
|
146
|
+
this.routes[route] = {
|
|
147
|
+
PUT: (req) =>
|
|
148
|
+
h<P>(...(this.middlewares as AtomicHandler<P>[]), ...handlers)(
|
|
149
|
+
req as Req<P>,
|
|
150
|
+
new Res()
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
delete<R extends string, P = ExtractRouteParams<R>>(
|
|
156
|
+
route: R,
|
|
157
|
+
...handlers: AtomicHandler<P>[]
|
|
158
|
+
) {
|
|
159
|
+
this.routes[route] = {
|
|
160
|
+
DELETE: (req) =>
|
|
161
|
+
h<P>(...(this.middlewares as AtomicHandler<P>[]), ...handlers)(
|
|
162
|
+
req as Req<P>,
|
|
163
|
+
new Res()
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
patch<R extends string, P = ExtractRouteParams<R>>(
|
|
169
|
+
route: R,
|
|
170
|
+
...handlers: AtomicHandler<P>[]
|
|
171
|
+
) {
|
|
172
|
+
this.routes[route] = {
|
|
173
|
+
PATCH: (req) =>
|
|
174
|
+
h<P>(...(this.middlewares as AtomicHandler<P>[]), ...handlers)(
|
|
175
|
+
req as Req<P>,
|
|
176
|
+
new Res()
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buntal/http",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"typescript": "^5"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@buntal/
|
|
13
|
+
"@buntal/handler": "latest"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|