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/schema.ts
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
export const Kind = Symbol.for('Galbe.SchemaType.Kind')
|
|
2
|
+
export const Optional = Symbol.for('Galbe.SchemaType.Optional')
|
|
3
|
+
export const Stream = Symbol.for('Galbe.SchemaType.Stream')
|
|
4
|
+
|
|
5
|
+
export interface Options {
|
|
6
|
+
title?: string
|
|
7
|
+
description?: string
|
|
8
|
+
default?: any
|
|
9
|
+
examples?: any
|
|
10
|
+
}
|
|
11
|
+
export interface ByteArrayOptions extends Options {
|
|
12
|
+
minLength?: number
|
|
13
|
+
maxLength?: number
|
|
14
|
+
}
|
|
15
|
+
export interface StringOptions extends Options {
|
|
16
|
+
minLength?: number
|
|
17
|
+
maxLength?: number
|
|
18
|
+
pattern?: RegExp
|
|
19
|
+
}
|
|
20
|
+
export interface NumberOptions extends Options {
|
|
21
|
+
min?: number
|
|
22
|
+
max?: number
|
|
23
|
+
exclusiveMin?: number
|
|
24
|
+
exclusiveMax?: number
|
|
25
|
+
}
|
|
26
|
+
export interface ArrayOptions extends Options {
|
|
27
|
+
minLength?: number
|
|
28
|
+
maxLength?: number
|
|
29
|
+
unique?: boolean
|
|
30
|
+
}
|
|
31
|
+
export interface STSchema extends Options {
|
|
32
|
+
[Kind]:
|
|
33
|
+
| 'boolean'
|
|
34
|
+
| 'byteArray'
|
|
35
|
+
| 'number'
|
|
36
|
+
| 'integer'
|
|
37
|
+
| 'string'
|
|
38
|
+
| 'literal'
|
|
39
|
+
| 'array'
|
|
40
|
+
| 'object'
|
|
41
|
+
| 'urlForm'
|
|
42
|
+
| 'multipartForm'
|
|
43
|
+
| 'any'
|
|
44
|
+
| 'union'
|
|
45
|
+
| 'stream'
|
|
46
|
+
[Optional]?: boolean
|
|
47
|
+
[Stream]?: boolean
|
|
48
|
+
params: unknown[]
|
|
49
|
+
static: unknown
|
|
50
|
+
[key: string]: any
|
|
51
|
+
}
|
|
52
|
+
export type STPropsValue =
|
|
53
|
+
| STBoolean
|
|
54
|
+
| STByteArray
|
|
55
|
+
| STNumber
|
|
56
|
+
| STInteger
|
|
57
|
+
| STString
|
|
58
|
+
| STLiteral
|
|
59
|
+
| STArray
|
|
60
|
+
| STObject
|
|
61
|
+
| STUnion
|
|
62
|
+
| STAny
|
|
63
|
+
export type STProps = Record<string | number, STPropsValue>
|
|
64
|
+
|
|
65
|
+
type Evaluate<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
|
|
66
|
+
export type Static<T extends STSchema, P extends unknown[] = unknown[]> = (T & { params: P })['static']
|
|
67
|
+
|
|
68
|
+
// Utils
|
|
69
|
+
export type STOptional<T extends STSchema> = T & {
|
|
70
|
+
[Optional]: true
|
|
71
|
+
}
|
|
72
|
+
export type STStream<T extends STSchema = STSchema> = T & {
|
|
73
|
+
[Stream]: true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ByteArray
|
|
77
|
+
export interface STByteArray extends STSchema, ByteArrayOptions {
|
|
78
|
+
[Kind]: 'byteArray'
|
|
79
|
+
static: Uint8Array
|
|
80
|
+
}
|
|
81
|
+
export function _ByteArray(options: ByteArrayOptions = {}): STByteArray {
|
|
82
|
+
return {
|
|
83
|
+
...options,
|
|
84
|
+
[Kind]: 'byteArray'
|
|
85
|
+
} as unknown as STByteArray
|
|
86
|
+
}
|
|
87
|
+
// Boolean
|
|
88
|
+
export interface STBoolean extends STSchema, Options {
|
|
89
|
+
[Kind]: 'boolean'
|
|
90
|
+
static: boolean
|
|
91
|
+
}
|
|
92
|
+
export function _Bool(options: Options = {}): STBoolean {
|
|
93
|
+
return {
|
|
94
|
+
...options,
|
|
95
|
+
[Kind]: 'boolean'
|
|
96
|
+
} as unknown as STBoolean
|
|
97
|
+
}
|
|
98
|
+
// String
|
|
99
|
+
export interface STString extends STSchema, NumberOptions {
|
|
100
|
+
[Kind]: 'string'
|
|
101
|
+
static: string
|
|
102
|
+
}
|
|
103
|
+
export function _String(options: StringOptions = {}): STString {
|
|
104
|
+
return {
|
|
105
|
+
...options,
|
|
106
|
+
[Kind]: 'string'
|
|
107
|
+
} as unknown as STString
|
|
108
|
+
}
|
|
109
|
+
// Number
|
|
110
|
+
export interface STNumber extends STSchema, NumberOptions {
|
|
111
|
+
[Kind]: 'number'
|
|
112
|
+
static: number
|
|
113
|
+
}
|
|
114
|
+
export function _Number(options: NumberOptions = {}): STNumber {
|
|
115
|
+
return {
|
|
116
|
+
...options,
|
|
117
|
+
[Kind]: 'number'
|
|
118
|
+
} as unknown as STNumber
|
|
119
|
+
}
|
|
120
|
+
// Integer
|
|
121
|
+
export interface STInteger extends STSchema, NumberOptions {
|
|
122
|
+
[Kind]: 'integer'
|
|
123
|
+
static: number
|
|
124
|
+
}
|
|
125
|
+
export function _Integer(options: NumberOptions = {}): STInteger {
|
|
126
|
+
return {
|
|
127
|
+
...options,
|
|
128
|
+
[Kind]: 'integer'
|
|
129
|
+
} as unknown as STInteger
|
|
130
|
+
}
|
|
131
|
+
// Literal
|
|
132
|
+
type STLiteralValue = string | number | boolean
|
|
133
|
+
export interface STLiteral<T extends STLiteralValue = STLiteralValue> extends STSchema, Options {
|
|
134
|
+
[Kind]: 'literal'
|
|
135
|
+
static: T
|
|
136
|
+
value: T
|
|
137
|
+
}
|
|
138
|
+
export function _Literal<T extends STLiteralValue>(value: T, options: Options = {}): STLiteral<T> {
|
|
139
|
+
return {
|
|
140
|
+
...options,
|
|
141
|
+
[Kind]: 'literal',
|
|
142
|
+
static: value,
|
|
143
|
+
value
|
|
144
|
+
} as unknown as STLiteral<T>
|
|
145
|
+
}
|
|
146
|
+
// Any
|
|
147
|
+
export interface STAny extends STSchema, Options {
|
|
148
|
+
[Kind]: 'any'
|
|
149
|
+
static: any
|
|
150
|
+
[key: string]: any
|
|
151
|
+
}
|
|
152
|
+
export function _Any(options: Options = {}): STAny {
|
|
153
|
+
return {
|
|
154
|
+
...options,
|
|
155
|
+
[Kind]: 'any'
|
|
156
|
+
} as unknown as STAny
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Object
|
|
160
|
+
export interface STObject<T extends STProps = STProps> extends STSchema {
|
|
161
|
+
[Kind]: 'object'
|
|
162
|
+
static: ObjectStatic<T, this['params']>
|
|
163
|
+
props: T
|
|
164
|
+
}
|
|
165
|
+
type ObjectStatic<T extends STProps, P extends unknown[]> = ObjectStaticProps<T, { [K in keyof T]: Static<T[K], P> }>
|
|
166
|
+
type OptionalPropertyKeys<T extends STProps> = {
|
|
167
|
+
[K in keyof T]: T[K] extends STOptional<STSchema> ? K : never
|
|
168
|
+
}[keyof T]
|
|
169
|
+
type RequiredPropertyKeys<T extends STProps> = keyof Omit<T, OptionalPropertyKeys<T>>
|
|
170
|
+
type ObjectStaticProps<T extends STProps, R extends Record<keyof any, unknown>> = Evaluate<
|
|
171
|
+
Partial<Pick<R, OptionalPropertyKeys<T>>> & Required<Pick<R, RequiredPropertyKeys<T>>>
|
|
172
|
+
>
|
|
173
|
+
function _Object<T extends STProps>(properties?: T, options: Options = {}): STObject<T> {
|
|
174
|
+
if (!properties) return { ...options, [Kind]: 'object' } as unknown as STObject<T>
|
|
175
|
+
const propertyKeys = globalThis.Object.getOwnPropertyNames(properties)
|
|
176
|
+
const optionalKeys = propertyKeys.filter(key => properties[key]?.[Optional])
|
|
177
|
+
const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
|
|
178
|
+
const clonedProperties = propertyKeys.reduce((acc, key) => ({ ...acc, [key]: { ...properties[key] } }), {} as STProps)
|
|
179
|
+
return (requiredKeys.length > 0
|
|
180
|
+
? { ...options, [Kind]: 'object', props: clonedProperties, required: requiredKeys }
|
|
181
|
+
: { ...options, [Kind]: 'object', props: clonedProperties }) as unknown as STObject<T>
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// UrlForm
|
|
185
|
+
export type STUrlFormValues =
|
|
186
|
+
| STByteArray
|
|
187
|
+
| STString
|
|
188
|
+
| STBoolean
|
|
189
|
+
| STNumber
|
|
190
|
+
| STInteger
|
|
191
|
+
| STString
|
|
192
|
+
| STLiteral
|
|
193
|
+
| STUnion
|
|
194
|
+
| STAny
|
|
195
|
+
| STArray
|
|
196
|
+
export type STUrlFormProps = Record<string, STUrlFormValues>
|
|
197
|
+
export interface STUrlForm<T extends STUrlFormProps = STUrlFormProps> extends STSchema {
|
|
198
|
+
[Kind]: 'urlForm'
|
|
199
|
+
static: T extends undefined ? Record<string, any> : ObjectStatic<T, this['params']>
|
|
200
|
+
props: T
|
|
201
|
+
}
|
|
202
|
+
function _UrlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
|
|
203
|
+
if (!properties) return { ...options, [Kind]: 'urlForm' } as unknown as STUrlForm<T>
|
|
204
|
+
const propertyKeys = globalThis.Object.getOwnPropertyNames(properties)
|
|
205
|
+
const optionalKeys = propertyKeys.filter(key => properties[key]?.[Optional])
|
|
206
|
+
const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
|
|
207
|
+
const clonedProperties = propertyKeys.reduce(
|
|
208
|
+
(acc, key) => ({ ...acc, [key]: { ...properties[key] } }),
|
|
209
|
+
{} as STUrlFormProps
|
|
210
|
+
)
|
|
211
|
+
return (requiredKeys.length > 0
|
|
212
|
+
? { ...options, [Kind]: 'urlForm', props: clonedProperties, required: requiredKeys }
|
|
213
|
+
: { ...options, [Kind]: 'urlForm', props: clonedProperties }) as unknown as STUrlForm<T>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// MultipartForm
|
|
217
|
+
export type STMultipartFormValues = STSchema
|
|
218
|
+
export interface MultipartFormData<K extends string = string, V extends Static<STMultipartForm> = any> {
|
|
219
|
+
headers: { type?: string; name: K; filename?: string }
|
|
220
|
+
content: V
|
|
221
|
+
}
|
|
222
|
+
export interface STMultipartForm<T extends STProps = STProps> extends STSchema {
|
|
223
|
+
[Kind]: 'multipartForm'
|
|
224
|
+
static: T extends undefined ? Record<string, any> : ObjectStatic<T, this['params']>
|
|
225
|
+
props: T
|
|
226
|
+
}
|
|
227
|
+
function _MultipartForm<T extends STProps>(properties?: T, options: Options = {}): STMultipartForm<T> {
|
|
228
|
+
if (!properties) return { ...options, [Kind]: 'multipartForm' } as unknown as STMultipartForm<T>
|
|
229
|
+
const propertyKeys = globalThis.Object.getOwnPropertyNames(properties)
|
|
230
|
+
const optionalKeys = propertyKeys.filter(key => properties[key]?.[Optional])
|
|
231
|
+
const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
|
|
232
|
+
const clonedProperties = propertyKeys.reduce((acc, key) => ({ ...acc, [key]: { ...properties[key] } }), {} as STProps)
|
|
233
|
+
return (requiredKeys.length > 0
|
|
234
|
+
? { ...options, [Kind]: 'multipartForm', props: clonedProperties, required: requiredKeys }
|
|
235
|
+
: { ...options, [Kind]: 'multipartForm', props: clonedProperties }) as unknown as STMultipartForm<T>
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Array
|
|
239
|
+
export interface STArray<T extends STSchema = STSchema> extends STSchema {
|
|
240
|
+
[Kind]: 'array'
|
|
241
|
+
static: Static<T>[]
|
|
242
|
+
items: T
|
|
243
|
+
}
|
|
244
|
+
export function _Array<T extends STSchema>(schema?: T, options: ArrayOptions = {}): STArray<T | STAny> {
|
|
245
|
+
return {
|
|
246
|
+
...options,
|
|
247
|
+
[Kind]: 'array',
|
|
248
|
+
items: schema ?? _Any()
|
|
249
|
+
} as unknown as T extends undefined ? STArray<STAny> : STArray<T>
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Union
|
|
253
|
+
type UnionStatic<T extends STSchema[], P extends unknown[]> = {
|
|
254
|
+
[K in keyof T]: T[K] extends STSchema ? Static<T[K], P> : never
|
|
255
|
+
}[number]
|
|
256
|
+
export interface STUnion<T extends STSchema[] = STSchema[]> extends STSchema {
|
|
257
|
+
[Kind]: 'union'
|
|
258
|
+
static: UnionStatic<T, this['params']>
|
|
259
|
+
anyOf: T
|
|
260
|
+
}
|
|
261
|
+
export function _Union<T extends STSchema[]>(schemas: [...T], options: Options): STUnion<T> {
|
|
262
|
+
const s = {
|
|
263
|
+
...options,
|
|
264
|
+
[Kind]: 'union',
|
|
265
|
+
anyOf: schemas.map(s => ({ ...s, ...options })) as T,
|
|
266
|
+
optional: () => ({ ...s, [Optional]: true })
|
|
267
|
+
}
|
|
268
|
+
return s as unknown as STUnion<T>
|
|
269
|
+
}
|
|
270
|
+
// Stream
|
|
271
|
+
type STStreamable = STByteArray | STString | STUrlForm | STMultipartForm
|
|
272
|
+
export function _Stream<T extends STStreamable>(schema: T): STStream<T> {
|
|
273
|
+
return {
|
|
274
|
+
...schema,
|
|
275
|
+
[Stream]: true
|
|
276
|
+
} as unknown as STStream<T>
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export class SchemaType {
|
|
280
|
+
/** Creates an Optional Schema Type Wrapper*/
|
|
281
|
+
public optional<T extends STSchema>(schema: T): STOptional<T> {
|
|
282
|
+
return { ...schema, [Optional]: true }
|
|
283
|
+
}
|
|
284
|
+
/** Creates a ByteArray Schema Type */
|
|
285
|
+
public byteArray(options: ByteArrayOptions = {}): STByteArray {
|
|
286
|
+
return _ByteArray(options)
|
|
287
|
+
}
|
|
288
|
+
/** Creates a Boolean Schema Type */
|
|
289
|
+
public boolean(options: Options = {}): STBoolean {
|
|
290
|
+
return _Bool(options)
|
|
291
|
+
}
|
|
292
|
+
/** Creates a String Schema Type */
|
|
293
|
+
public string(options: StringOptions = {}): STString {
|
|
294
|
+
return _String(options)
|
|
295
|
+
}
|
|
296
|
+
/** Creates a Number Schema Type */
|
|
297
|
+
public number(options: NumberOptions = {}): STNumber {
|
|
298
|
+
return _Number(options)
|
|
299
|
+
}
|
|
300
|
+
/** Creates an Integer Schema Type */
|
|
301
|
+
public integer(options: NumberOptions = {}): STInteger {
|
|
302
|
+
return _Integer(options)
|
|
303
|
+
}
|
|
304
|
+
/** Creates a Literal Schema Type */
|
|
305
|
+
public literal<T extends STLiteralValue>(value: T, options: Options = {}): STLiteral<T> {
|
|
306
|
+
return _Literal(value, options)
|
|
307
|
+
}
|
|
308
|
+
/** Creates an Any Schema Type */
|
|
309
|
+
public any(options: Options = {}): STAny {
|
|
310
|
+
return _Any(options)
|
|
311
|
+
}
|
|
312
|
+
/** Creates an Object Schema Type */
|
|
313
|
+
public object<T extends STProps>(properties?: T, options: Options = {}): STObject<T> {
|
|
314
|
+
return _Object(properties, options)
|
|
315
|
+
}
|
|
316
|
+
/** Alias for Object Schema Type */
|
|
317
|
+
public json<T extends STProps>(properties?: T, options: Options = {}): STObject<T> {
|
|
318
|
+
return _Object(properties, options)
|
|
319
|
+
}
|
|
320
|
+
/** Creates an UrlForm Schema Type */
|
|
321
|
+
public urlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
|
|
322
|
+
return _UrlForm(properties, options)
|
|
323
|
+
}
|
|
324
|
+
/** Creates an MultipartForm Schema Type */
|
|
325
|
+
public multipartForm<T extends STProps>(properties?: T, options: Options = {}): STMultipartForm<T> {
|
|
326
|
+
return _MultipartForm(properties, options)
|
|
327
|
+
}
|
|
328
|
+
/** Crates an Array Schema Type */
|
|
329
|
+
public array<T extends STSchema>(schema?: T, options: Options = {}): STArray<T | STAny> {
|
|
330
|
+
return _Array(schema, options)
|
|
331
|
+
}
|
|
332
|
+
/** Crates an Union Schema Type */
|
|
333
|
+
public union<T extends STSchema[]>(schemas: [...T], options: Options = {}): STUnion<T> {
|
|
334
|
+
return _Union(schemas, options)
|
|
335
|
+
}
|
|
336
|
+
/** Crates an Stream Schema Type */
|
|
337
|
+
public stream<T extends STUrlForm>(
|
|
338
|
+
schema: T
|
|
339
|
+
): Omit<STStream<T>, 'static'> & {
|
|
340
|
+
static: AsyncGenerator<
|
|
341
|
+
T['props'] extends undefined
|
|
342
|
+
? { [k: string]: Static<STUrlFormValues> }
|
|
343
|
+
: { [K in keyof T['props']]: [K, Static<T['props'][K]>] }[keyof T['props']]
|
|
344
|
+
>
|
|
345
|
+
params: unknown[]
|
|
346
|
+
}
|
|
347
|
+
public stream<T extends STMultipartForm>(
|
|
348
|
+
schema: T
|
|
349
|
+
): Omit<STStream<T>, 'static'> & {
|
|
350
|
+
static: AsyncGenerator<
|
|
351
|
+
T['props'] extends undefined
|
|
352
|
+
? {
|
|
353
|
+
[k: string]: {
|
|
354
|
+
headers: { type?: string; name: string; filename?: string }
|
|
355
|
+
content: Static<STMultipartFormValues>
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
: {
|
|
359
|
+
[K in keyof T['props']]: {
|
|
360
|
+
headers: { type?: string; name: K; filename?: string }
|
|
361
|
+
content: Static<T['props'][K]>
|
|
362
|
+
}
|
|
363
|
+
}[keyof T['props']],
|
|
364
|
+
void,
|
|
365
|
+
unknown
|
|
366
|
+
>
|
|
367
|
+
params: unknown[]
|
|
368
|
+
}
|
|
369
|
+
public stream<T extends STByteArray>(
|
|
370
|
+
schema: T
|
|
371
|
+
): Omit<STStream<T>, 'static'> & { static: AsyncGenerator<Uint8Array>; params: unknown[] }
|
|
372
|
+
public stream<T extends STString>(
|
|
373
|
+
schema: T
|
|
374
|
+
): Omit<STStream<T>, 'static'> & { static: AsyncGenerator<string>; params: unknown[] }
|
|
375
|
+
public stream<T extends STStreamable>(schema: T): STStream<T> {
|
|
376
|
+
return _Stream(schema)
|
|
377
|
+
}
|
|
378
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -80,7 +80,7 @@ export default async (galbe: Galbe, port?: number) => {
|
|
|
80
80
|
let inParams = requestPathParser(url.pathname, route.path)
|
|
81
81
|
|
|
82
82
|
context.body = await requestBodyParser(req.body, inHeaders, schema.body)
|
|
83
|
-
context.headers =
|
|
83
|
+
context.headers = inHeaders
|
|
84
84
|
context.query = inQuery
|
|
85
85
|
context.params = inParams
|
|
86
86
|
|