galbe 0.1.6 → 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/src/types.ts CHANGED
@@ -1,117 +1,54 @@
1
+ import type { ServeOptions, TLSServeOptions } from 'bun'
1
2
  import type {
2
- TSchema,
3
- TBoolean,
4
- TNumber,
5
- TInteger,
6
- TString,
7
- TLiteral,
8
- TArray,
9
- TObject,
10
- TProperties,
11
- TUnion,
12
- Static,
13
- OptionalPropertyKeys,
14
- ReadonlyOptionalPropertyKeys,
15
- ReadonlyPropertyKeys,
16
- RequiredPropertyKeys,
17
- TAny
18
- } from '@sinclair/typebox'
3
+ STArray,
4
+ STBoolean,
5
+ STByteArray,
6
+ STInteger,
7
+ STLiteral,
8
+ STMultipartForm,
9
+ STNumber,
10
+ STObject,
11
+ STStream,
12
+ STString,
13
+ STUnion,
14
+ STUrlForm,
15
+ Static
16
+ } from './schema'
17
+ import type { Galbe } from './index'
19
18
 
20
- import { Kind } from '@sinclair/typebox'
21
- import { Galbe } from './index'
22
-
23
- export const Stream = Symbol.for('Galbe.Stream')
24
- export type TStream<T extends TSchema = TSchema> = T & {
25
- [Stream]: 'Stream'
26
- }
27
-
28
- export type TBody =
29
- | TByteArray
30
- | TString
31
- | TBoolean
32
- | TNumber
33
- | TInteger
34
- | TObject
35
- | TArray
36
- | TUrlForm
37
- | TMultipartForm
38
- | TUnion
39
- | TStream
40
- export type TStreamable = TByteArray | TString | TUrlForm | TMultipartForm
41
- export type TUrlFormParam = TString | TByteArray | TBoolean | TNumber | TInteger | TLiteral | TArray | TAny | TUnion
42
- export type TMultipartFormParam = TByteArray | TUrlFormParam | TObject | TArray
19
+ export type STBody =
20
+ | STByteArray
21
+ | STString
22
+ | STBoolean
23
+ | STNumber
24
+ | STInteger
25
+ | STObject
26
+ | STArray
27
+ | STUrlForm
28
+ | STMultipartForm
29
+ | STUnion
30
+ | STStream
43
31
  export type MaybeArray<T> = T | T[]
44
32
 
45
- export interface MultipartFormData<
46
- K extends string | number | symbol = string,
47
- V extends Static<TMultipartFormParam> = any
48
- > {
49
- headers: { type?: string; name: K; filename?: string }
50
- content: V
51
- }
52
-
53
- export interface TByteArray extends TSchema {
54
- [Kind]: 'ByteArray'
55
- static: Uint8Array
56
- type: 'byteArray'
57
- }
58
- export interface TMultipartForm<T extends TMultipartProperties = TMultipartProperties> extends TSchema {
59
- [Kind]: 'MultipartForm'
60
- static: MultipartPropertiesReduce<T, this['params']>
61
- type: 'multipartForm'
62
- }
63
- export interface TUrlForm<T extends TUrlFormProperties = TUrlFormProperties> extends TSchema {
64
- [Kind]: 'UrlForm'
65
- static: UrlFormPropertiesReduce<T, this['params']>
66
- type: 'urlForm'
67
- }
33
+ export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'
34
+ type MaybePromise<T> = T | Promise<T>
68
35
 
69
- export type TMultipartProperties<V = TMultipartFormParam> = Record<string, V>
70
- export type MultipartPropertiesReduce<T extends TMultipartProperties, P extends unknown[]> = MultipartPropertiesReducer<
71
- T,
72
- {
73
- [K in keyof T]: Static<T[K], P>
74
- }
75
- >
76
- export type MultipartPropertiesReducer<
77
- T extends TMultipartProperties,
78
- R extends Record<keyof any, unknown>
79
- > = MultipartEvaluate<
80
- Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
81
- Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
82
- Partial<Pick<R, OptionalPropertyKeys<T>>> &
83
- Required<Pick<R, RequiredPropertyKeys<T>>>
84
- >
85
- export type MultipartEvaluate<T> = T extends infer O
86
- ? {
87
- [K in keyof O]: O[K] extends Static<TMultipartFormParam> ? MultipartFormData<K, O[K]> : never
88
- }
36
+ export type ExtractParams<T extends string> = T extends `/:${infer P}/${infer Rest}`
37
+ ? P | ExtractParams<Rest>
38
+ : T extends `${infer _}:${infer P}/${infer Rest}`
39
+ ? P | ExtractParams<Rest>
40
+ : T extends `${infer _}:${infer P}`
41
+ ? P
89
42
  : never
