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 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.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
 
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "galbe",
3
- "version": "0.1.13",
3
+ "version": "0.2.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/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.reduce((l: { name: string; cb: Function }[], p) => {
14
- if (p.init) l.push({ name: p.name, cb: p.init })
15
- return l
16
- }, []),
17
- onFetch: galbe.plugins.reduce((l: Function[], p) => {
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
- for (const { name, cb } of pluginsCb.init) await cb(galbe?.config?.plugin?.[name], galbe)
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 cb of pluginsCb.onFetch) {
55
- const r = await cb(req)
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 cb of pluginsCb.onRoute) {
71
- const r = await cb(route)
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 cb of pluginsCb.beforeHandle) {
117
- const r = await cb(context)
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 cb of pluginsCb.afterHandle) {
156
- const r = await cb(parsedResponse)
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?: (request: Request) => MaybePromise<Response | void>
263
- onRoute?: (route: Route) => MaybePromise<Response | void>
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
  }
@@ -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(req => {
43
- request = req
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