galbe 0.1.13 → 0.3.0

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/bun.lockb CHANGED
Binary file
package/docs/context.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Context
2
2
 
3
3
  An instance of the context object is created when a new request is initiated and carrieds out along durring all the request lifecycle.
4
- See the [Lifecycle](lifecycle) section to get more details.
4
+ See the [Lifecycle](https://galbe.dev/documentation/lifecycle) section to get more details.
5
5
 
6
6
  Its purpose is to carrie all the relevent information about the request and to allow sharing informations between each step of the request lifecycle.
7
7
 
@@ -137,6 +137,14 @@ A Glob Pattern or a list of Glob patterns defining the route files to be analyze
137
137
 
138
138
  A property that can be used by plugins to add plugin's specific configuration. Every key should correspond to a [Unique Plugin Identifier](plugins.md).
139
139
 
140
+ **requestValidator.enabled**
141
+
142
+ Enable or disable the _request_ schema validation (See [Request Schema definition](schemas.md#request-schema-definition)). Default value is `true`.
143
+
144
+ **responseValidator.enabled**
145
+
146
+ Enable or disable the _response_ schema validation (See [Request Schema definition](schemas.md#request-schema-definition)). Default value is `true`.
147
+
140
148
  ### Examples
141
149
 
142
150
  A common way to handle server configuration is to create new file `galbe.config.(js|ts|json)` at the root of your project directory and import it in your code. Here is an example:
package/docs/handler.md CHANGED
@@ -10,7 +10,7 @@ The handler should be declared as last argument of the [Route Definition](routes
10
10
  galbe.get('foo', schema, [hook1, hook2], ctx => {})
11
11
  ```
12
12
 
13
- Handler are called after the last hook call, or right after the request parsing if no hook is declared. To get a better understanding of the request lifecycle, you can refer to the [Lifecycle](lifecycle.md) section.
13
+ Handler are called after the last hook call, or right after the request parsing if no hook is declared. To get a better understanding of the request lifecycle, you can refer to the [Lifecycle](https://galbe.dev/documentation/lifecycle) section.
14
14
 
15
15
  ## Handler definition
16
16
 
package/docs/hooks.md CHANGED
@@ -35,7 +35,7 @@ Hooks should be declared just before the handler method in the [Route Definition
35
35
  galbe.get('foo', [ hook1, hook2, ... ], ctx => {})
36
36
  ```
37
37
 
38
- Hooks are called just before the [Handler](handler.md) in the order that they have been declared in the hook list of the [Route Definition](routes.md#route-defintion). To get a better understanding of hooks execution during the request lifecycle, you can refer to the [Lifecycle](lifecycle.md) section.
38
+ Hooks are called just before the [Handler](handler.md) in the order that they have been declared in the hook list of the [Route Definition](routes.md#route-defintion). To get a better understanding of hooks execution during the request lifecycle, you can refer to the [Lifecycle](https://galbe.dev/documentation/lifecycle) section.
39
39
 
40
40
  ### Examples
41
41
 
@@ -49,7 +49,7 @@ const hook2 = context => {
49
49
  console.log('hook2 called')
50
50
  }
51
51
 
52
- galbe.get('example', [hook1, hook2], ctx => {
52
+ galbe.get('/example', [hook1, hook2], ctx => {
53
53
  console.log('handler')
54
54
  })
55
55
  ```
@@ -64,18 +64,18 @@ handler
64
64
  Nested hooks declaration:
65
65
 
66
66
  ```ts
67
- const hook1 = (context, next) => {
67
+ const hook1 = async (context, next) => {
68
68
  console.log('hook1 start')
69
69
  await next()
70
70
  console.log('hook1 end')
71
71
  }
72
- const hook2 = context => {
72
+ const hook2 = async (context, next) => {
73
73
  console.log('hook2 start')
74
74
  await next()
75
75
  console.log('hook2 end')
76
76
  }
77
77
 
78
- galbe.get('example', [hook1, hook2], ctx => {
78
+ galbe.get('/example', [hook1, hook2], ctx => {
79
79
  console.log('handler')
80
80
  })
81
81
  ```
package/docs/plugins.md CHANGED
@@ -1 +1,118 @@
1
1
  # Plugins
2
+
3
+ Galbe provides a powerful plugin system that allows developers to extend and customize the behavior of the framework. The plugin capabilities are centered around the [Request Lifecycle](https://galbe.dev/documentation/lifecycle).
4
+
5
+ ## Definition
6
+
7
+ ```ts
8
+ type GalbePlugin = {
9
+ name: string
10
+ init?: (config: any, galbe: Galbe) => MaybePromise<void>
11
+ onFetch?: (context: Context) => MaybePromise<Response | void>
12
+ onRoute?: (context: Context) => MaybePromise<Response | void>
13
+ beforeHandle?: (context: Context) => MaybePromise<Response | void>
14
+ afterHandle?: (response: Response, context: Context) => MaybePromise<Response | void>
15
+ }
16
+ ```
17
+
18
+ **name**
19
+
20
+ The name should be a Unique Plugin Identifier. It should be chosen to be unique to avoid conflicts with other potential plugins. For example, Galbe's official plugins names will always start with `dev.galbe.*`.
21
+
22
+ **init**
23
+
24
+ This method is called right after the server starts. It takes two arguments: a `config` and a `galbe` instance. The `config` holds the configuration for the specific scope of the current plugin (See [Configuration](getting-started.md#properties) `plugin` property for more details). The `galbe` argument is the instance of the current server; you can for instance retrieve the current routes definitions with `galbe.router.routes`.
25
+
26
+ **onFetch**
27
+
28
+ This method is called at the beginning of an incoming request. It takes a single `context` argument representing the current request [Context](context.md).
29
+
30
+ It is preemptable, meaning that any returned value will be interpreted as a response to send back to the client. Therefore, the method should only return [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instances or nothing.
31
+
32
+ **onRoute**
33
+
34
+ This method is called after the router has found a matching route for the current request. Its takes a single `context` argument representing the current request [Context](context.md).
35
+
36
+ It is preemptable, meaning that any returned value will be interpreted as a response to send back to the client. Therefore, the method should only return [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instances or nothing.
37
+
38
+ **beforeHandle**
39
+
40
+ This method is called after the request has been validated and before the route hooks and the handler are called. It takes a single `context` argument representing the current request [Context](context.md).
41
+
42
+ It is preemptable, meaning that any returned value will be interpreted as a response to send back to the client. Therefore, the method should only return [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instances or nothing.
43
+
44
+ **afterHandle**
45
+
46
+ This method is called after the route handler has been called and before the response is sent. Its takes two arguments: a `response` object containing the [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) returned by the handler, and a `context` argument representing the current request [Context](context.md).
47
+
48
+ It is preemptable, meaning that any returned value will be interpreted as a response to send back to the client. Therefore, the method should only return [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instances or nothing.
49
+
50
+ ## Registration
51
+
52
+ To register a plugin with your Galbe server, you must use the `use` method from you galbe instance.
53
+
54
+ ```js
55
+ const galbe = new Galbe()
56
+ galbe.use(plugin)
57
+ ```
58
+
59
+ ## Example
60
+
61
+ Here is an example of a plugin implementation that handles routes tagged with `@deprecated` metadata (See [Route files](routes.md#route-files) section about metadata).
62
+
63
+ ```ts
64
+ // myPlugin.ts
65
+
66
+ import { Galbe, type Context, type Route } from 'galbe'
67
+
68
+ class MyPlugin {
69
+ name = 'dev.galbe.example'
70
+ deprecated: Record<string, string[]> = {}
71
+ // Retrieve and store all route with a @deprecated flag metadata
72
+ init(config: any, galbe: Galbe) {
73
+ if (config?.enabled && galbe.meta) {
74
+ for (const f of galbe.meta) {
75
+ for (const [path, methods] of Object.entries(f.routes)) {
76
+ for (const [method, meta] of Object.entries(methods)) {
77
+ if (meta.deprecated) {
78
+ if (!this.deprecated?.[method]) this.deprecated[method] = []
79
+ this.deprecated[method].push(path)
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ // Check if the current route is deprecated; if so, flag it as such and log it
87
+ onRoute(context: Context) {
88
+ let route = context.route
89
+ if (this.deprecated?.[route.method]?.includes(route.path)) {
90
+ context.state[this.name] = { deprecated: true }
91
+ console.warn(`Call to deprecated route [${route.method}]${route.path}`)
92
+ }
93
+ }
94
+ // Add a header if the request has previously been flagged as deprecated
95
+ afterHandle(response: Response, context: Context) {
96
+ if (context.state?.[this.name]?.deprecated) {
97
+ response.headers.set('x-deprecated', 'true')
98
+ }
99
+ }
100
+ }
101
+
102
+ export default new MyPlugin()
103
+ ```
104
+
105
+ ```ts
106
+ // index.ts
107
+
108
+ import { Galbe } from 'galbe'
109
+ import config from './galbe.config'
110
+ import myPlugin from './myPlugin'
111
+
112
+ const galbe = new Galbe(config)
113
+ galbe.use(myPlugin)
114
+
115
+ export default galbe
116
+ ```
117
+
118
+ As you can see in this example, the [Context](context.md#definition) `state` property is used to persist information between plugins interceptor methods. It is a good practice to scope any information stored in the state with the plugin name, as it can also be used by other plugins and hooks to store data in the context.
package/docs/schemas.md CHANGED
@@ -152,8 +152,7 @@ const schema = {
152
152
 
153
153
  <!-- prettier-ignore -->
154
154
  ```ts
155
- body: STByteArray | STString | STBoolean | STNumber | STInteger | STLiteral |
156
- STObject | STMulripartForm | STUrlForm
155
+ body: STByteArray | STString | STBoolean | STNumber | STInteger | STLiteral | STObject | STArray | STMulripartForm | STUrlForm | STStream
157
156
  ```
158
157
 
159
158
  #### Json
@@ -238,3 +237,21 @@ galbe.post(
238
237
  }
239
238
  })
240
239
  ```
240
+
241
+ ### response
242
+
243
+ <!-- prettier-ignore -->
244
+ ```ts
245
+ response: Record<number, STByteArray | STString | STBoolean | STNumber | STInteger | STLiteral | STObject | STArray | STStream>
246
+ ```
247
+
248
+ Same as for request body validation but to validate handler responses. Every schema type must be associated to a specific response status.
249
+
250
+ #### Example
251
+
252
+ ```ts
253
+ const response = {
254
+ 200: $T.object({ data: $T.array($T.number()) })
255
+ 404: $T.literal("Not found")
256
+ }
257
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "galbe",
3
- "version": "0.1.13",
3
+ "version": "0.3.0",
4
4
  "description": "Fast, lightweight and highly customizable JavaScript web framework based on Bun",
5
5
  "author": "Pierre Caillaud M (https://github.com/pierre-cm)",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  ErrorHandler,
12
12
  GalbePlugin,
13
13
  STBody,
14
+ STResponse,
14
15
  STParams,
15
16
  STHeaders,
16
17
  STQuery
@@ -19,7 +20,7 @@ import type {
19
20
  import server from './server'
20
21
  import { GalbeRouter } from './router'
21
22
  import { defineRoutes } from './routes'
22
- import { logRoute } from './util'
23
+ import { extractMetaRoute, logRoute } from './util'
23
24
  import { SchemaType, type STObject, type Static } from './schema'
24
25
 
25
26
  const overloadDiscriminer = <
@@ -27,17 +28,18 @@ const overloadDiscriminer = <
27
28
  H extends STHeaders,
28
29
  P extends Partial<STParams<Path>>,
29
30
  Q extends STQuery,
30
- B extends STBody
31
+ B extends STBody,
32
+ R extends STResponse
31
33
  >(
32
34
  galbe: Galbe,
33
35
  method: Method,
34
36
  path: Path,
35
37
  arg2:
36
- | RequestSchema<Path, H, P, Q, B>
37
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
38
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
39
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
40
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
38
+ | RequestSchema<Path, H, P, Q, B, R>
39
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
40
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
41
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
42
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
41
43
  ) => {
42
44
  const defaultSchema = {}
43
45
  if (typeof arg2 === 'function') {
@@ -57,14 +59,15 @@ const galbeMethod = <
57
59
  H extends STHeaders,
58
60
  P extends Partial<STParams<Path>>,
59
61
  Q extends STQuery,
60
- B extends STBody
62
+ B extends STBody,
63
+ R extends STResponse
61
64
  >(
62
65
  _galbe: Galbe,
63
66
  method: Method,
64
67
  path: Path,
65
- schema: RequestSchema<Path, H, P, Q, B> | undefined,
66
- hooks: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | undefined,
67
- handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
68
+ schema: RequestSchema<Path, H, P, Q, B, R> | undefined,
69
+ hooks: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | undefined,
70
+ handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
68
71
  ) => {
69
72
  schema = schema ?? {}
70
73
  hooks = hooks || []
@@ -92,6 +95,7 @@ const galbeMethod = <
92
95
  }
93
96
  }
94
97
 
98
+ /** Galbe Schema Type builder. See {@link https://galbe.dev/documentation/schemas#schema-types Schema Types} */
95
99
  export const $T = new SchemaType()
96
100
 
97
101
  export { RequestError } from './types'
@@ -126,12 +130,14 @@ export class Galbe {
126
130
  prefix: this.config?.basePath || '',
127
131
  cacheEnabled: this.config?.router?.cacheEnabled
128
132
  })
133
+ this.config.requestValidator = config?.requestValidator ?? { enabled: true }
134
+ this.config.responseValidator = config?.responseValidator ?? { enabled: true }
129
135
  }
130
136
  private add(route: any) {
131
137
  this.router.add(route)
132
138
  if (Bun.env.BUN_ENV === 'development') {
133
139
  if (!this.#prepare) indexRoutes.push({ method: route.method, path: route.path })
134
- else logRoute(route)
140
+ else logRoute(route, extractMetaRoute(route, this.meta))
135
141
  }
136
142
  }
137
143
  async use(plugin: GalbePlugin) {
@@ -144,7 +150,7 @@ export class Galbe {
144
150
  if (Bun.env.BUN_ENV === 'development') {
145
151
  this.#prepare = true
146
152
  console.log('🏗️ \x1b[1;30mConstructing routes\x1b[0m')
147
- for (const r of indexRoutes) logRoute(r)
153
+ for (const r of indexRoutes) logRoute(r, extractMetaRoute(r, this.meta))
148
154
  await defineRoutes(this.config || {}, this)
149
155
  console.log('\n✅ \x1b[1;30mdone\x1b[0m')
150
156
  this.server = await server(this, port)
@@ -168,90 +174,96 @@ export class Galbe {
168
174
  H extends STHeaders,
169
175
  P extends Partial<STParams<Path>>,
170
176
  Q extends STQuery,
171
- B extends STBody
177
+ B extends STBody,
178
+ R extends STResponse
172
179
  >(
173
180
  path: Path,
174
181
  arg2:
175
- | RequestSchema<Path, H, P, Q, B>
176
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
177
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
178
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
179
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
182
+ | RequestSchema<Path, H, P, Q, B, R>
183
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
184
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
185
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
186
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
180
187
  ) => this.add(overloadDiscriminer(this, 'get', path, arg2, arg3, arg4))
181
188
  post: Endpoint = <
182
189
  Path extends string,
183
190
  H extends STHeaders,
184
191
  P extends Partial<STParams<Path>>,
185
192
  Q extends STQuery,
186
- B extends STBody
193
+ B extends STBody,
194
+ R extends STResponse
187
195
  >(
188
196
  path: Path,
189
197
  arg2:
190
- | RequestSchema<Path, H, P, Q, B>
191
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
192
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
193
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
194
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
198
+ | RequestSchema<Path, H, P, Q, B, R>
199
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
200
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
201
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
202
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
195
203
  ) => this.add(overloadDiscriminer(this, 'post', path, arg2, arg3, arg4))
196
204
  put: Endpoint = <
197
205
  Path extends string,
198
206
  H extends STHeaders,
199
207
  P extends Partial<STParams<Path>>,
200
208
  Q extends STQuery,
201
- B extends STBody
209
+ B extends STBody,
210
+ R extends STResponse
202
211
  >(
203
212
  path: Path,
204
213
  arg2:
205
- | RequestSchema<Path, H, P, Q, B>
206
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
207
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
208
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
209
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
214
+ | RequestSchema<Path, H, P, Q, B, R>
215
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
216
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
217
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
218
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
210
219
  ) => this.add(overloadDiscriminer(this, 'put', path, arg2, arg3, arg4))
211
220
  patch: Endpoint = <
212
221
  Path extends string,
213
222
  H extends STHeaders,
214
223
  P extends Partial<STParams<Path>>,
215
224
  Q extends STQuery,
216
- B extends STBody
225
+ B extends STBody,
226
+ R extends STResponse
217
227
  >(
218
228
  path: Path,
219
229
  arg2:
220
- | RequestSchema<Path, H, P, Q, B>
221
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
222
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
223
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
224
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
230
+ | RequestSchema<Path, H, P, Q, B, R>
231
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
232
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
233
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
234
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
225
235
  ) => this.add(overloadDiscriminer(this, 'patch', path, arg2, arg3, arg4))
226
236
  delete: Endpoint = <
227
237
  Path extends string,
228
238
  H extends STHeaders,
229
239
  P extends Partial<STParams<Path>>,
230
240
  Q extends STQuery,
231
- B extends STBody
241
+ B extends STBody,
242
+ R extends STResponse
232
243
  >(
233
244
  path: Path,
234
245
  arg2:
235
- | RequestSchema<Path, H, P, Q, B>
236
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
237
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
238
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
239
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
246
+ | RequestSchema<Path, H, P, Q, B, R>
247
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
248
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
249
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
250
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
240
251
  ) => this.add(overloadDiscriminer(this, 'delete', path, arg2, arg3, arg4))
241
252
  options: Endpoint = <
242
253
  Path extends string,
243
254
  H extends STHeaders,
244
255
  P extends Partial<STParams<Path>>,
245
256
  Q extends STQuery,
246
- B extends STBody
257
+ B extends STBody,
258
+ R extends STResponse
247
259
  >(
248
260
  path: Path,
249
261
  arg2:
250
- | RequestSchema<Path, H, P, Q, B>
251
- | Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
252
- | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
253
- arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
254
- arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
262
+ | RequestSchema<Path, H, P, Q, B, R>
263
+ | Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[]
264
+ | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
265
+ arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B, R>>,
266
+ arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
255
267
  ) => this.add(overloadDiscriminer(this, 'options', path, arg2, arg3, arg4))
256
268
  }
257
269
 
package/src/parser.ts CHANGED
@@ -17,6 +17,7 @@ import { readableStreamToArrayBuffer } from 'bun'
17
17
  import { Kind, Optional, Stream } from './schema'
18
18
  import { validate } from './validator'
19
19
  import { InternalError, RequestError } from './index'
20
+ import { isIterator } from './util'
20
21
 
21
22
  const textDecoder = new TextDecoder()
22
23
  const textEncoder = new TextEncoder()
@@ -594,7 +595,7 @@ export const requestPathParser = (input: string, path: string) => {
594
595
  }
595
596
 
596
597
  export const parseEntry = <T extends STProps>(
597
- params: { [key: string]: string | string[] },
598
+ params: { [key: string]: any },
598
599
  schema: T,
599
600
  options?: { name?: string; i?: boolean }
600
601
  ): Static<STObject<T>> => {
@@ -629,8 +630,6 @@ export const parseEntry = <T extends STProps>(
629
630
  return parsedParams as Static<STObject<T>>
630
631
  }
631
632
 
632
- const isIterator = (obj: any) => typeof obj?.next === 'function'
633
-
634
633
  export const responseParser = (response: any, ctx: Context) => {
635
634
  const details = {
636
635
  status: ctx.set.status || 200,
package/src/router.ts CHANGED
@@ -89,6 +89,7 @@ export class GalbeRouter {
89
89
  }
90
90
  }
91
91
  find(method: string, path: string) {
92
+ method = method.toUpperCase()
92
93
  const staticRoute = this.cachedRoutes.get(`[${method}]${path}`)
93
94
  if (staticRoute === null) throw new NotFoundError()
94
95
  if (staticRoute !== undefined) return staticRoute
package/src/routes.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { GalbeConfig } from './types'
2
2
 
3
3
  import { readdir, lstat } from 'fs/promises'
4
- import { extname } from 'path'
4
+ import { extname, relative } from 'path'
5
5
  import { parse } from 'acorn'
6
6
  import { simple } from 'acorn-walk'
7
7
  import { Galbe } from './index'
@@ -81,13 +81,15 @@ export const metaAnalysis = async (filePath: string): Promise<RouteMeta> => {
81
81
  })
82
82
  simple(ast, {
83
83
  ExportDefaultDeclaration(node) {
84
- // @ts-ignore
85
- let galbeIdentifier = node.declaration.params[0].name
86
-
87
84
  const headerLine = node.loc?.start.line
88
85
  const headerCom = headerLine !== undefined && comments?.[headerLine] ? comments[headerLine] : ''
89
86
  const headerRef = parseComment(headerCom)
90
87
  meta.header = headerRef
88
+
89
+ // @ts-ignore
90
+ let galbeIdentifier = node.declaration?.params?.[0]?.name
91
+ if (!galbeIdentifier) return meta
92
+
91
93
  // @ts-ignore
92
94
  simple(node.declaration.body, {
93
95
  CallExpression(node) {
@@ -113,7 +115,10 @@ export const metaAnalysis = async (filePath: string): Promise<RouteMeta> => {
113
115
  }
114
116
 
115
117
  const importRoutes = async (filePath: string, galbe: Galbe) => {
116
- const routes = (await import(filePath)).default
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
117
122
  routes(galbe)
118
123
  }
119
124
 
@@ -138,16 +143,14 @@ export const defineRoutes = async (options: GalbeConfig, galbe: Galbe) => {
138
143
  try {
139
144
  const metadata = await metaAnalysis(f)
140
145
  galbe.meta?.push({ file: path, ...metadata })
141
- console.log(`\n\x1b\[0;36m ${f}\x1b[0m`)
146
+ console.log(`\n\x1b\[0;36m ${relative('.', f)}\x1b[0m`)
142
147
  await importRoutes(f, galbe)
143
- } catch (err) {
144
- // console.log(`\x1b\[0;31m ${f}\x1b[0m`)
145
- throw err
148
+ } catch (err: any) {
149
+ console.log(`\x1b\[0;31m Error: ${err?.message}\x1b[0m`)
146
150
  }
147
151
  }
148
152
  }
149
153
  if (noRouteFound) {
150
- process.stdout.write('\r\x1b[K')
151
154
  console.log(`\x1b\[38;5;245m No route found\x1b[0m`)
152
155
  return
153
156
  }
package/src/schema.ts CHANGED
@@ -63,6 +63,16 @@ export type STPropsValue =
63
63
  export type STProps = Record<string | number, STPropsValue>
64
64
 
65
65
  type Evaluate<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
66
+
67
+ /**
68
+ * Infer the static TypeScript type from a {@link https://galbe.dev/documentation/schemas#schema-types Schema Type}
69
+ * @example
70
+ * ```ts
71
+ * const schema = $T.object({ foo: $T.string() })
72
+ * type T = Static<typeof schema>
73
+ * // ^? type T = { foo: string }
74
+ * ```
75
+ */
66
76
  export type Static<T extends STSchema, P extends unknown[] = unknown[]> = (T & { params: P })['static']
67
77
 
68
78
  // Utils