galbe 0.3.0 → 0.4.1

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/src/router.ts CHANGED
@@ -1,39 +1,34 @@
1
- import type { Route, RouteNode, RouteTree } from './types'
2
- import { NotFoundError } from './types'
1
+ import type { Method, Route, RouteNode } from './types'
2
+ import { MethodNotAllowedError, NotFoundError } from './types'
3
3
 
4
4
  const ROUTE_REGEX = /^(\/(\*|:?\d+|:?\w+|:?[\w\d][\w-]+[\w\d]))*\/?$/
5
5
 
6
- const walkRoutes = (path: string[], node: RouteNode, alts: RouteNode[] = []): RouteNode => {
6
+ const walk = (path: string[], node: RouteNode, alts: RouteNode[] = []): RouteNode => {
7
7
  if (path.length < 1) throw new NotFoundError()
8
- if (path.length === 1 && node.route) return node
8
+ if (path.length === 1 && node.routes && !!Object.keys(node.routes).length) return node
9
9
 
10
10
  if (node.children?.['*']) alts.push(node.children['*'])
11
11
  if (node.param) alts.push(node.param)
12
12
 
13
13
  if (node.children && path[1] in node.children) {
14
14
  path.shift()
15
- return walkRoutes(path, node.children[path[0]], alts)
15
+ return walk(path, node.children[path[0]], alts)
16
16
  }
17
17
  if (node.param) {
18
18
  path.shift()
19
19
  alts.pop()
20
- try {
21
- return walkRoutes(path, node.param, alts)
22
- } catch (error) {
23
- if (error instanceof NotFoundError) console.log(error)
24
- else throw error
25
- }
20
+ return walk(path, node.param, alts)
26
21
  }
27
22
  if (alts.length > 1) {
28
- return walkRoutes(path, alts.pop() as RouteNode, alts)
23
+ return walk(path, alts.pop() as RouteNode, alts)
29
24
  }
30
25
  if (alts.length === 1) {
31
26
  let lastAlt = alts.pop() as RouteNode
32
27
  try {
33
- return walkRoutes(path, lastAlt, alts)
28
+ return walk(path, lastAlt, alts)
34
29
  } catch (error) {
35
30
  if (error instanceof NotFoundError) {
36
- if (lastAlt?.route) return lastAlt
31
+ if (lastAlt?.routes) return lastAlt
37
32
  } else throw error
38
33
  }
39
34
  }
@@ -42,12 +37,12 @@ const walkRoutes = (path: string[], node: RouteNode, alts: RouteNode[] = []): Ro
42
37
  }
43
38
 
44
39
  export class GalbeRouter {
45
- routes: RouteTree
40
+ routes: RouteNode
46
41
  prefix: string
47
42
  cacheEnabled: boolean
48
43
  cachedRoutes: Map<string, Route | null>
49
44
  constructor(options?: { prefix?: string; cacheEnabled?: boolean }) {
50
- this.routes = { GET: {}, POST: {}, PUT: {}, PATCH: {}, DELETE: {}, OPTIONS: {} }
45
+ this.routes = { routes: {} }
51
46
  let prefix = options?.prefix || ''
52
47
  if (prefix && !prefix.match(/^\//)) prefix = `/${prefix}`
53
48
  this.prefix = prefix
@@ -62,43 +57,46 @@ export class GalbeRouter {
62
57
  route.path = `${this.prefix || ''}${route.path}`
63
58
  let path = route.path.replace(/^\/$(.*)\/?$/, '$1').split('/')
64
59
  path.shift()
65
- let r = this.routes[route.method.toUpperCase()]
60
+ let r = this.routes
66
61
  if (!path.length) {
67
- r.route = route
62
+ r.routes[route.method] = route
68
63
  } else {
69
64
  while (path.length) {
70
65
  let p = path.shift()
71
66
  if (p === undefined) break
72
67
  if (!path.length) {
73
- if (p.match(/^:/)) r.param = { route }
74
- else {
68
+ if (p.match(/^:/)) {
69
+ if (!r.param) r.param = { routes: {} }
70
+ r.param.routes[route.method] = route
71
+ } else {
75
72
  if (!r.children) r.children = {}
76
- r.children[p] = { ...r.children[p], route }
73
+ if (!(p in r.children)) r.children[p] = { routes: {} }
74
+ r.children[p].routes[route.method] = route
77
75
  }
78
76
  } else {
79
77
  if (p.match(/^:/)) {
80
- if (!r.param) r.param = {}
78
+ if (!r.param) r.param = { routes: {} }
81
79
  r = r.param
82
80
  } else {
83
81
  if (!r.children) r.children = {}
84
- if (!(p in r.children)) r.children[p] = {}
82
+ if (!(p in r.children)) r.children[p] = { routes: {} }
85
83
  r = r.children[p]
86
84
  }
87
85
  }
88
86
  }
89
87
  }
90
88
  }
91
- find(method: string, path: string) {
92
- method = method.toUpperCase()
89
+ find(method: Method, path: string): Route {
93
90
  const staticRoute = this.cachedRoutes.get(`[${method}]${path}`)
94
91
  if (staticRoute === null) throw new NotFoundError()
95
92
  if (staticRoute !== undefined) return staticRoute
96
- let parts = path.split('/')
97
- const route = walkRoutes(parts, this.routes[method]).route
98
- if (!route) {
93
+ let parts = path === '/' ? [''] : path.split('/')
94
+ let r = walk(parts, this.routes)
95
+ if (!r || !Object.keys(r.routes).length) {
99
96
  if (this.cacheEnabled) this.cachedRoutes.set(`[${method}]${path}`, null)
100
97
  throw new NotFoundError()
101
- }
98
+ } else if (!(method in r.routes)) throw new MethodNotAllowedError()
99
+ const route = r.routes[method] as Route
102
100
  if (this.cacheEnabled) this.cachedRoutes.set(`[${method}]${path}`, route)
103
101
  return route
104
102
  }
package/src/routes.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { GalbeConfig } from './types'
1
+ import type { GalbeConfig, Route } from './types'
2
2
 
3
3
  import { readdir, lstat } from 'fs/promises'
4
- import { extname, relative } from 'path'
4
+ import { extname } from 'path'
5
5
  import { parse } from 'acorn'
6
6
  import { simple } from 'acorn-walk'
7
7
  import { Galbe } from './index'
@@ -10,14 +10,116 @@ import { Glob } from 'bun'
10
10
 
11
11
  export const DEFAULT_ROUTE_PATTERN = 'src/**/*.route.{js,ts}'
12
12
 
13
- export type RouteMeta = {
13
+ export type RouteMeta = { head?: string } & Record<string, boolean | string | string[]>
14
+ export type RoutesMeta = {
14
15
  header: Record<string, boolean | string | string[]>
15
- routes: Record<string, Record<string, Record<string, boolean | string | string[]>>>
16
+ routes: Record<string, Record<string, RouteMeta>>
16
17
  }
17
-
18
+ export type RouteInstanciationCallback = <T extends 'add' | 'error'>(event: {
19
+ type: T
20
+ error?: T extends 'error' ? any : undefined
21
+ route: T extends 'add' ? Route : undefined
22
+ filepath?: string
23
+ meta: T extends 'add' ? RouteMeta : undefined
24
+ }) => any | Promise<any>
18
25
  export type RouteFileMeta = {
19
26
  file: string
20
- } & RouteMeta
27
+ } & RoutesMeta
28
+
29
+ class GalbeProxy {
30
+ #g: Galbe
31
+ #cb?: RouteInstanciationCallback
32
+ filepath?: string
33
+ meta?: RoutesMeta
34
+ constructor(g: Galbe, cb?: RouteInstanciationCallback) {
35
+ this.#g = g
36
+ this.#cb = cb
37
+ }
38
+ async get(...args: any[]) {
39
+ //@ts-ignore
40
+ const route = this.#g.get(...args) as Route
41
+ if (this.#cb)
42
+ await this.#cb({
43
+ type: 'add',
44
+ route,
45
+ filepath: this.filepath || '',
46
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
47
+ })
48
+ return route
49
+ }
50
+ async post(...args: any[]) {
51
+ //@ts-ignore
52
+ const route = this.#g.post(...args) as Route
53
+ if (this.#cb)
54
+ await this.#cb({
55
+ type: 'add',
56
+ route,
57
+ filepath: this.filepath,
58
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
59
+ })
60
+ return route
61
+ }
62
+ async put(...args: any[]) {
63
+ //@ts-ignore
64
+ const route = this.#g.put(...args) as Route
65
+ if (this.#cb)
66
+ await this.#cb({
67
+ type: 'add',
68
+ route,
69
+ filepath: this.filepath,
70
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
71
+ })
72
+ return route
73
+ }
74
+ async patch(...args: any[]) {
75
+ //@ts-ignore
76
+ const route = this.#g.patch(...args) as Route
77
+ if (this.#cb)
78
+ await this.#cb({
79
+ type: 'add',
80
+ route,
81
+ filepath: this.filepath,
82
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
83
+ })
84
+ return route
85
+ }
86
+ async delete(...args: any[]) {
87
+ //@ts-ignore
88
+ const route = this.#g.delete(...args) as Route
89
+ if (this.#cb)
90
+ await this.#cb({
91
+ type: 'add',
92
+ route,
93
+ filepath: this.filepath,
94
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
95
+ })
96
+ return route
97
+ }
98
+ async options(...args: any[]) {
99
+ //@ts-ignore
100
+ const route = this.#g.options(...args) as Route
101
+ if (this.#cb)
102
+ await this.#cb({
103
+ type: 'add',
104
+ route,
105
+ filepath: this.filepath,
106
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
107
+ })
108
+ return route
109
+ }
110
+ async head(...args: any[]) {
111
+ //@ts-ignore
112
+ const route = this.#g.head(...args) as Route
113
+ if (this.#cb)
114
+ await this.#cb({
115
+ type: 'add',
116
+ route,
117
+ filepath: this.filepath,
118
+ meta: this.meta?.routes?.[route.path]?.[route.method] || {}
119
+ })
120
+ return route
121
+ }
122
+ }
21
123
 
