galbe 0.1.6 → 0.1.7
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/bin/cli.ts +11 -9
- package/bun.lockb +0 -0
- package/docs/getting-started.md +221 -3
- package/docs/routes.md +124 -0
- package/docs/schemas.md +244 -0
- package/package.json +1 -2
- package/src/index.ts +122 -219
- package/src/parser.ts +98 -100
- package/src/routes.ts +3 -1
- package/src/schema.ts +378 -0
- package/src/server.ts +1 -1
- package/src/types.ts +128 -147
- package/src/validator.ts +33 -35
- package/test/parser.test.ts +67 -67
- package/test/requests.test.ts +64 -118
- package/test/resources/test.route.comment.ts +3 -3
- package/test/responses.test.ts +3 -3
- package/test/routeFiles.test.ts +13 -0
- package/test/test.utils.ts +14 -12
- package/docs/Getting Started/basic.md +0 -1
- package/docs/Guides/routes.md +0 -1
package/bin/cli.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { RouteMeta } from '../src/routes'
|
|
|
5
5
|
import { program } from 'commander'
|
|
6
6
|
import { relative, resolve } from 'path'
|
|
7
7
|
import { mkdir, readdir, lstat, rm } from 'fs/promises'
|
|
8
|
-
import { metaAnalysis } from '../src/routes'
|
|
8
|
+
import { DEFAULT_ROUTE_PATTERN, metaAnalysis } from '../src/routes'
|
|
9
9
|
import { randomUUID } from 'crypto'
|
|
10
10
|
import { Galbe } from '../src'
|
|
11
11
|
|
|
@@ -14,7 +14,8 @@ const BUILD_ID = randomUUID()
|
|
|
14
14
|
|
|
15
15
|
Bun.env.FORCE_COLOR = '1'
|
|
16
16
|
|
|
17
|
-
const parseRoutes = async (routes?: string | string[]): Promise<{ path: string; meta: RouteMeta }[]> => {
|
|
17
|
+
const parseRoutes = async (routes?: boolean | string | string[]): Promise<{ path: string; meta: RouteMeta }[]> => {
|
|
18
|
+
routes = routes === true ? DEFAULT_ROUTE_PATTERN : routes
|
|
18
19
|
if (!routes) return []
|
|
19
20
|
let files: { path: string; meta: RouteMeta }[] = []
|
|
20
21
|
if (typeof routes === 'string') {
|
|
@@ -61,11 +62,12 @@ program.name('galbe').description('CLI to execute galbe utilities').version('0.1
|
|
|
61
62
|
|
|
62
63
|
program
|
|
63
64
|
.command('dev')
|
|
64
|
-
.description('
|
|
65
|
+
.description('Start a dev server running your Galbe application')
|
|
65
66
|
.argument('<string>', 'filename')
|
|
66
67
|
.option('-p, --port <number>', 'port number', '')
|
|
68
|
+
.option('-w, --watch', 'watch file changes', 'true')
|
|
67
69
|
.action(async (fileName, props) => {
|
|
68
|
-
const { port } = props
|
|
70
|
+
const { port, watch } = props
|
|
69
71
|
const devRoot = resolve(ROOT, '.galbe', 'dev')
|
|
70
72
|
await mkdir(devRoot, { recursive: true })
|
|
71
73
|
await Bun.write(
|
|
@@ -76,15 +78,15 @@ program
|
|
|
76
78
|
await rm(resolve(ROOT, '.galbe', 'dev'), { recursive: true })
|
|
77
79
|
})
|
|
78
80
|
|
|
79
|
-
await $`BUN_ENV=development bun run --watch ${resolve(devRoot, 'index.ts')}`.cwd(ROOT)
|
|
81
|
+
await $`BUN_ENV=development bun run ${watch ? '--watch' : ''} ${resolve(devRoot, 'index.ts')}`.cwd(ROOT)
|
|
80
82
|
})
|
|
81
83
|
|
|
82
84
|
program
|
|
83
85
|
.command('build')
|
|
84
|
-
.description('
|
|
86
|
+
.description('undle your Galbe application')
|
|
85
87
|
.argument('<string>', 'filename')
|
|
86
|
-
.option('-o, --out <string>', 'output file', '')
|
|
87
|
-
.option('-c, --compile', 'standalone executable', false)
|
|
88
|
+
.option('-o, --out <string>', 'output file/directory', '')
|
|
89
|
+
.option('-c, --compile', 'create a standalone executable', false)
|
|
88
90
|
.action(async (fileName, props) => {
|
|
89
91
|
const { out, compile } = props
|
|
90
92
|
const g: Galbe = (await import(resolve(ROOT, fileName))).default
|
|
@@ -97,7 +99,7 @@ program
|
|
|
97
99
|
buildIndex,
|
|
98
100
|
'--target',
|
|
99
101
|
'bun',
|
|
100
|
-
...(compile ? ['--compile', '--outfile', out ? out : '
|
|
102
|
+
...(compile ? ['--compile', '--outfile', out ? out : 'app'] : ['--outdir', out ? out : 'dist'])
|
|
101
103
|
].filter(c => c)
|
|
102
104
|
Bun.spawn(cmds, {
|
|
103
105
|
cwd: ROOT,
|
package/bun.lockb
CHANGED
|
Binary file
|
package/docs/getting-started.md
CHANGED
|
@@ -1,5 +1,223 @@
|
|
|
1
|
-
|
|
1
|
+
# Getting started
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Galbe is a Javascript web framework to build fast and versatile backend servers with Bun.
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
To start developing your Galbe project, you first need to install [Bun](https://bun.sh).
|
|
10
|
+
|
|
11
|
+
## Automatic Installation
|
|
12
|
+
|
|
13
|
+
This is the recommended way of setting up a Galbe project.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun create galbe app
|
|
17
|
+
cd app
|
|
18
|
+
bun install
|
|
5
19
|
```
|
|
20
|
+
|
|
21
|
+
This will create a new project under `app` directory and install it.
|
|
22
|
+
|
|
23
|
+
Now you can start the dev server by running:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bun dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This will start a web server on `loclahost:3000`.
|
|
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:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
$ curl localhost:3000/hello
|
|
35
|
+
Hello from Galbe!
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> [!TIP]
|
|
39
|
+
> By default, the dev server automatically reloads on every file change.
|
|
40
|
+
|
|
41
|
+
## Manual Installation
|
|
42
|
+
|
|
43
|
+
Init a new Bun project and add Galbe as dependency:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bun init
|
|
47
|
+
bun add galbe
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Open `package.json` file and add the following scripts:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "galbe dev index.ts",
|
|
56
|
+
"build": "galbe build index.ts",
|
|
57
|
+
"test": "bun test"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
As you can see, those scripts rely on Galbe CLI to run and build the application. You will find more info about Galbe CLI available options in the next section [Galbe CLI](#galbe-cli).
|
|
63
|
+
|
|
64
|
+
This require your `index.ts` to export a default Galbe instance in order to work. As in the following example:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { Galbe } from 'galbe'
|
|
68
|
+
|
|
69
|
+
const g = new Galbe({ port: 3000 })
|
|
70
|
+
g.get('/hello', () => 'Hello Mom!')
|
|
71
|
+
|
|
72
|
+
export default galbe
|
|
73
|
+
```
|
|
74
|
+
|
|
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
|
+
|
|
77
|
+
> [!WARNING]
|
|
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
|
+
|
|
80
|
+
### Galbe CLI
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
galbe <command> <argument> [options]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Here are the available commands:
|
|
87
|
+
|
|
88
|
+
#### dev
|
|
89
|
+
|
|
90
|
+
Start a dev server running your Galbe application.
|
|
91
|
+
|
|
92
|
+
_argument_
|
|
93
|
+
|
|
94
|
+
The path of the file exporting your Galbe instance
|
|
95
|
+
|
|
96
|
+
_options_
|
|
97
|
+
|
|
98
|
+
- `--port` or `-p`: port number (default: 3000)
|
|
99
|
+
- `--watch` or `-w`: watch file changes (default: true)
|
|
100
|
+
|
|
101
|
+
#### build
|
|
102
|
+
|
|
103
|
+
Bundle your Galbe application.
|
|
104
|
+
|
|
105
|
+
_argument_
|
|
106
|
+
|
|
107
|
+
The path of the file exporting your Galbe instance
|
|
108
|
+
|
|
109
|
+
_options_
|
|
110
|
+
|
|
111
|
+
- `--out` or `-o`: output file | directory (default: app | dist )
|
|
112
|
+
- `--compile` or `-c`: create a standalone executable (default: false)
|
|
113
|
+
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
To configure your Galbe server, you should pass your configuration to the Galbe constructor when you instanciate it.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const galbe = new Galbe(configuration)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Properties
|
|
123
|
+
|
|
124
|
+
#### port
|
|
125
|
+
|
|
126
|
+
The port number that the server will be listening on. Default is `3000`.
|
|
127
|
+
|
|
128
|
+
#### basePath
|
|
129
|
+
|
|
130
|
+
The base path is added as a prefix to all the routes created.
|
|
131
|
+
|
|
132
|
+
#### routes
|
|
133
|
+
|
|
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
|
+
|
|
136
|
+
#### plugin
|
|
137
|
+
|
|
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
|
+
|
|
140
|
+
### Examples
|
|
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:
|
|
143
|
+
|
|
144
|
+
galbe.config.js
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
export default {
|
|
148
|
+
port: Bun.env.GALBE_PORT
|
|
149
|
+
routes: 'src/**/*.route.ts',
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
index.js
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { Galbe } from 'galbe'
|
|
157
|
+
import config from './galbe.config'
|
|
158
|
+
|
|
159
|
+
export default new Galbe(config)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> [!TIP]
|
|
163
|
+
> If you are using Typescript, you can import `GalbeConfig` type from galbe package to ensure type consistency for your configuration. Here is an example:
|
|
164
|
+
>
|
|
165
|
+
> ```ts
|
|
166
|
+
> import type { GalbeConfig } from 'galbe'
|
|
167
|
+
> const config: GalbeConfig = {
|
|
168
|
+
> port: Number(Bun.env.GALBE_PORT),
|
|
169
|
+
> routes: 'routes/*.route.ts'
|
|
170
|
+
> }
|
|
171
|
+
> export default config
|
|
172
|
+
> ```
|
|
173
|
+
|
|
174
|
+
## Project Structure
|
|
175
|
+
|
|
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
|
+
|
|
178
|
+
Here are two examples of valid project structures by default:
|
|
179
|
+
|
|
180
|
+
**Example 1**
|
|
181
|
+
|
|
182
|
+
```txt
|
|
183
|
+
┌── src
|
|
184
|
+
│ ├── hooks
|
|
185
|
+
│ │ └── log.hook.ts
|
|
186
|
+
│ ├── routes
|
|
187
|
+
│ │ ├── foo.route.ts
|
|
188
|
+
│ │ └── foo.route.ts
|
|
189
|
+
│ └── schemas
|
|
190
|
+
│ ├── bar.schema.ts
|
|
191
|
+
│ └── bar.schema.ts
|
|
192
|
+
├── galbe.config.ts
|
|
193
|
+
├── index.ts
|
|
194
|
+
├── package.json
|
|
195
|
+
├── README.md
|
|
196
|
+
└── tsconfig.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Example 2**
|
|
200
|
+
|
|
201
|
+
```txt
|
|
202
|
+
┌── src
|
|
203
|
+
│ ├── hooks
|
|
204
|
+
│ │ └── log.hook.ts
|
|
205
|
+
│ ├── foo
|
|
206
|
+
│ │ ├── foo.route.ts
|
|
207
|
+
│ │ └── foo.schema.ts
|
|
208
|
+
│ └── bar
|
|
209
|
+
│ ├── bar.route.ts
|
|
210
|
+
│ └── bar.schema.ts
|
|
211
|
+
├── galbe.config.ts
|
|
212
|
+
├── index.ts
|
|
213
|
+
├── package.json
|
|
214
|
+
├── README.md
|
|
215
|
+
└── tsconfig.json
|
|
216
|
+
```
|
|
217
|
+
|
|
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
|
+
|
|
220
|
+
You can find more info about Route Files definition under the [Routes](routes.md) section.
|
|
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.
|
package/docs/routes.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Routes
|
|
2
|
+
|
|
3
|
+
## Route Definition
|
|
4
|
+
|
|
5
|
+
Here is how to define routes in Galbe.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
galbe.[method](path: string, schema?: Schema, hooks?: Hooks[], handler: Handler)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**method** ( get | post | put | delete | patch | options )
|
|
12
|
+
|
|
13
|
+
The [HTTP Request Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for the defined route.
|
|
14
|
+
|
|
15
|
+
**path** (string)
|
|
16
|
+
|
|
17
|
+
The path of the route. It should be composed of a sequence of segments separated by `/`. Each segment can be composed alphanumeric characters and dashes, but should not start or end with a dash.
|
|
18
|
+
|
|
19
|
+
There are two special segments:
|
|
20
|
+
|
|
21
|
+
- `:param` Any segment starting with `:` indicates a parameter segment.
|
|
22
|
+
- `*` To indicate a wildcard segment. This will match any segment or sequence of segments.
|
|
23
|
+
|
|
24
|
+
**schema** (Schema) _Optional_
|
|
25
|
+
|
|
26
|
+
See [Schemas](schemas) section.
|
|
27
|
+
|
|
28
|
+
**hooks** (Hook[]) _Optional_
|
|
29
|
+
|
|
30
|
+
See [Hooks](hooks) section.
|
|
31
|
+
|
|
32
|
+
**handler** (Handler)
|
|
33
|
+
|
|
34
|
+
See [Handler](handler) section.
|
|
35
|
+
|
|
36
|
+
### Examples
|
|
37
|
+
|
|
38
|
+
**Basic route**
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
galbe.get('/foo', ctx => 'Hello World!')
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Route with Schema**
|
|
45
|
+
|
|
46
|
+
<!-- prettier-ignore -->
|
|
47
|
+
```js
|
|
48
|
+
galbe.get(
|
|
49
|
+
'/foo/:bar',
|
|
50
|
+
{ params: { bar: $T.string() } },
|
|
51
|
+
ctx => `Hello ${ctx.params.bar} !`
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Route with Hooks**
|
|
56
|
+
|
|
57
|
+
<!-- prettier-ignore -->
|
|
58
|
+
```js
|
|
59
|
+
galbe.get(
|
|
60
|
+
'/foo/:bar',
|
|
61
|
+
[() => console.log('Hook1'), () => console.log('Hook2')],
|
|
62
|
+
ctx => `Hello ${ctx.params.bar} !`
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Route with Schemas and Hooks**
|
|
67
|
+
|
|
68
|
+
<!-- prettier-ignore -->
|
|
69
|
+
```js
|
|
70
|
+
galbe.get(
|
|
71
|
+
'/foo/:bar',
|
|
72
|
+
{ params: { bar: $T.string() } },
|
|
73
|
+
[() => console.log('Hook')],
|
|
74
|
+
ctx => `Hello ${ctx.params.bar} !`
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Automatic Route Analyzer
|
|
79
|
+
|
|
80
|
+
> [!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.
|
|
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.
|
|
86
|
+
|
|
87
|
+
### Route Files
|
|
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:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
export default g => {
|
|
93
|
+
g.get('/foo/:bar', ctx => ctx.params.bar)
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The same example using Typescript:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import type { Galbe } from 'galbe'
|
|
101
|
+
export default (g: Galbe) => {
|
|
102
|
+
g.get('/foo/:bar', ctx => ctx.params.bar)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
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.
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
/**
|
|
110
|
+
* This is the header's head comment
|
|
111
|
+
* @annotation example of header's annotation
|
|
112
|
+
*/
|
|
113
|
+
export default g => {
|
|
114
|
+
/**
|
|
115
|
+
* This is a route head comment
|
|
116
|
+
* @deprecated
|
|
117
|
+
* @tag tag1
|
|
118
|
+
* @tag tag2
|
|
119
|
+
*/
|
|
120
|
+
g.get('/foo/:bar', ctx => ctx.params.bar)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
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.
|
package/docs/schemas.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Shemas
|
|
2
|
+
|
|
3
|
+
Galbe offers a custom Schema Type processor that provides type safety along with data parsing and validation.
|
|
4
|
+
|
|
5
|
+
The prime intention of that features is to offer an easy way to manage automatically request inputs validation and error handling. Moreover, it also greatly improve developper's experience by infering static Typescript types from schema definitions.
|
|
6
|
+
|
|
7
|
+
## Schema Types
|
|
8
|
+
|
|
9
|
+
To get started with Schema defintion, just import `$T` from `galbe` library:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { $T } from 'galbe'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Here the list of available Schema types in Galbe:
|
|
16
|
+
|
|
17
|
+
#### Boolean
|
|
18
|
+
|
|
19
|
+
Schema Type matching `boolean` values.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const boolSchema = $T.boolean()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### String
|
|
26
|
+
|
|
27
|
+
Schema Type matching `string` vlues.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const strSchema = $T.string(options)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### Number
|
|
34
|
+
|
|
35
|
+
Schema Type matching `number` values.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const numSchema = $T.number(options)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Integer
|
|
42
|
+
|
|
43
|
+
Schema Type matching integer `number` values.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const intSchema = $T.integer(options)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Any
|
|
50
|
+
|
|
51
|
+
Schema Type matching `any` of the previous Schema Types.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const anySchema = $T.any()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Array
|
|
58
|
+
|
|
59
|
+
Schema Type matching `array` values.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const arraySchema = $T.array($T.any(), options)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Union
|
|
66
|
+
|
|
67
|
+
Creates an union of Schema Types. .
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const unionSchema = $T.union([$T.string(), $T.number()])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### TOptional
|
|
74
|
+
|
|
75
|
+
Makes any type optional. In practice, this allows for `undefined` values.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const optionalSchema = $T.optional($T.string())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Request Schema definition
|
|
82
|
+
|
|
83
|
+
The Request Schema definition allows you to define a schema for your request on your [Route Definition](). It must be defined right after the [path]() of your route.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
const schema = {}
|
|
87
|
+
galbe.get('/foo/:bar', schema, ctx => {})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The Request Schema has 4 optional properties
|
|
91
|
+
|
|
92
|
+
### headers
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
headers: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This is a key-value object where each key represents a request `header` name and the value the Schema associated.
|
|
99
|
+
|
|
100
|
+
**Example**:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const schema = {
|
|
104
|
+
headers: {
|
|
105
|
+
'User-Agent': $T.optional($T.string({ pattern: '^Bun' }))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### params
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
params: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This is a key-value object where each key represents a request `path parameter` name and the value the Schema associated.
|
|
117
|
+
|
|
118
|
+
**Example**:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const schema = {
|
|
122
|
+
params: {
|
|
123
|
+
name: $T.string(),
|
|
124
|
+
age: $T.integer({ min: 0 })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> [!WARNING]
|
|
130
|
+
> Every key should match an existing [route path]() parameter. Otherwise Typescript will show you an error.
|
|
131
|
+
>
|
|
132
|
+
> By default, if no schema is defined for a given parameter. Galbe will assume it is of type `string`.
|
|
133
|
+
|
|
134
|
+
### query
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
query: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This is a key-value object where each key represents a request `query parameter` name and the value the Schema associated.
|
|
141
|
+
|
|
142
|
+
**Example**:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const schema = {
|
|
146
|
+
query: {
|
|
147
|
+
name: $T.literal('Galbe'),
|
|
148
|
+
list: $T.array($T.number())
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### body
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
body: STByteArray | STString | STBoolean | STNumber | STInteger | STLiteral | STObject | STMulripartForm | STUrlForm
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Json
|
|
160
|
+
|
|
161
|
+
To define an `application/json` request body. You must use `STObject` Schema Type. Example:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const jsonBody = $T.object({
|
|
165
|
+
name: $T.string(),
|
|
166
|
+
age: $T.integer({ min: 0 })
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Multipart
|
|
171
|
+
|
|
172
|
+
To define a `multipart/form-data` request body. You must use `TMultipartForm` Schema Type. Example:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const multipartBody = $T.multipartForm({
|
|
176
|
+
name: $T.string(),
|
|
177
|
+
age: $T.integer({ minimum: 0 })
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Url Form
|
|
182
|
+
|
|
183
|
+
To define an `application/x-www-form-urlencoded` request body. You must use `TUrlForm` Schema Type. Example:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const urlBody = $T.urlForm({
|
|
187
|
+
name: $T.string(),
|
|
188
|
+
age: $T.integer({ minimum: 0 })
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Stream
|
|
193
|
+
|
|
194
|
+
Some body request types can be streamed by using `STStream` Schema Type wrapper. Streamable Schema Types are `STByteArray`, `STString`, `STUrlForm` and `STMultipartForm`.
|
|
195
|
+
|
|
196
|
+
This can be usefull to imporve performances in case you have heavy body payloads and you want to perform early validations on the body.
|
|
197
|
+
|
|
198
|
+
Let's see a concrete example where that could be usefull. Imagine you want a `multipart/form-data` body request that has two properties `username` and `heavyImageFile`. In the normal case you would define something like that:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
galbe.post(
|
|
202
|
+
'user/create',
|
|
203
|
+
{
|
|
204
|
+
body: $T.multipartForm({
|
|
205
|
+
username: $T.string(),
|
|
206
|
+
heavyImageFile: $T.byteArray()
|
|
207
|
+
})
|
|
208
|
+
},
|
|
209
|
+
ctx => {
|
|
210
|
+
// At that point, the full body request has been processed
|
|
211
|
+
if(!isValid(ctx.body.username))
|
|
212
|
+
throw new RequestError({ status: 400 })
|
|
213
|
+
else ctx.set.status = 201
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This means that in the case where the username wouldn't pass the validation, the full request body including the `heavyImageFile` would have been processed for nothing as it is not used. Inducing unnecessary time and resource consumption.
|
|
219
|
+
|
|
220
|
+
The `STStream` Schema Type wrapper was created to remediate to that issue. In practice it allows you to perform validations on the fly.
|
|
221
|
+
|
|
222
|
+
Now in your handler, instead of receiving an object as ctx.body, you'll receive an [AsyncGenerator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator).
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
galbe.post(
|
|
226
|
+
'user/create',
|
|
227
|
+
{
|
|
228
|
+
body: $T.stream($T.multipartForm({
|
|
229
|
+
username: $T.string(),
|
|
230
|
+
heavyImageFile: $T.byteArray()
|
|
231
|
+
}))
|
|
232
|
+
},
|
|
233
|
+
ctx => {
|
|
234
|
+
// At that point, the body has not been processed yet.
|
|
235
|
+
for await (const [key, value] of ctx.body) {
|
|
236
|
+
if (key === "username" && !isValid(value)) {
|
|
237
|
+
// Returns an early response before heavyImageFile is processed
|
|
238
|
+
throw new RequestError({ status: 400 })
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
ctx.set.status = 201
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "galbe",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"typescript": "^5.0.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@sinclair/typebox": "^0.31.28",
|
|
45
44
|
"@swc/core": "^1.3.107",
|
|
46
45
|
"@swc/wasm": "^1.4.0",
|
|
47
46
|
"acorn": "^8.11.2",
|