galbe 0.7.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,98 +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
- async get(...args: any[]) {
47
+ get server() {
48
+ return this.#g.server
49
+ }
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
+
39
78
  //@ts-ignore
40
- const route = this.#g.get(...args) as Route
41
- if (this.#cb)
42
- await this.#cb({
79
+ const route = this.#g[method](...args) as Route
80
+
81
+ if (this._cb && !meta?.ignore) {
82
+ await this._cb({
43
83
  type: 'add',
44
84
  route,
45
- filepath: this.filepath || '',
46
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
85
+ filepath: this._filepath || '',
86
+ meta
47
87
  })
88
+ }
48
89
  return route
49
90
  }
91
+ async get(...args: any[]) {
92
+ return this.handleRoute('get', ...args)
93
+ }
50
94
  async post(...args: any[]) {
51
- //@ts-ignore
52
- const route = this.#g.post(...args) as Route
53
- if (this.#cb)
54
- await this.#cb({
55
- type: 'add',
56
- route,
57
- filepath: this.filepath,
58
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
59
- })
60
- return route
95
+ return this.handleRoute('post', ...args)
61
96
  }
62
97
  async put(...args: any[]) {
63
- //@ts-ignore
64
- const route = this.#g.put(...args) as Route
65
- if (this.#cb)
66
- await this.#cb({
67
- type: 'add',
68
- route,
69
- filepath: this.filepath,
70
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
71
- })
72
- return route
98
+ return this.handleRoute('put', ...args)
73
99
  }
74
100
  async patch(...args: any[]) {
75
- //@ts-ignore
76
- const route = this.#g.patch(...args) as Route
77
- if (this.#cb)
78
- await this.#cb({
79
- type: 'add',
80
- route,
81
- filepath: this.filepath,
82
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
83
- })
84
- return route
101
+ return this.handleRoute('patch', ...args)
85
102
  }
86
103
  async delete(...args: any[]) {
87
- //@ts-ignore
88
- const route = this.#g.delete(...args) as Route
89
- if (this.#cb)
90
- await this.#cb({
91
- type: 'add',
92
- route,
93
- filepath: this.filepath,
94
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
95
- })
96
- return route
104
+ return this.handleRoute('delete', ...args)
97
105
  }
98
106
  async options(...args: any[]) {
99
- //@ts-ignore
100
- const route = this.#g.options(...args) as Route
101
- if (this.#cb)
102
- await this.#cb({
103
- type: 'add',
104
- route,
105
- filepath: this.filepath,
106
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
107
- })
108
- return route
107
+ return this.handleRoute('options', ...args)
109
108
  }
110
109
  async head(...args: any[]) {
111
- //@ts-ignore
112
- const route = this.#g.head(...args) as Route
113
- if (this.#cb)
114
- await this.#cb({
115
- type: 'add',
116
- route,
117
- filepath: this.filepath,
118
- meta: this.meta?.routes?.[route.path]?.[route.method] || {}
119
- })
120
- 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)
121
118
  }
122
119
  }
123
120
 
@@ -172,15 +169,22 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
172
169
  }
173
170
 
174
171
  const comments: Record<number, Record<number, string>> = {}
