galbe 0.3.0 → 0.4.1
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/build_test.yml +2 -4
- package/.github/workflows/release.yml +2 -2
- package/bin/cli.ts +8 -104
- package/bin/commands/build.ts +95 -0
- package/bin/commands/dev.ts +53 -0
- package/bin/commands/generate/client.ts +192 -0
- package/bin/commands/generate/code/openapi.parser.ts +479 -0
- package/bin/commands/generate/code.ts +75 -0
- package/bin/commands/generate/index.ts +12 -0
- package/bin/commands/generate/spec.ts +94 -0
- package/bin/res/cli.template.js +120 -0
- package/bin/res/client.template.ts +161 -0
- package/bin/util.ts +164 -0
- package/bun.lockb +0 -0
- package/docs/routes.md +3 -3
- package/package.json +10 -8
- package/scripts/postinstall.ts +1 -3
- package/src/extras/spec/openapi.serializer.ts +264 -0
- package/src/extras.ts +1 -0
- package/src/index.ts +103 -73
- package/src/parser.ts +27 -5
- package/src/router.ts +27 -29
- package/src/routes.ts +137 -38
- package/src/schema.ts +69 -8
- package/src/server.ts +9 -10
- package/src/types.ts +73 -41
- package/src/util.ts +76 -10
- package/src/validator.ts +2 -0
- package/test/parser.test.ts +34 -0
- package/test/requests.test.ts +5 -12
- package/test/resources/test.route.comment.ts +20 -0
- package/test/responses.test.ts +109 -11
- package/test/routeFiles.test.ts +44 -27
- package/test/router.test.ts +67 -42
- package/scripts/build.ts +0 -14
package/src/types.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { ServeOptions, TLSServeOptions } from 'bun'
|
|
2
2
|
import type {
|
|
3
|
+
STAny,
|
|
3
4
|
STArray,
|
|
4
5
|
STBoolean,
|
|
5
6
|
STByteArray,
|
|
6
7
|
STInteger,
|
|
8
|
+
STJson,
|
|
7
9
|
STLiteral,
|
|
8
10
|
STMultipartForm,
|
|
9
11
|
STNumber,
|
|
10
12
|
STObject,
|
|
13
|
+
STOptional,
|
|
14
|
+
STSchema,
|
|
11
15
|
STStream,
|
|
12
16
|
STString,
|
|
13
17
|
STUnion,
|
|
@@ -29,6 +33,7 @@ export type STBody =
|
|
|
29
33
|
| STMultipartForm
|
|
30
34
|
| STUnion
|
|
31
35
|
| STStream
|
|
36
|
+
| undefined
|
|
32
37
|
|
|
33
38
|
export type STResponseValue =
|
|
34
39
|
| STByteArray
|
|
@@ -38,14 +43,18 @@ export type STResponseValue =
|
|
|
38
43
|
| STInteger
|
|
39
44
|
| STLiteral
|
|
40
45
|
| STObject
|
|
46
|
+
| STJson
|
|
41
47
|
| STArray
|
|
42
48
|
| STUnion
|
|
43
49
|
| STStream
|
|
50
|
+
| STAny
|
|
44
51
|
export type STResponse = Record<number, STResponseValue>
|
|
45
52
|
|
|
46
53
|
export type MaybeArray<T> = T | T[]
|
|
54
|
+
export type MaybeSTArray<T extends STSchema> = T | STArray<T>
|
|
55
|
+
export type MaybeSTUnion<T extends STSchema> = T | STUnion<[T, ...T[]]>
|
|
47
56
|
|
|
48
|
-
export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'
|
|
57
|
+
export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head'
|
|
49
58
|
type MaybePromise<T> = T | Promise<T>
|
|
50
59
|
|
|
51
60
|
export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Rest}`
|
|
@@ -56,13 +65,16 @@ export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Re
|
|
|
56
65
|
? P
|
|
57
66
|
: never
|
|
58
67
|
|
|
59
|
-
type
|
|
68
|
+
type STHeadersPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
|
|
69
|
+
type STHeadersValue = MaybeSTUnion<STHeadersPrimaryValue>
|
|
60
70
|
export type STHeaders = Record<string, STHeadersValue>
|
|
61
71
|
|
|
62
|
-
type
|
|
72
|
+
type STParamsPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
|
|
73
|
+
type STParamsValue = MaybeSTUnion<STParamsPrimaryValue>
|
|
63
74
|
export type STParams<Path extends string = string> = Record<ExtractParams<Path>, STParamsValue>
|
|
64
75
|
|
|
65
|
-
type
|
|
76
|
+
type STQueryPrimaryValue = STString | STBoolean | STNumber | STInteger | STLiteral
|
|
77
|
+
type STQueryValue = MaybeSTArray<MaybeSTUnion<STQueryPrimaryValue>>
|
|
66
78
|
export type STQuery = Record<string, STQueryValue>
|
|
67
79
|
|
|
68
80
|
/**
|
|
@@ -112,6 +124,7 @@ export type GalbeConfig = {
|
|
|
112
124
|
* ```
|
|
113
125
|
*/
|
|
114
126
|
export type RequestSchema<
|
|
127
|
+
M extends Method = Method,
|
|
115
128
|
Path extends string = string,
|
|
116
129
|
H extends STHeaders = STHeaders,
|
|
117
130
|
P extends Partial<STParams<Path>> = Partial<STParams<Path>>,
|
|
@@ -134,14 +147,18 @@ type OmitNotDefined<S extends RequestSchema> = {
|
|
|
134
147
|
: //@ts-ignore
|
|
135
148
|
never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
|
|
136
149
|
}
|
|
137
|
-
|
|
138
|
-
export type Context<
|
|
150
|
+
type StaticBody<T extends STSchema> = T extends STOptional<STSchema> ? Static<T> | null : Static<T>
|
|
151
|
+
export type Context<
|
|
152
|
+
M extends Method = Method,
|
|
153
|
+
Path extends string = string,
|
|
154
|
+
S extends RequestSchema = RequestSchema
|
|
155
|
+
> = {
|
|
139
156
|
headers: Static<STObject<Exclude<S['headers'], undefined>>>
|
|
140
157
|
params: {
|
|
141
158
|
[K in ExtractParams<Path>]: K extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[K] : string
|
|
142
159
|
}
|
|
143
160
|
query: Static<STObject<Exclude<S['query'], undefined>>>
|
|
144
|
-
body:
|
|
161
|
+
body: M extends 'get' | 'options' | 'head' ? null : StaticBody<Exclude<S['body'], undefined>>
|
|
145
162
|
request: Request
|
|
146
163
|
route?: Route
|
|
147
164
|
state: Record<string, any>
|
|
@@ -153,61 +170,63 @@ export type Context<Path extends string = string, S extends RequestSchema = Requ
|
|
|
153
170
|
}
|
|
154
171
|
}
|
|
155
172
|
export type Next = () => void | Promise<void>
|
|
156
|
-
export type Hook<Path extends string = string, S extends RequestSchema = RequestSchema> = (
|
|
157
|
-
ctx: Context<Path, S>,
|
|
173
|
+
export type Hook<M extends Method = Method, Path extends string = string, S extends RequestSchema = RequestSchema> = (
|
|
174
|
+
ctx: Context<M, Path, S>,
|
|
158
175
|
next: Next
|
|
159
176
|
) => any | Promise<any>
|
|
160
|
-
export type Handler<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
177
|
+
export type Handler<
|
|
178
|
+
M extends Method = Method,
|
|
179
|
+
Path extends string = string,
|
|
180
|
+
S extends RequestSchema = RequestSchema
|
|
181
|
+
> = (ctx: Context<M, Path, S>) => any
|
|
182
|
+
export type Endpoint<M extends Method> = {
|
|
164
183
|
<
|
|
165
184
|
Path extends string,
|
|
166
|
-
H extends STHeaders,
|
|
167
185
|
P extends Partial<STParams<Path>>,
|
|
168
|
-
|
|
186
|
+
H extends STHeaders = any,
|
|
187
|
+
Q extends STQuery = any,
|
|
169
188
|
B extends STBody = any,
|
|
170
189
|
R extends STResponse = STResponse
|
|
171
190
|
>(
|
|
172
191
|
path: Path,
|
|
173
|
-
schema: RequestSchema<Path, H, P, Q, B, R>,
|
|
174
|
-
hooks: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[],
|
|
175
|
-
handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
|
|
192
|
+
schema: RequestSchema<M, Path, H, P, Q, B, R>,
|
|
193
|
+
hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
|
|
194
|
+
handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
|
|
176
195
|
): void
|
|
177
196
|
<
|
|
178
197
|
Path extends string,
|
|
179
|
-
H extends STHeaders,
|
|
180
198
|
P extends Partial<STParams<Path>>,
|
|
181
|
-
|
|
199
|
+
H extends STHeaders = any,
|
|
200
|
+
Q extends STQuery = any,
|
|
182
201
|
B extends STBody = any,
|
|
183
202
|
R extends STResponse = STResponse
|
|
184
203
|
>(
|
|
185
204
|
path: Path,
|
|
186
|
-
schema: RequestSchema<Path, H, P, Q, B, R>,
|
|
187
|
-
handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
|
|
205
|
+
schema: RequestSchema<M, Path, H, P, Q, B, R>,
|
|
206
|
+
handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
|
|
188
207
|
): void
|
|
189
208
|
<
|
|
190
209
|
Path extends string,
|
|
191
|
-
H extends STHeaders,
|
|
192
210
|
P extends Partial<STParams<Path>>,
|
|
193
|
-
|
|
211
|
+
H extends STHeaders = any,
|
|
212
|
+
Q extends STQuery = any,
|
|
194
213
|
B extends STBody = any,
|
|
195
214
|
R extends STResponse = STResponse
|
|
196
215
|
>(
|
|
197
216
|
path: Path,
|
|
198
|
-
hooks: Hook<Path, RequestSchema<Path, H, P, Q, B, R>>[],
|
|
199
|
-
handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
|
|
217
|
+
hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
|
|
218
|
+
handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
|
|
200
219
|
): void
|
|
201
220
|
<
|
|
202
221
|
Path extends string,
|
|
203
|
-
H extends STHeaders,
|
|
204
222
|
P extends Partial<STParams<Path>>,
|
|
205
|
-
|
|
223
|
+
H extends STHeaders = any,
|
|
224
|
+
Q extends STQuery = any,
|
|
206
225
|
B extends STBody = any,
|
|
207
226
|
R extends STResponse = STResponse
|
|
208
227
|
>(
|
|
209
228
|
path: Path,
|
|
210
|
-
handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
|
|
229
|
+
handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
|
|
211
230
|
): void
|
|
212
231
|
}
|
|
213
232
|
|
|
@@ -223,29 +242,26 @@ export class RequestError {
|
|
|
223
242
|
export type ErrorHandler = (error: any, context: Context) => any
|
|
224
243
|
|
|
225
244
|
export type RouteNode = {
|
|
226
|
-
|
|
245
|
+
routes: { [K in Method]?: Route }
|
|
227
246
|
param?: RouteNode
|
|
228
247
|
children?: Record<string, RouteNode>
|
|
229
248
|
}
|
|
230
249
|
|
|
231
250
|
export type Route<
|
|
251
|
+
M extends Method = Method,
|
|
232
252
|
Path extends string = string,
|
|
233
|
-
H extends STHeaders = {},
|
|
234
253
|
P extends Partial<STParams<Path>> = {},
|
|
235
|
-
|
|
254
|
+
H extends STHeaders = STHeaders,
|
|
255
|
+
Q extends STQuery = STQuery,
|
|
236
256
|
B extends STBody = STBody,
|
|
237
257
|
R extends STResponse = STResponse
|
|
238
258
|
> = {
|
|
239
|
-
method:
|
|
259
|
+
method: M
|
|
240
260
|
path: Path
|
|
241
|
-
schema: RequestSchema<Path, H, P, Q, B, R>
|
|
242
|
-
context: Context<Path, RequestSchema<Path, H, P, Q, B, R>>
|
|
261
|
+
schema: RequestSchema<M, Path, H, P, Q, B, R>
|
|
262
|
+
context: Context<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
|
|
243
263
|
hooks: Hook[]
|
|
244
|
-
handler: Handler<Path, RequestSchema<Path, H, P, Q, B, R>>
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export type RouteTree = {
|
|
248
|
-
[key: string]: RouteNode
|
|
264
|
+
handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
|
|
249
265
|
}
|
|
250
266
|
|
|
251
267
|
export class NotFoundError extends RequestError {
|
|
@@ -254,6 +270,12 @@ export class NotFoundError extends RequestError {
|
|
|
254
270
|
}
|
|
255
271
|
}
|
|
256
272
|
|
|
273
|
+
export class MethodNotAllowedError extends RequestError {
|
|
274
|
+
constructor(message?: any) {
|
|
275
|
+
super({ status: 405, payload: message ?? 'Method not allowed' })
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
257
279
|
export class InternalError extends RequestError {
|
|
258
280
|
constructor(message?: any) {
|
|
259
281
|
super({ status: 500, payload: message ?? 'Internal Server Error' })
|
|
@@ -262,7 +284,7 @@ export class InternalError extends RequestError {
|
|
|
262
284
|
|
|
263
285
|
/**
|
|
264
286
|
* #### GalbePlugin
|
|
265
|
-
* Define a plugin for a Galbe
|
|
287
|
+
* Define a plugin for a Galbe application
|
|
266
288
|
*
|
|
267
289
|
* ---
|
|
268
290
|
* @example
|
|
@@ -288,4 +310,14 @@ export type GalbePlugin = {
|
|
|
288
310
|
onRoute?: (context: Context) => MaybePromise<Response | void>
|
|
289
311
|
beforeHandle?: (context: Context) => MaybePromise<Response | void>
|
|
290
312
|
afterHandle?: (response: Response, context: Context) => MaybePromise<Response | void>
|
|
313
|
+
cli?: (commands: GalbeCLICommand[]) => MaybePromise<GalbeCLICommand[] | void>
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export type GalbeCLICommand = {
|
|
317
|
+
name: string
|
|
318
|
+
description?: string
|
|
319
|
+
route: Route
|
|
320
|
+
arguments?: { name: string; type: string; description: string }[]
|
|
321
|
+
options?: { name: string; short: string; type: string; description: string; default: any }[]
|
|
322
|
+
action?: (props: any) => MaybePromise<void>
|
|
291
323
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Route, RouteNode } from '.'
|
|
2
|
+
import type { RouteMeta } from './routes'
|
|
2
3
|
|
|
3
4
|
const METHOD_COLOR: Record<string, string> = {
|
|
4
5
|
get: '\x1b[32m',
|
|
@@ -6,23 +7,88 @@ const METHOD_COLOR: Record<string, string> = {
|
|
|
6
7
|
put: '\x1b[36m',
|
|
7
8
|
patch: '\x1b[33m',
|
|
8
9
|
delete: '\x1b[31m',
|
|
9
|
-
options: ''
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export const extractMetaRoute = (route: { path: string; method: string }, meta?: Array<RouteFileMeta>) => {
|
|
13
|
-
return meta?.map(e => (route.path in e.routes ? e.routes[route.path]?.[route.method] : null)).filter(e => e)?.[0]
|
|
10
|
+
options: '',
|
|
11
|
+
head: ''
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
export const logRoute = (
|
|
17
15
|
r: { method: string; path: string },
|
|
18
|
-
meta?:
|
|
16
|
+
meta?: RouteMeta,
|
|
17
|
+
format?: { maxPathLength?: number }
|
|
19
18
|
) => {
|
|
20
19
|
let color = METHOD_COLOR?.[r.method] || ''
|
|
21
20
|
console.log(
|
|
22
|
-
` [${color}${`${r.method.toUpperCase()}\x1b[0m]`.padEnd(12, ' ')} ${r.path
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
` [${color}${`${r.method.toUpperCase()}\x1b[0m]`.padEnd(12, ' ')} ${r.path
|
|
22
|
+
.padEnd(format?.maxPathLength ?? r.path.length, ' ')
|
|
23
|
+
.replaceAll(/:([^\/]+)/g, '\x1b[0;33m:$1\x1b[0m')}${meta?.head ? ` ${meta.head}` : ''}`
|
|
25
24
|
)
|
|
26
25
|
}
|
|
27
26
|
|
|
27
|
+
export const walkRoutes = (node: RouteNode, cb: (route: Route) => void) => {
|
|
28
|
+
if (node?.routes) Object.values(node.routes).forEach(r => cb(r))
|
|
29
|
+
for (let c of Object.values(node?.children || {})) walkRoutes(c, cb)
|
|
30
|
+
if (node?.param) walkRoutes(node.param, cb)
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
export const isIterator = (obj: any) => typeof obj?.next === 'function'
|
|
34
|
+
|
|
35
|
+
export const HttpStatus = {
|
|
36
|
+
100: 'Continue',
|
|
37
|
+
101: 'Switching Protocols',
|
|
38
|
+
102: 'Processing',
|
|
39
|
+
103: 'Early Hints',
|
|
40
|
+
200: 'OK',
|
|
41
|
+
201: 'Created',
|
|
42
|
+
202: 'Accepted',
|
|
43
|
+
203: 'Non Authoritative Information',
|
|
44
|
+
204: 'No Content',
|
|
45
|
+
205: 'Reset Content',
|
|
46
|
+
206: 'Partial Content',
|
|
47
|
+
207: 'Multi-Status',
|
|
48
|
+
300: 'Multiple Choices',
|
|
49
|
+
301: 'Moved Permanently',
|
|
50
|
+
302: 'Moved Temporarily',
|
|
51
|
+
303: 'See Other',
|
|
52
|
+
304: 'Not Modified',
|
|
53
|
+
305: 'Use Proxy',
|
|
54
|
+
307: 'Temporary Redirect',
|
|
55
|
+
308: 'Permanent Redirect',
|
|
56
|
+
400: 'Bad Request',
|
|
57
|
+
401: 'Unauthorized',
|
|
58
|
+
402: 'Payment Required',
|
|
59
|
+
403: 'Forbidden',
|
|
60
|
+
404: 'Not Found',
|
|
61
|
+
405: 'Method Not Allowed',
|
|
62
|
+
406: 'Not Acceptable',
|
|
63
|
+
407: 'Proxy Authentication Required',
|
|
64
|
+
408: 'Request Timeout',
|
|
65
|
+
409: 'Conflict',
|
|
66
|
+
410: 'Gone',
|
|
67
|
+
411: 'Length Required',
|
|
68
|
+
412: 'Precondition Failed',
|
|
69
|
+
413: 'Request Entity Too Large',
|
|
70
|
+
414: 'Request-URI Too Long',
|
|
71
|
+
415: 'Unsupported Media Type',
|
|
72
|
+
416: 'Requested Range Not Satisfiable',
|
|
73
|
+
417: 'Expectation Failed',
|
|
74
|
+
418: "I'm a teapot",
|
|
75
|
+
419: 'Insufficient Space on Resource',
|
|
76
|
+
420: 'Method Failure',
|
|
77
|
+
421: 'Misdirected Request',
|
|
78
|
+
422: 'Unprocessable Entity',
|
|
79
|
+
423: 'Locked',
|
|
80
|
+
424: 'Failed Dependency',
|
|
81
|
+
426: 'Upgrade Required',
|
|
82
|
+
428: 'Precondition Required',
|
|
83
|
+
429: 'Too Many Requests',
|
|
84
|
+
431: 'Request Header Fields Too Large',
|
|
85
|
+
451: 'Unavailable For Legal Reasons',
|
|
86
|
+
500: 'Internal Server Error',
|
|
87
|
+
501: 'Not Implemented',
|
|
88
|
+
502: 'Bad Gateway',
|
|
89
|
+
503: 'Service Unavailable',
|
|
90
|
+
504: 'Gateway Timeout',
|
|
91
|
+
505: 'HTTP Version Not Supported',
|
|
92
|
+
507: 'Insufficient Storage',
|
|
93
|
+
511: 'Network Authentication Required'
|
|
94
|
+
}
|
package/src/validator.ts
CHANGED
|
@@ -50,6 +50,8 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
|
|
|
50
50
|
}
|
|
51
51
|
})
|
|
52
52
|
if (Object.keys(err).length) errors.push(err)
|
|
53
|
+
} else if (schema[Kind] === 'json') {
|
|
54
|
+
elt = validate(elt, { ...schema, [Kind]: schema.type }, parse)
|
|
53
55
|
} else if (schema[Kind] === 'array') {
|
|
54
56
|
if (parse && typeof elt === 'string') {
|
|
55
57
|
try {
|
package/test/parser.test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isAsyncIterator
|
|
11
11
|
} from './test.utils'
|
|
12
12
|
import { Galbe, $T } from '../src'
|
|
13
|
+
import { schemaToTypeStr } from '../src/schema'
|
|
13
14
|
|
|
14
15
|
const port = 7357
|
|
15
16
|
|
|
@@ -1195,4 +1196,37 @@ describe('parser', () => {
|
|
|
1195
1196
|
expect(body).toEqual(expected.body)
|
|
1196
1197
|
}
|
|
1197
1198
|
})
|
|
1199
|
+
|
|
1200
|
+
test('schema to type', async () => {
|
|
1201
|
+
let type = schemaToTypeStr(
|
|
1202
|
+
$T.object({
|
|
1203
|
+
boolean: $T.boolean(),
|
|
1204
|
+
byteArray: $T.byteArray(),
|
|
1205
|
+
number: $T.number(),
|
|
1206
|
+
integer: $T.integer(),
|
|
1207
|
+
string: $T.string(),
|
|
1208
|
+
any: $T.any(),
|
|
1209
|
+
literal: $T.literal('literal'),
|
|
1210
|
+
array: $T.array($T.string()),
|
|
1211
|
+
object: $T.object({
|
|
1212
|
+
foo: $T.string()
|
|
1213
|
+
}),
|
|
1214
|
+
union: $T.union([$T.number(), $T.string()])
|
|
1215
|
+
})
|
|
1216
|
+
)
|
|
1217
|
+
expect(type).toBe(
|
|
1218
|
+
`{` +
|
|
1219
|
+
`'boolean':boolean;` +
|
|
1220
|
+
`'byteArray':Uint8Array;` +
|
|
1221
|
+
`'number':number;` +
|
|
1222
|
+
`'integer':number;` +
|
|
1223
|
+
`'string':string;` +
|
|
1224
|
+
`'any':any;` +
|
|
1225
|
+
`'literal':'literal';` +
|
|
1226
|
+
`'array':Array<string>;` +
|
|
1227
|
+
`'object':{'foo':string};` +
|
|
1228
|
+
`'union':number|string` +
|
|
1229
|
+
`}`
|
|
1230
|
+
)
|
|
1231
|
+
})
|
|
1198
1232
|
})
|
package/test/requests.test.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { expect, test, describe, beforeAll } from 'bun:test'
|
|
2
2
|
import { Galbe, $T } from '../src'
|
|
3
|
-
import {
|
|
4
|
-
formdata,
|
|
5
|
-
type Case,
|
|
6
|
-
fileHash,
|
|
7
|
-
schema_objectBase,
|
|
8
|
-
schema_object,
|
|
9
|
-
handleBody,
|
|
10
|
-
isAsyncIterator,
|
|
11
|
-
handleUrlFormStream
|
|
12
|
-
} from './test.utils'
|
|
3
|
+
import { formdata, type Case, fileHash, handleBody, isAsyncIterator } from './test.utils'
|
|
13
4
|
|
|
14
5
|
const port = 7358
|
|
15
|
-
const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options']
|
|
6
|
+
const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head']
|
|
16
7
|
|
|
17
8
|
describe('requests', () => {
|
|
18
9
|
beforeAll(async () => {
|
|
@@ -24,6 +15,7 @@ describe('requests', () => {
|
|
|
24
15
|
galbe.patch('/test', () => {})
|
|
25
16
|
galbe.delete('/test', () => {})
|
|
26
17
|
galbe.options('/test', () => {})
|
|
18
|
+
galbe.head('/test', () => {})
|
|
27
19
|
|
|
28
20
|
galbe.get('/headers', ctx => {
|
|
29
21
|
return ctx.headers
|
|
@@ -76,7 +68,8 @@ describe('requests', () => {
|
|
|
76
68
|
p2: $T.number(),
|
|
77
69
|
p3: $T.boolean(),
|
|
78
70
|
p4: $T.union([$T.number(), $T.boolean()]),
|
|
79
|
-
p5: $T.optional($T.string())
|
|
71
|
+
p5: $T.optional($T.string()),
|
|
72
|
+
p6: $T.array($T.union([$T.string()]))
|
|
80
73
|
}
|
|
81
74
|
},
|
|
82
75
|
ctx => {
|
|
@@ -35,4 +35,24 @@ export default (g: Galbe) => {
|
|
|
35
35
|
* @other Hello Mom!
|
|
36
36
|
*/
|
|
37
37
|
g.put('/test', { body: $T.object({ foo: $T.string() }) }, [() => {}], _ => {})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* patch method
|
|
41
|
+
*/
|
|
42
|
+
g.patch('/test', _ => {})
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* options method
|
|
46
|
+
*/
|
|
47
|
+
g.options('/test', _ => {})
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* delete method
|
|
51
|
+
*/
|
|
52
|
+
g.delete('/test', _ => {})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* head method
|
|
56
|
+
*/
|
|
57
|
+
g.head('/test', _ => {})
|
|
38
58
|
}
|
package/test/responses.test.ts
CHANGED
|
@@ -50,6 +50,10 @@ describe('responses', () => {
|
|
|
50
50
|
galbe.post('/bool', { response: { 200: $T.boolean() } }, handleResp)
|
|
51
51
|
galbe.post('/num', { response: { 200: $T.number() } }, handleResp)
|
|
52
52
|
galbe.post('/str', { response: { 200: $T.string() } }, handleResp)
|
|
53
|
+
galbe.post('/json/bool', { response: { 200: $T.json($T.boolean()) } }, handleResp)
|
|
54
|
+
galbe.post('/json/num', { response: { 200: $T.json($T.number()) } }, handleResp)
|
|
55
|
+
galbe.post('/json/str', { response: { 200: $T.json($T.string()) } }, handleResp)
|
|
56
|
+
galbe.post('/json/obj', { response: { 200: $T.json($T.object()) } }, handleResp)
|
|
53
57
|
galbe.post('/arr', { response: { 200: $T.array() } }, handleResp)
|
|
54
58
|
galbe.post('/obj', { response: { 200: $T.object($T.any()) } }, handleResp)
|
|
55
59
|
galbe.post('/stream/ba', { response: { 200: $T.stream($T.byteArray()) } }, ctx =>
|
|
@@ -126,14 +130,23 @@ describe('responses', () => {
|
|
|
126
130
|
}
|
|
127
131
|
})
|
|
128
132
|
|
|
129
|
-
const
|
|
133
|
+
const reader = resp.body?.getReader()
|
|
134
|
+
let body = new Uint8Array()
|
|
135
|
+
while (reader) {
|
|
136
|
+
const { value, done } = await reader.read()
|
|
137
|
+
if (done) break
|
|
138
|
+
let buff = new Uint8Array(body.length + value.length)
|
|
139
|
+
buff.set(body)
|
|
140
|
+
buff.set(value, body.length)
|
|
141
|
+
body = buff
|
|
142
|
+
}
|
|
130
143
|
|
|
131
144
|
expect(resp.status).toBe(200)
|
|
132
|
-
expect(resp.headers.get('content-type')).toBe('application/
|
|
145
|
+
expect(resp.headers.get('content-type')).toBe('application/octet-stream')
|
|
133
146
|
expect(body).toEqual(reqBody)
|
|
134
147
|
})
|
|
135
148
|
|
|
136
|
-
test('response, ba, validation
|
|
149
|
+
test('response, ba, validation error', async () => {
|
|
137
150
|
let bodyStr = 'Hello mom!'
|
|
138
151
|
|
|
139
152
|
let resp = await fetch(`http://localhost:${port}/ba`, {
|
|
@@ -166,7 +179,7 @@ describe('responses', () => {
|
|
|
166
179
|
expect(body).toEqual(reqBody)
|
|
167
180
|
})
|
|
168
181
|
|
|
169
|
-
test('response, bool, validation
|
|
182
|
+
test('response, bool, validation error', async () => {
|
|
170
183
|
let bodyStr = 'true'
|
|
171
184
|
|
|
172
185
|
let resp = await fetch(`http://localhost:${port}/bool`, {
|
|
@@ -199,7 +212,7 @@ describe('responses', () => {
|
|
|
199
212
|
expect(body).toEqual(reqBody)
|
|
200
213
|
})
|
|
201
214
|
|
|
202
|
-
test('response, num, validation
|
|
215
|
+
test('response, num, validation error', async () => {
|
|
203
216
|
let bodyStr = '"test"'
|
|
204
217
|
|
|
205
218
|
let resp = await fetch(`http://localhost:${port}/num`, {
|
|
@@ -231,10 +244,95 @@ describe('responses', () => {
|
|
|
231
244
|
expect(body).toEqual(bodyStr)
|
|
232
245
|
})
|
|
233
246
|
|
|
234
|
-
test('response,
|
|
247
|
+
test('response, json bool, validation OK', async () => {
|
|
248
|
+
let bodyStr = 'false'
|
|
249
|
+
let reqBody = false
|
|
250
|
+
|
|
251
|
+
let resp = await fetch(`http://localhost:${port}/json/bool`, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
body: bodyStr,
|
|
254
|
+
headers: {
|
|
255
|
+
'content-type': 'application/json'
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const body = await resp.json()
|
|
260
|
+
|
|
261
|
+
expect(resp.status).toBe(200)
|
|
262
|
+
expect(resp.headers.get('content-type')).toBe('application/json')
|
|
263
|
+
expect(body).toEqual(reqBody)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('response, json bool, validation error', async () => {
|
|
235
267
|
let bodyStr = '3.14'
|
|
236
268
|
|
|
237
|
-
let resp = await fetch(`http://localhost:${port}/
|
|
269
|
+
let resp = await fetch(`http://localhost:${port}/json/bool`, {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
body: bodyStr,
|
|
272
|
+
headers: {
|
|
273
|
+
'content-type': 'application/json'
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
expect(resp.status).toBe(500)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test('response, json num, validation OK', async () => {
|
|
281
|
+
let bodyStr = '42'
|
|
282
|
+
let reqBody = 42
|
|
283
|
+
|
|
284
|
+
let resp = await fetch(`http://localhost:${port}/json/num`, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
body: bodyStr,
|
|
287
|
+
headers: {
|
|
288
|
+
'content-type': 'application/json'
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const body = await resp.json()
|
|
293
|
+
|
|
294
|
+
expect(resp.status).toBe(200)
|
|
295
|
+
expect(resp.headers.get('content-type')).toBe('application/json')
|
|
296
|
+
expect(body).toEqual(reqBody)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('response, json num, validation error', async () => {
|
|
300
|
+
let bodyStr = '"Hi"'
|
|
301
|
+
|
|
302
|
+
let resp = await fetch(`http://localhost:${port}/json/bool`, {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
body: bodyStr,
|
|
305
|
+
headers: {
|
|
306
|
+
'content-type': 'application/json'
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
expect(resp.status).toBe(500)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
test('response, json str, validation OK', async () => {
|
|
314
|
+
let bodyStr = '"Hello Mom!"'
|
|
315
|
+
let reqBody = 'Hello Mom!'
|
|
316
|
+
|
|
317
|
+
let resp = await fetch(`http://localhost:${port}/json/str`, {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
body: bodyStr,
|
|
320
|
+
headers: {
|
|
321
|
+
'content-type': 'application/json'
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
const body = await resp.json()
|
|
326
|
+
|
|
327
|
+
expect(resp.status).toBe(200)
|
|
328
|
+
expect(resp.headers.get('content-type')).toBe('application/json')
|
|
329
|
+
expect(body).toEqual(reqBody)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
test('response, json str, validation error', async () => {
|
|
333
|
+
let bodyStr = '3.14'
|
|
334
|
+
|
|
335
|
+
let resp = await fetch(`http://localhost:${port}/json/str`, {
|
|
238
336
|
method: 'POST',
|
|
239
337
|
body: bodyStr,
|
|
240
338
|
headers: {
|
|
@@ -264,7 +362,7 @@ describe('responses', () => {
|
|
|
264
362
|
expect(body).toEqual(reqBody)
|
|
265
363
|
})
|
|
266
364
|
|
|
267
|
-
test('response, array, validation
|
|
365
|
+
test('response, array, validation error', async () => {
|
|
268
366
|
let bodyStr = '0'
|
|
269
367
|
|
|
270
368
|
let resp = await fetch(`http://localhost:${port}/arr`, {
|
|
@@ -297,7 +395,7 @@ describe('responses', () => {
|
|
|
297
395
|
expect(body).toEqual(reqBody)
|
|
298
396
|
})
|
|
299
397
|
|
|
300
|
-
test('response, object, validation
|
|
398
|
+
test('response, object, validation error', async () => {
|
|
301
399
|
let bodyStr = '"This"'
|
|
302
400
|
|
|
303
401
|
let resp = await fetch(`http://localhost:${port}/obj`, {
|
|
@@ -334,7 +432,7 @@ describe('responses', () => {
|
|
|
334
432
|
expect(body).toMatch(new RegExp(`id:${UUID_RGX}\ndata:Hello\n\nid:${UUID_RGX}\ndata:Mom!\n\n`))
|
|
335
433
|
})
|
|
336
434
|
|
|
337
|
-
test('response, stream ba, validation
|
|
435
|
+
test('response, stream ba, validation error', async () => {
|
|
338
436
|
let bodyStr = ''
|
|
339
437
|
|
|
340
438
|
let resp = await fetch(`http://localhost:${port}/stream/ba`, {
|
|
@@ -368,7 +466,7 @@ describe('responses', () => {
|
|
|
368
466
|
expect(body).toMatch(new RegExp(`id:${UUID_RGX}\ndata:Hello\n\nid:${UUID_RGX}\ndata:Mom!\n\n`))
|
|
369
467
|
})
|
|
370
468
|
|
|
371
|
-
test('response, stream str, validation
|
|
469
|
+
test('response, stream str, validation error', async () => {
|
|
372
470
|
let bodyStr = '0'
|
|
373
471
|
|
|
374
472
|
let resp = await fetch(`http://localhost:${port}/stream/str`, {
|