22
124
  const parseComment = (comment: string): Record<string, string | string[]> => {
23
125
  const head =
@@ -40,11 +142,11 @@ const parseComment = (comment: string): Record<string, string | string[]> => {
40
142
  }
41
143
  return refs
42
144
  }
43
- export const metaAnalysis = async (filePath: string): Promise<RouteMeta> => {
145
+ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
44
146
  const file = Bun.file(filePath)
45
147
  const fileExt = extname(filePath)
46
148
  let content = await file.text()
47
- let meta: RouteMeta = { header: {}, routes: {} }
149
+ let meta: RoutesMeta = { header: {}, routes: {} }
48
150
 
49
151
  if (fileExt === '.ts') {
50
152
  //// Much faster but doesn't include comments. See https://github.com/oven-sh/bun/pull/7055
@@ -69,20 +171,24 @@ export const metaAnalysis = async (filePath: string): Promise<RouteMeta> => {
69
171
  }).code
70
172
  }
71
173
 
72
- const comments: Record<number, string> = {}
174
+ const comments: Record<number, Record<number, string>> = {}
73
175
 
74
176
  const ast = parse(content, {
75
177
  ecmaVersion: 'latest',
76
178
  sourceType: 'module',
77
179
  locations: true,
78
180
  onComment: (isBlock, text, _start, _end, _locStart, locEnd) => {
79
- if (isBlock && locEnd?.line) comments[locEnd?.line] = text
181
+ if (isBlock && locEnd?.line !== undefined && locEnd?.column !== undefined) {
182
+ if (!comments?.[locEnd.line]) comments[locEnd.line] = []
183
+ comments[locEnd.line][locEnd.column] = text
184
+ }
80
185
  }
81
186
  })
