galbe 0.1.4 → 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/.github/workflows/release.yml +39 -0
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/bin/cli.ts +11 -9
- package/bun.lockb +0 -0
- package/bunfig.toml +2 -0
- package/docs/getting-started.md +221 -3
- package/docs/routes.md +124 -0
- package/docs/schemas.md +244 -0
- package/package.json +15 -5
- 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/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",
|
|
@@ -30,20 +30,30 @@
|
|
|
30
30
|
"build": "bun ./scripts/build.ts",
|
|
31
31
|
"clean": "rm -rf dist",
|
|
32
32
|
"test": "bun test",
|
|
33
|
-
"postinstall": "bun run ./scripts/postinstall.ts"
|
|
33
|
+
"postinstall": "bun run ./scripts/postinstall.ts",
|
|
34
|
+
"release": "release-it"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"@types/bun": "^1.0.4"
|
|
37
|
+
"@types/bun": "^1.0.4",
|
|
38
|
+
"release-it": "^17.1.1"
|
|
37
39
|
},
|
|
38
40
|
"peerDependencies": {
|
|
39
41
|
"typescript": "^5.0.0"
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
42
|
-
"@sinclair/typebox": "^0.31.28",
|
|
43
44
|
"@swc/core": "^1.3.107",
|
|
44
45
|
"@swc/wasm": "^1.4.0",
|
|
45
46
|
"acorn": "^8.11.2",
|
|
46
47
|
"acorn-walk": "^8.3.0",
|
|
47
48
|
"commander": "^11.1.0"
|
|
49
|
+
},
|
|
50
|
+
"release-it": {
|
|
51
|
+
"git": {
|
|
52
|
+
"pushRepo": "git@github.com:pierre-cm/galbe.git"
|
|
53
|
+
},
|
|
54
|
+
"github": {
|
|
55
|
+
"requireBranch": "main",
|
|
56
|
+
"release": "true"
|
|
57
|
+
}
|
|
48
58
|
}
|
|
49
|
-
}
|
|
59
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,62 +1,43 @@
|
|
|
1
1
|
import type { Server } from 'bun'
|
|
2
|
-
import type {
|
|
3
|
-
ArrayOptions,
|
|
4
|
-
NumericOptions,
|
|
5
|
-
ObjectOptions,
|
|
6
|
-
SchemaOptions,
|
|
7
|
-
Static,
|
|
8
|
-
StringOptions,
|
|
9
|
-
TAny,
|
|
10
|
-
TArray,
|
|
11
|
-
TBoolean,
|
|
12
|
-
TInteger,
|
|
13
|
-
TLiteral,
|
|
14
|
-
TLiteralValue,
|
|
15
|
-
TNever,
|
|
16
|
-
TNumber,
|
|
17
|
-
TObject,
|
|
18
|
-
TOptional,
|
|
19
|
-
TProperties,
|
|
20
|
-
TSchema,
|
|
21
|
-
TString,
|
|
22
|
-
TUnion
|
|
23
|
-
} from '@sinclair/typebox'
|
|
24
2
|
import type { RouteFileMeta } from './routes'
|
|
25
3
|
import type {
|
|
26
4
|
GalbeConfig,
|
|
27
5
|
Method,
|
|
28
|
-
|
|
6
|
+
RequestSchema,
|
|
29
7
|
Hook,
|
|
30
8
|
Handler,
|
|
31
9
|
Endpoint,
|
|
32
10
|
Context,
|
|
33
11
|
ErrorHandler,
|
|
34
12
|
GalbePlugin,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
TUrlForm,
|
|
40
|
-
TBody,
|
|
41
|
-
TByteArray,
|
|
42
|
-
TStreamable,
|
|
43
|
-
MultipartFormData
|
|
13
|
+
STBody,
|
|
14
|
+
STParams,
|
|
15
|
+
STHeaders,
|
|
16
|
+
STQuery
|
|
44
17
|
} from './types'
|
|
45
18
|
|
|
46
|
-
import { TypeClone, Kind, TypeBuilder, Optional, TypeGuard } from '@sinclair/typebox'
|
|
47
19
|
import server from './server'
|
|
48
20
|
import { GalbeRouter } from './router'
|
|
49
21
|
import { defineRoutes } from './routes'
|
|
50
|
-
import { Stream } from './types'
|
|
51
22
|
import { logRoute } from './util'
|
|
23
|
+
import { SchemaType, type STObject, type Static } from './schema'
|
|
52
24
|
|
|
53
|
-
const overloadDiscriminer = <
|
|
25
|
+
const overloadDiscriminer = <
|
|
26
|
+
Path extends string,
|
|
27
|
+
H extends STHeaders,
|
|
28
|
+
P extends Partial<STParams<Path>>,
|
|
29
|
+
Q extends STQuery,
|
|
30
|
+
B extends STBody
|
|
31
|
+
>(
|
|
54
32
|
galbe: Galbe,
|
|
55
33
|
method: Method,
|
|
56
|
-
path:
|
|
57
|
-
arg2:
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
path: Path,
|
|
35
|
+
arg2:
|
|
36
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
37
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
38
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
39
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
40
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
60
41
|
) => {
|
|
61
42
|
const defaultSchema = {}
|
|
62
43
|
if (typeof arg2 === 'function') {
|
|
@@ -69,22 +50,28 @@ const overloadDiscriminer = <H extends TProperties, P extends TProperties, Q ext
|
|
|
69
50
|
else if (typeof arg3 === 'function') return galbeMethod(galbe, method, path, arg2, undefined, arg3)
|
|
70
51
|
}
|
|
71
52
|
}
|
|
72
|
-
throw new Error('Undefined
|
|
53
|
+
throw new Error('Undefined route signature')
|
|
73
54
|
}
|
|
74
|
-
const galbeMethod = <
|
|
55
|
+
const galbeMethod = <
|
|
56
|
+
Path extends string,
|
|
57
|
+
H extends STHeaders,
|
|
58
|
+
P extends Partial<STParams<Path>>,
|
|
59
|
+
Q extends STQuery,
|
|
60
|
+
B extends STBody
|
|
61
|
+
>(
|
|
75
62
|
_galbe: Galbe,
|
|
76
63
|
method: Method,
|
|
77
|
-
path:
|
|
78
|
-
schema:
|
|
79
|
-
hooks: Hook<
|
|
80
|
-
handler: Handler<
|
|
64
|
+
path: Path,
|
|
65
|
+
schema: RequestSchema<Path, H, P, Q, B> | undefined,
|
|
66
|
+
hooks: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | undefined,
|
|
67
|
+
handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
81
68
|
) => {
|
|
82
69
|
schema = schema ?? {}
|
|
83
70
|
hooks = hooks || []
|
|
84
|
-
const context: Context<typeof schema> = {
|
|
85
|
-
headers: {} as Static<
|
|
86
|
-
params: {} as
|
|
87
|
-
query: {} as Static<
|
|
71
|
+
const context: Context<Path, typeof schema> = {
|
|
72
|
+
headers: {} as Static<STObject<Exclude<(typeof schema)['headers'], undefined>>>,
|
|
73
|
+
params: {} as any,
|
|
74
|
+
query: {} as Static<STObject<Exclude<(typeof schema)['query'], undefined>>>,
|
|
88
75
|
body: {} as Static<Exclude<(typeof schema)['body'], undefined>>,
|
|
89
76
|
request: {} as Request,
|
|
90
77
|
state: {},
|
|
@@ -96,7 +83,6 @@ const galbeMethod = <H extends TProperties, P extends TProperties, Q extends TPr
|
|
|
96
83
|
redirect?: string
|
|
97
84
|
}
|
|
98
85
|
}
|
|
99
|
-
//@ts-ignore
|
|
100
86
|
return {
|
|
101
87
|
method,
|
|
102
88
|
path,
|
|
@@ -107,145 +93,7 @@ const galbeMethod = <H extends TProperties, P extends TProperties, Q extends TPr
|
|
|
107
93
|
}
|
|
108
94
|
}
|
|
109
95
|
|
|
110
|
-
export
|
|
111
|
-
/** `[Json]` Creates an Optional property */
|
|
112
|
-
public Optional<T extends TSchema>(schema: T): TOptional<T> {
|
|
113
|
-
return { ...TypeClone.Type(schema), [Optional]: 'Optional' }
|
|
114
|
-
}
|
|
115
|
-
/** `[Json]` Creates an Any type */
|
|
116
|
-
public Any(options: SchemaOptions = {}): TAny {
|
|
117
|
-
return this.Create({ ...options, [Kind]: 'Any' })
|
|
118
|
-
}
|
|
119
|
-
/** `[Json]` Creates an Array type */
|
|
120
|
-
public Array<T extends TSchema>(schema: T, options: ArrayOptions = {}): TArray<T> {
|
|
121
|
-
return this.Create({ ...options, [Kind]: 'Array', type: 'array', items: TypeClone.Type(schema) })
|
|
122
|
-
}
|
|
123
|
-
/** `[Json]` Creates a Boolean type */
|
|
124
|
-
public Boolean(options: SchemaOptions = {}): TBoolean {
|
|
125
|
-
return this.Create({ ...options, [Kind]: 'Boolean', type: 'boolean' })
|
|
126
|
-
}
|
|
127
|
-
/** `[Json]` Creates an Integer type */
|
|
128
|
-
public Integer(options: NumericOptions<number> = {}): TInteger {
|
|
129
|
-
return this.Create({ ...options, [Kind]: 'Integer', type: 'integer' })
|
|
130
|
-
}
|
|
131
|
-
/** `[Json]` Creates a Literal type */
|
|
132
|
-
public Literal<T extends TLiteralValue>(value: T, options: SchemaOptions = {}): TLiteral<T> {
|
|
133
|
-
return this.Create({
|
|
134
|
-
...options,
|
|
135
|
-
[Kind]: 'Literal',
|
|
136
|
-
const: value,
|
|
137
|
-
type: typeof value as 'string' | 'number' | 'boolean'
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
/** `[Json]` Creates a Number type */
|
|
141
|
-
public Number(options: NumericOptions<number> = {}): TNumber {
|
|
142
|
-
return this.Create({ ...options, [Kind]: 'Number', type: 'number' })
|
|
143
|
-
}
|
|
144
|
-
/** `[Json]` Creates an Object type */
|
|
145
|
-
public Object<T extends TProperties>(properties: T, options: ObjectOptions = {}): TObject<T> {
|
|
146
|
-
const propertyKeys = Object.getOwnPropertyNames(properties)
|
|
147
|
-
const optionalKeys = propertyKeys.filter(key => TypeGuard.TOptional(properties[key]))
|
|
148
|
-
const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
|
|
149
|
-
const clonedAdditionalProperties = TypeGuard.TSchema(options.additionalProperties)
|
|
150
|
-
? { additionalProperties: TypeClone.Type(options.additionalProperties) }
|
|
151
|
-
: {}
|
|
152
|
-
const clonedProperties = propertyKeys.reduce(
|
|
153
|
-
(acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }),
|
|
154
|
-
{} as TProperties
|
|
155
|
-
)
|
|
156
|
-
return requiredKeys.length > 0
|
|
157
|
-
? this.Create({
|
|
158
|
-
...options,
|
|
159
|
-
...clonedAdditionalProperties,
|
|
160
|
-
[Kind]: 'Object',
|
|
161
|
-
type: 'object',
|
|
162
|
-
properties: clonedProperties,
|
|
163
|
-
required: requiredKeys
|
|
164
|
-
})
|
|
165
|
-
: this.Create({
|
|
166
|
-
...options,
|
|
167
|
-
...clonedAdditionalProperties,
|
|
168
|
-
[Kind]: 'Object',
|
|
169
|
-
type: 'object',
|
|
170
|
-
properties: clonedProperties
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
/** `[Json]` Creates a String type */
|
|
174
|
-
public String(options: StringOptions = {}): TString {
|
|
175
|
-
return this.Create({ ...options, [Kind]: 'String', type: 'string' })
|
|
176
|
-
}
|
|
177
|
-
/** `[Json]` Creates a Union type */
|
|
178
|
-
public Union(anyOf: [], options?: SchemaOptions): TNever
|
|
179
|
-
/** `[Json]` Creates a Union type */
|
|
180
|
-
public Union<T extends [TSchema]>(anyOf: [...T], options?: SchemaOptions): T[0]
|
|
181
|
-
/** `[Json]` Creates a Union type */
|
|
182
|
-
public Union<T extends TSchema[]>(anyOf: [...T], options?: SchemaOptions): TUnion<T>
|
|
183
|
-
/** `[Json]` Creates a Union type */
|
|
184
|
-
public Union(union: TSchema[], options: SchemaOptions = {}) {
|
|
185
|
-
// prettier-ignore
|
|
186
|
-
return (() => {
|
|
187
|
-
const anyOf = union
|
|
188
|
-
if (anyOf.length === 0) throw new Error("Union type must decalre at least one schema")
|
|
189
|
-
if (anyOf.length === 1) return this.Create(TypeClone.Type(anyOf[0], options))
|
|
190
|
-
const clonedAnyOf = TypeClone.Rest(anyOf)
|
|
191
|
-
return this.Create({ ...options, [Kind]: 'Union', anyOf: clonedAnyOf })
|
|
192
|
-
})()
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
class GalbeTypeBuilder extends TypeboxTypeBuilder {
|
|
197
|
-
/** `[Galbe]` Creates an Stream type */
|
|
198
|
-
public Stream<T extends TUrlForm>(
|
|
199
|
-
schema: T
|
|
200
|
-
): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<[string, string | number | boolean]>; params: unknown[] }
|
|
201
|
-
public Stream<T extends TMultipartForm>(
|
|
202
|
-
schema: T
|
|
203
|
-
): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<MultipartFormData, void, unknown>; params: unknown[] }
|
|
204
|
-
public Stream<T extends TByteArray>(
|
|
205
|
-
schema: T
|
|
206
|
-
): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<Uint8Array>; params: unknown[] }
|
|
207
|
-
public Stream<T extends TString>(
|
|
208
|
-
schema: T
|
|
209
|
-
): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<string>; params: unknown[] }
|
|
210
|
-
public Stream<T extends TStreamable>(schema: T): TStream<T> {
|
|
211
|
-
return {
|
|
212
|
-
...TypeClone.Type(schema),
|
|
213
|
-
[Stream]: 'Stream'
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/** `[Galbe]` Creates an ByteArray type */
|
|
217
|
-
public ByteArray(): TByteArray {
|
|
218
|
-
return this.Create({ [Kind]: 'ByteArray', type: 'byteArray', params: {} })
|
|
219
|
-
}
|
|
220
|
-
/** `[Galbe]` Creates an MultipartForm type */
|
|
221
|
-
public MultipartForm<T extends TMultipartProperties>(properties?: T): TMultipartForm {
|
|
222
|
-
if (!properties) return this.Create({ [Kind]: 'MultipartForm', type: 'multipartForm' })
|
|
223
|
-
const propertyKeys = Object.getOwnPropertyNames(properties)
|
|
224
|
-
const clonedProperties = propertyKeys.reduce(
|
|
225
|
-
//@ts-ignore
|
|
226
|
-
(acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }),
|
|
227
|
-
{} as TProperties
|
|
228
|
-
)
|
|
229
|
-
return this.Create({
|
|
230
|
-
[Kind]: 'MultipartForm',
|
|
231
|
-
type: 'multipartForm',
|
|
232
|
-
properties: clonedProperties
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
/** `[Galbe]` Creates an UrlForm type */
|
|
236
|
-
public UrlForm<T extends TUrlFormProperties>(properties?: T): TUrlForm {
|
|
237
|
-
if (!properties) return this.Create({ [Kind]: 'UrlForm', type: 'urlForm' })
|
|
238
|
-
const propertyKeys = Object.getOwnPropertyNames(properties)
|
|
239
|
-
const clonedProperties = propertyKeys.reduce(
|
|
240
|
-
//@ts-ignore
|
|
241
|
-
(acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }),
|
|
242
|
-
{} as TProperties
|
|
243
|
-
)
|
|
244
|
-
return this.Create({ [Kind]: 'UrlForm', type: 'urlForm', properties: clonedProperties })
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export const T = new GalbeTypeBuilder()
|
|
96
|
+
export const $T = new SchemaType()
|
|
249
97
|
|
|
250
98
|
export { RequestError } from './types'
|
|
251
99
|
|
|
@@ -274,6 +122,7 @@ export class Galbe {
|
|
|
274
122
|
plugins: GalbePlugin[] = []
|
|
275
123
|
constructor(config?: GalbeConfig) {
|
|
276
124
|
this.config = config ?? {}
|
|
125
|
+
this.config.routes = this.config.routes ?? true
|
|
277
126
|
this.router = new GalbeRouter(this.config?.basePath || '')
|
|
278
127
|
}
|
|
279
128
|
private add(route: any) {
|
|
@@ -312,41 +161,95 @@ export class Galbe {
|
|
|
312
161
|
onError(handler: ErrorHandler) {
|
|
313
162
|
this.errorHandler = handler
|
|
314
163
|
}
|
|
315
|
-
get: Endpoint = <
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
164
|
+
get: Endpoint = <
|
|
165
|
+
Path extends string,
|
|
166
|
+
H extends STHeaders,
|
|
167
|
+
P extends Partial<STParams<Path>>,
|
|
168
|
+
Q extends STQuery,
|
|
169
|
+
B extends STBody
|
|
170
|
+
>(
|
|
171
|
+
path: Path,
|
|
172
|
+
arg2:
|
|
173
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
174
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
175
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
176
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
177
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
320
178
|
) => this.add(overloadDiscriminer(this, 'get', path, arg2, arg3, arg4))
|
|
321
|
-
post: Endpoint = <
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
179
|
+
post: Endpoint = <
|
|
180
|
+
Path extends string,
|
|
181
|
+
H extends STHeaders,
|
|
182
|
+
P extends Partial<STParams<Path>>,
|
|
183
|
+
Q extends STQuery,
|
|
184
|
+
B extends STBody
|
|
185
|
+
>(
|
|
186
|
+
path: Path,
|
|
187
|
+
arg2:
|
|
188
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
189
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
190
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
191
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
192
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
326
193
|
) => this.add(overloadDiscriminer(this, 'post', path, arg2, arg3, arg4))
|
|
327
|
-
put: Endpoint = <
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
194
|
+
put: Endpoint = <
|
|
195
|
+
Path extends string,
|
|
196
|
+
H extends STHeaders,
|
|
197
|
+
P extends Partial<STParams<Path>>,
|
|
198
|
+
Q extends STQuery,
|
|
199
|
+
B extends STBody
|
|
200
|
+
>(
|
|
201
|
+
path: Path,
|
|
202
|
+
arg2:
|
|
203
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
204
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
205
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
206
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
207
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
332
208
|
) => this.add(overloadDiscriminer(this, 'put', path, arg2, arg3, arg4))
|
|
333
|
-
patch: Endpoint = <
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
209
|
+
patch: Endpoint = <
|
|
210
|
+
Path extends string,
|
|
211
|
+
H extends STHeaders,
|
|
212
|
+
P extends Partial<STParams<Path>>,
|
|
213
|
+
Q extends STQuery,
|
|
214
|
+
B extends STBody
|
|
215
|
+
>(
|
|
216
|
+
path: Path,
|
|
217
|
+
arg2:
|
|
218
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
219
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
220
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
221
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
222
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
338
223
|
) => this.add(overloadDiscriminer(this, 'patch', path, arg2, arg3, arg4))
|
|
339
|
-
delete: Endpoint = <
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
224
|
+
delete: Endpoint = <
|
|
225
|
+
Path extends string,
|
|
226
|
+
H extends STHeaders,
|
|
227
|
+
P extends Partial<STParams<Path>>,
|
|
228
|
+
Q extends STQuery,
|
|
229
|
+
B extends STBody
|
|
230
|
+
>(
|
|
231
|
+
path: Path,
|
|
232
|
+
arg2:
|
|
233
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
234
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
235
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
236
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
237
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
344
238
|
) => this.add(overloadDiscriminer(this, 'delete', path, arg2, arg3, arg4))
|
|
345
|
-
options: Endpoint = <
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
239
|
+
options: Endpoint = <
|
|
240
|
+
Path extends string,
|
|
241
|
+
H extends STHeaders,
|
|
242
|
+
P extends Partial<STParams<Path>>,
|
|
243
|
+
Q extends STQuery,
|
|
244
|
+
B extends STBody
|
|
245
|
+
>(
|
|
246
|
+
path: Path,
|
|
247
|
+
arg2:
|
|
248
|
+
| RequestSchema<Path, H, P, Q, B>
|
|
249
|
+
| Hook<Path, RequestSchema<Path, H, P, Q, B>>[]
|
|
250
|
+
| Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
251
|
+
arg3?: Hook<Path, RequestSchema<Path, H, P, Q, B>>[] | Handler<Path, RequestSchema<Path, H, P, Q, B>>,
|
|
252
|
+
arg4?: Handler<Path, RequestSchema<Path, H, P, Q, B>>
|
|
350
253
|
) => this.add(overloadDiscriminer(this, 'options', path, arg2, arg3, arg4))
|
|
351
254
|
}
|
|
352
255
|
|