rajt 0.0.74 → 0.0.76

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rajt",
3
3
  "description": "A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.",
4
- "version": "0.0.74",
4
+ "version": "0.0.76",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "bin": {
@@ -42,18 +42,21 @@
42
42
  "forj": "^0.0.6",
43
43
  "t0n": "^0.1",
44
44
  "@hono/node-server": "^1.19.9",
45
- "@hono/zod-validator": "^0.7.6",
45
+ "@hono/standard-validator": "^0.2.2",
46
46
  "@iarna/toml": "^2.2.5",
47
+ "@scalar/hono-api-reference": "^0.9.40",
47
48
  "chokidar-cli": "^3.0.0",
48
49
  "citty": "^0.1.6",
49
50
  "dotenv": "^16.5.0",
50
51
  "esbuild": "^0.25.2",
51
52
  "hono": "^4.11.7",
53
+ "hono-openapi": "^1.2.0",
52
54
  "miniflare": "^4.20251217.0",
53
55
  "tiny-glob": "^0.2",
54
56
  "tsx": "^4.19.3",
55
57
  "ua-parser-js": "^2.0.8",
56
- "wrangler": "^4.61.0"
58
+ "wrangler": "^4.61.0",
59
+ "zod-openapi": "4"
57
60
  },
58
61
  "devDependencies": {
59
62
  "@cloudflare/workers-types": "^4.20251230.0",
@@ -4,7 +4,7 @@ import { spawn, type ChildProcess } from 'node:child_process'
4
4
  import { defineCommand } from 'citty'
5
5
  import type { Miniflare } from 'miniflare'
6
6
  import { _root, build, wait, watch, normalizePlatform, platformError, getRuntime, createMiniflare, getDockerHost } from './utils'
7
- import { step, error, event, warn } from '../../utils/log'
7
+ import { error, event, log, rn, warn } from '../../utils/log'
8
8
  import { withPort } from '../../utils/port'
9
9
  import shutdown from '../../utils/shutdown'
10
10
 
@@ -39,49 +39,83 @@ export default defineCommand({
39
39
 
40
40
  const desiredPort = args.port ? Number(args.port) : 3000
41
41
  const host = args.host ? String(args.host) : 'localhost'
42
+
43
+ let isBuilding = false
44
+ const startApp = async (start: Function, stop: Function|undefined = undefined, building: boolean = true) => {
45
+ if (building) {
46
+ if (isBuilding) return
47
+ isBuilding = true
48
+ event('Building..')
49
+ }
50
+ const fn = async () => {
51
+ building && await build(platform)
52
+ await start()
53
+ }
54
+
55
+ try {
56
+ await fn()
57
+ watch(async () => {
58
+ event('Restarting..')
59
+ await fn()
60
+ // event('Restarted...')
61
+ })
62
+ // @ts-ignore
63
+ stop && shutdown(stop)
64
+ } catch (e: any) {
65
+ error(e)
66
+ process.exit(0)
67
+ } finally {
68
+ isBuilding = false
69
+ }
70
+ }
71
+
72
+ const applyExit = async (app: ChildProcess | null) => {
73
+ if (!app) return null
74
+
75
+ app //?.on('exit', code => process.exit(code ?? 0))
76
+ .on('message', msg => {
77
+ process.send && process.send(msg)
78
+ }).on('disconnect', () => {
79
+ process.disconnect && process.disconnect()
80
+ })
81
+ }
82
+ const killProcess = async (app: ChildProcess | null) => {
83
+ if (!app) return null
84
+ // event('Stopping..')
85
+ try {
86
+ if (!app?.killed) {
87
+ app.kill('SIGTERM')
88
+ await wait(1000)
89
+
90
+ if (!app?.killed) { // force kill
91
+ app.kill('SIGKILL')
92
+ await wait(1000)
93
+ }
94
+ }
95
+
96
+ return null
97
+ } catch (e) {
98
+ error('Error stopping:', e)
99
+ }
100
+
101
+ return null
102
+ }
103
+
104
+ const started = (port: number) => {
105
+ log(`Starting API on http://${host}:${port}`)
106
+ rn()
107
+ }
108
+
42
109
  switch (platform) {
43
110
  case 'aws':
44
111
  return withPort(desiredPort, async (port) => {
45
- let isBuilding = false
112
+ started(port)
46
113
  let lambda: ChildProcess | null = null
47
-
48
- const buildLambda = async () => {
49
- if (isBuilding) return
50
- isBuilding = true
51
- step('Building lambda')
52
- try {
53
- await build(platform)
54
- if (!lambda) await startLambda()
55
- } catch (e: any) {
56
- error('Build failed:', e?.message || e)
57
- process.exit(0)
58
- } finally {
59
- isBuilding = false
60
- }
61
- }
62
-
63
114
  const stopLambda = async () => {
64
- if (!lambda) return
65
- step('Stopping lambda process...')
66
- try {
67
- if (!lambda?.killed) {
68
- lambda.kill('SIGTERM')
69
- await wait(1000)
70
-
71
- if (!lambda?.killed) { // force kill
72
- lambda.kill('SIGKILL')
73
- await wait(1000)
74
- }
75
- }
76
-
77
- lambda = null
78
- } catch (e) {
79
- warn('Error stopping lambda:', e)
80
- }
115
+ lambda = await killProcess(lambda)
81
116
  }
82
-
83
117
  const startLambda = async () => {
84
- await stopLambda()
118
+ if (lambda) await stopLambda()
85
119
 
86
120
  lambda = spawn(
87
121
  'sam',
@@ -97,106 +131,77 @@ export default defineCommand({
97
131
  shell: process.platform == 'win32',
98
132
  env: {...process.env, DOCKER_HOST: getDockerHost()},
99
133
  }
100
- ).on('exit', code => {
101
- step(`Lambda process exited with code ${code ?? 0}`)
102
- if (code != 0 && code != null)
103
- error('Lambda process crashed, waiting for restart...')
104
-
105
- lambda = null
106
- })
107
- .on('message', msg => {
108
- process.send && process.send(msg)
109
- }).on('disconnect', () => {
110
- process.disconnect && process.disconnect()
111
- }).on('error', e => {
112
- error('Lambda process error:', e)
113
- lambda = null
114
- })
115
-
134
+ )
135
+ //.on('exit', code => {
136
+ // warn(`Lambda process exited with code ${code ?? 0}`)
137
+ // if (code != 0 && code != null)
138
+ // error('Lambda process crashed, waiting for restart...')
139
+
140
+ // lambda = null
141
+ // }).on('message', msg => {
142
+ // process.send && process.send(msg)
143
+ // }).on('disconnect', () => {
144
+ // process.disconnect && process.disconnect()
145
+ // }).on('error', e => {
146
+ // error('Lambda process error:', e)
147
+ // lambda = null
148
+ // })
149
+ applyExit(lambda)
116
150
  await wait(2000)
117
-
118
- step('Lambda process started successfully')
119
151
  }
120
152
 
121
- await buildLambda()
122
- event(`API running on http://${host}:${port}`)
123
-
124
- watch(async () => {
125
- await buildLambda()
126
- })
127
-
128
- shutdown(async () => {
129
- await stopLambda()
130
- })
153
+ await startApp(startLambda, stopLambda)
131
154
  })
132
155
  case 'cf':
133
156
  return withPort(desiredPort, async (port) => {
134
- let isBuilding = false
135
-
136
- const buildWorker = async () => {
137
- if (isBuilding) return
138
- isBuilding = true
139
- step('Building worker')
140
- try {
141
- await build(platform)
142
- await startWorker()
143
- } catch (e: any) {
144
- error('Build failed:', e?.message || e)
145
- process.exit(0)
146
- } finally {
147
- isBuilding = false
148
- }
149
- }
150
-
157
+ started(port)
151
158
  let worker: Miniflare | null = null
152
159
  const startWorker = async () => {
153
160
  if (worker) await worker.dispose()
154
-
155
161
  worker = await createMiniflare({ port, host, liveReload: false })
156
162
  }
157
163
 
158
- await buildWorker()
159
- event(`API running on http://${host}:${port}`)
160
-
161
- watch(async () => {
162
- step('Restarting server')
163
- await buildWorker()
164
- step('Server restarted')
165
- })
164
+ await startApp(startWorker)
166
165
  })
167
166
  default:
168
167
  case 'node':
169
168
  return withPort(desiredPort, async (port) => {
169
+ started(port)
170
170
  const isBun = getRuntime() == 'bun'
171
171
  const params = isBun
172
172
  ? ['run', '--port='+ port, '--hot', '--silent', '--no-clear-screen', '--no-summary', join(_root, 'node_modules/rajt/src/dev.ts')]
173
173
  : [join(_root, 'node_modules/.bin/tsx'), 'watch', join(_root, 'node_modules/rajt/src/dev-node.ts')]
174
174
 
175
- const child = spawn(
176
- process.execPath,
177
- params,
178
- {
179
- stdio: ['inherit', isBun ? 'pipe' : 'inherit', 'inherit', 'ipc'],
180
- env: {...process.env, PORT: port},
181
- }
182
- )
183
175
 
184
- event(`API running on http://${host}:${port}`)
176
+ let nodeApp: ChildProcess | null = null
177
+ const stopNode = async () => {
178
+ nodeApp = await killProcess(nodeApp)
179
+ }
180
+
181
+ const startNode = async () => {
182
+ if (nodeApp) await stopNode()
183
+
184
+ nodeApp = spawn(
185
+ process.execPath,
186
+ params,
187
+ {
188
+ stdio: ['inherit', isBun ? 'pipe' : 'inherit', 'inherit', 'ipc'],
189
+ env: {...process.env, PORT: port},
190
+ }
191
+ )
192
+
193
+ if (isBun && nodeApp?.stdout) {
194
+ nodeApp.stdout?.on('data', data => {
195
+ const output = data.toString()
196
+ if (!output.includes('Started development server'))
197
+ process.stdout.write(output)
198
+ })
199
+ }
185
200
 
186
- if (isBun && child?.stdout) {
187
- child.stdout?.on('data', data => {
188
- const output = data.toString()
189
- if (!output.includes('Started development server'))
190
- process.stdout.write(output)
191
- })
201
+ applyExit(nodeApp)
192
202
  }
193
203
 
194
- child.on('exit', code => process.exit(code ?? 0))
195
- .on('message', msg => {
196
- process.send && process.send(msg)
197
- }).on('disconnect', () => {
198
- process.disconnect && process.disconnect()
199
- })
204
+ await startApp(startNode, stopNode, false)
200
205
  })
201
206
  }
202
207
  },
@@ -10,7 +10,7 @@ import { gray } from '../../utils/colors'
10
10
  import type { ChokidarEventName, Platform } from './types'
11
11
 
12
12
  import { cacheRoutes } from '../../routes'
13
- import { step, substep, event, error, warn } from '../../utils/log'
13
+ import { step, substep, event, error, wait as wwait, warn, log } from '../../utils/log'
14
14
  import { platforms } from './constants'
15
15
 
16
16
  export const _root = join(dirname(new URL(import.meta.url).pathname), '../../../../../')
@@ -292,7 +292,7 @@ export async function watch(cb: (e: ChokidarEventName | string, file: string) =>
292
292
  let restartTimeout: NodeJS.Timeout | null = null
293
293
 
294
294
  const watcher = (e: ChokidarEventName) => async (file: string) => {
295
- step(getAssetChangeMessage(e, file))
295
+ log(getAssetChangeMessage(e, file))
296
296
 
297
297
  if (restartTimeout)
298
298
  clearTimeout(restartTimeout)
@@ -308,7 +308,7 @@ export async function watch(cb: (e: ChokidarEventName | string, file: string) =>
308
308
  codeWatcher.on('addDir', watcher('addDir'))
309
309
  codeWatcher.on('unlinkDir', watcher('unlinkDir'))
310
310
 
311
- step('Watching for file changes')
311
+ wwait('Watching for file changes')
312
312
  }
313
313
 
314
314
  export async function wait(ms: number) {
package/src/create-app.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import { Hono } from 'hono'
2
2
  import { logger } from 'hono/logger'
3
3
  import { matchedRoutes } from 'hono/route'
4
+ import { basicAuth } from 'hono/basic-auth'
5
+ import { openAPIRouteHandler, describeRoute, resolver } from 'hono-openapi'
6
+ import { Scalar } from '@scalar/hono-api-reference'
4
7
  import { Envir, Datte } from 't0n'
5
8
  import type {
6
9
  Env, Context, Next,
7
10
  HTTPResponseError,
8
11
  ServerOptions,
12
+ DescribeRouteOptions,
9
13
  } from './types'
10
14
  import { resolve, resolveMiddleware } from './utils/resolve'
11
15
  import { getMiddlewares, getHandler } from './register'
@@ -13,6 +17,7 @@ import request, { GET_REQUEST } from './request'
13
17
  import response from './response'
14
18
  import { isDev } from './utils/environment'
15
19
  import { gray } from './utils/colors'
20
+ import packageJson from '../../../package.json'
16
21
 
17
22
  const NFHandler = () => response.notFound()
18
23
  const EHandler = async (e: Error | HTTPResponseError) => {
@@ -60,7 +65,7 @@ const EHandler = async (e: Error | HTTPResponseError) => {
60
65
  // stack: isDev (? e.stack : undefined
61
66
  }
62
67
 
63
- export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
68
+ export const createApp = <E extends Env>(options?: ServerOptions<E> & { configs: any }) => {
64
69
  // const root = options?.root ?? '/'
65
70
  const app = options?.app ?? new Hono<E>()
66
71
 
@@ -110,12 +115,102 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
110
115
  const routes = options?.routes || []
111
116
  for (const route of routes) {
112
117
  if (Array.isArray(route)) { // @ts-ignore
113
- app[route[0]](route[1], ...mw(route[2], route[3]), ...resolve(getHandler(route[3]), route[3]))
118
+ app[route[0]](route[1], describeRoute(route[4]), ...mw(route[2], route[3]), ...resolve(getHandler(route[3]), route[3]))
114
119
  } else { // @ts-ignore
115
- app[route.method](route.path, ...mw(route.middlewares, route.name), ...resolve(route.handle, route.name))
120
+ app[route.method](route.path, describeRoute(route.desc), ...mw(route.middlewares, route.name), ...resolve(route.handle, route.name))
116
121
  }
117
122
  }
118
123
 
124
+ const _docs = options?.configs?.docs ?? {}
125
+ const docs = {
126
+ ..._docs,
127
+ disable: !!_docs?.disable,
128
+ path: _docs?.path || '/docs',
129
+ auth: _docs?.auth || {},
130
+ }
131
+
132
+ if (docs.disable) return app
133
+
134
+ if (docs?.auth?.username && docs?.auth?.password) {
135
+ app.use(docs.path +'/*', async (c, next) => {
136
+ const realm = docs.auth?.realm || 'Docs'
137
+ const unauthorized = response.unauthorized(
138
+ null,
139
+ {'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
140
+ )
141
+ if (!c.req.raw.headers.get('Authorization')) return unauthorized
142
+ const auth = basicAuth({ username: docs.auth.username, password: docs.auth.password, realm })
143
+
144
+ try {
145
+ await auth(c, next)
146
+ } catch {
147
+ return unauthorized
148
+ }
149
+ })
150
+ }
151
+
152
+ const appName = Envir.get('APP_NAME', packageJson?.name || 'API Docs')
153
+ const appVersion = Envir.get('APP_VERSION', packageJson?.version || '1.0.0')
154
+
155
+ app.get(
156
+ docs.path +'/openapi',
157
+ openAPIRouteHandler(app, {
158
+ documentation: {
159
+ info: {
160
+ title: appName,
161
+ version: appVersion,
162
+ description: Envir.get('APP_DESCRIPTION', packageJson?.description || ''),
163
+ },
164
+ components: {
165
+ securitySchemes: {
166
+ JWT: {
167
+ type: 'http',
168
+ scheme: 'bearer',
169
+ bearerFormat: 'JWT',
170
+ },
171
+ },
172
+ responses: {
173
+ 500: {
174
+ description: 'Internal Server Error',
175
+ content: {
176
+ 'application/json': {
177
+ schema: {
178
+ type: 'object',
179
+ properties: {
180
+ m: {
181
+ type: 'array',
182
+ items: { type: 'string' },
183
+ },
184
+ },
185
+ },
186
+ },
187
+ },
188
+ },
189
+ ...docs?.responses,
190
+ },
191
+ },
192
+ },
193
+ })
194
+ )
195
+
196
+ app.get(
197
+ docs.path,
198
+ Scalar({
199
+ theme: 'saturn',
200
+ url: docs.path +'/openapi',
201
+ showDeveloperTools: 'never',
202
+ telemetry: false,
203
+ documentDownloadType: 'json', //'direct',
204
+ isLoading: true,
205
+ persistAuth: true,
206
+ hideClientButton: true,
207
+ slug: appName.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ appVersion,
208
+ // hideDownloadButton: true,
209
+ // onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
210
+ customCss: `[href="https://www.scalar.com"]{display:none}`,
211
+ })
212
+ )
213
+
119
214
  return app
120
215
  }
121
216
 
package/src/dev.ts CHANGED
@@ -25,6 +25,6 @@ middlewares.forEach(mw => registerMiddleware(mw.handle))
25
25
  Ability.fromRoutes(routes)
26
26
  Ability.roles = Config.get('roles', {})
27
27
 
28
- const app = createApp({ routes })
28
+ const app = createApp({ routes, configs: Config.get('rajt', {}) })
29
29
 
30
30
  export default app
package/src/http.ts CHANGED
@@ -5,7 +5,8 @@ import { Ability } from './auth'
5
5
  import mergeMiddleware from './utils/merge-middleware'
6
6
  import type {
7
7
  Context, Next,
8
- IRequest
8
+ IRequest,
9
+ DescribeRouteOptions,
9
10
  } from './types'
10
11
 
11
12
  function method(method: string, ...args: any[]): void | ClassDecorator {
@@ -104,6 +105,13 @@ export function Auth(...args: any[]): void | ClassDecorator {
104
105
  }
105
106
 
106
107
  function _auth(target: Function | any) {
108
+ if (!target?.d) target.d = {}
109
+ if (!target.d?.security) target.d.security = []
110
+ target.d.security.push({JWT: []})
111
+
112
+ if (!target.d?.responses) target.d.responses = {}
113
+ target.d.responses[401] = {description: 'Unauthorized'}
114
+
107
115
  mergeMiddleware(target, async (c: Context, next: Next) => {
108
116
  const req = c.get(GET_REQUEST as unknown as string) as IRequest
109
117
  const ability = Ability.fromAction(target)
@@ -114,3 +122,13 @@ function _auth(target: Function | any) {
114
122
  await next()
115
123
  })
116
124
  }
125
+
126
+ function _describe(spec: DescribeRouteOptions): ClassDecorator{
127
+ return (target: any) => {
128
+ target.d = spec
129
+ }
130
+ }
131
+
132
+ export const OpenApi = _describe
133
+ export const Describe = _describe
134
+ export const Desc = _describe
package/src/prod.ts CHANGED
@@ -12,4 +12,4 @@ Ability.fromRoutes(routes)
12
12
  Ability.roles = Config.get('roles', {})
13
13
 
14
14
  // @ts-ignore
15
- export const app = createApp({ routes })
15
+ export const app = createApp({ routes, configs: Config.get('rajt', {}) })
package/src/response.ts CHANGED
@@ -28,13 +28,13 @@ export default class $Response {
28
28
  return new Response(body ?? null, b)
29
29
  }
30
30
 
31
- static text(data?: string, status?: StatusCode) {
32
- return this.raw(status, data, 'text/plain; charset=UTF-8' as BaseMime)
31
+ static text(str?: string, status?: StatusCode, headers?: HeaderRecord) {
32
+ return this.raw(status, str, 'text/plain; charset=UTF-8' as BaseMime, headers)
33
33
  }
34
34
 
35
35
  static json<T>(data?: T, status?: StatusCode, headers?: HeaderRecord) {
36
36
  if (data == null)
37
- return this.raw(status)
37
+ return this.raw(status, null, undefined, headers)
38
38
 
39
39
  return this.raw(status, JSON.stringify(data), 'application/json', headers)
40
40
  }
@@ -64,70 +64,70 @@ export default class $Response {
64
64
  }
65
65
 
66
66
  static ok(): Response
67
- static ok<T>(data: T): Response
68
- static ok<T>(data?: T) {
69
- return this.json(data, 200)
67
+ static ok<T>(data: T, headers?: HeaderRecord): Response
68
+ static ok<T>(data?: T, headers?: HeaderRecord) {
69
+ return this.json(data, 200, headers)
70
70
  }
71
71
 
72
72
  static created(): Response
73
- static created<T>(data: T): Response
74
- static created<T>(data?: T) {
75
- return this.json(data, 201)
73
+ static created<T>(data: T, headers?: HeaderRecord): Response
74
+ static created<T>(data?: T, headers?: HeaderRecord) {
75
+ return this.json(data, 201, headers)
76
76
  }
77
77
 
78
78
  static accepted(): Response
79
- static accepted<T>(data: T): Response
80
- static accepted<T>(data?: T) {
81
- return this.json(data, 202)
79
+ static accepted<T>(data: T, headers?: HeaderRecord): Response
80
+ static accepted<T>(data?: T, headers?: HeaderRecord) {
81
+ return this.json(data, 202, headers)
82
82
  }
83
83
 
84
- static deleted() {
85
- return this.noContent()
84
+ static deleted(headers?: HeaderRecord) {
85
+ return this.noContent(headers)
86
86
  }
87
87
 
88
- static noContent() {
89
- return this.raw(204)
88
+ static noContent(headers?: HeaderRecord) {
89
+ return this.json(null, 204, headers)
90
90
  }
91
91
 
92
92
  static badRequest(): Response
93
- static badRequest(errors?: Errors, msg?: string) {
94
- return this.error(errors, msg, 400)
93
+ static badRequest(errors?: Errors, msg?: string, headers?: HeaderRecord) {
94
+ return this.error(errors, msg, 400, headers)
95
95
  }
96
96
 
97
97
  static unauthorized(): Response
98
- static unauthorized<T>(data: T): Response
99
- static unauthorized<T>(data?: T) {
100
- return this.json(data, 401)
98
+ static unauthorized<T>(data: T, headers?: HeaderRecord): Response
99
+ static unauthorized<T>(data?: T, headers?: HeaderRecord) {
100
+ return this.json(data, 401, headers)
101
101
  }
102
102
 
103
103
  static forbidden(): Response
104
- static forbidden<T>(data: T): Response
105
- static forbidden<T>(data?: T) {
106
- return this.json(data, 403)
104
+ static forbidden<T>(data: T, headers?: HeaderRecord): Response
105
+ static forbidden<T>(data?: T, headers?: HeaderRecord) {
106
+ return this.json(data, 403, headers)
107
107
  }
108
108
 
109
109
  static notFound(): Response
110
- static notFound(msg: string): Response
111
- static notFound(msg?: string) {
112
- return this.raw(404, msg)
110
+ static notFound<T>(msg: T, headers?: HeaderRecord): Response
111
+ static notFound<T>(msg?: T, headers?: HeaderRecord) {
112
+ return this.json(msg, 404, headers)
113
113
  }
114
114
 
115
115
  static conflict(): Response
116
- static conflict(errors?: Errors, msg?: string) {
117
- return this.error(errors, msg, 409)
116
+ static conflict(errors?: Errors, msg?: string, headers?: HeaderRecord) {
117
+ return this.error(errors, msg, 409, headers)
118
118
  }
119
119
 
120
120
  static unsupportedMediaType(): Response
121
- static unsupportedMediaType(errors?: Errors, msg?: string) {
122
- return this.error(errors, msg, 415)
121
+ static unsupportedMediaType(errors?: Errors, msg?: string, headers?: HeaderRecord) {
122
+ return this.error(errors, msg, 415, headers)
123
123
  }
124
124
 
125
125
  static internalError(): Response
126
- static internalError(errors?: Errors, msg?: string) {
127
- return this.error(errors, msg, 500)
126
+ static internalError(errors?: Errors, msg?: string, headers?: HeaderRecord) {
127
+ return this.error(errors, msg, 500, headers)
128
128
  }
129
129
 
130
- static error(errors?: Errors, msg?: string, status?: ContentfulStatusCode) {
130
+ static error(errors?: Errors, msg?: string, status?: ContentfulStatusCode, headers?: HeaderRecord) {
131
131
  status ??= 500
132
132
  if (!errors && !msg)
133
133
  return this.raw(status, msg)
@@ -136,6 +136,6 @@ export default class $Response {
136
136
  if (msg) resp.m = msg
137
137
  if (errors) resp.e = errors
138
138
 
139
- return this.json(resp, status)
139
+ return this.json(resp, status, headers)
140
140
  }
141
141
  }
package/src/routes.ts CHANGED
@@ -64,6 +64,15 @@ export async function getRoutes(
64
64
 
65
65
  const m = handle?.m?.toLowerCase()
66
66
  const [method, uri] = m ? [m, handle?.p] : [extractHttpVerb(path), extractHttpPath(path)]
67
+ const d = handle?.d || {}
68
+ const desc = {
69
+ summary: handle?.d?.summary || name,
70
+ ...d,
71
+ responses: {
72
+ 500: {$ref: '#/components/responses/500'},
73
+ ...d?.responses,
74
+ }
75
+ }
67
76
  routes.push({
68
77
  method, path: uri,
69
78
  name,
@@ -71,6 +80,7 @@ export async function getRoutes(
71
80
  // @ts-ignore
72
81
  middlewares,
73
82
  handle,
83
+ desc,
74
84
  })
75
85
 
76
86
  if (!keys.has(name)) {
@@ -290,6 +300,7 @@ try {
290
300
  route.path,
291
301
  route.middlewares,
292
302
  route.name,
303
+ route.desc,
293
304
  ])))
294
305
  }
295
306
 
package/src/types.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  import type { ResponseHeader } from 'hono/utils/headers'
8
8
  // import type { StatusCode } from 'hono/utils/http-status'
9
9
  import type { BaseMime } from 'hono/utils/mime'
10
+ import type { DescribeRouteOptions } from 'hono-openapi'
10
11
  import z from 'zod'
11
12
  import Action from './action'
12
13
  import request from './request'
@@ -28,7 +29,7 @@ export type {
28
29
  RedirectStatusCode,
29
30
  StatusCode,
30
31
  } from 'hono/utils/http-status'
31
- export type { BaseMime }
32
+ export type { BaseMime, DescribeRouteOptions }
32
33
 
33
34
  type PublicMethods<T> = {
34
35
  [K in keyof T]: K extends `#${string}` | `$${string}` | symbol | 'prototype' ? never : K
@@ -52,6 +53,7 @@ export type Route = {
52
53
  file: string,
53
54
  middlewares: Function[],
54
55
  handle: Handlers,
56
+ desc: DescribeRouteOptions,
55
57
  }
56
58
 
57
59
  // export type ActionType = Function | Handler | Action | (new () => Action)
package/src/utils/log.ts CHANGED
@@ -43,6 +43,7 @@ export const prefixes = {
43
43
  info: white(bold(' ')),
44
44
  event: green(bold('✓')),
45
45
  trace: magenta(bold('»')),
46
+ log: gray(bold('⁕')),
46
47
  } as const
47
48
 
48
49
  const LOGGING_METHOD = {
@@ -76,6 +77,10 @@ function prefixedLog(prefixType: keyof typeof prefixes, ...msg: any[]) {
76
77
  }
77
78
  }
78
79
 
80
+ export function log(...msg: any[]) {
81
+ prefixedLog('log', ...msg)
82
+ }
83
+
79
84
  export function wait(...msg: any[]) {
80
85
  prefixedLog('wait', ...msg)
81
86
  }
package/src/validator.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ZodObject } from 'zod'
2
- import { zValidator } from '@hono/zod-validator'
2
+ import { validator } from 'hono-openapi'
3
3
  import response from './response'
4
4
  import type {
5
5
  Rule, Rules,
@@ -29,16 +29,16 @@ export default class $Validator {
29
29
  return fn
30
30
  }
31
31
 
32
- static readonly json = $Validator.fn('json')
33
- static readonly form = $Validator.fn('form')
34
- static readonly query = $Validator.fn('query')
35
- static readonly param = $Validator.fn('param')
36
- static readonly header = $Validator.fn('header')
37
- static readonly cookie = $Validator.fn('cookie')
32
+ static readonly json = $Validator.fn('json')!
33
+ static readonly form = $Validator.fn('form')!
34
+ static readonly query = $Validator.fn('query')!
35
+ static readonly param = $Validator.fn('param')!
36
+ static readonly header = $Validator.fn('header')!
37
+ static readonly cookie = $Validator.fn('cookie')!
38
38
 
39
39
  static parse(rules: Rules): Function[] {
40
40
  return (Array.isArray(rules) ? rules : [rules]) // @ts-ignore
41
- .flatMap(rule => zValidator(rule.target, rule.schema, (result, c) => {
41
+ .flatMap(rule => validator(rule.target, rule.schema, (result, c) => {
42
42
  if (!result.success) // @ts-ignore
43
43
  return response.badRequest({ ...result.error.flatten()[rule.eTarget] })
44
44
  }))