galbe 0.8.0 → 0.9.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/routes.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { GalbeConfig, Method, Route } from './types'
1
+ import { cpSync } from 'fs'
2
+ import type { GalbeConfig, GalbePlugin, Method, Route } from './types'
2
3
 
3
4
  import { readdir, lstat } from 'fs/promises'
4
5
  import { extname } from 'path'
@@ -10,10 +11,15 @@ import { Glob } from 'bun'
10
11
 
11
12
  export const DEFAULT_ROUTE_PATTERN = 'src/**/*.route.{js,ts}'
12
13
 
13
- export type RouteMeta = { head?: string } & Record<string, boolean | string | string[]>
14
+ const IGNORE_COMMENT_RGX = /^\s*\@galbe-ignore\s*$/
15
+ const HIDE_COMMENT_RGX = /^\s*\@galbe-hide\s*$/
16
+
17
+ export type RouteMeta = { head?: string, ignore?: boolean, hide?: boolean } & Record<string, boolean | string | string[]>
14
18
  export type RoutesMeta = {
15
19
  header: Record<string, boolean | string | string[]>
16
- routes: Record<string, Partial<Record<Method, RouteMeta>>>
20
+ routes: Record<string, Partial<Record<Method | 'static', RouteMeta>>>
21
+ ignore?: boolean
22
+ hide?: boolean
17
23
  }
