galbe 0.1.7 → 0.1.13

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.
@@ -16,5 +16,5 @@ jobs:
16
16
  -H "Accept: application/vnd.github+json" \
17
17
  -H "Authorization: Bearer ${{ secrets.GH_WEBSITE_TOKEN }}" \
18
18
  -H "X-GitHub-Api-Version: 2022-11-28" \
19
- https://api.github.com/repos/pierre-cm/galbe-website/actions/workflows/ci.yml/dispatches \
19
+ https://api.github.com/repos/pierre-cm/galbe-website/actions/workflows/deploy.yml/dispatches \
20
20
  -d '{"ref":"main"}'
@@ -32,7 +32,7 @@ jobs:
32
32
  git config user.name "${GITHUB_ACTOR}"
33
33
  - name: Install dependencies & build
34
34
  run: |
35
- bun install & bun run build
35
+ bun install && bun run build
36
36
  - name: Release
37
37
  run: |
38
38
  npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
package/README.md CHANGED
@@ -12,7 +12,9 @@ Galbe is a fast, lightweight and highly customizable JavaScript web framework ba
12
12
  ## Getting started
13
13
 
14
14
  ```bash
15
- bun create galbe
15
+ bun create galbe app
16
+ cd app
17
+ bun install && bun dev
16
18
  ```
17
19
 
18
20
  ## Documentation