82
187
  simple(ast, {
83
188
  ExportDefaultDeclaration(node) {
84
- const headerLine = node.loc?.start.line
85
- const headerCom = headerLine !== undefined && comments?.[headerLine] ? comments[headerLine] : ''
189
+ const headerLine = node.loc?.start.line || -1
190
+ const headerCol = node.loc?.start.column || -1
191
+ const headerCom = comments?.[headerLine]?.[headerCol - 1] ? comments[headerLine][headerCol - 1] : ''
86
192
  const headerRef = parseComment(headerCom)
87
193
  meta.header = headerRef
88
194
 
@@ -99,8 +205,9 @@ export const metaAnalysis = async (filePath: string): Promise<RouteMeta> => {
99
205
  const path = node.arguments[0].value
100
206
  // @ts-ignore
101
207
  const method = node.callee.property.name
102
- const line = node.loc?.start.line
103
- const com = line !== undefined && comments?.[line] ? comments[line] : ''
208
+ const line = node.loc?.start.line || -1
209
+ const col = node.loc?.start.column || -1
210
+ const com = comments?.[line]?.[col - 1] ? comments[line][col - 1] : ''
104
211
 
105
212
  const routeRefs = parseComment(com)
106
213
 
@@ -114,49 +221,41 @@ export const metaAnalysis = async (filePath: string): Promise<RouteMeta> => {
114
221
  return meta
115
222
  }
116
223
 
117
- const importRoutes = async (filePath: string, galbe: Galbe) => {
118
- const imported = await import(filePath)
119
- if (!imported?.default) throw new Error('No default export function')
120
- if (typeof imported.default !== 'function') throw new Error('Default export must be a function')
121
- const routes = imported.default
122
- routes(galbe)
123
- }
124
-
125
- export const defineRoutes = async (options: GalbeConfig, galbe: Galbe) => {
224
+ export const defineRoutes = async (
225
+ options: Pick<GalbeConfig, 'routes'>,
226
+ galbe: Galbe,
227
+ cb?: RouteInstanciationCallback
228
+ ) => {
126
229
  const routes = options?.routes === true ? DEFAULT_ROUTE_PATTERN : options?.routes
127
- if (!routes) {
128
- console.log(`\x1b\[38;5;245m No route file defined\x1b[0m`)
129
- return
130
- }
230
+ const proxy = new GalbeProxy(galbe, cb)
231
+ if (!routes) return
131
232
  const root = process.cwd()
132
233
  if (typeof routes === 'string') {
133
- let noRouteFound = true
134
234
  for await (const path of new Glob(routes).scan({ cwd: root, absolute: true, onlyFiles: false })) {
135
- noRouteFound = false
136
235
  const isDir = (await lstat(path)).isDirectory()
137
236
 
138
237
  let files: string[] = []
139
238
  if (!isDir) files.push(path)
140
239
  else files = files.concat((await readdir(path)).map(f => `${path}/${f}`))
141
- if (files.length === 0) console.log(`\x1b\[38;5;245m No route found\x1b[0m`)
142
240
  for (const f of files) {
143
241
  try {
144
242
  const metadata = await metaAnalysis(f)
243
+ proxy.filepath = f
244
+ proxy.meta = metadata
145
245
  galbe.meta?.push({ file: path, ...metadata })
146
- console.log(`\n\x1b\[0;36m ${relative('.', f)}\x1b[0m`)
147
- await importRoutes(f, galbe)
246
+ const imported = await import(f)
247
+ if (!imported?.default) throw new Error('No default export function')
248
+ if (typeof imported.default !== 'function') throw new Error('Default export must be a function')
249
+ const routes = imported.default
250
+ routes(proxy)
148
251
  } catch (err: any) {
149
- console.log(`\x1b\[0;31m Error: ${err?.message}\x1b[0m`)
252
+ if (cb) await cb({ type: 'error', error: err, filepath: f, route: undefined, meta: undefined })
150
253
  }
151
254
  }
152
255
  }
153
- if (noRouteFound) {
154
- console.log(`\x1b\[38;5;245m No route found\x1b[0m`)
155
- return
156
- }
157
256
  } else if (Array.isArray(routes)) {
158
257
  for (const r of routes) {
159
- await defineRoutes({ routes: r }, galbe)
258
+ await defineRoutes({ routes: r }, galbe, cb)
160
259
  }
161
260
  }
162
261
  }
package/src/schema.ts CHANGED
@@ -3,6 +3,7 @@ export const Optional = Symbol.for('Galbe.SchemaType.Optional')
3
3
  export const Stream = Symbol.for('Galbe.SchemaType.Stream')
4
4
 
5
5
  export interface Options {
6
+ id?: string
6
7
  title?: string
7
8
  description?: string
8
9
  default?: any
@@ -38,11 +39,11 @@ export interface STSchema extends Options {
38
39
  | 'literal'
39
40
  | 'array'
40
41
  | 'object'
42
+ | 'json'
41
43
  | 'urlForm'
42
44
  | 'multipartForm'
43
45
  | 'any'
44
46
  | 'union'
45
- | 'stream'
46
47
  [Optional]?: boolean
47
48
  [Stream]?: boolean
48
49
  params: unknown[]
@@ -172,6 +173,11 @@ export interface STObject<T extends STProps = STProps> extends STSchema {
172
173
  static: ObjectStatic<T, this['params']>
173
174
  props: T
174
175
  }
176
+ export interface STJson<T extends STBoolean | STNumber | STString | STObject = any> extends STSchema {
177
+ [Kind]: 'json'
178
+ type: 'boolean' | 'number' | 'string' | 'object' | 'unknown'
179
+ static: Static<T>
180
+ }
175
181
  type ObjectStatic<T extends STProps, P extends unknown[]> = ObjectStaticProps<T, { [K in keyof T]: Static<T[K], P> }>
176
182
  type OptionalPropertyKeys<T extends STProps> = {
177
183
  [K in keyof T]: T[K] extends STOptional<STSchema> ? K : never
@@ -190,6 +196,13 @@ function _Object<T extends STProps>(properties?: T, options: Options = {}): STOb
190
196
  ? { ...options, [Kind]: 'object', props: clonedProperties, required: requiredKeys }
191
197
  : { ...options, [Kind]: 'object', props: clonedProperties }) as unknown as STObject<T>
192
198
  }
199
+ function _Json<T extends STBoolean | STNumber | STString | STObject>(value?: T, options: Options = {}): STJson<T> {
200
+ if (value?.[Kind] === 'boolean') return { ..._Bool(options), [Kind]: 'json', type: 'boolean' }
201
+ if (value?.[Kind] === 'number') return { ..._Number(options), [Kind]: 'json', type: 'number' }
202
+ if (value?.[Kind] === 'string') return { ..._String(options), [Kind]: 'json', type: 'string' }
203
+ if (value?.[Kind] === 'object') return { ..._Object(value.props, options), [Kind]: 'json', type: 'object' }
204
+ throw Error('Invalid Json type definition')
205
+ }
193
206
 
194
207
  // UrlForm
195
208
  export type STUrlFormValues =
@@ -231,7 +244,19 @@ export interface MultipartFormData<K extends string = string, V extends Static<S
231
244
  }
232
245
  export interface STMultipartForm<T extends STProps = STProps> extends STSchema {
233
246
  [Kind]: 'multipartForm'
234
- static: T extends undefined ? Record<string, any> : ObjectStatic<T, this['params']>
247
+ static: T extends undefined
248
+ ? {
249
+ [k: string]: {
250
+ headers: { type?: string; name: string; filename?: string }
251
+ content: Static<STMultipartFormValues>
252
+ }
253
+ }
254
+ : {
255
+ [K in keyof T]: {
256
+ headers: { type?: string; name: K; filename?: string }
257
+ content: Static<T[K]>
258
+ }
259
+ }
235
260
  props: T
236
261
  }
237
262
  function _MultipartForm<T extends STProps>(properties?: T, options: Options = {}): STMultipartForm<T> {
@@ -251,12 +276,12 @@ export interface STArray<T extends STSchema = STSchema> extends STSchema {
251
276
  static: Static<T>[]
252
277
  items: T
253
278
  }
254
- export function _Array<T extends STSchema>(schema?: T, options: ArrayOptions = {}): STArray<T | STAny> {
279
+ export function _Array<T extends STSchema>(schema?: T, options: ArrayOptions = {}): STArray<T> {
255
280
  return {
256
281
  ...options,
257
282
  [Kind]: 'array',
258
283
  items: schema ?? _Any()
259
- } as unknown as T extends undefined ? STArray<STAny> : STArray<T>
284
+ } as unknown as STArray<T>
260
285
  }
261
286
 
262
287
  // Union
@@ -323,9 +348,12 @@ export class SchemaType {
323
348
  public object<T extends STProps>(properties?: T, options: Options = {}): STObject<T> {
324
349
  return _Object(properties, options)
325
350
  }
326
- /** Alias for Object Schema Type */
327
- public json<T extends STProps>(properties?: T, options: Options = {}): STObject<T> {
328
- return _Object(properties, options)
351
+ /** Creates a JSON Schema Type */
352
+ public json<T extends STString | STBoolean | STNumber | STObject<STProps>>(
353
+ value: T,
354
+ options: Options = {}
355
+ ): STJson<T> {
356
+ return _Json(value, options)
329
357
  }
330
358
  /** Creates an UrlForm Schema Type */
331
359
  public urlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
@@ -336,7 +364,7 @@ export class SchemaType {
336
364
  return _MultipartForm(properties, options)
337
365
  }
338
366
  /** Crates an Array Schema Type */
339
- public array<T extends STSchema>(schema?: T, options: Options = {}): STArray<T | STAny> {
367
+ public array<T extends STSchema>(schema?: T, options: Options = {}): STArray<T> {
340
368
  return _Array(schema, options)
341
369
  }
342
370
  /** Crates an Union Schema Type */
@@ -386,3 +414,36 @@ export class SchemaType {
386
414
  return _Stream(schema)
387
415
  }
388
416
  }
417
+
418
+ export const schemaToTypeStr = (schema: STSchema): string => {
419
+ let type = 'unknown'
420
+ let kind = schema[Kind]
421
+
422
+ if (kind === 'boolean') type = 'boolean'
423
+ else if (kind === 'byteArray') type = 'Uint8Array'
424
+ else if (kind === 'number') type = 'number'
425
+ else if (kind === 'integer') type = 'number'
426
+ else if (kind === 'string') type = 'string'
427
+ else if (kind === 'any') type = 'any'
428
+ else if (kind === 'literal') {
429
+ let value = (schema as STLiteral).value
430
+ if (typeof value === 'string') type = `'${value}'`
431
+ else type = String(value)
432
+ } else if (kind === 'array') {
433
+ type = `Array<${schemaToTypeStr((schema as STArray).items)}>`
434
+ } else if (kind === 'object') {
435
+ let props = (schema as STObject).props
436
+ type = `{${Object.entries(props)
437
+ .map(([k, v]) => `${typeof k === 'string' ? `'${k}'` : k}:${schemaToTypeStr(v)}`)
438
+ .join(';')}}`
439
+ } else if (kind === 'json') {
440
+ type = `Json<${schemaToTypeStr({ ...schema, [Kind]: schema.type })}>`
441
+ } else if (kind === 'union') {
442
+ let anyOf = (schema as STUnion).anyOf
443
+ type = anyOf.map(s => schemaToTypeStr(s)).join('|')
444
+ }
445
+
446
+ if (schema[Optional]) type = `${type}|undefined`
447
+
448
+ return type
449
+ }
package/src/server.ts CHANGED
@@ -1,11 +1,12 @@
1
- import type { Context, Route } from './types'
1
+ import type { Context, Method, Route } from './types'
2
2
 
3
3
  import { InternalError, RequestError } from './types'
4
4
  import { parseEntry, requestBodyParser, requestPathParser, responseParser } from './parser'
5
5
  import { Galbe } from './index'
6
6
  import { validateResponse } from './validator'
7
7
 
8
- const LOADABLE_METHODS = ['POST', 'PUT', 'PATCH']
8
+ const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']
9
+ const EMPTY_BODY_METHODS = ['GET', 'OPTIONS', 'HEAD']
9
10
 
10
11
  const handleInternalError = (error: any) => {
11
12
  console.error(error)
@@ -13,7 +14,6 @@ const handleInternalError = (error: any) => {
13
14
  }
14
15
 
15
16
  const setupPluginCallbacks = (galbe: Galbe) => ({
16
- init: galbe.plugins.filter(p => p.init),
17
17
  onFetch: galbe.plugins.filter(p => p.onFetch),
18
18
  onRoute: galbe.plugins.filter(p => p.onRoute),
19
19
  beforeHandle: galbe.plugins.filter(p => p.beforeHandle),
@@ -25,12 +25,11 @@ export default async (galbe: Galbe, port?: number) => {
25
25
  if (galbe?.config?.basePath && galbe?.config?.basePath[0] !== '/')
26
26
  galbe.config.basePath = `/${galbe?.config?.basePath}`
27
27
  let pluginsCb = setupPluginCallbacks(galbe)
28
- //@ts-ignore
29
- for (const p of pluginsCb.init) await p.init(galbe?.config?.plugin?.[p.name], galbe)
30
28
 
31
29
  return Bun.serve({
32
30
  port: port || galbe.config?.port || 3000,
33
31
  async fetch(req) {
32
+ if (!METHODS.includes(req.method)) return new Response('', { status: 501 })
34
33
  const context: Context = {
35
34
  request: req,
36
35
  set: { headers: {} },
@@ -51,7 +50,7 @@ export default async (galbe: Galbe, port?: number) => {
51
50
  try {
52
51
  // find route
53
52
  try {
54
- route = router.find(req.method, url.pathname)
53
+ route = router.find(req.method.toLowerCase() as Method, url.pathname)
55
54
  } catch (error) {
56
55
  if (error instanceof RequestError) throw error
57
56
  else throw handleInternalError(error)
@@ -72,7 +71,7 @@ export default async (galbe: Galbe, port?: number) => {
72
71
  for (let [k, v] of url.searchParams) inQuery[k] = v
73
72
  let inParams = requestPathParser(url.pathname, route.path)
74
73
 
75
- context.body = LOADABLE_METHODS.includes(req.method)
74
+ context.body = !EMPTY_BODY_METHODS.includes(req.method)
76
75
  ? await requestBodyParser(req.body, inHeaders, schema.body)
77
76
  : null
78
77
  context.headers = inHeaders
@@ -147,10 +146,10 @@ export default async (galbe: Galbe, port?: number) => {
147
146
  if (r) response = r
148
147
  } else response = await handlerWrapper(context)
149
148
 
150
- const parsedResponse = responseParser(response, context)
149
+ const parsedResponse = responseParser(response, context, schema.response)
151
150
 
152
151
  if (galbe.config?.responseValidator?.enabled && schema.response)
153
- validateResponse(response, schema.response, context.set.status || 200)
152
+ validateResponse(response, schema.response, parsedResponse.status || 200)
154
153
 
155
154
  for (const p of pluginsCb.afterHandle) {
156
155
  //@ts-ignore
@@ -180,7 +179,7 @@ export default async (galbe: Galbe, port?: number) => {
180
179
  status: error.status,
181
180
  headers: { 'Content-Type': 'application/json' }
182
181
  })
183
- }
182
+ } else console.log(error)
184
183
  return new Response('"Internal Server Error"', {
185
184
  status: 500,
186
185
  headers: {