galbe 0.12.0 → 0.12.2

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.
Files changed (42) hide show
  1. package/bin/commands/generate/code/openapi.parser.ts +2 -2
  2. package/package.json +6 -1
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  5. package/.github/workflows/build_test.yml +0 -17
  6. package/.github/workflows/deploy_website.yml +0 -20
  7. package/.github/workflows/release.yml +0 -39
  8. package/.prettierrc +0 -10
  9. package/bun.lock +0 -904
  10. package/bunfig.toml +0 -2
  11. package/docs/CONTRIBUTING.md +0 -105
  12. package/docs/cli.md +0 -343
  13. package/docs/configuration.md +0 -80
  14. package/docs/context.md +0 -104
  15. package/docs/error-handler.md +0 -54
  16. package/docs/getting-started.md +0 -164
  17. package/docs/handler.md +0 -119
  18. package/docs/hooks.md +0 -90
  19. package/docs/plugins.md +0 -146
  20. package/docs/router.md +0 -10
  21. package/docs/routes.md +0 -133
  22. package/docs/schemas.md +0 -327
  23. package/test/hooks.test.ts +0 -200
  24. package/test/parser.test.ts +0 -1358
  25. package/test/plugins.test.ts +0 -239
  26. package/test/requests.test.ts +0 -917
  27. package/test/resources/image.png +0 -0
  28. package/test/resources/object.badSyntax.json +0 -8
  29. package/test/resources/object.json +0 -8
  30. package/test/resources/object.missing.json +0 -6
  31. package/test/resources/static/chameleon.png +0 -0
  32. package/test/resources/static/index.html +0 -13
  33. package/test/resources/static/sub/index.html +0 -13
  34. package/test/resources/static/sub/other.html +0 -13
  35. package/test/resources/test.route.comment.ts +0 -58
  36. package/test/resources/test.route.empty.ts +0 -9
  37. package/test/responses.test.ts +0 -483
  38. package/test/routeFiles.test.ts +0 -239
  39. package/test/router.test.ts +0 -231
  40. package/test/test.utils.ts +0 -109
  41. package/test/types.test.ts +0 -909
  42. package/tsconfig.json +0 -23