18
24
  export type RouteInstanciationCallback = <T extends 'add' | 'error'>(event: {
19
25
  type: T
@@ -26,101 +32,89 @@ export type RouteFileMeta = {
26
32
  file: string
27
33
  } & RoutesMeta
28
34
 
29
- class GalbeProxy {
35
+ export class GalbeProxy {
30
36
  #g: Galbe
31
- #cb?: RouteInstanciationCallback
32
- filepath?: string
33
- meta?: RoutesMeta
37
+ #plugins: GalbePlugin[] = []
38
+ _cb?: RouteInstanciationCallback
39
+ _metaTmp?: RoutesMeta
40
+ _filepath?: string
41
+ _meta: Array<RouteFileMeta> = []
34
42
  constructor(g: Galbe, cb?: RouteInstanciationCallback) {
35
43
  this.#g = g
36
- this.#cb = cb
44
+ this._cb = cb
45
+ this.#plugins = g.plugins
37
46
  }
38
47
  get server() {
39
48
  return this.#g.server
40
49
  }
41
- async get(...args: any[]) {
50
+ get router() {
51
+ return this.#g.router
52
+ }
53
+ get config() {
54
+ return this.#g.config
55
+ }
56
+ get meta() {
57
+ return this._meta
58
+ }
59
+ set meta(meta: Array<RouteFileMeta>) {
60
+ this._meta = meta
61
+ this.#g.meta = meta
62
+ }
63
+ async init() {
64
+ for (const p of this.#plugins) {
65
+ // @ts-ignore
66
+ if (p.init) await p.init(this.#g.config?.plugin?.[p.name] || {}, this)
67
+ }
68
+ }
69
+ private async handleRoute(method: Method | 'static', ...args: any[]): Promise<Route | undefined> {
70
+ let path = args[0]
71
+ let meta = {
72
+ ...this._metaTmp?.routes?.[path]?.[method],
73
+ ...(this._metaTmp?.hide ? { hide: true } : {}),
74
+ ...(this._metaTmp?.ignore ? { ignore: true } : {})
75
+ }
76
+ if (meta?.ignore) return
77
+
42
78
  //@ts-ignore
43
- const route = this.#g.get(...args) as Route
44
- if (this.#cb)
45
- await this.#cb({
79
+ const route = this.#g[method](...args) as Route
80
+
81
+ if (this._cb && !meta?.ignore) {
82
+ await this._cb({
46
83
  type: 'add',
47
84
  route,
48
- filepath: this.filepath || '',
49
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
85
+ filepath: this._filepath || '',
86
+ meta
50
87
  })
88
+ }
51
89
  return route
52
90
  }
91
+ async get(...args: any[]) {
92
+ return this.handleRoute('get', ...args)
93
+ }
53
94
  async post(...args: any[]) {
54
- //@ts-ignore
55
- const route = this.#g.post(...args) as Route
56
- if (this.#cb)
57
- await this.#cb({
58
- type: 'add',
59
- route,
60
- filepath: this.filepath,
61
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
62
- })
63
- return route
95
+ return this.handleRoute('post', ...args)
64
96
  }
65
97
  async put(...args: any[]) {
66
- //@ts-ignore
67
- const route = this.#g.put(...args) as Route
68
- if (this.#cb)
69
- await this.#cb({
70
- type: 'add',
71
- route,
72
- filepath: this.filepath,
73
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
74
- })
75
- return route
98
+ return this.handleRoute('put', ...args)
76
99
  }
77
100
  async patch(...args: any[]) {
78
- //@ts-ignore
79
- const route = this.#g.patch(...args) as Route
80
- if (this.#cb)
81
- await this.#cb({
82
- type: 'add',
83
- route,
84
- filepath: this.filepath,
85
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
86
- })
87
- return route
101
+ return this.handleRoute('patch', ...args)
88
102
  }
89
103
  async delete(...args: any[]) {
90
- //@ts-ignore
91
- const route = this.#g.delete(...args) as Route
92
- if (this.#cb)
93
- await this.#cb({
94
- type: 'add',
95
- route,
96
- filepath: this.filepath,
97
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
98
- })
99
- return route
104
+ return this.handleRoute('delete', ...args)
100
105
  }
101
106
  async options(...args: any[]) {
102
- //@ts-ignore
103
- const route = this.#g.options(...args) as Route
104
- if (this.#cb)
105
- await this.#cb({
106
- type: 'add',
107
- route,
108
- filepath: this.filepath,
109
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
110
- })
111
- return route
107
+ return this.handleRoute('options', ...args)
112
108
  }
113
109
  async head(...args: any[]) {
114
- //@ts-ignore
115
- const route = this.#g.head(...args) as Route
116
- if (this.#cb)
117
- await this.#cb({
118
- type: 'add',
119
- route,
120
- filepath: this.filepath,
121
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
122
- })
123
- return route
110
+ return this.handleRoute('head', ...args)
111
+ }
112
+ async static(...args: any[]) {
113
+ if (!!Bun.env.GALBE_BUILD_OUT) {
114
+ let [_, target] = args
115
+ cpSync(target, `${Bun.env.GALBE_BUILD_OUT}/static-${Bun.env.GALBE_BUILD}/${target}`, { recursive: true, dereference: true })
116
+ }
117
+ return this.handleRoute('static', ...args)
124
118
  }
125
119
  }
126
120
 
@@ -175,15 +169,22 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
175
169
  }
176
170
 
177
171
  const comments: Record<number, Record<number, string>> = {}
