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/src/parser.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MaybeArray, STBody, Context } from './index'
|
|
2
2
|
import type {
|
|
3
|
+
STStream,
|
|
4
|
+
STUrlForm,
|
|
5
|
+
STMultipartForm,
|
|
6
|
+
Static,
|
|
7
|
+
STProps,
|
|
8
|
+
STObject,
|
|
3
9
|
MultipartFormData,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Context
|
|
12
|
-
} from './index'
|
|
10
|
+
STUrlFormValues,
|
|
11
|
+
STMultipartFormValues,
|
|
12
|
+
STUnion,
|
|
13
|
+
STLiteral,
|
|
14
|
+
STArray,
|
|
15
|
+
STSchema
|
|
16
|
+
} from './schema'
|
|
13
17
|
|
|
14
|
-
import { Kind, Optional } from '@sinclair/typebox'
|
|
15
|
-
import { RequestError, Stream, T } from './index'
|
|
16
|
-
import { validate } from './validator'
|
|
17
18
|
import { readableStreamToArrayBuffer } from 'bun'
|
|
19
|
+
import { Kind, Optional, Stream } from './schema'
|
|
20
|
+
import { validate } from './validator'
|
|
21
|
+
import { RequestError, $T } from './index'
|
|
18
22
|
|
|
19
23
|
const textDecoder = new TextDecoder()
|
|
20
24
|
const textEncoder = new TextEncoder()
|
|
@@ -36,7 +40,7 @@ const MP_HEADER_RX = /^multipart\/form-data/
|
|
|
36
40
|
export const requestBodyParser = async (
|
|
37
41
|
body: ReadableStream | null,
|
|
38
42
|
headers: { 'content-type'?: string; 'content-length'?: string },
|
|
39
|
-
schema?:
|
|
43
|
+
schema?: STBody
|
|
40
44
|
) => {
|
|
41
45
|
const { 'content-type': contentType, 'content-length': _contentLength } = headers
|
|
42
46
|
const kind = schema?.[Kind]
|
|
@@ -44,7 +48,7 @@ export const requestBodyParser = async (
|
|
|
44
48
|
try {
|
|
45
49
|
if (body === null) {
|
|
46
50
|
if (schema) {
|
|
47
|
-
if (kind === '
|
|
51
|
+
if (kind === 'byteArray') {
|
|
48
52
|
if (Stream in schema) {
|
|
49
53
|
return new ReadableStream({
|
|
50
54
|
start(controller) {
|
|
@@ -72,20 +76,20 @@ export const requestBodyParser = async (
|
|
|
72
76
|
} else return null
|
|
73
77
|
}
|
|
74
78
|
} else {
|
|
75
|
-
if (kind === '
|
|
79
|
+
if (kind === 'byteArray') {
|
|
76
80
|
if (isStream) return rsToAsyncIterator(body)
|
|
77
81
|
const bytes = await readableStreamToArrayBuffer(body)
|
|
78
82
|
return new Uint8Array(bytes)
|
|
79
|
-
} else if (kind === '
|
|
83
|
+
} else if (kind === 'string' && isStream) {
|
|
80
84
|
return $streamToString(body)
|
|
81
85
|
} else if (
|
|
82
|
-
(!contentType?.match(FORM_HEADER_RX) && kind === '
|
|
83
|
-
(!contentType?.match(MP_HEADER_RX) && kind === '
|
|
86
|
+
(!contentType?.match(FORM_HEADER_RX) && kind === 'urlForm') ||
|
|
87
|
+
(!contentType?.match(MP_HEADER_RX) && kind === 'multipartForm')
|
|
84
88
|
) {
|
|
85
89
|
let received = contentType?.match(FORM_HEADER_RX)
|
|
86
|
-
? '
|
|
90
|
+
? 'urlForm'
|
|
87
91
|
: contentType?.match(MP_HEADER_RX)
|
|
88
|
-
? '
|
|
92
|
+
? 'multipartForm'
|
|
89
93
|
: contentType
|
|
90
94
|
throw new RequestError({ status: 400, error: { body: `Expected ${kind}, received ${received}` } })
|
|
91
95
|
} else if (!contentType || contentType === BA_HEADER) {
|
|
@@ -100,7 +104,7 @@ export const requestBodyParser = async (
|
|
|
100
104
|
}
|
|
101
105
|
} else if (contentType === JSON_HEADER) {
|
|
102
106
|
if (!schema) {
|
|
103
|
-
return await streamToString(body, T.
|
|
107
|
+
return await streamToString(body, $T.object($T.any()))
|
|
104
108
|
} else {
|
|
105
109
|
const str = await streamToString(body)
|
|
106
110
|
let json
|
|
@@ -124,21 +128,21 @@ export const requestBodyParser = async (
|
|
|
124
128
|
if (!schema) {
|
|
125
129
|
return await streamToUrlForm(body)
|
|
126
130
|
} else {
|
|
127
|
-
if (kind !== '
|
|
128
|
-
throw new RequestError({ status: 400, error: { body: `Expected ${kind}, received
|
|
129
|
-
if (isStream) return $streamToUrlForm(body, schema as
|
|
130
|
-
else return await streamToUrlForm(body, schema as
|
|
131
|
+
if (kind !== 'urlForm')
|
|
132
|
+
throw new RequestError({ status: 400, error: { body: `Expected ${kind}, received urlForm` } })
|
|
133
|
+
if (isStream) return $streamToUrlForm(body, schema as STStream<STUrlForm>)
|
|
134
|
+
else return await streamToUrlForm(body, schema as STUrlForm)
|
|
131
135
|
}
|
|
132
136
|
} else if (contentType.match(MP_HEADER_RX)) {
|
|
133
137
|
const boundary = contentType.match(/boundary\="?([^"]*)"?;?.*$/)?.[1] || ''
|
|
134
138
|
if (!schema) {
|
|
135
139
|
return await streamToMultipartForm(body, boundary)
|
|
136
140
|
} else {
|
|
137
|
-
if (kind !== '
|
|
141
|
+
if (kind !== 'multipartForm')
|
|
138
142
|
throw new RequestError({ status: 400, error: { body: `Expected ${kind}, received MultipartForm` } })
|
|
139
|
-
if (isStream) return $streamToMultipartForm(body, boundary, schema as
|
|
143
|
+
if (isStream) return $streamToMultipartForm(body, boundary, schema as STStream<STMultipartForm>)
|
|
140
144
|
else {
|
|
141
|
-
return streamToMultipartForm(body, boundary, schema as
|
|
145
|
+
return streamToMultipartForm(body, boundary, schema as STMultipartForm)
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
} else {
|
|
@@ -153,7 +157,7 @@ export const requestBodyParser = async (
|
|
|
153
157
|
async function* $streamToString(body: ReadableStream) {
|
|
154
158
|
for await (const chunk of body) yield textDecoder.decode(chunk)
|
|
155
159
|
}
|
|
156
|
-
const streamToString = async (body: ReadableStream, schema?:
|
|
160
|
+
const streamToString = async (body: ReadableStream, schema?: STBody): Promise<any> => {
|
|
157
161
|
let res = ''
|
|
158
162
|
for await (const chunk of $streamToString(body)) res += chunk
|
|
159
163
|
if (schema) return validate(res, schema, true)
|
|
@@ -161,14 +165,14 @@ const streamToString = async (body: ReadableStream, schema?: TBody): Promise<any
|
|
|
161
165
|
}
|
|
162
166
|
async function* $streamToUrlForm(
|
|
163
167
|
body: ReadableStream<Uint8Array>,
|
|
164
|
-
schema?:
|
|
168
|
+
schema?: STStream<STUrlForm>
|
|
165
169
|
): AsyncGenerator<[string, any]> {
|
|
166
170
|
let rest: Uint8Array = new Uint8Array()
|
|
167
171
|
let bK: Uint8Array = new Uint8Array()
|
|
168
172
|
let bV: Uint8Array = new Uint8Array()
|
|
169
173
|
let start = 0
|
|
170
174
|
const required = Object.fromEntries(
|
|
171
|
-
Object.entries(schema?.
|
|
175
|
+
Object.entries(schema?.props || {}).filter(([_, v]: [string, any]) => !v?.[Optional])
|
|
172
176
|
)
|
|
173
177
|
for await (const chunk of body) {
|
|
174
178
|
start = 0
|
|
@@ -182,8 +186,7 @@ async function* $streamToUrlForm(
|
|
|
182
186
|
decodeURIComponent(textDecoder.decode(bV))
|
|
183
187
|
]
|
|
184
188
|
try {
|
|
185
|
-
let s =
|
|
186
|
-
schema?.properties?.[key]?.[Kind] === 'Array' ? schema?.properties?.[key].items : schema?.properties?.[key]
|
|
189
|
+
let s = schema?.props?.[key]?.[Kind] === 'array' ? schema?.props?.[key].items : schema?.props?.[key]
|
|
187
190
|
val = s ? paramParser(val, s) : val
|
|
188
191
|
} catch (error) {
|
|
189
192
|
throw new RequestError({ status: 400, error: { body: { [key]: error } } })
|
|
@@ -214,7 +217,7 @@ async function* $streamToUrlForm(
|
|
|
214
217
|
decodeURIComponent(textDecoder.decode(rest))
|
|
215
218
|
]
|
|
216
219
|
try {
|
|
217
|
-
let s = schema?.
|
|
220
|
+
let s = schema?.props?.[key]?.[Kind] === 'array' ? schema?.props?.[key].items : schema?.props?.[key]
|
|
218
221
|
val = s ? paramParser(val, s) : val
|
|
219
222
|
} catch (error) {
|
|
220
223
|
throw new RequestError({ status: 400, error: { body: { [key]: error } } })
|
|
@@ -222,8 +225,7 @@ async function* $streamToUrlForm(
|
|
|
222
225
|
delete required[key]
|
|
223
226
|
yield [key, val]
|
|
224
227
|
for (const [k, s] of Object.entries(required)) {
|
|
225
|
-
|
|
226
|
-
if (s[Kind] === 'Array') {
|
|
228
|
+
if (s[Kind] === 'array') {
|
|
227
229
|
yield [k, []]
|
|
228
230
|
delete required[k]
|
|
229
231
|
}
|
|
@@ -235,10 +237,10 @@ async function* $streamToUrlForm(
|
|
|
235
237
|
error: { body: `Missing field${reqKeys.length > 1 ? 's' : ''}: ${reqKeys.join(', ')}` }
|
|
236
238
|
})
|
|
237
239
|
}
|
|
238
|
-
const streamToUrlForm = async (body: ReadableStream<Uint8Array>, schema?:
|
|
240
|
+
const streamToUrlForm = async (body: ReadableStream<Uint8Array>, schema?: STUrlForm) => {
|
|
239
241
|
let entries = []
|
|
240
242
|
const required = Object.fromEntries(
|
|
241
|
-
Object.entries(schema?.
|
|
243
|
+
Object.entries(schema?.props || {}).filter(([_, v]: [string, any]) => !v?.[Optional])
|
|
242
244
|
)
|
|
243
245
|
let errors: Record<string, any> = {}
|
|
244
246
|
for await (const chunk of $streamToUrlForm(body)) entries.push(chunk)
|
|
@@ -247,21 +249,20 @@ const streamToUrlForm = async (body: ReadableStream<Uint8Array>, schema?: TUrlFo
|
|
|
247
249
|
if (e[0] in object) {
|
|
248
250
|
if (Array.isArray(object[e[0]])) object[e[0]].push(e[1])
|
|
249
251
|
else object[e[0]] = [object[e[0]], e[1]]
|
|
250
|
-
} else object[e[0]] = schema?.
|
|
252
|
+
} else object[e[0]] = schema?.props?.[e[0]]?.[Kind] === 'array' ? [e[1]] : e[1]
|
|
251
253
|
}
|
|
252
|
-
if (schema?.
|
|
254
|
+
if (schema?.props)
|
|
253
255
|
for (let [k, v] of Object.entries(object)) {
|
|
254
256
|
delete required[k]
|
|
255
257
|
try {
|
|
256
|
-
object[k] = schema?.
|
|
258
|
+
object[k] = schema?.props && k in schema?.props ? paramParser(v, schema?.props[k]) : v
|
|
257
259
|
} catch (error) {
|
|
258
260
|
errors[k] = k in errors ? [...errors[k], error] : error
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
if (Object.keys(errors).length) throw new RequestError({ status: 400, error: { body: errors } })
|
|
262
264
|
for (const [k, s] of Object.entries(required)) {
|
|
263
|
-
|
|
264
|
-
if (s[Kind] === 'Array') {
|
|
265
|
+
if (s[Kind] === 'array') {
|
|
265
266
|
object[k] = []
|
|
266
267
|
delete required[k]
|
|
267
268
|
}
|
|
@@ -274,14 +275,14 @@ const streamToUrlForm = async (body: ReadableStream<Uint8Array>, schema?: TUrlFo
|
|
|
274
275
|
})
|
|
275
276
|
return object
|
|
276
277
|
}
|
|
277
|
-
async function* $streamToMultipartForm(data: ReadableStream<Uint8Array>, boundary: string, schema?:
|
|
278
|
+
async function* $streamToMultipartForm(data: ReadableStream<Uint8Array>, boundary: string, schema?: STMultipartForm) {
|
|
278
279
|
const bound = textEncoder.encode(boundary)
|
|
279
280
|
let rest = new Uint8Array()
|
|
280
281
|
let bK: Uint8Array = new Uint8Array()
|
|
281
282
|
let bV: Uint8Array = new Uint8Array()
|
|
282
283
|
let start = 0
|
|
283
284
|
const required = Object.fromEntries(
|
|
284
|
-
Object.entries(schema?.
|
|
285
|
+
Object.entries(schema?.props || {}).filter(([_, v]: [string, any]) => !v?.[Optional])
|
|
285
286
|
)
|
|
286
287
|
for await (const chunk of data) {
|
|
287
288
|
start = 0
|
|
@@ -346,8 +347,7 @@ async function* $streamToMultipartForm(data: ReadableStream<Uint8Array>, boundar
|
|
|
346
347
|
}
|
|
347
348
|
}
|
|
348
349
|
for (const [k, s] of Object.entries(required)) {
|
|
349
|
-
|
|
350
|
-
if (s[Kind] === 'Array') {
|
|
350
|
+
if (s[Kind] === 'array') {
|
|
351
351
|
yield { headers: { name: k }, content: [] }
|
|
352
352
|
delete required[k]
|
|
353
353
|
}
|
|
@@ -381,59 +381,59 @@ const parseMultipartHeader = (header: string): { name: string; [key: string]: st
|
|
|
381
381
|
const parseMultipartContent = (
|
|
382
382
|
content: Uint8Array,
|
|
383
383
|
headers: { name: string; type?: string },
|
|
384
|
-
schema?:
|
|
384
|
+
schema?: STMultipartForm
|
|
385
385
|
) => {
|
|
386
386
|
const type = headers?.type ?? 'text/plain'
|
|
387
387
|
let result: any = content
|
|
388
388
|
if (type === 'text/plain') {
|
|
389
389
|
const str = textDecoder.decode(content).trim()
|
|
390
|
-
let s = schema?.
|
|
391
|
-
return s ? paramParser(str, s?.[Kind] === '
|
|
390
|
+
let s = schema?.props?.[headers.name]
|
|
391
|
+
return s ? paramParser(str, s?.[Kind] === 'array' ? s?.items : s) : str
|
|
392
392
|
} else if (type === 'application/json') {
|
|
393
|
-
if (!schema?.
|
|
393
|
+
if (!schema?.props || !(headers.name in schema?.props)) {
|
|
394
394
|
try {
|
|
395
395
|
result = JSON.parse(textDecoder.decode(content).trim())
|
|
396
396
|
} catch (err: any) {
|
|
397
397
|
throw new RequestError({ status: 400, error: { body: { [headers.name]: err?.message || 'Parsing error' } } })
|
|
398
398
|
}
|
|
399
|
-
} else if (schema?.
|
|
400
|
-
if (schema?.
|
|
399
|
+
} else if (schema?.props) {
|
|
400
|
+
if (schema?.props[headers.name][Kind] === 'object') {
|
|
401
401
|
try {
|
|
402
402
|
result = JSON.parse(textDecoder.decode(content).trim())
|
|
403
403
|
} catch (err: any) {
|
|
404
404
|
throw new RequestError({ status: 400, error: { body: { [headers.name]: err?.message || 'Parsing error' } } })
|
|
405
405
|
}
|
|
406
406
|
try {
|
|
407
|
-
validate(result, schema?.
|
|
407
|
+
validate(result, schema?.props[headers.name])
|
|
408
408
|
} catch (err) {
|
|
409
409
|
throw new RequestError({ status: 400, error: { body: { [headers.name]: err } } })
|
|
410
410
|
}
|
|
411
|
-
} else if (schema?.
|
|
411
|
+
} else if (schema?.props[headers.name][Kind] === 'byteArray') {
|
|
412
412
|
return content
|
|
413
|
-
} else if (schema?.
|
|
413
|
+
} else if (schema?.props[headers.name][Kind] === 'string') {
|
|
414
414
|
result = textDecoder.decode(content).trim()
|
|
415
415
|
} else {
|
|
416
416
|
throw new RequestError({
|
|
417
417
|
status: 400,
|
|
418
|
-
error: { body: { [headers.name]: `Expect ${schema?.
|
|
418
|
+
error: { body: { [headers.name]: `Expect ${schema?.props[headers.name][Kind]} found json` } }
|
|
419
419
|
})
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
|
-
} else if (schema?.
|
|
422
|
+
} else if (schema?.props?.[headers.name]) {
|
|
423
423
|
try {
|
|
424
|
-
let s = schema?.
|
|
425
|
-
validate(result, s?.[Kind] === '
|
|
424
|
+
let s = schema?.props[headers.name]
|
|
425
|
+
validate(result, s?.[Kind] === 'array' ? s?.items : s)
|
|
426
426
|
} catch (err) {
|
|
427
427
|
throw new RequestError({ status: 400, error: { body: { [headers.name]: err } } })
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
430
|
return result
|
|
431
431
|
}
|
|
432
|
-
const streamToMultipartForm = async (data: ReadableStream<Uint8Array>, boundary: string, schema?:
|
|
432
|
+
const streamToMultipartForm = async (data: ReadableStream<Uint8Array>, boundary: string, schema?: STMultipartForm) => {
|
|
433
433
|
const res: Record<string, MultipartFormData> = {}
|
|
434
434
|
const errors: Record<string, any> = {}
|
|
435
435
|
const required = Object.fromEntries(
|
|
436
|
-
Object.entries(schema?.
|
|
436
|
+
Object.entries(schema?.props || {}).filter(([_, v]: [string, any]) => !v?.[Optional])
|
|
437
437
|
)
|
|
438
438
|
for await (const chunk of $streamToMultipartForm(data, boundary)) {
|
|
439
439
|
if (chunk.headers.name in res) {
|
|
@@ -441,29 +441,26 @@ const streamToMultipartForm = async (data: ReadableStream<Uint8Array>, boundary:
|
|
|
441
441
|
res[chunk.headers.name].content = [res[chunk.headers.name].content]
|
|
442
442
|
res[chunk.headers.name].content.push(chunk.content)
|
|
443
443
|
} else {
|
|
444
|
-
if (schema?.
|
|
444
|
+
if (schema?.props?.[chunk.headers.name]?.[Kind] === 'array')
|
|
445
445
|
res[chunk.headers.name] = { ...chunk, content: [chunk.content] }
|
|
446
446
|
else res[chunk.headers.name] = chunk
|
|
447
447
|
}
|
|
448
448
|
delete required[chunk.headers.name]
|
|
449
449
|
|
|
450
|
-
if (schema?.
|
|
450
|
+
if (schema?.props && chunk?.headers?.name in schema.props) {
|
|
451
451
|
try {
|
|
452
|
-
if (
|
|
453
|
-
Array.isArray(res[chunk.headers.name].content) &&
|
|
454
|
-
schema?.properties?.[chunk.headers.name]?.[Kind] !== 'Array'
|
|
455
|
-
)
|
|
452
|
+
if (Array.isArray(res[chunk.headers.name].content) && schema?.props?.[chunk.headers.name]?.[Kind] !== 'array')
|
|
456
453
|
throw `Multiple values found`
|
|
457
454
|
res[chunk.headers.name].content = validate(
|
|
458
455
|
res[chunk.headers.name].content,
|
|
459
|
-
schema?.
|
|
456
|
+
schema?.props[chunk.headers.name],
|
|
460
457
|
true
|
|
461
458
|
)
|
|
462
|
-
if (schema.
|
|
459
|
+
if (schema.props[chunk.headers.name][Kind] === 'array')
|
|
463
460
|
for (let [k, v] of Object.entries(res[chunk.headers.name].content)) {
|
|
464
461
|
try {
|
|
465
462
|
//@ts-ignore
|
|
466
|
-
res[chunk.headers.name].content[k] = paramParser(v, schema.
|
|
463
|
+
res[chunk.headers.name].content[k] = paramParser(v, schema.props[chunk.headers.name].items)
|
|
467
464
|
} catch (error) {
|
|
468
465
|
errors[chunk.headers.name] = chunk.headers.name in errors ? [...errors[chunk.headers.name], error] : error
|
|
469
466
|
}
|
|
@@ -479,8 +476,7 @@ const streamToMultipartForm = async (data: ReadableStream<Uint8Array>, boundary:
|
|
|
479
476
|
error: { body: errors }
|
|
480
477
|
})
|
|
481
478
|
for (const [k, s] of Object.entries(required)) {
|
|
482
|
-
|
|
483
|
-
if (s[Kind] === 'Array') {
|
|
479
|
+
if (s[Kind] === 'array') {
|
|
484
480
|
res[k] = { headers: { name: k }, content: [] }
|
|
485
481
|
delete required[k]
|
|
486
482
|
}
|
|
@@ -493,19 +489,22 @@ const streamToMultipartForm = async (data: ReadableStream<Uint8Array>, boundary:
|
|
|
493
489
|
})
|
|
494
490
|
return res
|
|
495
491
|
}
|
|
496
|
-
const paramParser = (
|
|
492
|
+
const paramParser = (
|
|
493
|
+
value: string | string[] | null,
|
|
494
|
+
type: STMultipartFormValues
|
|
495
|
+
): MaybeArray<Static<STUrlFormValues>> => {
|
|
497
496
|
if (value === undefined) {
|
|
498
|
-
if (type[Optional]) return type?.default
|
|
497
|
+
if (type?.[Optional]) return type?.default
|
|
499
498
|
else throw `Required`
|
|
500
499
|
} else if (value === null) return null
|
|
501
500
|
else if (Array.isArray(value)) {
|
|
502
|
-
if (type[Kind] !== '
|
|
501
|
+
if (type[Kind] !== 'array') throw `Multiple values found`
|
|
503
502
|
validate(value, type)
|
|
504
503
|
let pv = []
|
|
505
504
|
let errors: Record<number, any> = {}
|
|
506
505
|
for (let [idx, v] of value.entries()) {
|
|
507
506
|
try {
|
|
508
|
-
pv.push(paramParser(v, type.items as
|
|
507
|
+
pv.push(paramParser(v, type.items as STMultipartFormValues) as Static<STUrlFormValues>)
|
|
509
508
|
} catch (error) {
|
|
510
509
|
errors[idx] = error
|
|
511
510
|
}
|
|
@@ -513,44 +512,46 @@ const paramParser = (value: string | string[] | null, type: TMultipartFormParam)
|
|
|
513
512
|
if (Object.keys(errors).length) throw errors
|
|
514
513
|
return pv
|
|
515
514
|
} else {
|
|
516
|
-
if (type[Kind] === '
|
|
515
|
+
if (type[Kind] === 'boolean') {
|
|
517
516
|
if (typeof value === 'boolean') return value
|
|
518
517
|
if (value === 'true') return true
|
|
519
518
|
if (value === 'false') return false
|
|
520
519
|
else throw `${value} is not a valid boolean. Should be 'true' or 'false'`
|
|
521
|
-
} else if (type[Kind] === '
|
|
520
|
+
} else if (type[Kind] === 'integer') {
|
|
522
521
|
if (value === null || value === undefined) throw `${value} is not a valid integer`
|
|
523
522
|
const parsedValue = parseInt(value, 10)
|
|
524
523
|
if (isNaN(parsedValue) || String(parsedValue) !== String(value)) throw `${value} is not a valid integer`
|
|
525
524
|
validate(parsedValue, type)
|
|
526
525
|
return parsedValue
|
|
527
|
-
} else if (type[Kind] === '
|
|
526
|
+
} else if (type[Kind] === 'number') {
|
|
528
527
|
if (value === null || value === undefined) throw `${value} is not a valid number`
|
|
529
528
|
const parsedValue = Number(value)
|
|
530
529
|
if (isNaN(parsedValue) || String(parsedValue) !== String(value)) throw `${value} is not a valid number`
|
|
531
530
|
validate(parsedValue, type)
|
|
532
531
|
return parsedValue
|
|
533
|
-
} else if (type[Kind] === '
|
|
532
|
+
} else if (type[Kind] === 'string') {
|
|
534
533
|
validate(value, type)
|
|
535
534
|
return value
|
|
536
|
-
} else if (type[Kind] === '
|
|
537
|
-
if (value !== type.
|
|
535
|
+
} else if (type[Kind] === 'literal') {
|
|
536
|
+
if (value !== type.value) throw `${value} is not a valid value`
|
|
538
537
|
return value
|
|
539
|
-
} else if (type[Kind] === '
|
|
540
|
-
return [paramParser(value, type.items as
|
|
541
|
-
} else if (type[Kind] === '
|
|
538
|
+
} else if (type[Kind] === 'array') {
|
|
539
|
+
return [paramParser(value, type.items as STMultipartFormValues) as Static<STUrlFormValues>]
|
|
540
|
+
} else if (type[Kind] === 'byteArray') {
|
|
542
541
|
return Uint8Array.from(value, c => c.charCodeAt(0))
|
|
543
|
-
} else if (type[Kind] === '
|
|
542
|
+
} else if (type[Kind] === 'union') {
|
|
544
543
|
const union = Object.values(type.anyOf)
|
|
545
544
|
for (const elt of union) {
|
|
546
545
|
try {
|
|
547
|
-
return paramParser(value, elt as
|
|
546
|
+
return paramParser(value, elt as STMultipartFormValues)
|
|
548
547
|
} catch (err) {
|
|
549
548
|
continue
|
|
550
549
|
}
|
|
551
550
|
}
|
|
552
|
-
throw `${value} could not be parsed to any of ${union
|
|
553
|
-
|
|
551
|
+
throw `${value} could not be parsed to any of ${union
|
|
552
|
+
.map(u => (u as STLiteral)?.value ?? (u as STSchema)[Kind])
|
|
553
|
+
.join(', ')}`
|
|
554
|
+
} else if (type[Kind] === 'any') {
|
|
554
555
|
return value
|
|
555
556
|
}
|
|
556
557
|
throw `Unknown parsing type ${type[Kind]}`
|
|
@@ -570,12 +571,12 @@ export const requestPathParser = (input: string, path: string) => {
|
|
|
570
571
|
return params
|
|
571
572
|
}
|
|
572
573
|
|
|
573
|
-
export const parseEntry = <T extends
|
|
574
|
+
export const parseEntry = <T extends STProps>(
|
|
574
575
|
params: { [key: string]: string | string[] },
|
|
575
576
|
schema: T,
|
|
576
577
|
options?: { name?: string; i?: boolean }
|
|
577
|
-
): Static<
|
|
578
|
-
const parsedParams: Partial<Static<
|
|
578
|
+
): Static<STObject<T>> => {
|
|
579
|
+
const parsedParams: Partial<Static<STObject<T>>> = {}
|
|
579
580
|
const errors: { [key: string]: string | string[] } = {}
|
|
580
581
|
|
|
581
582
|
if (options?.i === true) {
|
|
@@ -588,9 +589,9 @@ export const parseEntry = <T extends TProperties>(
|
|
|
588
589
|
Object.entries(schema).forEach(([key, s]) => {
|
|
589
590
|
const k = options?.i === true ? key.toLowerCase() : key
|
|
590
591
|
let v = params[k]
|
|
591
|
-
if (s[Kind] === '
|
|
592
|
+
if (s[Kind] === 'array' && options?.name === 'query' && typeof v === 'string') v = v.split(',')
|
|
592
593
|
try {
|
|
593
|
-
let p = paramParser(v, s as
|
|
594
|
+
let p = paramParser(v, s as STMultipartFormValues)
|
|
594
595
|
//@ts-ignore
|
|
595
596
|
if (p !== undefined) parsedParams[k] = p
|
|
596
597
|
} catch (errMsg) {
|
|
@@ -603,7 +604,7 @@ export const parseEntry = <T extends TProperties>(
|
|
|
603
604
|
throw new RequestError({ status: 400, error: options?.name ? { [options.name]: errors } : errors })
|
|
604
605
|
}
|
|
605
606
|
|
|
606
|
-
return parsedParams as Static<
|
|
607
|
+
return parsedParams as Static<STObject<T>>
|
|
607
608
|
}
|
|
608
609
|
|
|
609
610
|
const isIterator = (obj: any) => typeof obj?.next === 'function'
|
|
@@ -616,7 +617,6 @@ export const responseParser = (response: any, ctx: Context) => {
|
|
|
616
617
|
if (response instanceof Response) return response
|
|
617
618
|
else if (typeof response === 'string') {
|
|
618
619
|
if (!details?.headers?.has('content-type')) details?.headers?.set('content-type', 'text/plain')
|
|
619
|
-
//@ts-ignore
|
|
620
620
|
return new Response(response, details)
|
|
621
621
|
}
|
|
622
622
|
if (response instanceof ReadableStream) {
|
|
@@ -641,12 +641,10 @@ export const responseParser = (response: any, ctx: Context) => {
|
|
|
641
641
|
}
|
|
642
642
|
})
|
|
643
643
|
details.headers.set('Content-Type', 'text/event-stream')
|
|
644
|
-
//@ts-ignore
|
|
645
644
|
return new Response(rs, details)
|
|
646
645
|
} else {
|
|
647
646
|
try {
|
|
648
647
|
if (!details?.headers?.has('content-type')) details?.headers?.set('content-type', 'application/json')
|
|
649
|
-
//@ts-ignore
|
|
650
648
|
return new Response(JSON.stringify(response), details)
|
|
651
649
|
} catch (error) {
|
|
652
650
|
console.error(error)
|
package/src/routes.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { Galbe } from './index'
|
|
|
8
8
|
import { transformSync } from '@swc/core'
|
|
9
9
|
import { Glob } from 'bun'
|
|
10
10
|
|
|
11
|
+
export const DEFAULT_ROUTE_PATTERN = 'src/**/*.route.{js,ts}'
|
|
12
|
+
|
|
11
13
|
export type RouteMeta = {
|
|
12
14
|
header: Record<string, boolean | string | string[]>
|
|
13
15
|
routes: Record<string, Record<string, Record<string, boolean | string | string[]>>>
|
|
@@ -116,7 +118,7 @@ const importRoutes = async (filePath: string, galbe: Galbe) => {
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
export const defineRoutes = async (options: GalbeConfig, galbe: Galbe) => {
|
|
119
|
-
const routes = options?.routes
|
|
121
|
+
const routes = options?.routes === true ? DEFAULT_ROUTE_PATTERN : options?.routes
|
|
120
122
|
if (!routes) {
|
|
121
123
|
console.log(`\x1b\[38;5;245m No route file defined\x1b[0m`)
|
|
122
124
|
return
|