175
-
172
+ const ignoredLines = new Set<number>()
173
+ const hideLines = new Set<number>()
176
174
  const ast = parse(content, {
177
175
  ecmaVersion: 'latest',
178
176
  sourceType: 'module',
179
177
  locations: true,
180
- onComment: (isBlock, text, _start, _end, _locStart, locEnd) => {
181
- if (isBlock && locEnd?.line !== undefined && locEnd?.column !== undefined) {
178
+ onComment: (_isBlock, text, _start, _end, _locStart, locEnd) => {
179
+ if (locEnd?.line !== undefined && locEnd?.column !== undefined) {
182
180
  if (!comments?.[locEnd.line]) comments[locEnd.line] = []
183
- 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
184
188
  }
185
189
  }
186
190
  })
@@ -189,6 +193,12 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
189
193
  const headerLine = node.loc?.start.line || -1
190
194
  const headerCol = node.loc?.start.column || -1
191
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
+ }
192
202
  const headerRef = parseComment(headerCom)
193
203
  meta.header = headerRef
194
204
 
@@ -202,17 +212,16 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
202
212
  // @ts-ignore
203
213
  if (node?.callee?.object?.name === galbeIdentifier) {
204
214
  // @ts-ignore
205
- const path = node.arguments[0].value
215
+ let path = node.arguments[0].value
216
+ if (!path?.startsWith("/")) path = `/${path}`
206
217
  // @ts-ignore
207
218
  const method = node.callee.property.name as Method
208
219
  const line = node.loc?.start.line || -1
209
220
  const col = node.loc?.start.column || -1
210
221
  const com = comments?.[line]?.[col - 1] ? comments[line][col - 1] : ''
211
-
212
- const routeRefs = parseComment(com)
213
-
222
+ const routeRefs = ignoredLines.has(line) ? { ignore: true } : { ...parseComment(com), ...(hide || hideLines.has(line) ? { hide: true } : {}) }
214
223
  if (!(path in meta.routes)) meta.routes[path] = {}
215
- 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
216
225
  }
217
226
  }
218
227
  })
@@ -223,11 +232,9 @@ export const metaAnalysis = async (filePath: string): Promise<RoutesMeta> => {
223
232
 
224
233
  export const defineRoutes = async (
225
234
  options: Pick<GalbeConfig, 'routes'>,
226
- galbe: Galbe,
227
- cb?: RouteInstanciationCallback
235
+ proxy: GalbeProxy,
228
236
  ) => {
229
237
  const routes = options?.routes === true ? DEFAULT_ROUTE_PATTERN : options?.routes
230
- const proxy = new GalbeProxy(galbe, cb)
231
238
  if (!routes) return
232
239
  const root = process.cwd()
233
240
  if (typeof routes === 'string') {
@@ -240,22 +247,23 @@ export const defineRoutes = async (
240
247
  for (const f of files) {
241
248
  try {
242
249
  const metadata = await metaAnalysis(f)
243
- proxy.filepath = f
244
- proxy.meta = metadata
245
- 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 }]
246
254
  const imported = await import(f)
247
255
  if (!imported?.default) throw new Error('No default export function')
248
256
  if (typeof imported.default !== 'function') throw new Error('Default export must be a function')
249
257
  const routes = imported.default
250
258
  routes(proxy)
251
259
  } catch (err: any) {
252
- 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 })
253
261
  }
254
262
  }
255
263
  }
256
264
  } else if (Array.isArray(routes)) {
257
265
  for (const r of routes) {
258
- await defineRoutes({ routes: r }, galbe, cb)
266
+ await defineRoutes({ routes: r }, proxy)
259
267
  }
260
268
  }
261
269
  }
package/src/server.ts CHANGED
@@ -28,7 +28,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
28
28
  galbe.config.basePath = `/${galbe?.config?.basePath}`
29
29
  let pluginsCb = setupPluginCallbacks(galbe)
30
30
 
31
- return Bun.serve({
31
+ const server = Bun.serve({
32
32
  port: port || galbe.config?.port || 3000,
33
33
  hostname: hostname || galbe.config?.hostname || 'localhost',
34
34
  tls: galbe.config?.tls,
@@ -37,7 +37,8 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
37
37
  if (!METHODS.includes(req.method)) return new Response('', { status: 501 })
38
38
  const context = {
39
39
  request: req,
40
- set: { headers: {} },
40
+ remoteAddress: server.requestIP(req),
41
+ set: { headers: { 'set-cookie': [] } },
41
42
  state: {}
42
43
  } as MakeOptional<Context, 'headers' | 'params' | 'query' | 'body'>
43
44
  for (const p of pluginsCb.onFetch) {
@@ -128,12 +129,12 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
128
129
  if (nextCalled) console.error('Hook already called - ignored')
129
130
  else {
130
131
  nextCalled = true
131
- await callChain[idx + 1].call()
132
+ return await callChain[idx + 1].call()
132
133
  }
133
134
  }
134
135
  let r = await hook(context as Context, next)
135
136
  if (r) return r
136
- if (!nextCalled && !handlerCalled) await next()
137
+ if (!nextCalled && !handlerCalled) return await next()
137
138
  }
138
139
  }))
