galbe 0.11.0 → 0.12.0

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/src/schema.ts CHANGED
@@ -45,10 +45,12 @@ export interface STSchema extends Options {
45
45
  | 'multipartForm'
46
46
  | 'any'
47
47
  | 'union'
48
+ | 'intersection'
48
49
  [Optional]?: boolean
49
50
  [Stream]?: boolean
50
51
  params: unknown[]
51
52
  static: unknown
53
+ props?: STProps
52
54
  [key: string]: any
53
55
  }
54
56
  export type STPropsValue =
@@ -67,6 +69,10 @@ export type STProps = Record<string | number, STPropsValue>
67
69
 
68
70
  type Evaluate<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
69
71
 
72
+ type Entries<T extends object> = {
73
+ [K in keyof T]-?: [K, T[K]]
74
+ }[keyof T]
75
+
70
76
  /**
71
77
  * Infer the static TypeScript type from a {@link https://galbe.dev/documentation/schemas#schema-types Schema Type}
72
78
  * @example
@@ -76,7 +82,9 @@ type Evaluate<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
76
82
  * // ^? type T = { foo: string }
77
83
  * ```
78
84
  */
79
- export type Static<T extends STSchema, P extends unknown[] = unknown[]> = (T & { params: P })['static']
85
+ export type Static<T extends STSchema, P extends unknown[] = unknown[]> = T[typeof Optional] extends true
86
+ ? (T & { params: P })['static'] | undefined
87
+ : (T & { params: P })['static']
80
88
 
81
89
  // Utils