90
43
 
91
- export type TUrlFormProperties<V = TUrlFormParam> = Record<string, V>
92
- export type UrlFormPropertiesReduce<T extends TUrlFormProperties, P extends unknown[]> = UrlFormPropertiesReducer<
93
- T,
94
- {
95
- [K in keyof T]: Static<T[K], P>
96
- }
97
- >
98
- export type UrlFormPropertiesReducer<
99
- T extends TUrlFormProperties,
100
- R extends Record<keyof any, unknown>
101
- > = UrlFormEvaluate<
102
- Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
103
- Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
104
- Partial<Pick<R, OptionalPropertyKeys<T>>> &
105
- Required<Pick<R, RequiredPropertyKeys<T>>>
106
- >
107
- export type UrlFormEvaluate<T> = T extends infer O
108
- ? {
109
- [K in keyof O]: O[K] extends Static<TUrlFormParam> ? O[K] : never
110
- }
111
- : never
44
+ type STHeadersValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
45
+ export type STHeaders = Record<string, STHeadersValue>
112
46
 
113
- export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'
114
- type MaybePromise<T> = T | Promise<T>
47
+ type STParamsValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
48
+ export type STParams<Path extends string = string> = Record<ExtractParams<Path>, STParamsValue>
49
+
50
+ type STQueryValue = STString | STBoolean | STNumber | STInteger | STLiteral | STUnion
51
+ export type STQuery = Record<string, STQueryValue>
115
52
 
116
53
  /**
117
54
  * #### GalbeConfig
@@ -133,7 +70,8 @@ type MaybePromise<T> = T | Promise<T>
133
70
  export type GalbeConfig = {
134
71
  port?: number
135
72
  basePath?: string
136
- routes?: string | string[]
73
+ server?: Exclude<ServeOptions, 'port'> | TLSServeOptions
74
+ routes?: boolean | string | string[]
137
75
  plugin?: Record<string, any>
138
76
  }
139
77
  /**
@@ -143,33 +81,46 @@ export type GalbeConfig = {
143
81
  * ---
144
82
  * @example
145
83
  * ```typescript
146
- * import { T, Schema } from 'galbe'
84
+ * import { $T } from 'galbe'
147
85
  * const MyRequestSchema = {
148
86
  * params: {
149
- * id: T.Number(),
87
+ * id: $T.number(),
150
88
  * },
151
- * body: T.Object({
152
- * name: T.String()
153
- * age: T.Optional(T.Number({minimum: 0})),
89
+ * body: $T.object({
90
+ * name: $T.string()
91
+ * age: $T.optional($T.number({min: 0})),
154
92
  * })
155
93
  * }
156
94
  * ```
157
95
  */
158
- export type Schema<
159
- H extends TProperties = TProperties,
160
- P extends TProperties = TProperties,
161
- Q extends TProperties = TProperties,
162
- B extends TBody = TBody
96
+ export type RequestSchema<
97
+ Path extends string = string,
98
+ H extends STHeaders = {},
99
+ P extends Partial<STParams<Path>> = {},
100
+ Q extends STQuery = {},
101
+ B extends STBody = STBody
163
102
  > = {
164
103
  headers?: H
165
104
  params?: P
166
105
  query?: Q
167
106
  body?: B
168
107
  }