package/docs/schemas.md DELETED
@@ -1,327 +0,0 @@
1
- # Schemas
2
-
3
- Galbe provides a custom Schema Type processor that offers type safety, data parsing, and validation. The use of Schemas highly simplifies request input validation and automatic error handling. Additionally, it enhances the developer experience by inferring static TypeScript types from schema definitions.
4
-
5
- ## Schema Types
6
-
7
- To use Schema definitions, import `$T` from the `galbe` library:
8
-
9
- ```js
10
- import { $T } from 'galbe'
11
- ```
12
-
13
- Here is the list of available Schema types in Galbe:
14
-
15
- #### boolean
16
-
17
- Schema Type matching `boolean` values.
18
-
19
- ```ts
20
- const boolSchema = $T.boolean()
21
- ```
22
-
23
- #### string
24
-
25
- Schema Type matching `string` values.
26
-
27
- ```ts
28
- const strSchema = $T.string(options)
29
- ```
30
-
31
- #### number
32
-
33
- Schema Type matching `number` values.
34
-
35
- ```ts
36
- const numSchema = $T.number(options)
37
- ```
38
-
39
- #### integer
40
-
41
- Schema Type matching integer `number` values.
42
-
43
- ```ts
44
- const intSchema = $T.integer(options)
45
- ```
46
-
47
- #### null
48
-
49
- Schema Type matching `null` values.
50
-
51
- ```ts
52
- const nullSchema = $T.null(options)
53
- ```
54
-
55
- #### any
56
-
57
- Schema Type matching `any` of the previous Schema Types.
58
-
59
- ```ts
60
- const anySchema = $T.any()
61
- ```
62
-
63
- #### array
64
-
65
- Schema Type matching `array` values.
66
-
67
- ```ts
68
- const arraySchema = $T.array($T.any(), options)
69
- ```
70
-
71
- #### optional
72
-
73
- Makes any type optional, allowing `undefined` values.
74
-
75
- ```ts
76
- const optionalSchema = $T.optional($T.string())
77
- ```
78
-
79
- #### nullable
80
-
81
- Makes any type nullable, allowing `null` values.
82
-
83
- ```ts
84
- const nullableSchema = $T.nullable($T.string())
85
- ```
86
-
87
- #### nullish
88
-
89
- Makes any type nullish, allowing both `undefined` and `null` values.
90
-
91
- ```ts
92
- const nullishSchema = $T.nullish($T.string())
93
- ```
94
-
95
- #### union
96
-
97
- Creates a union of Schema Types.
98
-
99
- ```ts
100
- const unionSchema = $T.union([$T.string(), $T.number()])
101
- ```
102
-
103
- ## Request Schema Definition
104
-
105
- The Request Schema definition allows you to define a schema for your request in your [Route Definition](routes.md#route-definition). It must be defined right after the route path.
106
-
107
- ```js
108
- const schema = {}
109
- galbe.get('/foo/:bar', schema, ctx => {})
110
- ```
111
-
112
- The Request Schema has four optional properties:
113
-
114
- ### headers
115
-
116
- ```ts
117
- headers: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral }
118
- ```
119
-
120
- Defines request headers with their respective Schema types.
121
-
122
- **Example:**
123
-
124
- ```ts
125
- const schema = {
126
- headers: {
127
- 'User-Agent': $T.optional($T.string({ pattern: '^Bun' }))
128
- }
129
- }
130
- ```
131
-
132
- ### params
133
-
134
- ```ts
135
- params: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral }
136
- ```
137
-
138
- Defines route parameters with their respective Schema types.
139
-
140
- **Example:**
141
-
142
- ```ts
143
- const schema = {
144
- params: {
145
- name: $T.string(),
146
- age: $T.integer({ min: 0 })
147
- }
148
- }
149
- ```
150
-
151
- > [!WARNING]
152
- > Every key should match an existing [route path](routes.md#route-definition) parameter. Otherwise, TypeScript will show an error. If no schema is defined for a given parameter, Galbe assumes it is of type `string`.
153
-
154
- ### query
155
-
156
- ```ts
157
- query: { [key: string]: STString | STBoolean | STNumber | STInteger | STLiteral }
158
- ```
159
-
160
- Defines query parameters with their respective Schema types.
161
-
162
- **Example:**
163
-
164
- ```ts
165
- const schema = {
166
- query: {
167
- name: $T.literal('Galbe'),
168
- list: $T.array($T.number())
169
- }
170
- }
171
- ```
172
-
173
- ### body
174
-
175
- <!-- prettier-ignore -->
176
- ```ts
177
- body: {
178
- byteArray?: STByteArray | STStream
179
- text?: STString | STLiteral | STBoolean | STNumber | STInteger | STUnion | STStream
180
- json?: STJson | STObject | STBoolean | STInteger | STNumber | STString | STArray | STUnion
181
- urlForm?: STObject | STStream | STUnion
182
- multipart?: STMultipartForm | STStream | STUnion
183
- default?: STString | STByteArray | STStream | STAny
184
- }
185
- ```
186
-
187
- Defines the request body Schema type based on content type.
188
-
189
- #### Byte Array
190
-
191
- Defines an `application/octet-stream` request body.
192
-
193
- ```ts
194
- const body = {
195
- byteArray: $T.byteArray()
196
- }
197
- ```
198
-
199
- #### Text
200
-
201
- Defines an `text/*` request body.
202
-
203
- ```ts
204
- const body = {
205
- text: $T.string()
206
- }
207
- ```
208
-
209
- #### JSON
210
-
211
- Defines an `application/json` request body.
212
-
213
- ```ts
214
- const body = {
215
- json: $T.object({
216
- name: $T.string(),
217
- age: $T.integer({ min: 0 })
218
- })
219
- }
220
- ```
221
-
222
- #### URL Form
223
-
224
- Defines an `application/x-www-form-urlencoded` request body.
225
-
226
- ```ts
227
- const body = {
228
- urlForm: {$T.object({
229
- name: $T.string(),
230
- age: $T.integer({ min: 0 })
231
- })
232
- }
233
- ```
234
-
235
- #### Multipart Form
236
-
237
- Defines a `multipart/form-data` request body.
238
-
239
- ```ts
240
- const body = {
241
- multipart: $T.multipartForm({
242
- name: $T.string(),
243
- age: $T.integer({ min: 0 })
244
- })
245
- }
246
- ```
247
-
248
- #### stream
249
-
250
- Certain request body types can be streamed using `STStream` wrapper, improving performance by validating data incrementally.
251
- This can be usefull to imporve performances in case you have heavy body payloads by leveraging early validation and fail fast behaviors.
252
-
253
- **Example**
254
-
255
- Let's consider a `multipart/form-data` body request that has two properties: `username` and `heavyImageFile`:
256
-
257
- ```ts
258
- galbe.post(
259
- 'user/create',
260
- {
261
- body: {
262
- multipart: $T.multipartForm({
263
- username: $T.string(),
264
- heavyImageFile: $T.byteArray()
265
- })
266
- }
267
- },
268
- ctx => {
269
- // At that point, the full body request has been processed
270
- if(!isValid(ctx.body.username))
271
- throw new RequestError({ status: 400 })
272
- else ctx.set.status = 201
273
- }
274
- })
275
- ```
276
-
277
- In that case, even if the `username` doesn't pass the validation, the full request body, including the `heavyImageFile` is processed before sending the response. This induces unnecessary time and resource consumption because the `heavyImageFile` is processed despite never been used.
278
-
279
- A better approach would consist in leveraging `STStream` wrapper to implement early validation and fail fast behavior. By defining the request body as a stream. Instead of receiving a plain js object as `ctx.body`, you will receive an [AsyncGenerator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator).
280
-
281
- ```ts
282
- galbe.post(
283
- 'user/create',
284
- {
285
- body: {
286
- multipart: $T.stream($T.multipartForm({
287
- username: $T.string(),
288
- heavyImageFile: $T.byteArray()
289
- }))
290
- }
291
- },
292
- async ctx => {
293
- // At that point, the body has not been processed yet.
294
- for await (const [key, value] of ctx.body) {
295
- if (key === "username" && !isValid(value)) {
296
- // Returns an early response before heavyImageFile is processed
297
- throw new RequestError({ status: 400 })
298
- }
299
- }
300
- ctx.set.status = 201
301
- }
302
- })
303
- ```
304
-
305
- ### response
306
-
307
- <!-- prettier-ignore -->
308
- ```ts
309
- response: Record<number | 'default', STByteArray | STString | STBoolean | STNumber | STInteger | STLiteral | STObject | STArray | STStream>
310
- ```
311
-
312
- Defines response validation by associating schema types with specific HTTP status codes. The special key `default` can also be used to define the Response Schema for the remaining status codes.
313
-
314
- **Example:**
315
-
316
- ```ts
317
- const response = {
318
- 200: $T.object({ data: $T.array($T.number()) }),
319
- 404: $T.literal("Not found"),
320
- default: $T.string(),
321
- }
322
- ```
323
-
324
- This ensures all responses adhere to the defined schema.
325
-
326
- > [!NOTE]
327
- > The response validation is enabled by default, meaning that every enpoint response that has a schema defined will be validated at runtime. To disable runtime validation, you can set the `responseValidator?.enabled` option to `false` in the [configuration](getting-started.md#configuration).
@@ -1,200 +0,0 @@
1
- import { expect, test, describe } from 'bun:test'
2
- import { Galbe } from '../src'
3
-
4
- const port = 7360
5
-
6
- describe('hooks', async () => {
7
- const galbe = new Galbe()
8
- await galbe.listen(port)
9
-
10
- test('hooks, empty', async () => {
11
- galbe.get('/hooks/empty', [], () => 'handled')
12
-
13
- let resp = await fetch(`http://localhost:${port}/hooks/empty`, {
14
- method: 'GET'
15
- })
16
- expect(resp.status).toBe(200)
17
- expect(await resp?.text()).toBe('handled')
18
- })
19
-
20
- test('hooks, void', async () => {
21
- galbe.get('/hooks/void', [(_ctx, _next) => {}], () => 'handled')
22
-
23
- let resp = await fetch(`http://localhost:${port}/hooks/void`, {
24
- method: 'GET'
25
- })
26
- expect(resp.status).toBe(200)
27
- expect(await resp?.text()).toBe('handled')
28
- })
29
-
30
- test('hooks, called without next', async () => {
31
- let hookCalled = 0
32
- galbe.get(
33
- '/hooks/called',
34
- [
35
- _ => {
36
- hookCalled++
37
- }
38
- ],
39
- () => 'handled'
40
- )
41
- expect(hookCalled).toBe(0)
42
- let resp = await fetch(`http://localhost:${port}/hooks/called`, {
43
- method: 'GET'
44
- })
45
- expect(hookCalled).toBe(1)
46
- expect(resp.status).toBe(200)
47
- expect(await resp?.text()).toBe('handled')
48
- })
49
-
50
- test('hooks, called with next', async () => {
51
- let hookCalled = 0
52
- galbe.get(
53
- '/hooks/called',
54
- [
55
- async (_, next) => {
56
- hookCalled++
57
- await Bun.sleep(10)
58
- await next()
59
- }
60
- ],
61
- () => 'handled'
62
- )
63
- expect(hookCalled).toBe(0)
64
- let resp = await fetch(`http://localhost:${port}/hooks/called`, {
65
- method: 'GET'
66
- })
67
- expect(hookCalled).toBe(1)
68
- expect(resp.status).toBe(200)
69
- expect(await resp?.text()).toBe('handled')
70
- })
71
-
72
- test('hooks, wrapper hook', async () => {
73
- let before = 0
74
- let after = 0
75
- galbe.get(
76
- '/hooks/called',
77
- [
78
- async (_, next) => {
79
- before++
80
- await Bun.sleep(10)
81
- await next()
82
- after++
83
- }
84
- ],
85
- () => {
86
- expect(before).toBe(1)
87
- expect(after).toBe(0)
88
- return 'handled'
89
- }
90
- )
91
- expect(before).toBe(0)
92
- expect(after).toBe(0)
93
- let resp = await fetch(`http://localhost:${port}/hooks/called`, {
94
- method: 'GET'
95
- })
96
- expect(before).toBe(1)
97
- expect(after).toBe(1)
98
- expect(resp.status).toBe(200)
99
- expect(await resp?.text()).toBe('handled')
100
- })
101
-
102
- test('hooks, nested wrappers', async () => {
103
- let before1 = 0
104
- let after1 = 0
105
- let before2 = 0
106
- let after2 = 0
107
- galbe.get(
108
- '/hooks/called',
109
- [
110
- async (_, next) => {
111
- before1++
112
- await Bun.sleep(10)
113
- await next()
114
- after1++
115
- },
116
- async (_, next) => {
117
- before2++
118
- await Bun.sleep(10)
119
- await next()
120
- after2++
121
- }
122
- ],
123
- () => {
124
- expect(before1).toBe(1)
125
- expect(before2).toBe(1)
126
- expect(after1).toBe(0)
127
- expect(after2).toBe(0)
128
- return 'handled'
129
- }
130
- )
131
- expect(before1).toBe(0)
132
- expect(before2).toBe(0)
133
- expect(after1).toBe(0)
134
- expect(after2).toBe(0)
135
- let resp = await fetch(`http://localhost:${port}/hooks/called`, {
136
- method: 'GET'
137
- })
138
- expect(before1).toBe(1)
139
- expect(before2).toBe(1)
140
- expect(after1).toBe(1)
141
- expect(after2).toBe(1)
142
- expect(resp.status).toBe(200)
143
- expect(await resp?.text()).toBe('handled')
144
- })
145
-
146
- test('hooks, linear chaining', async () => {
147
- let hook1 = 0
148
- let hook2 = 0
149
- galbe.get(
150
- '/hooks/called',
151
- [
152
- async _ => {
153
- hook1++
154
- await Bun.sleep(10)
155
- },
156
- async _ => {
157
- hook2++
158
- await Bun.sleep(10)
159
- }
160
- ],
161
- () => {
162
- expect(hook1).toBe(1)
163
- expect(hook2).toBe(1)
164
- return 'handled'
165
- }
166
- )
167
- expect(hook1).toBe(0)
168
- expect(hook2).toBe(0)
169
- let resp = await fetch(`http://localhost:${port}/hooks/called`, {
170
- method: 'GET'
171
- })
172
- expect(hook1).toBe(1)
173
- expect(hook2).toBe(1)
174
- expect(resp.status).toBe(200)
175
- expect(await resp?.text()).toBe('handled')
176
- })
177
-
178
- test('hooks, early response', async () => {
179
- let hook = 0
180
- galbe.get(
181
- '/hooks',
182
- [
183
- async _ => {
184
- hook++
185
- return 'hook'
186
- }
187
- ],
188
- () => {
189
- expect.unreachable()
190
- }
191
- )
192
- expect(hook).toBe(0)
193
- let resp = await fetch(`http://localhost:${port}/hooks`, {
194
- method: 'GET'
195
- })
196
- expect(hook).toBe(1)
197
- expect(resp.status).toBe(200)
198
- expect(await resp?.text()).toBe('hook')
199
- })
200
- })