@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 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
@@ -0,0 +1,3 @@
1
+ export * from './cookie'
2
+ export * from './request'
3
+ export * from './response'
package/app/request.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { Req as BaseReq } from '@buntal/handler'
2
+ import { cookie } from './cookie'
3
+
4
+ export class Req<P = Record<string, string>, T = unknown> extends BaseReq {
5
+ get cookies() {
6
+ return cookie.getAll(this)
7
+ }
8
+ }
@@ -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.1",
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/core": "latest"
13
+ "@buntal/handler": "latest"
14
14
  },
15
15
  "repository": {
16
16
  "type": "git",
@@ -0,0 +1,5 @@
1
+ export const buildRouter = (dir: string) =>
2
+ new Bun.FileSystemRouter({
3
+ style: 'nextjs',
4
+ dir: dir
5
+ })