178
-
172
+ const ignoredLines = new Set<number>()
173
+ const hideLines = new Set<number>()
179
174
  const ast = parse(content, {
180
175
  ecmaVersion: 'latest',
181
176
  sourceType: 'module',
182
177
  locations: true,
183
- onComment: (isBlock, text, _start, _end, _locStart, locEnd) => {
184
- if (isBlock && locEnd?.line !== undefined && locEnd?.column !== undefined) {
178
+ onComment: (_isBlock, text, _start, _end, _locStart, locEnd) => {
179
+ if (locEnd?.line !== undefined && locEnd?.column !== undefined) {
185
180
  if (!comments?.[locEnd.line]) comments[locEnd.line] = []
186
- comments[locEnd.line][locEnd.column] = text
181
+ if (IGNORE_COMMENT_RGX.test(text)) {
182
+ ignoredLines.add(locEnd.line + 1)
183
+ }
184
+ else if (HIDE_COMMENT_RGX.test(text)) {
185
+ hideLines.add(locEnd.line + 1)
186
+ }
187
+ else comments[locEnd.line][locEnd.column] = text
187
188
  }
188
189
  }
189
190
  })
@@ -192,6 +193,12 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
192
193
  const headerLine = node.loc?.start.line || -1
193
194
  const headerCol = node.loc?.start.column || -1
194
195
  const headerCom = comments?.[headerLine]?.[headerCol - 1] ? comments[headerLine][headerCol - 1] : ''
196
+ const hide = hideLines.has(headerLine)
197
+ if (hide) meta.hide = true
198
+ if (ignoredLines.has(headerLine)) {
199
+ meta.ignore = true
200
+ return meta
201
+ }
195
202
  const headerRef = parseComment(headerCom)
196
203
  meta.header = headerRef
197
204
 
@@ -205,17 +212,16 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
205
212
  // @ts-ignore
206
213
  if (node?.callee?.object?.name === galbeIdentifier) {
207
214
  // @ts-ignore
208
- const path = node.arguments[0].value
215
+ let path = node.arguments[0].value
216
+ if (!path?.startsWith("/")) path = `/${path}`
209
217
  // @ts-ignore
210
218
  const method = node.callee.property.name as Method
211
219
  const line = node.loc?.start.line || -1
212
220
  const col = node.loc?.start.column || -1
213
221
  const com = comments?.[line]?.[col - 1] ? comments[line][col - 1] : ''
214
-
215
- const routeRefs = parseComment(com)
216
-
222
+ const routeRefs = ignoredLines.has(line) ? { ignore: true } : { ...parseComment(com), ...(hide || hideLines.has(line) ? { hide: true } : {}) }
217
223
  if (!(path in meta.routes)) meta.routes[path] = {}
218
- if (!(method in meta.routes[path])) meta.routes[path][method] = routeRefs
224
+ if (!(method in meta.routes[path])) meta.routes[path][method] = routeRefs as RouteMeta
219
225
  }
220
226
  }
221
227
  })
@@ -226,11 +232,9 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
226
232
 
227
233
  export const defineRoutes = async (
228
234
  options: Pick<GalbeConfig, 'routes'>,
229
- galbe: Galbe,
230
- cb?: RouteInstanciationCallback
235
+ proxy: GalbeProxy,
231
236
  ) => {
232
237
  const routes = options?.routes === true ? DEFAULT_ROUTE_PATTERN : options?.routes
233
- const proxy = new GalbeProxy(galbe, cb)
234
238
  if (!routes) return
235
239
  const root = process.cwd()
236
240
  if (typeof routes === 'string') {
@@ -243,22 +247,23 @@ export const defineRoutes = async (
243
247
  for (const f of files) {
244
248
  try {
245
249
  const metadata = await metaAnalysis(f)
246
- proxy.filepath = f
247
- proxy.meta = metadata
248
- galbe.meta?.push({ file: path, ...metadata })
250
+ if (metadata?.ignore) continue
251
+ proxy._filepath = f
252
+ proxy._metaTmp = metadata
253
+ proxy.meta = [...proxy.meta, { file: path, ...metadata }]
249
254
  const imported = await import(f)
250
255
  if (!imported?.default) throw new Error('No default export function')
251
256
  if (typeof imported.default !== 'function') throw new Error('Default export must be a function')
252
257
  const routes = imported.default
253
258
  routes(proxy)
254
259
  } catch (err: any) {
255
- if (cb) await cb({ type: 'error', error: err, filepath: f, route: undefined, meta: undefined })
260
+ if (proxy._cb) await proxy._cb({ type: 'error', error: err, filepath: f, route: undefined, meta: undefined })
256
261
  }
257
262
  }
258
263
  }
259
264
  } else if (Array.isArray(routes)) {
260
265
  for (const r of routes) {
261
- await defineRoutes({ routes: r }, galbe, cb)
266
+ await defineRoutes({ routes: r }, proxy)
262
267
  }
263
268
  }
264
269
  }