@@ -0,0 +1,114 @@
1
+ # Context
2
+
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.
5
+
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
+
8
+ ## Definition
9
+
10
+ A context has the following properties:
11
+
12
+ **request**
13
+
14
+ An instance of the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object created by th server.
15
+
16
+ **headers**
17
+
18
+ A javascript object representing the `headers` of the current request.
19
+
20
+ - key (string): header name
21
+ - value: (string | [schema defined](schemas.md#headers)): header value
22
+
23
+ ```js
24
+ {
25
+ "accept": "*/*",
26
+ "accept-encoding": "gzip, deflate, br",
27
+ "cookie": "Cookie_1=value; Cookie_2=value",
28
+ "host": "localhost:3000",
29
+ "user-agent": "galbe/1.0.0"
30
+ }
31
+ ```
32
+
33
+ **params**
34
+
35
+ A javascript object representing the request `parameters` of the current request.
36
+
37
+ - key (string): parameter name
38
+ - value: (string | [schema defined](schemas.md#params)): parameter value
39
+
40
+ ```js
41
+ galbe.get('/default/:p1/foo/:p2', ctx => console.log(ctx.params))
42
+ // GET /default/four/foo/2
43
+ { p1: "four", p2: "2" }
44
+ ```
45
+
46
+ **query**
47
+
48
+ A javascript object representing the request `query parameters` of the current request.
49
+
50
+ - key (string): query parameter name
51
+ - value: (string | [schema defined](schemas.md#query)): query parameter value
52
+
53
+ ```js
54
+ galbe.get('/test', ctx => console.log(ctx.params))
55
+ // GET /test?one=1&two=2
56
+ { one: "1", two: "2" }
57
+ ```
58
+
59
+ **body**
60
+
61
+ The body payload of the incoming request. The body type is computed according to the following rules.
62
+
63
+ If no [Schema](schemas.md) is defined, Galbe will parse the body type according to `content-type` Header value:
64
+
65
+ - `text/.*`: string
66
+ - `application/json`: object
67
+ - `application/x-www-form-urlencoded`: { [key: string]: any }
68
+ - `multipart/form-data`: { [key: string]:
69
+ { headers: { name: string; type?: string; filename?: string };
70
+ content: any
71
+ } }
72
+ - `other`: AsyncGenerator\<Uint8Array\>
73
+
74
+ If a [Schema](schemas.md) is defined, Galbe will parse the body type according to the [Schema.body](schemas.md#body) defined for the current route.
75
+
76
+ **set**
77
+
78
+ The set property contains modifiable properties which purpose are to give informations to the Response parser.
79
+
80
+ - `status`: Set the response status
81
+ - `headers`: Set the response headers
82
+
83
+ ```js
84
+ galbe.get('/example', ctx => {
85
+ ctx.set.status = 418
86
+ return "I don't do coffee"
87
+ })
88
+ ```
89
+
90
+ **state**
91
+
92
+ The state property purpose is to carry custom user object accross request lifecycle. In general it is used to share informations between the [hooks](hooks.md) and the [handler](handler.md).
93
+
94
+ - key (string): user defined key
95
+ - value (any): user defined object
96
+
97
+ ```js
98
+ galbe.get(
99
+ '/example',
100
+ [
101
+ ctx => {
102
+ ctx.state['foo'] = 'bar'
103
+ }
104
+ ],
105
+ ctx => {
106
+ return ctx.state.foo
107
+ }
108
+ )
109
+ ```
110
+
111
+ ```bash
112
+ $ curl http://localhost:3000/example
113
+ bar
114
+ ```
@@ -0,0 +1,54 @@
1
+ # Error handler
2
+
3
+ Any error happening during a request lifecycle will be intercepted by the error handler.
4
+
5
+ You can customize the default error handling behavior by defining a custom error handler using Galbe's intance `onError` method.
6
+
7
+ ```js
8
+ const galbe = new Galbe()
9
+ galbe.onError(customErrorHandler)
10
+ ```
11
+
12
+ ## Definition
13
+
14
+ The error handler should be a function that takes two aguments: an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) and a [Context](context.md). This function may potentially return a [Response type](handler.md#response-types).
15
+
16
+ ```js
17
+ galbe.onErrorHandler((error, ctx) => {
18
+ if (error.status === 500) {
19
+ return new Response(`Server error ❌`, { status: 500 })
20
+ }
21
+ if (error.status === 404) {
22
+ return new Response(`Not found 🔎`, { status: 404 })
23
+ }
24
+ })
25
+ ```
26
+
27
+ The `error` argument could be any type of error thrown by your application. If the error originates from Galbe framework, it will be an instance of [RequestError](#request-error).
28
+
29
+ For instance, the [Router](router.md) will throw a `RequestError` with a `404` status if no route matches the incoming request path. Similarly, the Parser will throw a `RequestError` with a `400` status.
30
+
31
+ ## Request Error
32
+
33
+ The `RequestError` class is utilized to instanciate a runtime request error in Galbe. It has two optional attributes: a `status` and a `payload`.
34
+
35
+ If your application throws a `RequestError` instance, Galbe will, by default, construct a Response from your `RequestError` and send it back to the client.
36
+
37
+ ```js
38
+ import { Galbe, RequestError } from 'galbe'
39
+
40
+ const galbe = new Galbe()
41
+
42
+ galbe.get('/coffee', () => throw new RequestError({ status: 418, payload: '🫖' }))
43
+ ```
44
+
45
+ When called, above endpoint should respond:
46
+
47
+ ```bash
48
+ $ curl -i http://localhost:3000/coffee
49
+ HTTP/1.1 418 I'm a Teapot
50
+ Content-Type: application/json
51
+ Content-Length: 6
52
+
53
+ "🫖"
54
+ ```
@@ -1,21 +1,21 @@
1
1
  # Getting started
2
2
 
3
- Galbe is a Javascript web framework to build fast and versatile backend servers with Bun.
3
+ Galbe is a Javascript web framework for building fast and versatile backend servers with Bun.
4
4
 
5
- It was designed with simplicity in mind, allowing you to quickly create and setup a project. In addition, Galbe also offers usefull features, allowing you to focus on your application logic rather than the rest.
5
+ Designed with simplicity in mind, Galbe allows you to quickly create and set up a project. In addition to its ease of use, Galbe also offers a range of useful features that help you focus on the core logic of your application.
6
6
 
7
7
  ## Requirements
8
8
 
9
9
  To start developing your Galbe project, you first need to install [Bun](https://bun.sh).
10
10
 
11
- ## Automatic Installation
11
+ ## Automatic installation
12
12
 
13
13
  This is the recommended way of setting up a Galbe project.
14
14
 
15
15
  ```bash
16
- bun create galbe app
17
- cd app
18
- bun install
16
+ $ bun create galbe app
17
+ $ cd app
18
+ $ bun install
19
19
  ```
20
20
 
21
21
  This will create a new project under `app` directory and install it.
@@ -23,12 +23,12 @@ This will create a new project under `app` directory and install it.
23
23
  Now you can start the dev server by running:
24
24
 
25
25
  ```bash
26
- bun dev
26
+ $ bun dev
27
27
  ```
28
28
 
29
- This will start a web server on `loclahost:3000`.
29
+ This will start a web server on `localhost:3000`.
30
30
 
31
- To verify that the project was setup correctly and is running, try to reach `loclahost:3000/hello` endpoint, this should return following greeting message:
31
+ To verify that the project was setup correctly and is running, try to reach `localhost:3000/hello` endpoint, this should return following greeting message:
32
32
 
33
33
  ```bash
34
34
  $ curl localhost:3000/hello
@@ -38,13 +38,13 @@ Hello from Galbe!
38
38
  > [!TIP]
39
39
  > By default, the dev server automatically reloads on every file change.
40
40
 
41
- ## Manual Installation
41
+ ## Manual installation
42
42
 
43
43
  Init a new Bun project and add Galbe as dependency:
44
44
 
45
45
  ```bash
46
- bun init
47
- bun add galbe
46
+ $ bun init
47
+ $ bun add galbe
48
48
  ```
49
49
 
50
50
  Open `package.json` file and add the following scripts:
@@ -74,18 +74,18 @@ export default galbe
74
74
 
75
75
  This is the recommended way to proceed but it is not mandatory. Galbe instances also provide a `listen` method that will allow you to manually start your server instance from the code.
76
76
 
77
- > [!WARNING]
77
+ > [!WARNING]
78
78
  > In the case you decide to not rely on Galbe CLI to run/build your app, you will not have access to [Automatic Route Analyzer](routes.md#automatic-route-analyzer) feature.
79
79
 
80
80
  ### Galbe CLI
81
81
 
82
82
  ```bash
83
- galbe <command> <argument> [options]
83
+ $ galbe <command> <argument> [options]
84
84
  ```
85
85
 
86
86
  Here are the available commands:
87
87
 
88
- #### dev
88
+ **dev**
89
89
 
90
90
  Start a dev server running your Galbe application.
91
91
 
@@ -98,7 +98,7 @@ _options_
98
98
  - `--port` or `-p`: port number (default: 3000)
99
99
  - `--watch` or `-w`: watch file changes (default: true)
100
100
 
101
- #### build
101
+ **build**
102
102
 
103
103
  Bundle your Galbe application.
104
104
 
@@ -121,25 +121,25 @@ const galbe = new Galbe(configuration)
121
121
 
122
122
  ### Properties
123
123
 
124
- #### port
124
+ **port**
125
125
 
126
126
  The port number that the server will be listening on. Default is `3000`.
127
127
 
128
- #### basePath
128
+ **basePath**
129
129
 
130
130
  The base path is added as a prefix to all the routes created.
131
131
 
132
- #### routes
132
+ **routes**
133
133
 
134
134
  A Glob Pattern or a list of Glob patterns defining the route files to be analyzed by the [Automatic Route Analyzer](routes.md#automatic-route-analyzer). Default is `src/**/*.route.{js,ts}`.
135
135
 
136
- #### plugin
136
+ **plugin**
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
140
  ### Examples
141
141
 
142
- An common way to handle server configuration is to create new file a `galbe.config.(js|ts|json)` at the root of your project directory and import it in your code. Here is an example:
142
+ 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:
143
143
 
144
144
  galbe.config.js
145
145
 
@@ -171,7 +171,7 @@ export default new Galbe(config)
171
171
  > export default config
172
172
  > ```
173
173
 
174
- ## Project Structure
174
+ ## Project structure
175
175
 
176
176
  One key aspect of Galbe, is its versatility in terms of project structure. This is partly allowed by the [Automatic Route Analyzer](routes.md#automatic-route-analyzer) and the `routes` config property which defaults to `src/**/*.route.{js,ts}`.
177
177
 
@@ -217,7 +217,7 @@ Here are two examples of valid project structures by default:
217
217
 
218
218
  In both cases, the [Automatic Route Analyzer](routes.md#automatic-route-analyzer) will analyze `foo.route.ts` and `bar.route.ts` Route Files to find route definitions.
219
219
 
220
- You can find more info about Route Files definition under the [Routes](routes.md) section.
220
+ You can find more info about Route Files definition in the [Routes Files](routes.md#route-files) section.
221
221
 
222
- > [!NOTE]
223
- > Those are just examples that will work with the default configuration. You can of course redefine `routes` property with your own pattern(s) to fit your own project structure.
222
+ > [!NOTE]
223
+ > The examples provided above will work with the default configuration, but you can easily customize the routes property to fit your own project structure. Simply redefine the `routes` property with your own pattern(s) to to fit your own project structure.
@@ -0,0 +1,105 @@
1
+ # Handler
2
+
3
+ A handler is a function that gets executed when a request matches the route definition. It is responsible for processing the request and sending a response.
4
+
5
+ ## Handler declaration
6
+
7
+ The handler should be declared as last argument of the [Route Definition](routes.md#route-defintion) method.
8
+
9
+ ```js
10
+ galbe.get('foo', schema, [hook1, hook2], ctx => {})
11
+ ```
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.
14
+
15
+ ## Handler definition
16
+
17
+ ```js
18
+ const handler = ctx => {
19
+ const { name } = ctx.query
20
+ return `Hello ${name}!`
21
+ }
22
+ ```
23
+
24
+ The handler function takes a `context` object as single argument and might return a `response`.
25
+
26
+ **context**
27
+
28
+ The `context` object contains the request information as well as a `set` object that serves as a response modifier. You can find more detailed informations about the `context` object in the [Context](context.md) section.
29
+
30
+ **response**
31
+
32
+ To send a response, your handler can return an object. The response sent will depend on the type of the object returned. There are four types of responses that can be returned by a handler method. More about that in the next section.
33
+
34
+ ## Response types
35
+
36
+ > [!NOTE]
37
+ > This section only cover response body payloads, to return specific response headers and/or status, you should define them with the `context.set` object before the return statement. More about it in the [Context](context.md) section.
38
+
39
+ ### String
40
+
41
+ Case where `string` is returned by the handler.
42
+
43
+ - status: 200
44
+ - content-type: `text-plain`
45
+
46
+ **Example**
47
+
48
+ ```js
49
+ galbe.get('/example', ctx => {
50
+ return 'Hello Mom!'
51
+ })
52
+ ```
53
+
54
+ ### Object
55
+
56
+ Case where an `object` is returned by the handler.
57
+
58
+ - status: 200
59
+ - content-type: `application/json`
60
+
61
+ **Example**
62
+
63
+ ```js
64
+ galbe.get('/example', ctx => {
65
+ return 'Hello Mom!'
66
+ })
67
+ ```
68
+
69
+ ### Response instance
70
+
71
+ Case where a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instance is returned by the handler.
72
+
73
+ In that case, `context.set` properties are not taken into account to contruct the response.
74
+
75
+ **Example**
76
+
77
+ <!-- prettier-ignore -->
78
+ ```js
79
+ galbe.get('/example', ctx => {
80
+ return new Response(
81
+ 'Hello Mom',
82
+ { status: 200, headers: { 'content-type': 'text/plain' }
83
+ })
84
+ })
85
+ ```
86
+
87
+ ### Generator
88
+
89
+ Case where a [Generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) instance is returned by the handler.
90
+
91
+ - status: 200
92
+ - content-type: `text/event-stream`
93
+
94
+ **Example**
95
+
96
+ ```js
97
+ async function* generator(array) {
98
+ for (const item of array) {
99
+ await Bun.sleep(500)
100
+ yield item
101
+ }
102
+ }
103
+
104
+ galbe.get('/example', ctx => generator(['one', 'two', 'three']))
105
+ ```
package/docs/hooks.md ADDED
@@ -0,0 +1,90 @@
1
+ # Hooks
2
+
3
+ Hooks provide a simple way to perform specific actions before and/or after reaching a specific route endpoint.
4
+
5
+ ## Hook definition
6
+
7
+ ```ts
8
+ const hook = (context, next) => {
9
+ context.state['foo'] = 'bar'
10
+ await next()
11
+ console.log('Hook end')
12
+ }
13
+ ```
14
+
15
+ The hook takes only two arguments, a `context` object and a `next` function.
16
+
17
+ **context**
18
+
19
+ The `context` object contains the request information along with a state property that is modifiable and preserved across all hooks and the handler. It is useful for sharing information or objects across hooks and handler. You can find more information about it in the [Context](context.md) section.
20
+
21
+ **next**
22
+
23
+ The `next` function calls the next hook in the hook list or the handler if the current hook is the last one declared. The `next` function should be called at most once. If it is omitted, Galbe will call it automatically at the end of the execution of the current hook.
24
+
25
+ > [!TIP]
26
+ > Hooks are interruptible objects, meaning they can return a response at any moment. This provides a powerful mechanism for implementing custom logic, such as authentication, authorization, caching, and more.
27
+ >
28
+ > To learn more about response types, ou can take a look at the [Response types](handler.md#response-types) section.
29
+
30
+ ## Hooks declaration
31
+
32
+ Hooks should be declared just before the handler method in the [Route Definition](routes.md#route-defintion) method as a list of Hooks.
33
+
34
+ ```ts
35
+ galbe.get('foo', [ hook1, hook2, ... ], ctx => {})
36
+ ```
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.
39
+
40
+ ### Examples
41
+
42
+ Linear hooks declaration:
43
+
44
+ ```ts
45
+ const hook1 = context => {
46
+ console.log('hook1 called')
47
+ }
48
+ const hook2 = context => {
49
+ console.log('hook2 called')
50
+ }
51
+
52
+ galbe.get('example', [hook1, hook2], ctx => {
53
+ console.log('handler')
54
+ })
55
+ ```
56
+
57
+ ```bash
58
+ $ curl http://localhost:3000/example
59
+ hook1
60
+ hook2
61
+ handler
62
+ ```
63
+
64
+ Nested hooks declaration:
65
+
66
+ ```ts
67
+ const hook1 = (context, next) => {
68
+ console.log('hook1 start')
69
+ await next()
70
+ console.log('hook1 end')
71
+ }
72
+ const hook2 = context => {
73
+ console.log('hook2 start')
74
+ await next()
75
+ console.log('hook2 end')
76
+ }
77
+
78
+ galbe.get('example', [hook1, hook2], ctx => {
79
+ console.log('handler')
80
+ })
81
+ ```
82
+
83
+ ```bash
84
+ $ curl http://localhost:3000/example
85
+ hook1 start
86
+ hook2 start
87
+ handler
88
+ hook2 end
89
+ hook1 end
90
+ ```
@@ -0,0 +1 @@
1
+ # Plugins
package/docs/router.md ADDED
@@ -0,0 +1,10 @@
1
+ # Router
2
+
3
+ Galbe router employs a hybrid approach to store and locating routes.
4
+
5
+ The static routes are maintained in a Map structure. This ensure that any incoming request path matching a static route is resolved in a constant time `O(1)`.
6
+
7
+ > [!NOTE]
8
+ > A static route is a route that doesn't contain any parameter (e.g.,`:param`) or wildcards `*`.
9
+
10
+ All other routes are stored in a [Trie](https://en.wikipedia.org/wiki/Trie)-like data structure. The time complexity of the search operation in this case is `O(n)`, where `n` represents the number of segments in the incoming request path.
package/docs/routes.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Routes
2
2
 
3
- ## Route Definition
3
+ Routes are the entry points for handling client requests in a Galbe application. In this section, we'll cover how to define routes, the various options available for route definitions, and how to use the Automatic Route Analyzer to simplify route setup.
4
+
5
+ ## Route definition
4
6
 
5
7
  Here is how to define routes in Galbe.
6
8
 
@@ -23,15 +25,15 @@ There are two special segments:
23
25
 
24
26
  **schema** (Schema) _Optional_
25
27
 
26
- See [Schemas](schemas) section.
28
+ See [Schemas](schemas.md) section.
27
29
 
28
30
  **hooks** (Hook[]) _Optional_
29
31
 
30
- See [Hooks](hooks) section.
32
+ See [Hooks](hooks.md) section.
31
33
 
32
34
  **handler** (Handler)
33
35
 
34
- See [Handler](handler) section.
36
+ See [Handler](handler.md) section.
35
37
 
36
38
  ### Examples
37
39
 
@@ -78,15 +80,13 @@ galbe.get(
78
80
  ## Automatic Route Analyzer
79
81
 
80
82
  > [!NOTE]
81
- > This feature is only available if you run/build the app via the [Galbe CLI](), which is the case by default if you created your app following the [Automatic Installation]() step or properly configured your package.json to do so.
82
-
83
- The Automatic Route Analyzer is in charge of analyzing all the Route Files of your project and set up the routes defintions to your Glabe server automatically.
83
+ > This feature is only available if you run/build the app via the [Galbe CLI](getting-started.md#galbe-cli), which is the case by default if you created your app following the [Automatic Installation](getting-started.md#automatic-installation) step or if you configured your package.json to do so.
84
84
 
85
- By default, the analyzer will search for route files matching paths like `'src/**/*.route.{js,ts}'`. This can be configured by modifying the value of `routes` property of your Galbe configuration. A value of `false` will disable the analyzer.
85
+ The Automatic Route Analyzer is responsible for analyzing all the Route Files of your project and setting up the route definitions for your Galbe server automatically. By default, the analyzer will search for Route Files matching paths like `src/**/*.route.{js,ts}`. This can be configured by modifying the value of `routes` property of your Galbe configuration. A value of `false` will disable the analyzer.
86
86
 
87
87
  ### Route Files
88
88
 
89
- In order to be properly analyzed, Route Files must export a default function that takes a Galbe instance as unique argument. Your routes should be defined using that Galbe instance. Here a basic js example:
89
+ In order to be properly analyzed, Route Files must export a default function that takes a Galbe instance as unique argument. Your routes should be defined using that Galbe instance. Here's a basic example in JavaScript:
90
90
 
91
91
  ```ts
92
92
  export default g => {
@@ -94,7 +94,7 @@ export default g => {
94
94
  }
95
95
  ```
96
96
 
97
- The same example using Typescript:
97
+ The same example in Typescript:
98
98
 
99
99
  ```ts
100
100
  import type { Galbe } from 'galbe'
@@ -103,7 +103,7 @@ export default (g: Galbe) => {
103
103
  }
104
104
  ```
105
105
 
106
- The Automatic Route Analyzer is also capable of collecting metadata about your Routefile and your routes by analyzing multiline comments. This can be used by some plugins to perform specific tasks. Here is an example of Routefile with multiline comments metadata.
106
+ The Automatic Route Analyzer can also collect metadata about your Route File and your routes by analyzing multiline comments. This can be used by some plugins to perform specific tasks. Here's an example of a Route File with multiline comment metadata:
107
107
 
108
108
  ```js
109
109
  /**
@@ -121,4 +121,4 @@ export default g => {
121
121
  }
122
122
  ```
123
123
 
124
- You will find more information about comment's metadata and how to use them along with examples in a plugin in the [Plugin](plugins) section.
124
+ You will find more information about comment metadata and how to use them along with examples in the [Plugin](plugins.md) section.