82
90
  export type STOptional<T extends STSchema> = T & {
@@ -94,7 +102,7 @@ export interface STNull extends STSchema, Options {
94
102
  export function _Null(options: Options = {}): STNull {
95
103
  return {
96
104
  ...options,
97
- [Kind]: 'null'
105
+ [Kind]: 'null',
98
106
  } as unknown as STNull
99
107
  }
100
108
  // ByteArray
@@ -105,7 +113,7 @@ export interface STByteArray extends STSchema, ByteArrayOptions {
105
113
  export function _ByteArray(options: ByteArrayOptions = {}): STByteArray {
106
114
  return {
107
115
  ...options,
108
- [Kind]: 'byteArray'
116
+ [Kind]: 'byteArray',
109
117
  } as unknown as STByteArray
110
118
  }
111
119
  // Boolean
@@ -116,7 +124,7 @@ export interface STBoolean extends STSchema, Options {
116
124
  export function _Bool(options: Options = {}): STBoolean {
117
125
  return {
118
126
  ...options,
119
- [Kind]: 'boolean'
127
+ [Kind]: 'boolean',
120
128
  } as unknown as STBoolean
121
129
  }
122
130
  // String
@@ -127,7 +135,7 @@ export interface STString extends STSchema, NumberOptions {
127
135
  export function _String(options: StringOptions = {}): STString {
128
136
  return {
129
137
  ...options,
130
- [Kind]: 'string'
138
+ [Kind]: 'string',
131
139
  } as unknown as STString
132
140
  }
133
141
  // Number
@@ -138,7 +146,7 @@ export interface STNumber extends STSchema, NumberOptions {
138
146
  export function _Number(options: NumberOptions = {}): STNumber {
139
147
  return {
140
148
  ...options,
141
- [Kind]: 'number'
149
+ [Kind]: 'number',
142
150
  } as unknown as STNumber
143
151
  }
144
152
  // Integer
@@ -149,7 +157,7 @@ export interface STInteger extends STSchema, NumberOptions {
149
157
  export function _Integer(options: NumberOptions = {}): STInteger {
150
158
  return {
151
159
  ...options,
152
- [Kind]: 'integer'
160
+ [Kind]: 'integer',
153
161
  } as unknown as STInteger
154
162
  }
155
163
  // Literal
@@ -164,7 +172,7 @@ export function _Literal<T extends STLiteralValue>(value: T, options: Options =
164
172
  ...options,
165
173
  [Kind]: 'literal',
166
174
  static: value,
167
- value
175
+ value,
168
176
  } as unknown as STLiteral<T>
169
177
  }
170
178
  // Any
@@ -176,7 +184,7 @@ export interface STAny extends STSchema, Options {
176
184
  export function _Any(options: Options = {}): STAny {
177
185
  return {
178
186
  ...options,
179
- [Kind]: 'any'
187
+ [Kind]: 'any',
180
188
  } as unknown as STAny
181
189
  }
182
190
 
@@ -218,36 +226,36 @@ function _Json<T extends STBoolean | STNumber | STString | STObject>(value?: T,
218
226
  }
219
227
 
220
228
  // UrlForm
221
- export type STUrlFormValues =
222
- | STByteArray
223
- | STString
224
- | STBoolean
225
- | STNumber
226
- | STInteger
227
- | STString
228
- | STLiteral
229
- | STUnion
230
- | STAny
231
- | STArray
232
- export type STUrlFormProps = Record<string, STUrlFormValues>
233
- export interface STUrlForm<T extends STUrlFormProps = STUrlFormProps> extends STSchema {
234
- [Kind]: 'urlForm'
235
- static: T extends undefined ? Record<string, any> : ObjectStatic<T, this['params']>
236
- props: T
237
- }
238
- function _UrlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
239
- if (!properties) return { ...options, [Kind]: 'urlForm' } as unknown as STUrlForm<T>
240
- const propertyKeys = globalThis.Object.getOwnPropertyNames(properties)
241
- const optionalKeys = propertyKeys.filter(key => properties[key]?.[Optional])
242
- const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
243
- const clonedProperties = propertyKeys.reduce(
244
- (acc, key) => ({ ...acc, [key]: { ...properties[key] } }),
245
- {} as STUrlFormProps
246
- )
247
- return (requiredKeys.length > 0
248
- ? { ...options, [Kind]: 'urlForm', props: clonedProperties, required: requiredKeys }
249
- : { ...options, [Kind]: 'urlForm', props: clonedProperties }) as unknown as STUrlForm<T>
250
- }
229
+ // export type STUrlFormValues =
230
+ // | STByteArray
231
+ // | STBoolean
232
+ // | STNumber
233
+ // | STInteger
234
+ // | STString
235
+ // | STLiteral
236
+ // | STObject
237
+ // | STUnion
238
+ // | STAny
239
+ // | STArray
240
+ // export type STUrlFormProps = Record<string, STUrlFormValues>
241
+ // export interface STUrlForm<T extends STUrlFormProps = STUrlFormProps> extends STSchema {
242
+ // [Kind]: 'urlForm'
243
+ // static: T extends undefined ? Record<string, any> : ObjectStatic<T, this['params']>
244
+ // props: T
245
+ // }
246
+ // function _UrlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
247
+ // if (!properties) return { ...options, [Kind]: 'urlForm' } as unknown as STUrlForm<T>
248
+ // const propertyKeys = globalThis.Object.getOwnPropertyNames(properties)
249
+ // const optionalKeys = propertyKeys.filter(key => properties[key]?.[Optional])
250
+ // const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
251
+ // const clonedProperties = propertyKeys.reduce(
252
+ // (acc, key) => ({ ...acc, [key]: { ...properties[key] } }),
253
+ // {} as STUrlFormProps
254
+ // )
255
+ // return (requiredKeys.length > 0
256
+ // ? { ...options, [Kind]: 'urlForm', props: clonedProperties, required: requiredKeys }
257
+ // : { ...options, [Kind]: 'urlForm', props: clonedProperties }) as unknown as STUrlForm<T>
258
+ // }
251
259
 
252
260
  // MultipartForm
253
261
  export type STMultipartFormValues = STSchema
@@ -284,6 +292,7 @@ function _MultipartForm<T extends STProps>(properties?: T, options: Options = {}
284
292
  }
285
293
 
286
294
  // Array
295
+ type NonEmptyArray<T> = [T, ...T[]]
287
296
  export interface STArray<T extends STSchema = STSchema> extends STSchema {
288
297
  [Kind]: 'array'
289
298
  static: Static<T>[]
@@ -293,7 +302,7 @@ export function _Array<T extends STSchema>(schema?: T, options: ArrayOptions = {
293
302
  return {
294
303
  ...options,
295
304
  [Kind]: 'array',
296
- items: schema ?? _Any()
305
+ items: schema ?? _Any(),
297
306
  } as unknown as STArray<T>
298
307
  }
299
308
 
@@ -301,45 +310,86 @@ export function _Array<T extends STSchema>(schema?: T, options: ArrayOptions = {
301
310
  type UnionStatic<T extends STSchema[], P extends unknown[]> = {
302
311
  [K in keyof T]: T[K] extends STSchema ? Static<T[K], P> : never
303
312
  }[number]
304
- export interface STUnion<T extends STSchema[] = STSchema[]> extends STSchema {
313
+ export interface STUnion<T extends NonEmptyArray<STSchema> = NonEmptyArray<STSchema>> extends STSchema {
305
314
  [Kind]: 'union'
306
315
  static: UnionStatic<T, this['params']>
316
+ props: T[number]['props']
307
317
  anyOf: T
308
318
  }
309
- export function _Union<T extends STSchema[]>(schemas: [...T], options: Options): STUnion<T> {
319
+ export function _Union<T extends NonEmptyArray<STSchema>>(schemas: [...T], options: Options): STUnion<T> {
310
320
  const s = {
311
321
  ...options,
312
322
  [Kind]: 'union',
313
- anyOf: schemas.map(s => ({ ...s, ...options })) as T,
314
- optional: () => ({ ...s, [Optional]: true })
323
+ anyOf: schemas as T,
324
+ optional: () => ({ ...s, [Optional]: true }),
315
325
  }
316
326
  return s as unknown as STUnion<T>
317
327
  }
328
+
329
+ // Intersection
330
+ type IntersectionStatic<T extends readonly STSchema[], P extends unknown[]> = T extends readonly [infer H, ...infer R]
331
+ ? H extends STSchema
332
+ ? R extends readonly STSchema[]
333
+ ? Static<H, P> & IntersectionStatic<R, P>
334
+ : never
335
+ : never
336
+ : unknown
337
+ export interface STIntersection<T extends NonEmptyArray<STObject | STUnion | STIntersection> = NonEmptyArray<STObject>>
338
+ extends STSchema {
339
+ [Kind]: 'intersection'
340
+ static: IntersectionStatic<T, this['params']>
341
+ props: T[number]['props']
342
+ allOf: T
343
+ }
344
+
345
+ export function _Intersection<T extends NonEmptyArray<STObject | STUnion | STIntersection>>(
346
+ schemas: [...T],
347
+ options: Options
348
+ ): STIntersection<T> {
349
+ const s = {
350
+ ...options,
351
+ [Kind]: 'intersection',
352
+ allOf: schemas as T,
353
+ props: schemas.reduce(
354
+ (b, c) => ({
355
+ ...b,
356
+ ...(c.props || {}),
357
+ ...Object.fromEntries(
358
+ Object.entries(b).filter(([_, v]) => (v as STSchema)[Kind] === 'literal' || !(v as STSchema)[Optional])
359
+ // TODO: handle cases where left != right
360
+ ),
361
+ }),
362
+ {}
363
+ ) as STProps,
364
+ optional: () => ({ ...s, [Optional]: true }),
365
+ }
366
+ return s as unknown as STIntersection<T>
367
+ }
368
+
318
369
  // Stream
319
- type STStreamable = STByteArray | STString | STUrlForm | STMultipartForm
370
+ type STStreamable = STByteArray | STString | STMultipartForm | STObject | STUnion | STIntersection
320
371
  export function _Stream<T extends STStreamable>(schema: T): STStream<T> {
321
372
  return {
322
373
  ...schema,
323
- [Stream]: true
374
+ [Stream]: true,
324
375
  } as unknown as STStream<T>
325
376
  }
326
377
  // Nullable
327
378
  type STNullable<T extends STSchema> = STUnion<[T, STNull]>
328
379
  // Nullish
329
- type STNullish<T extends STSchema> = STOptional<STNullable<T>>
330
-
380
+ type STNullish<T extends STSchema> = (T | STNull) & { [Kind]: 'union'; anyOf: [T, STNull]; [Optional]: true }
331
381
  export class SchemaType {
332
- /** Creates an Optional Schema Type Wrapper*/
382
+ /** Creates an Optional Schema Type Wrapper */
333
383
  public optional<T extends STSchema>(schema: T): STOptional<T> {
334
384
  return { ...schema, [Optional]: true }
335
385
  }
336
- /** Creates an Nullable Schema Type Wrapper*/
386
+ /** Creates an Nullable Schema Type Wrapper */
337
387
  public nullable<T extends STSchema>(schema: T): STNullable<T> {
338
388
  return { ..._Union([schema, _Null()], {}) }
339
389
  }
340
- /** Creates an Nullish Schema Type Wrapper*/
341
- public nullish<T extends STSchema>(schema: T): STNullish<T> {
342
- return { ..._Union([schema, _Null()], {}), [Optional]: true }
390
+ /** Creates an Nullish Schema Type Wrapper */
391
+ public nullish<T extends STSchema>(schema: T): STOptional<STNullable<T>> {
392
+ return this.optional(this.nullable(schema)) as STOptional<STNullable<T>>
343
393
  }
344
394
  /** Creates a Null Schema Type */
345
395
  public null(options: Options = {}): STNull {
@@ -385,29 +435,64 @@ export class SchemaType {
385
435
  return _Json(value, options)
386
436
  }
387
437
  /** Creates an UrlForm Schema Type */
388
- public urlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
389
- return _UrlForm(properties, options)
390
- }
391
- /** Creates an MultipartForm Schema Type */
438
+ // public urlForm<T extends STUrlFormProps>(properties?: T, options: Options = {}): STUrlForm<T> {
439
+ // return _UrlForm(properties, options)
440
+ // }
441
+ /** Creates a MultipartForm Schema Type */
392
442
  public multipartForm<T extends STProps>(properties?: T, options: Options = {}): STMultipartForm<T> {
393
443
  return _MultipartForm(properties, options)
394
444
  }
395
- /** Crates an Array Schema Type */
445
+ /** Creates an Array Schema Type */
396
446
  public array<T extends STSchema>(schema?: T, options: Options = {}): STArray<T> {
397
447
  return _Array(schema, options)
398
448
  }
399
- /** Crates an Union Schema Type */
400
- public union<T extends STSchema[]>(schemas: [...T], options: Options = {}): STUnion<T> {
449
+ /** Creates an Union Schema Type */
450
+ public union<T extends NonEmptyArray<STSchema>>(schemas: [...T], options: Options = {}): STUnion<T> {
401
451
  return _Union(schemas, options)
402
452
  }
403
- /** Crates an Stream Schema Type */
404
- public stream<T extends STUrlForm>(
453
+ /** Creates an Intersection Schema Type */
454
+ public intersection<T extends NonEmptyArray<STObject | STUnion | STIntersection>>(
455
+ schemas: [...T],
456
+ options: Options = {}
457
+ ): STIntersection<T> {
458
+ return _Intersection(schemas, options)
459
+ }
460
+ /** Creates a Stream Schema Type */
461
+ public stream<T extends STObject>(
462
+ schema: T
463
+ ): Omit<STStream<T>, 'static'> & {
464
+ static: AsyncGenerator<
465
+ T['props'] extends undefined
466
+ ? { [k: string]: Static<STPropsValue> }
467
+ : T['props'] extends STProps
468
+ ? { [K in keyof T['props']]: [K, Static<T['props'][K]>] }[keyof T['props']]
469
+ : never
470
+ >
471
+ params: unknown[]
472
+ }
473
+ public stream<T extends STUnion>(
474
+ schema: T
475
+ ): Omit<STStream<T>, 'static'> & {
476
+ static: AsyncGenerator<
477
+ Entries<{
478
+ [P in KeysOfUnion<T['props']> as ValueAt<T['props'], P> extends STSchema ? P : never]: Static<
479
+ ValueAt<T['props'], P>
480
+ >
481
+ }>
482
+ >
483
+ params: unknown[]
484
+ }
485
+ public stream<T extends STIntersection>(
405
486
  schema: T
406
487
  ): Omit<STStream<T>, 'static'> & {
407
488
  static: AsyncGenerator<
408
489
  T['props'] extends undefined
409
- ? { [k: string]: Static<STUrlFormValues> }
410
- : { [K in keyof T['props']]: [K, Static<T['props'][K]>] }[keyof T['props']]
490
+ ? never
491
+ : T['props'] extends STProps
492
+ ? Entries<{
493
+ [K in keyof Static<T>]: Static<T>[K]
494
+ }>
495
+ : never
411
496
  >
412
497
  params: unknown[]
413
498
  }
@@ -443,6 +528,8 @@ export class SchemaType {
443
528
  return _Stream(schema)
444
529
  }
445
530
  }
531
+ type KeysOfUnion<U> = U extends unknown ? keyof U : never
532
+ type ValueAt<U, K extends PropertyKey> = U extends unknown ? (K extends keyof U ? U[K] : never) : never
446
533
 
447
534
  export const schemaToTypeStr = (schema: STSchema): string => {
448
535
  let type = 'unknown'
@@ -464,16 +551,19 @@ export const schemaToTypeStr = (schema: STSchema): string => {
464
551
  } else if (kind === 'object') {
465
552
  let props = (schema as STObject).props
466
553
  type = `{${Object.entries(props)
467
- .map(([k, v]) => `${typeof k === 'string' ? `'${k}'` : k}:${schemaToTypeStr(v)}`)
554
+ .map(([k, v]) => `${typeof k === 'string' ? `'${k}'` : k}${v?.[Optional] ? '?' : ''}:${schemaToTypeStr(v)}`)
468
555
  .join(';')}}`
469
556
  } else if (kind === 'json') {
470
557
  type = `Json<${schemaToTypeStr({ ...schema, [Kind]: schema.type })}>`
471
558
  } else if (kind === 'union') {
472
559
  let anyOf = (schema as STUnion).anyOf
473
560
  type = anyOf.map(s => schemaToTypeStr(s)).join('|')
561
+ } else if (kind === 'intersection') {
562
+ let allOf = (schema as STIntersection).allOf
563
+ type = allOf.map(s => schemaToTypeStr(s)).join('&')
474
564
  }
475
565
 
476
- if (schema[Optional]) type = `${type}|undefined`
566
+ // if (schema[Optional]) type = `${type}|undefined`
477
567
 
478
568
  return type
479
569
  }
package/src/server.ts CHANGED
@@ -4,6 +4,7 @@ import { InternalError, RequestError } from './types'
4
4
  import { parseEntry, requestBodyParser, requestPathParser, responseParser } from './parser'
5
5
  import { Galbe } from './index'
6
6
  import { validateResponse } from './validator'
7
+ import { inferBodyType } from './util'
7
8
 
8
9
  type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
9
10
 
@@ -19,7 +20,7 @@ const setupPluginCallbacks = (galbe: Galbe) => ({
19
20
  onFetch: galbe.plugins.filter(p => p.onFetch),
20
21
  onRoute: galbe.plugins.filter(p => p.onRoute),
21
22
  beforeHandle: galbe.plugins.filter(p => p.beforeHandle),
22
- afterHandle: galbe.plugins.filter(p => p.afterHandle)
23
+ afterHandle: galbe.plugins.filter(p => p.afterHandle),
23
24
  })
24
25
 
25
26
  export default async (galbe: Galbe, port?: number, hostname?: string) => {
@@ -38,9 +39,12 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
38
39
  if (!METHODS.includes(req.method)) return new Response('', { status: 501 })
39
40
  const context = {
40
41
  request: req,
42
+ contentType: !EMPTY_BODY_METHODS.includes(req.method)
43
+ ? inferBodyType(req.headers.get('content-type'))
44
+ : undefined,
41
45
  remoteAddress: server.requestIP(req),
42
46
  set: { headers: { 'set-cookie': [] } },
43
- state: {}
47
+ state: {},
44
48
  } as MakeOptional<Context, 'headers' | 'params' | 'query' | 'body'>
45
49
  for (const p of pluginsCb.onFetch) {
46
50
  //@ts-ignore
@@ -80,9 +84,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
80
84
  }
81
85
  let inParams = requestPathParser(url.pathname, route.path)
82
86
 
83
- context.body = !EMPTY_BODY_METHODS.includes(req.method)
84
- ? await requestBodyParser(req.body, inHeaders, schema.body)
85
- : null
87
+ context.body = await requestBodyParser(req.body, inHeaders, schema.body, context.contentType)
86
88
  context.headers = inHeaders
87
89
  context.query = inQuery
88
90
  context.params = inParams
@@ -94,7 +96,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
94
96
  if (schema?.headers)
95
97
  context.headers = {
96
98
  ...context.headers,
97
- ...parseEntry(context.headers, schema.headers, { name: 'headers', i: true })
99
+ ...parseEntry(context.headers, schema.headers, { name: 'headers', i: true }),
98
100
  }
99
101
  } catch (error) {
100
102
  if (error instanceof RequestError) errors.push(error)
@@ -142,13 +144,13 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
142
144
  let r = await hook(context as Context, next)
143
145
  if (r) return r
144
146
  if (!nextCalled && !handlerCalled) return await next()
145
- }
147
+ },
146
148
  }))
147
149
  callChain.push({
148
150
  call: async () => {
149
151
  response = await handlerWrapper(context as Context)
150
152
  context.set.status = response instanceof Response ? response.status : context.set.status || 200
151
- }
153
+ },
152
154
  })
153
155
  if (callChain.length > 1) {
154
156
  let r = await callChain[0].call()
@@ -176,7 +178,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
176
178
  console.log(`Internal Error`, error?.payload || '')
177
179
  return new Response('Internal Server Error', {
178
180
  status: error.status,
179
- headers: { 'Content-Type': 'application/json' }
181
+ headers: { 'content-type': 'application/json' },
180
182
  })
181
183
  } else if (error instanceof RequestError) {
182
184
  let payload = error.payload
@@ -187,19 +189,19 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
187
189
  headers.set('content-type', 'application/json')
188
190
  try {
189
191
  payload = JSON.stringify(error.payload)
190
- } catch (err) { }
192
+ } catch (err) {}
191
193
  }
192
194
  }
193
195
  return new Response(payload, {
194
196
  status: error.status,
195
- headers
197
+ headers,
196
198
  })
197
199
  } else console.log(error)
198
200
  return new Response('"Internal Server Error"', {
199
201
  status: 500,
200
202
  headers: {
201
- 'content-type': 'application/json'
202
- }
203
+ 'content-type': 'application/json',
204
+ },
203
205
  })
204
206
  }
205
207
  },
@@ -208,10 +210,10 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
208
210
  return new Response('"Internal Server Error"', {
209
211
  status: 500,
210
212
  headers: {
211
- 'content-type': 'application/json'
212
- }
213
+ 'content-type': 'application/json',
214
+ },
213
215
  })
214
- }
216
+ },
215
217
  })