package/src/server.ts CHANGED
@@ -38,7 +38,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
38
38
  const context = {
39
39
  request: req,
40
40
  remoteAddress: server.requestIP(req),
41
- set: { headers: {} },
41
+ set: { headers: { 'set-cookie': [] } },
42
42
  state: {}
43
43
  } as MakeOptional<Context, 'headers' | 'params' | 'query' | 'body'>
44
44
  for (const p of pluginsCb.onFetch) {
@@ -176,7 +176,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
176
176
  if (typeof error.payload === 'string') payload = error.payload
177
177
  try {
178
178
  payload = JSON.stringify(error.payload)
179
- } catch (err) {}
179
+ } catch (err) { }
180
180
  return new Response(payload, {
181
181
  status: error.status,
182
182
  headers: { 'Content-Type': 'application/json' }
package/src/types.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  Static
21
21
  } from './schema'
22
22
  import type { Galbe } from './index'
23
+ import { HttpStatus } from './util'
23
24
 
24
25
  export type STBody =
25
26
  | STByteArray
@@ -34,6 +35,7 @@ export type STBody =
34
35
  | STMultipartForm
35
36
  | STUnion
36
37
  | STStream
38
+ | STAny
37
39
  | undefined
38
40
 
39
41
  export type STResponseValue =
@@ -50,7 +52,7 @@ export type STResponseValue =
50
52
  | STStream
51
53
  | STAny
52
54
  | STNull
53
- export type STResponse = Record<number, STResponseValue>
55
+ export type STResponse = Record<number | 'default', STResponseValue>
54
56
 
55
57
  export type MaybeArray<T> = T | T[]
56
58
  export type MaybeSTArray<T extends STSchema> = T | STArray<T>
@@ -134,7 +136,7 @@ export type RequestSchema<
134
136
  P extends Partial<STParams<Path>> = Partial<STParams<Path>>,
135
137
  Q extends STQuery = STQuery,
136
138
  B extends STBody = STBody,
137
- R extends STResponse = STResponse
139
+ R extends Partial<STResponse> = Partial<STResponse>
138
140
  > = {
139
141
  headers?: H
140
142
  params?: P
@@ -147,9 +149,9 @@ type OmitNotDefined<S extends RequestSchema> = {
147
149
  [K in keyof Exclude<S['params'], undefined> as Exclude<S['params'], undefined>[K] extends Required<
148
150
  Exclude<S['params'], undefined>
149
151
  >[K]
150
- ? K
151
- : //@ts-ignore
152
- never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
152
+ ? K
153
+ : //@ts-ignore
154
+ never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
153
155
  }
154
156
  type StaticBody<T extends STSchema> = T extends STOptional<STSchema> ? Static<T> | null : Static<T>
155
157
  export type Context<
@@ -169,7 +171,8 @@ export type Context<
169
171
  state: Record<string, any>
170
172
  set: {
171
173
  headers: {
172
- [header: string]: string
174
+ 'set-cookie': string[]
175
+ [header: string]: string | string[]
173
176
  }
174
177
  status?: number
175
178
  }
@@ -191,49 +194,54 @@ export type Endpoint<M extends Method> = {
191
194
  H extends STHeaders = any,
192
195
  Q extends STQuery = any,
193
196
  B extends STBody = any,
194
- R extends STResponse = STResponse
197
+ R extends Partial<STResponse> = Partial<STResponse>
195
198
  >(
196
199
  path: Path,
197
200
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
198
201
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
199
202
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
200
- ): void
203
+ ): Route<M, Path, P, H, Q, B, R>
201
204
  <
202
205
  Path extends string,
203
206
  P extends Partial<STParams<Path>>,
204
207
  H extends STHeaders = any,
205
208
  Q extends STQuery = any,
206
209
  B extends STBody = any,
207
- R extends STResponse = STResponse
210
+ R extends Partial<STResponse> = Partial<STResponse>
208
211
  >(
209
212
  path: Path,
210
213
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
211
214
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
212
- ): void
215
+ ): Route<M, Path, P, H, Q, B, R>
213
216
  <
214
217
  Path extends string,
215
218
  P extends Partial<STParams<Path>>,
216
219
  H extends STHeaders = any,
217
220
  Q extends STQuery = any,
218
221
  B extends STBody = any,
219
- R extends STResponse = STResponse
222
+ R extends Partial<STResponse> = Partial<STResponse>
220
223
  >(
221
224
  path: Path,
222
225
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
223
226
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
224
- ): void
227
+ ): Route<M, Path, P, H, Q, B, R>
225
228
  <
226
229
  Path extends string,
227
230
  P extends Partial<STParams<Path>>,
228
231
  H extends STHeaders = any,
229
232
  Q extends STQuery = any,
230
233
  B extends STBody = any,
231
- R extends STResponse = STResponse
234
+ R extends Partial<STResponse> = Partial<STResponse>
232
235
  >(
233
236
  path: Path,
234
237
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
235
- ): void
238
+ ): Route<M, Path, P, H, Q, B, R>
239
+ }
240
+
241
+ export type StaticEndpointOptions = {
242
+ resolve?: (path: string, target: string) => string | null | undefined | void
236
243
  }
