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 +0 -0
- package/docs/context.md +1 -1
- package/docs/getting-started.md +8 -0
- package/docs/handler.md +1 -1
- package/docs/hooks.md +5 -5
- package/docs/plugins.md +117 -0
- package/docs/schemas.md +19 -2
- package/package.json +1 -1
- package/src/index.ts +61 -49
- package/src/parser.ts +2 -3
- package/src/router.ts +1 -0
- package/src/routes.ts +13 -10
- package/src/schema.ts +10 -0
- package/src/server.ts +69 -57
- package/src/types.ts +51 -26
- package/src/util.ts +18 -2
- package/src/validator.ts +25 -5
- package/test/plugins.test.ts +3 -3
- package/test/responses.test.ts +288 -13
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
|
|
package/docs/getting-started.md
CHANGED
|
@@ -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
|
|
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
|
|
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
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]:
|
|
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
|
|
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
|
-
|
|
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
|