216
218
  return server
217
219
  }
package/src/types.ts CHANGED
@@ -16,28 +16,11 @@ import type {
16
16
  STStream,
17
17
  STString,
18
18
  STUnion,
19
- STUrlForm,
20
- Static
19
+ Static,
21
20
  } from './schema'
22
21
  import type { Galbe } from './index'
23
22
  import { HttpStatus } from './util'
24
23
 
25
- export type STBody =
26
- | STByteArray
27
- | STString
28
- | STBoolean
29
- | STNumber
30
- | STInteger
31
- | STLiteral
32
- | STObject
33
- | STArray
34
- | STUrlForm
35
- | STMultipartForm
36
- | STUnion
37
- | STStream
38
- | STAny
39
- | undefined
40
-
41
24
  export type STResponseValue =
42
25
  | STByteArray
43
26
  | STString
@@ -52,6 +35,19 @@ export type STResponseValue =
52
35
  | STStream
53
36
  | STAny
54
37
  | STNull
38
+ export type STBody =
39
+ | STNull
40
+ | Partial<{
41
+ byteArray?: STByteArray | STStream
42
+ text?: STString | STLiteral | STBoolean | STNumber | STInteger | STUnion | STStream
43
+ json?: STJson | STObject | STBoolean | STInteger | STNumber | STString | STArray | STUnion
44
+ urlForm?: STObject | STStream | STUnion
45
+ multipart?: STMultipartForm | STStream | STUnion
46
+ default?: STString | STByteArray | STStream | STAny
47
+ }>
48
+ export type STBodyType = keyof STBody
49
+ export type STBodyValue = STBody[STBodyType]
50
+
55
51
  export type STResponse = Partial<Record<number | 'default', STResponseValue>>