244
+ export type StaticEndpoint<P extends string = string, T extends string = string> = (path: P, target: T, options?: StaticEndpointOptions) => Route<"get", P, {}, {}, {}, STBody, STResponse, T>
237
245
 
238
246
  export class RequestError {
239
247
  status: number
@@ -259,31 +267,40 @@ export type Route<
259
267
  H extends STHeaders = STHeaders,
260
268
  Q extends STQuery = STQuery,
261
269
  B extends STBody = STBody,
262
- R extends STResponse = STResponse
270
+ R extends Partial<STResponse> = Partial<STResponse>,
271
+ SP extends string = string,
272
+ SR extends string = string
263
273
  > = {
264
274
  method: M
265
275
  path: Path
266
276
  schema: RequestSchema<M, Path, H, P, Q, B, R>
267
277
  context: Context<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
268
- hooks: Hook[]
278
+ hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[]
269
279
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
280
+ static?: { path: SP, root: SR }
270
281
  }
271
282
 
272
283
  export class NotFoundError extends RequestError {
273
284
  constructor(message?: any) {
274
- super({ status: 404, payload: message ?? 'Not found' })
285
+ super({ status: 404, payload: message ?? HttpStatus[404] })
275
286
  }
276
287
  }
277
288
 
278
289
  export class MethodNotAllowedError extends RequestError {
279
290
  constructor(message?: any) {
280
- super({ status: 405, payload: message ?? 'Method not allowed' })
291
+ super({ status: 405, payload: message ?? HttpStatus[405] })
281
292
  }
282
293
  }
283
294
 
284
295
  export class InternalError extends RequestError {
285
296
  constructor(message?: any) {
286
- super({ status: 500, payload: message ?? 'Internal Server Error' })
297
+ super({ status: 500, payload: message ?? HttpStatus[500] })
298
+ }
299
+ }
300
+
301
+ export class NotImplementedError extends RequestError {
302
+ constructor(message?: any) {
303
+ super({ status: 501, payload: message ?? HttpStatus[501] })
287
304
  }
288
305
  }
289
306
 
package/src/util.ts CHANGED
@@ -13,16 +13,30 @@ const METHOD_COLOR: Record<string, string> = {
13
13
  const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
14
14
 
15
15
  export const logRoute = (
16
- r: { method: string; path: string },
16
+ r: { method: string; path: string, static?: { path: string, root: string } },
17
17
  meta?: RouteMeta,
18
18
  format?: { maxPathLength?: number }
19
19
  ) => {
20
- let color = METHOD_COLOR?.[r.method] || ''
21
- let routeLog = `[${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}` : ''}\x1b[0m`
24
- if (meta?.deprecated) routeLog = `\x1b[0;9m\x1b[38;5;244m${routeLog.replaceAll(ansiRegex, '')}\x1b[0m`
25
- console.log(` ${routeLog}`)
20
+ let path = r.path === '' ? '/' : r.path
21
+ let method = r.method
22
+
23
+ let routeLog = ''
24
+
25
+ if (r?.static) {
26
+ routeLog = `[\x1b[0;33m${`STATIC\x1b[0m]`.padEnd(12, ' ')} ${path
27
+ .padEnd(format?.maxPathLength ?? path.length, ' ')} \x1b[0;33m⇒\x1b[0m ${r.static.path}\x1b[0m`
28
+ if (meta?.deprecated) routeLog = `\x1b[0;9m\x1b[38;5;244m${routeLog.replaceAll(ansiRegex, '')}\x1b[0m`
29
+ } else {
30
+ let color = METHOD_COLOR?.[method] || ''
31
+ let [_, summary, _description] = meta?.head?.match(/^([^\n]*)\n\n(.*)/) || []
32
+ if(!summary) _description = meta?.head || ''
33
+ routeLog = `[${color}${`${method.toUpperCase()}\x1b[0m]`.padEnd(12, ' ')} ${path
34
+ .padEnd(format?.maxPathLength ?? path.length, ' ')
35
+ .replaceAll(/:([^\/]+)/g, '\x1b[0;33m:$1\x1b[0m')}${(summary?` ${summary}`:'').replace(/\n/, '')}\x1b[0m`
36
+ if (meta?.deprecated) routeLog = `\x1b[0;9m\x1b[38;5;244m${routeLog.replaceAll(ansiRegex, '')}\x1b[0m`
37
+ }
38
+
39
+ if (routeLog) console.log(` ${routeLog}`)
26
40
  }
27
41
 
28
42
  /**
package/src/validator.ts CHANGED
@@ -15,29 +15,29 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
15
15
  if (parse) elt = elt === 'true' ? true : elt === 'false' ? false : null
16
16
  else throw `Expected boolean, got string.`
17
17
  }
18
- if (elt !== true && elt !== false) throw `${iElt} is not a valid boolean. Should be 'true' or 'false'`
18
+ if (elt !== true && elt !== false) throw `Not a valid boolean. Should be 'true' or 'false'`
19
19
  } else if (schema[Kind] === 'integer') {
20
20
  if (parse && typeof elt === 'string') elt = Number(elt)
21
- if (!Number.isInteger(elt)) throw `${iElt} is not a valid integer`
21
+ if (!Number.isInteger(elt)) throw `Not a valid integer`
22
22
  schemaValidation(elt, schema)
23
23
  } else if (schema[Kind] === 'number') {
24
24
  if (parse && typeof elt === 'string') elt = Number(elt)
25
- if (!Number.isFinite(elt)) throw `${iElt} is not a valid number`
25
+ if (!Number.isFinite(elt)) throw `Not a valid number`
26
26
  schemaValidation(elt, schema)
27
27
  } else if (schema[Kind] === 'string') {
28
- if (!(typeof elt === 'string')) throw `${iElt} is not a valid string`
28
+ if (!(typeof elt === 'string')) throw `Not a valid string`
29
29
  schemaValidation(elt, schema)
30
30
  } else if (schema[Kind] === 'literal') {
31
- if (elt !== schema.value) throw `${iElt} is not a valid value`
31
+ if (elt !== schema.value) throw `Not a valid value`
32
32
  } else if (schema[Kind] === 'object') {
33
33
  if (parse && typeof elt === 'string') {
34
34
  try {
35
35
  elt = JSON.parse(elt)
36
36
  } catch {
37
- throw `${iElt} is not a valid object`
37
+ throw `Not a valid object`
38
38
  }
39
39
  }
40
- if (typeof elt !== 'object') throw `${iElt} is not a valid object`
40
+ if (typeof elt !== 'object') throw `Not a valid object`
41
41
  if (Array.isArray(elt)) throw `Expected an object, not an array`
42
42
  const err: ValidationError = {}
43
43
  Object.entries(schema.props as STProps).forEach(([k, s]) => {
@@ -81,7 +81,7 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
81
81
  }
82
82
  }
83
83
  // @ts-ignore
84
- if (!valid) throw `${elt} could not be parsed to any of: ${union.map(u => u?.value ?? u[Kind]).join(', ')}`
84
+ if (!valid) throw `Could not be parsed to any of [${union.map(u => u?.value ?? u[Kind]).join(', ')}]`
85
85
  } else if (schema[Kind] === 'any') {
86
86
  } else {
87
87
  throw `Unsupported schema type ${schema[Kind]}`
@@ -95,7 +95,7 @@ export const validate = (elt: any, schema: STSchema, parse = false): any => {
95
95
 
96
96
  export const validateResponse = (response: any, schema: STResponse, status: number) => {
97
97
  if (!(status in schema)) return
98
- const s = schema[status]
98
+ const s = schema?.[status] || schema?.['default']
99
99
  if (response instanceof ReadableStream) {
100
100
  if (!s[Stream]) throw new InternalError(`Expected ${s[Kind]} response, but got ReadableStream`)
101
101
  } else if (isIterator(response)) {
@@ -113,20 +113,20 @@ const schemaValidation = (value: any, schema: STSchema) => {
113
113
  const errors = []
114
114
  if (schema[Kind] === 'integer' || schema[Kind] === 'number') {
115
115
  if (schema.exclusiveMin !== undefined)
116
- if ((value as number) <= schema.exclusiveMin) errors.push(`${value} is less or equal to ${schema.exclusiveMin}`)
116
+ if ((value as number) <= schema.exclusiveMin) errors.push(`Is less or equal to ${schema.exclusiveMin}`)
117
117
  if (schema.exclusiveMax !== undefined)
118
118
  if ((value as number) >= schema.exclusiveMax)
119
- errors.push(`${value} is greater or equal to ${schema.exclusiveMax}`)
120
- if (schema.min !== undefined) if ((value as number) < schema.min) errors.push(`${value} is less than ${schema.min}`)
119
+ errors.push(`Is greater or equal to ${schema.exclusiveMax}`)
120
+ if (schema.min !== undefined) if ((value as number) < schema.min) errors.push(`Is less than ${schema.min}`)
121
121
  if (schema.max !== undefined)
122
- if ((value as number) > schema.max) errors.push(`${value} is greater than ${schema.max}`)
122
+ if ((value as number) > schema.max) errors.push(`Is greater than ${schema.max}`)
123
123
  } else if (schema[Kind] === 'string') {
124
124
  if (schema.minLength !== undefined && (value as string).length < schema.minLength)
125
- errors.push(`${value} length is too small (${schema.minLength} char min)`)
125
+ errors.push(`Length is too small (${schema.minLength} char min)`)
126
126
  if (schema.maxLength !== undefined && (value as string).length > schema.maxLength)
127
- errors.push(`${value} length is too large (${schema.maxLength} char max)`)
127
+ errors.push(`Length is too large (${schema.maxLength} char max)`)
128
128
  if (schema.pattern !== undefined && !(value as string).match(schema.pattern))
129
- errors.push(`${value} does not match pattern ${schema.pattern}`)
129
+ errors.push(`Does not match pattern ${schema.pattern}`)
130
130
  } else if (schema[Kind] === 'array') {
131
131
  if (schema.minItems !== undefined && (value as any[]).length < schema.minItems)
132
132
  errors.push(`Must contain at least ${schema.minItems} item${schema.minItems > 1 ? 's' : ''}`)