169
- export type Context<S extends Schema = any> = {
170
- headers: Static<TObject<Exclude<S['headers'], undefined>>>
171
- params: Static<TObject<Exclude<S['params'], undefined>>>
172
- query: Static<TObject<Exclude<S['query'], undefined>>>
108
+
109
+ type OmitNotDefined<S extends RequestSchema> = {
110
+ [K in keyof Exclude<S['params'], undefined> as Exclude<S['params'], undefined>[K] extends Required<
111
+ Exclude<S['params'], undefined>
112
+ >[K]
113
+ ? K
114
+ : //@ts-ignore
115
+ never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
116
+ }
117
+
118
+ export type Context<Path extends string = string, S extends RequestSchema = RequestSchema> = {
119
+ headers: Static<STObject<Exclude<S['headers'], undefined>>>
120
+ params: {
121
+ [K in ExtractParams<Path>]: K extends keyof OmitNotDefined<S> ? OmitNotDefined<S>[K] : string
122
+ }
123
+ query: Static<STObject<Exclude<S['query'], undefined>>>
173
124
  body: Static<Exclude<S['body'], undefined>>
174
125
  request: Request
175
126
  state: Record<string, any>
@@ -182,28 +133,57 @@ export type Context<S extends Schema = any> = {
182
133
  }
183
134
  }
184
135
  export type Next = () => void | Promise<void>
185
- export type Hook<S extends Schema = Schema> = (ctx: Context<S>, next: Next) => any | Promise<any>
186
- export type Handler<S extends Schema = Schema> = (ctx: Context<S>) => any
136
+ export type Hook<Path extends string = string, S extends RequestSchema = RequestSchema> = (
137
+ ctx: Context<Path, S>,
138
+ next: Next
139
+ ) => any | Promise<any>
140
+ export type Handler<Path extends string = string, S extends RequestSchema = RequestSchema> = (
141
+ ctx: Context<Path, S>
142
+ ) => any
187
143
  export type Endpoint = {
188
- <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody = TObject>(
189
- path: string,
190
- schema: Schema<H, P, Q, B>,
191
- hooks: Hook<Schema<H, P, Q, B>>[],
192
- handler: Handler<Schema<H, P, Q, B>>
144
+ <
145
+ Path extends string,
146
+ H extends STHeaders,
147
+ P extends Partial<STParams<Path>>,
148
+ Q extends STQuery,
149
+ B extends STBody = STObject
150
+ >(
151
+ path: Path,
152
+ schema: RequestSchema<Path, H, P, Q, B>,
153
+ hooks: Hook<Path, RequestSchema<Path, H, P, Q, B>>[],
154
+ handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
193
155
  ): void
194
- <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody = TObject>(
195
- path: string,
196
- schema: Schema<H, P, Q, B>,
197
- handler: Handler<Schema<H, P, Q, B>>
156
+ <
157
+ Path extends string,
158
+ H extends STHeaders,
159
+ P extends Partial<STParams<Path>>,
160
+ Q extends STQuery,
161
+ B extends STBody = STObject
162
+ >(
163
+ path: Path,
164
+ schema: RequestSchema<Path, H, P, Q, B>,
165
+ handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
198
166
  ): void
199
- <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody = TObject>(
200
- path: string,
201
- hooks: Hook<Schema<H, P, Q, B>>[],
202
- handler: Handler<Schema<H, P, Q, B>>
167
+ <
168
+ Path extends string,
169
+ H extends STHeaders,
170
+ P extends Partial<STParams<Path>>,
171
+ Q extends STQuery,
172
+ B extends STBody = STObject
173
+ >(
174
+ path: Path,
175
+ hooks: Hook<Path, RequestSchema<Path, H, P, Q, B>>[],
176
+ handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
203
177
  ): void
204
- <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody = TObject>(
205
- path: string,
206
- handler: Handler<Schema<H, P, Q, B>>
178
+ <
179
+ Path extends string,
180
+ H extends STHeaders,
181
+ P extends Partial<STParams<Path>>,
182
+ Q extends STQuery,
183
+ B extends STBody = STObject
184
+ >(
185
+ path: Path,
186
+ handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
207
187
  ): void
208
188
  }
209
189
 
@@ -225,17 +205,18 @@ export type RouteNode = {
225
205
  }
226
206
 
227
207
  export type Route<
228
- H extends TProperties = TProperties,
229
- P extends TProperties = TProperties,
230
- Q extends TProperties = TProperties,
231
- B extends TBody = TBody
208
+ Path extends string = string,
209
+ H extends STHeaders = {},
210
+ P extends Partial<STParams<Path>> = {},
211
+ Q extends STQuery = {},
212
+ B extends STBody = STBody
232
213
  > = {
233
214
  method: Method
234
- path: string
235
- schema: Schema<H, P, Q, B>
236
- context: Context<Schema<H, P, Q, B>>
215
+ path: Path
216
+ schema: RequestSchema<Path, H, P, Q, B>
217
+ context: Context<Path, RequestSchema<Path, H, P, Q, B>>
237
218
  hooks: Hook[]
238
- handler: Handler<Schema<H, P, Q, B>>
219
+ handler: Handler<Path, RequestSchema<Path, H, P, Q, B>>
239
220
  }
240
221
 
241
222
  export type RouteTree = {
package/src/validator.ts CHANGED
@@ -1,29 +1,28 @@
1
- import type { TProperties, TSchema } from '@sinclair/typebox'
1
+ import type { STSchema, STProps, STUnion } from './schema'
2
+ import { Kind, Optional } from './schema'
2
3
 
3
- import { Kind, Optional } from '@sinclair/typebox'
4
-
5
- export const validate = (elt: any, schema: TSchema, parse = false): any => {
4
+ export const validate = (elt: any, schema: STSchema, parse = false): any => {
6
5
  type ValidationError = string | string[] | { [key: string]: ValidationError }
7
6
  const errors: ValidationError[] = []
8
7
  const iElt = elt
9
8
 
10
- if (schema[Kind] === 'Boolean') {
9
+ if (schema[Kind] === 'boolean') {
11
10
  if (parse && typeof elt === 'string') elt = elt === 'true' ? true : elt === 'false' ? false : null
12
11
  if (elt !== true && elt !== false) throw `${iElt} is not a valid boolean. Should be 'true' or 'false'`
13
- } else if (schema[Kind] === 'Integer') {
12
+ } else if (schema[Kind] === 'integer') {
14
13
  if (parse && typeof elt === 'string') elt = Number(elt)
15
14
  if (!Number.isInteger(elt)) throw `${iElt} is not a valid integer`
16
15
  schemaValidation(elt, schema)
17
- } else if (schema[Kind] === 'Number') {
16
+ } else if (schema[Kind] === 'number') {
18
17
  if (parse && typeof elt === 'string') elt = Number(elt)
19
18
  if (!Number.isFinite(elt)) throw `${iElt} is not a valid number`
20
19
  schemaValidation(elt, schema)
21
- } else if (schema[Kind] === 'String') {
20
+ } else if (schema[Kind] === 'string') {
22
21
  if (!(typeof elt === 'string')) throw `${iElt} is not a valid string`
23
22
  schemaValidation(elt, schema)
24
- } else if (schema[Kind] === 'Literal') {
25
- if (elt !== schema.const) throw `${iElt} is not a valid value`
26
- } else if (schema[Kind] === 'Object') {
23
+ } else if (schema[Kind] === 'literal') {
24
+ if (elt !== schema.value) throw `${iElt} is not a valid value`
25
+ } else if (schema[Kind] === 'object') {
27
26
  if (parse && typeof elt === 'string') {
28
27
  try {
29
28
  elt = JSON.parse(elt)
@@ -34,9 +33,9 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
34
33
  if (typeof elt !== 'object') throw `${iElt} is not a valid object`
35
34
  if (Array.isArray(elt)) throw `Expected an object, not an array`
36
35
  const err: ValidationError = {}
37
- Object.entries(schema.properties as TProperties).forEach(([k, s]) => {
36
+ Object.entries(schema.props as STProps).forEach(([k, s]) => {
38
37
  if (!(k in elt)) {
39
- if (!s[Optional]) err[k] = 'Required'
38
+ if (!s?.[Optional]) err[k] = 'Required'
40
39
  return
41
40
  }
42
41
  try {
@@ -46,7 +45,7 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
46
45
  }
47
46
  })
48
47
  if (Object.keys(err).length) errors.push(err)
49
- } else if (schema[Kind] === 'Array') {
48
+ } else if (schema[Kind] === 'array') {
50
49
  if (parse && typeof elt === 'string') {
51
50
  try {
52
51
  elt = JSON.parse(elt)
@@ -56,16 +55,16 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
56
55
  }
57
56
  if (!Array.isArray(elt)) throw 'Not a valid array'
58
57
  schemaValidation(elt, schema)
59
- } else if (schema[Kind] === 'ByteArray') {
58
+ } else if (schema[Kind] === 'byteArray') {
60
59
  if (parse && typeof elt === 'string') elt = Uint8Array.from(elt, c => c.charCodeAt(0))
61
60
  else if (parse && Array.isArray(elt)) elt = new Uint8Array(elt)
62
- if (!(elt instanceof Uint8Array)) throw 'Not a valid ByteArray'
63
- } else if (schema[Kind] === 'Union') {
64
- const union = Object.values(schema.anyOf)
61
+ if (!(elt instanceof Uint8Array)) throw 'Not a valid byteArray'
62
+ } else if (schema[Kind] === 'union') {
63
+ const union = Object.values((schema as STUnion).anyOf)
65
64
  let valid = false
66
65
  for (const u of union) {
67
66
  try {
68
- elt = validate(elt, u as TSchema, parse)
67
+ elt = validate(elt, u as STSchema, parse)
69
68
  valid = true
70
69
  break
71
70
  } catch (err) {
@@ -73,8 +72,8 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
73
72
  }
74
73
  }
75
74
  // @ts-ignore
76
- if (!valid) throw `${elt} could not be parsed to any of: ${union.map(u => u?.const ?? u[Kind]).join(', ')}`
77
- } else if (schema[Kind] === 'Any') {
75
+ if (!valid) throw `${elt} could not be parsed to any of: ${union.map(u => u?.value ?? u[Kind]).join(', ')}`
76
+ } else if (schema[Kind] === 'any') {
78
77
  } else {
79
78
  throw `Unsupported schema type ${schema[Kind]}`
80
79
  }
@@ -85,32 +84,31 @@ export const validate = (elt: any, schema: TSchema, parse = false): any => {
85
84
  return elt
86
85
  }
87
86
 
88
- const schemaValidation = (value: any, schema: TSchema) => {
87
+ const schemaValidation = (value: any, schema: STSchema) => {
89
88
  const errors = []
90
- if (schema[Kind] === 'Integer' || schema[Kind] === 'Number') {
91
- if (schema.exclusiveMinimum !== undefined)
92
- if ((value as number) <= schema.exclusiveMinimum)
93
- errors.push(`${value} is less or equal to ${schema.exclusiveMinimum}`)
94
- if (schema.exclusiveMaximum !== undefined)
95
- if ((value as number) >= schema.exclusiveMaximum)
96
- errors.push(`${value} is greater or equal to ${schema.exclusiveMaximum}`)
89
+ if (schema[Kind] === 'integer' || schema[Kind] === 'number') {
90
+ if (schema.exclusiveMin !== undefined)
91
+ if ((value as number) <= schema.exclusiveMin) errors.push(`${value} is less or equal to ${schema.exclusiveMin}`)
92
+ if (schema.exclusiveMax !== undefined)
93
+ if ((value as number) >= schema.exclusiveMax)
94
+ errors.push(`${value} is greater or equal to ${schema.exclusiveMax}`)
97
95
  if (schema.minimum !== undefined)
98
- if ((value as number) < schema.minimum) errors.push(`${value} is less than ${schema.minimum}`)
96
+ if ((value as number) < schema.min) errors.push(`${value} is less than ${schema.min}`)
99
97
  if (schema.maximum !== undefined)
100
- if ((value as number) > schema.maximum) errors.push(`${value} is greater than ${schema.maximum}`)
101
- } else if (schema[Kind] === 'String') {
98
+ if ((value as number) > schema.max) errors.push(`${value} is greater than ${schema.max}`)
99
+ } else if (schema[Kind] === 'string') {
102
100
  if (schema.minLength !== undefined && (value as string).length < schema.minLength)
103
101
  errors.push(`${value} length is too small (${schema.minLength} char min)`)
104
102
  if (schema.maxLength !== undefined && (value as string).length > schema.maxLength)
105
103
  errors.push(`${value} length is too large (${schema.maxLength} char max)`)
106
- if (schema.pattern !== undefined && !(value as string).match(new RegExp(schema.pattern)))
104
+ if (schema.pattern !== undefined && !(value as string).match(schema.pattern))
107
105
  errors.push(`${value} does not match pattern ${schema.pattern}`)
108
- } else if (schema[Kind] === 'Array') {
106
+ } else if (schema[Kind] === 'array') {
109
107
  if (schema.minItems !== undefined && (value as any[]).length < schema.minItems)
110
108
  errors.push(`Must contain at least ${schema.minItems} item${schema.minItems > 1 ? 's' : ''}`)
111
109
  if (schema.maxItems !== undefined && (value as any[]).length > schema.maxItems)
112
110
  errors.push(`Must contain at most (${schema.maxItems} item${schema.maxItems > 1 ? 's' : ''}`)
113
- if (schema.uniqueItems === true && new Set(value as any[]).size !== (value as any[]).length)
111
+ if (schema.unique === true && new Set(value as any[]).size !== (value as any[]).length)
114
112
  errors.push(`Has duplicate values`)
115
113
  }
116
114
  if (errors.length) throw Array.isArray(errors) && errors.length === 1 ? errors[0] : errors