56
52
 
57
53
  export type MaybeArray<T> = T | T[]
@@ -159,9 +155,9 @@ type OmitNotDefined<S extends RequestSchema> = {
159
155
  [K in keyof Exclude<S['params'], undefined> as Exclude<S['params'], undefined>[K] extends Required<
160
156
  Exclude<S['params'], undefined>
161
157
  >[K]
162
- ? K
163
- : //@ts-ignore
164
- never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
158
+ ? K
159
+ : //@ts-ignore
160
+ never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
165
161
  }
166
162
  type StaticBody<T extends STSchema> = T extends STOptional<STSchema> ? Static<T> | null : Static<T>
167
163
  export type Context<
@@ -169,24 +165,35 @@ export type Context<
169
165
  Path extends string = string,
170
166
  S extends RequestSchema = RequestSchema
171
167
  > = {
172
- headers: Static<STObject<Exclude<S['headers'], undefined>>>
173
- params: {
174
- [K in ExtractParams<Path>]: K extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[K] : string
175
- }
176
- query: Static<STObject<Exclude<S['query'], undefined>>>
177
- body: M extends 'get' | 'options' | 'head' ? null : StaticBody<Exclude<S['body'], undefined>>
178
- request: Request
179
- remoteAddress: SocketAddress | null
180
- route?: Route
181
- state: Record<string, any>
182
- set: {
183
- headers: {
184
- 'set-cookie': string[]
185
- [header: string]: string | string[]
186
- }
187
- status?: number
188
- }
189
- }
168
+ [K in STBodyType]: K extends keyof Exclude<S['body'], undefined>
169
+ ? {
170
+ headers: Static<STObject<Exclude<S['headers'], undefined>>>
171
+ params: {
172
+ [P in ExtractParams<Path>]: P extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[P] : string
173
+ }
174
+ query: Static<STObject<Exclude<S['query'], undefined>>>
175
+ contentType: M extends 'get' | 'options' | 'head' ? undefined : K
176
+ body: M extends 'get' | 'options' | 'head'
177
+ ? null
178
+ : Exclude<S['body'], undefined> extends STNull
179
+ ? null
180
+ : K extends STBodyType
181
+ ? StaticBody<Exclude<Exclude<S['body'], undefined>[K], undefined>>
182
+ : never
183
+ request: Request
184
+ remoteAddress: SocketAddress | null
185
+ route?: Route
186
+ state: Record<string, any>
187
+ set: {
188
+ headers: {
189
+ 'set-cookie': string[]
190
+ [header: string]: string | string[]
191
+ }
192
+ status?: number
193
+ }
194
+ }
195
+ : never
196
+ }[STBodyType]
190
197
  export type Next = () => void | Promise<any>