139
140
  callChain.push({
@@ -175,7 +176,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
175
176
  if (typeof error.payload === 'string') payload = error.payload
176
177
  try {
177
178
  payload = JSON.stringify(error.payload)
178
- } catch (err) {}
179
+ } catch (err) { }
179
180
  return new Response(payload, {
180
181
  status: error.status,
181
182
  headers: { 'Content-Type': 'application/json' }
@@ -199,4 +200,5 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
199
200
  })
200
201
  }
201
202
  })
203
+ return server
202
204
  }
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ServeOptions, TLSOptions, TLSServeOptions } from 'bun'
1
+ import type { ServeOptions, SocketAddress, TLSOptions, TLSServeOptions } from 'bun'
2
2
  import type {
3
3
  STAny,
4
4
  STArray,
@@ -8,6 +8,7 @@ import type {
8
8
  STJson,
9
9
  STLiteral,
10
10
  STMultipartForm,
11
+ STNull,
11
12
  STNumber,
12
13
  STObject,
13
14
  STOptional,
@@ -19,6 +20,7 @@ import type {
19
20
  Static
20
21
  } from './schema'
21
22
  import type { Galbe } from './index'
23
+ import { HttpStatus } from './util'
22
24
 
23
25
  export type STBody =
24
26
  | STByteArray
@@ -33,6 +35,7 @@ export type STBody =
33
35
  | STMultipartForm
34
36
  | STUnion
35
37
  | STStream
38
+ | STAny
36
39
  | undefined
37
40
 
38
41
  export type STResponseValue =
@@ -48,7 +51,8 @@ export type STResponseValue =
48
51
  | STUnion
49
52
  | STStream
50
53
  | STAny
51
- export type STResponse = Record<number, STResponseValue>
54
+ | STNull
55
+ export type STResponse = Record<number | 'default', STResponseValue>
52
56
 
53
57
  export type MaybeArray<T> = T | T[]
54
58
  export type MaybeSTArray<T extends STSchema> = T | STArray<T>
@@ -132,7 +136,7 @@ export type RequestSchema<
132
136
  P extends Partial<STParams<Path>> = Partial<STParams<Path>>,
133
137
  Q extends STQuery = STQuery,
134
138
  B extends STBody = STBody,
135
- R extends STResponse = STResponse
139
+ R extends Partial<STResponse> = Partial<STResponse>
136
140
  > = {
137
141
  headers?: H
138
142
  params?: P
@@ -145,9 +149,9 @@ type OmitNotDefined<S extends RequestSchema> = {
145
149
  [K in keyof Exclude<S['params'], undefined> as Exclude<S['params'], undefined>[K] extends Required<
146
150
  Exclude<S['params'], undefined>
147
151
  >[K]
148
- ? K
149
- : //@ts-ignore
150
- never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
152
+ ? K
153
+ : //@ts-ignore
154
+ never]: Static<STObject<Exclude<S['params'], undefined>>>[K]
151
155
  }
152
156
  type StaticBody<T extends STSchema> = T extends STOptional<STSchema> ? Static<T> | null : Static<T>
153
157
  export type Context<
@@ -162,16 +166,18 @@ export type Context<
162
166
  query: Static<STObject<Exclude<S['query'], undefined>>>
163
167
  body: M extends 'get' | 'options' | 'head' ? null : StaticBody<Exclude<S['body'], undefined>>
164
168
  request: Request
169
+ remoteAddress: SocketAddress | null
165
170
  route?: Route
166
171
  state: Record<string, any>
167
172
  set: {
168
173
  headers: {
169
- [header: string]: string
174
+ 'set-cookie': string[]
175
+ [header: string]: string | string[]
170
176
  }
171
177
  status?: number
172
178
  }
173
179
  }
174
- export type Next = () => void | Promise<void>
180
+ export type Next = () => void | Promise<any>
175
181
  export type Hook<M extends Method = Method, Path extends string = string, S extends RequestSchema = RequestSchema> = (
176
182
  ctx: Context<M, Path, S>,
177
183
  next: Next
@@ -188,49 +194,54 @@ export type Endpoint<M extends Method> = {
188
194
  H extends STHeaders = any,
189
195
  Q extends STQuery = any,
190
196
  B extends STBody = any,
191
- R extends STResponse = STResponse
197
+ R extends Partial<STResponse> = Partial<STResponse>
192
198
  >(
193
199
  path: Path,
194
200
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
195
201
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
196
202
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
197
- ): void
203
+ ): Route<M, Path, P, H, Q, B, R>
198
204
  <
199
205
  Path extends string,
200
206
  P extends Partial<STParams<Path>>,
201
207
  H extends STHeaders = any,
202
208
  Q extends STQuery = any,
203
209
  B extends STBody = any,
204
- R extends STResponse = STResponse
210
+ R extends Partial<STResponse> = Partial<STResponse>
205
211
  >(
206
212
  path: Path,
207
213
  schema: RequestSchema<M, Path, H, P, Q, B, R>,
208
214
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
209
- ): void
215
+ ): Route<M, Path, P, H, Q, B, R>
210
216
  <
211
217
  Path extends string,
212
218
  P extends Partial<STParams<Path>>,
213
219
  H extends STHeaders = any,
214
220
  Q extends STQuery = any,
215
221
  B extends STBody = any,
216
- R extends STResponse = STResponse
222
+ R extends Partial<STResponse> = Partial<STResponse>
217
223
  >(
218
224
  path: Path,
219
225
  hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[],
220
226
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
221
- ): void
227
+ ): Route<M, Path, P, H, Q, B, R>
222
228
  <
223
229
  Path extends string,
224
230
  P extends Partial<STParams<Path>>,
225
231
  H extends STHeaders = any,
226
232
  Q extends STQuery = any,
227
233
  B extends STBody = any,
228
- R extends STResponse = STResponse
234
+ R extends Partial<STResponse> = Partial<STResponse>
229
235
  >(
230
236
  path: Path,
231
237
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
232
- ): 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
233
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>
234
245
 
235
246
  export class RequestError {
236
247
  status: number
@@ -256,31 +267,40 @@ export type Route<
256
267
  H extends STHeaders = STHeaders,
257
268
  Q extends STQuery = STQuery,
258
269
  B extends STBody = STBody,
259
- R extends STResponse = STResponse
270
+ R extends Partial<STResponse> = Partial<STResponse>,
271
+ SP extends string = string,
272
+ SR extends string = string
260
273
  > = {
261
274
  method: M
262
275
  path: Path
263
276
  schema: RequestSchema<M, Path, H, P, Q, B, R>
264
277
  context: Context<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
265
- hooks: Hook[]
278
+ hooks: Hook<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>[]
266
279
  handler: Handler<M, Path, RequestSchema<M, Path, H, P, Q, B, R>>
280
+ static?: { path: SP, root: SR }
267
281
  }
268
282
 
269
283
  export class NotFoundError extends RequestError {
270
284
  constructor(message?: any) {
271
- super({ status: 404, payload: message ?? 'Not found' })
285
+ super({ status: 404, payload: message ?? HttpStatus[404] })
272
286
  }
273
287
  }
274
288
 
275
289
  export class MethodNotAllowedError extends RequestError {
276
290
  constructor(message?: any) {
277
- super({ status: 405, payload: message ?? 'Method not allowed' })
291
+ super({ status: 405, payload: message ?? HttpStatus[405] })
278
292
  }
279
293
  }
280
294
 
281
295
  export class InternalError extends RequestError {
282
296
  constructor(message?: any) {
283
- 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] })
284
304
  }
285
305
  }
286
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' : ''}`)