galbe 0.1.13 → 0.2.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/docs/context.md +1 -1
- package/docs/handler.md +1 -1
- package/docs/hooks.md +1 -1
- package/docs/plugins.md +123 -0
- package/package.json +1 -1
- package/src/server.ts +20 -29
- package/src/types.ts +4 -3
- package/test/plugins.test.ts +3 -3
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/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
|
|
package/docs/plugins.md
CHANGED
|
@@ -1 +1,124 @@
|
|
|
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
|
+
/**
|
|
72
|
+
* Retrieve and store all route with a @deprecated flag metadata
|
|
73
|
+
*/
|
|
74
|
+
init(config: any, galbe: Galbe) {
|
|
75
|
+
if (config?.enabled && galbe.meta) {
|
|
76
|
+
for (const f of galbe.meta) {
|
|
77
|
+
for (const [path, methods] of Object.entries(f.routes)) {
|
|
78
|
+
for (const [method, meta] of Object.entries(methods)) {
|
|
79
|
+
if (meta.deprecated) {
|
|
80
|
+
if (!this.deprecated?.[method]) this.deprecated[method] = []
|
|
81
|
+
this.deprecated[method].push(path)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if the current route is deprecated, flags it as is and logs it
|
|
90
|
+
*/
|
|
91
|
+
onRoute(context: Context) {
|
|
92
|
+
let route = context.route
|
|
93
|
+
if (this.deprecated?.[route.method]?.includes(route.path)) {
|
|
94
|
+
context.state[this.name] = { deprecated: true }
|
|
95
|
+
console.warn(`Call to deprecated route [${route.method}]${route.path}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Adds a header if the request has previously been flagged as deprecated
|
|
100
|
+
*/
|
|
101
|
+
afterHandle(response: Response, context: Context) {
|
|
102
|
+
if (context.state?.[this.name]?.deprecated) {
|
|
103
|
+
response.headers.set('x-deprecated', 'true')
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default new MyPlugin()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// index.ts
|
|
113
|
+
|
|
114
|
+
import { Galbe } from 'galbe'
|
|
115
|
+
import config from './galbe.config'
|
|
116
|
+
import myPlugin from './myPlugin'
|
|
117
|
+
|
|
118
|
+
const galbe = new Galbe(config)
|
|
119
|
+
galbe.use(myPlugin)
|
|
120
|
+
|
|
121
|
+
export default galbe
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
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/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -10,26 +10,11 @@ const handleInternalError = (error: any) => {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const setupPluginCallbacks = (galbe: Galbe) => ({
|
|
13
|
-
init: galbe.plugins.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (p.onFetch) l.push(p.onFetch)
|
|
19
|
-
return l
|
|
20
|
-
}, []),
|
|
21
|
-
onRoute: galbe.plugins.reduce((l: Function[], p) => {
|
|
22
|
-
if (p.onRoute) l.push(p.onRoute)
|
|
23
|
-
return l
|
|
24
|
-
}, []),
|
|
25
|
-
beforeHandle: galbe.plugins.reduce((l: Function[], p) => {
|
|
26
|
-
if (p.beforeHandle) l.push(p.beforeHandle)
|
|
27
|
-
return l
|
|
28
|
-
}, []),
|
|
29
|
-
afterHandle: galbe.plugins.reduce((l: Function[], p) => {
|
|
30
|
-
if (p.afterHandle) l.push(p.afterHandle)
|
|
31
|
-
return l
|
|
32
|
-
}, [])
|
|
13
|
+
init: galbe.plugins.filter(p => p.init),
|
|
14
|
+
onFetch: galbe.plugins.filter(p => p.onFetch),
|
|
15
|
+
onRoute: galbe.plugins.filter(p => p.onRoute),
|
|
16
|
+
beforeHandle: galbe.plugins.filter(p => p.beforeHandle),
|
|
17
|
+
afterHandle: galbe.plugins.filter(p => p.afterHandle)
|
|
33
18
|
})
|
|
34
19
|
|
|
35
20
|
export default async (galbe: Galbe, port?: number) => {
|
|
@@ -37,7 +22,8 @@ export default async (galbe: Galbe, port?: number) => {
|
|
|
37
22
|
if (galbe?.config?.basePath && galbe?.config?.basePath[0] !== '/')
|
|
38
23
|
galbe.config.basePath = `/${galbe?.config?.basePath}`
|
|
39
24
|
let pluginsCb = setupPluginCallbacks(galbe)
|
|
40
|
-
|
|
25
|
+
//@ts-ignore
|
|
26
|
+
for (const p of pluginsCb.init) await p.init(galbe?.config?.plugin?.[p.name], galbe)
|
|
41
27
|
|
|
42
28
|
return Bun.serve({
|
|
43
29
|
port: port || galbe.config?.port || 3000,
|
|
@@ -51,8 +37,9 @@ export default async (galbe: Galbe, port?: number) => {
|
|
|
51
37
|
body: {},
|
|
52
38
|
state: {}
|
|
53
39
|
}
|
|
54
|
-
for (const
|
|
55
|
-
|
|
40
|
+
for (const p of pluginsCb.onFetch) {
|
|
41
|
+
//@ts-ignore
|
|
42
|
+
const r = await p.onFetch(context)
|
|
56
43
|
if (r) return r
|
|
57
44
|
}
|
|
58
45
|
const url = new URL(req.url)
|
|
@@ -66,9 +53,11 @@ export default async (galbe: Galbe, port?: number) => {
|
|
|
66
53
|
if (error instanceof RequestError) throw error
|
|
67
54
|
else throw handleInternalError(error)
|
|
68
55
|
}
|
|
56
|
+
context.route = route
|
|
69
57
|
|
|
70
|
-
for (const
|
|
71
|
-
|
|
58
|
+
for (const p of pluginsCb.onRoute) {
|
|
59
|
+
//@ts-ignore
|
|
60
|
+
const r = await p.onRoute(context)
|
|
72
61
|
if (r) return r
|
|
73
62
|
}
|
|
74
63
|
|
|
@@ -113,8 +102,9 @@ export default async (galbe: Galbe, port?: number) => {
|
|
|
113
102
|
throw new RequestError({ status: 400, payload: errors.reduce((acc, c) => ({ ...acc, ...c.payload }), {}) })
|
|
114
103
|
}
|
|
115
104
|
|
|
116
|
-
for (const
|
|
117
|
-
|
|
105
|
+
for (const p of pluginsCb.beforeHandle) {
|
|
106
|
+
//@ts-ignore
|
|
107
|
+
const r = await p.beforeHandle(context)
|
|
118
108
|
if (r) return r
|
|
119
109
|
}
|
|
120
110
|
|
|
@@ -152,8 +142,9 @@ export default async (galbe: Galbe, port?: number) => {
|
|
|
152
142
|
|
|
153
143
|
const parsedResponse = responseParser(response, context)
|
|
154
144
|
|
|
155
|
-
for (const
|
|
156
|
-
|
|
145
|
+
for (const p of pluginsCb.afterHandle) {
|
|
146
|
+
//@ts-ignore
|
|
147
|
+
const r = await p.afterHandle(parsedResponse, context)
|
|
157
148
|
if (r) return r
|
|
158
149
|
}
|
|
159
150
|
|
package/src/types.ts
CHANGED
|
@@ -124,6 +124,7 @@ export type Context<Path extends string = string, S extends RequestSchema = Requ
|
|
|
124
124
|
query: Static<STObject<Exclude<S['query'], undefined>>>
|
|
125
125
|
body: Static<Exclude<S['body'], undefined>>
|
|
126
126
|
request: Request
|
|
127
|
+
route?: Route
|
|
127
128
|
state: Record<string, any>
|
|
128
129
|
set: {
|
|
129
130
|
headers: {
|
|
@@ -259,8 +260,8 @@ export class InternalError extends RequestError {
|
|
|
259
260
|
export type GalbePlugin = {
|
|
260
261
|
name: string
|
|
261
262
|
init?: (config: any, galbe: Galbe) => MaybePromise<void>
|
|
262
|
-
onFetch?: (
|
|
263
|
-
onRoute?: (
|
|
263
|
+
onFetch?: (context: Context) => MaybePromise<Response | void>
|
|
264
|
+
onRoute?: (context: Context) => MaybePromise<Response | void>
|
|
264
265
|
beforeHandle?: (context: Context) => MaybePromise<Response | void>
|
|
265
|
-
afterHandle?: (response: Response) => MaybePromise<Response | void>
|
|
266
|
+
afterHandle?: (response: Response, context: Context) => MaybePromise<Response | void>
|
|
266
267
|
}
|
package/test/plugins.test.ts
CHANGED
|
@@ -39,8 +39,8 @@ describe('plugins', async () => {
|
|
|
39
39
|
let request: any = null
|
|
40
40
|
const plugin: GalbePlugin = {
|
|
41
41
|
name: 'dev.galbe.test.init',
|
|
42
|
-
onFetch: mock(
|
|
43
|
-
request =
|
|
42
|
+
onFetch: mock(ctx => {
|
|
43
|
+
request = ctx.request
|
|
44
44
|
})
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -86,7 +86,7 @@ describe('plugins', async () => {
|
|
|
86
86
|
const plugin: GalbePlugin = {
|
|
87
87
|
name: 'dev.galbe.test.init',
|
|
88
88
|
onRoute: mock(r => {
|
|
89
|
-
route = r
|
|
89
|
+
route = r.route
|
|
90
90
|
})
|
|
91
91
|
}
|
|
92
92
|
|