191
198
  export type Hook<M extends Method = Method, Path extends string = string, S extends RequestSchema = RequestSchema> = (
192
199
  ctx: Context<M, Path, S>,
@@ -251,13 +258,17 @@ export type Endpoint<M extends Method> = {
251
258
  export type StaticEndpointOptions = {
252
259
  resolve?: (path: string, target: string) => string | null | undefined | void
253
260
  }
254
- export type StaticEndpoint<P extends string = string, T extends string = string> = (path: P, target: T, options?: StaticEndpointOptions) => Route<"get", P, {}, {}, {}, STBody, STResponse, T>
261
+ export type StaticEndpoint<P extends string = string, T extends string = string> = (
262
+ path: P,
263
+ target: T,
264
+ options?: StaticEndpointOptions
265
+ ) => Route<'get', P, {}, {}, {}, STBody, STResponse, T>
255
266
 
256
267
  export class RequestError {
257
268
  status: number
258
269
  payload?: any
259
270
  headers?: Record<string, string>
260
- constructor(options: { status?: number; payload?: any, headers?: Record<string, string> }) {
271
+ constructor(options: { status?: number; payload?: any; headers?: Record<string, string> }) {
261
272
  this.status = options.status ?? 500
262
273
  this.payload = options.payload
263
274
  this.headers = options.headers
@@ -289,7 +300,7 @@ export type Route<
289
300
  context: Context<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
290
301
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[]
291
302
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
292
- static?: { path: SP, root: SR }
303
+ static?: { path: SP; root: SR }
293
304
  }
294
305
 
295
306
  export class NotFoundError extends RequestError {
@@ -349,6 +360,7 @@ export type GalbePlugin = {
349
360
 
350
361
  export type GalbeCLICommand = {
351
362
  name: string
363
+ tags: string[]
352
364
  description?: string
353
365
  route: Route
354
366
  arguments?: { name: string; type: string; description: string }[]