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 +6 -3
- package/src/cli/commands/dev.ts +117 -112
- package/src/cli/commands/utils.ts +3 -3
- package/src/create-app.ts +98 -3
- package/src/dev.ts +1 -1
- package/src/http.ts +19 -1
- package/src/prod.ts +1 -1
- package/src/response.ts +35 -35
- package/src/routes.ts +11 -0
- package/src/types.ts +3 -1
- package/src/utils/log.ts +5 -0
- package/src/validator.ts +8 -8
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.
|
|
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/
|
|
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",
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.on('message', msg => {
|
|
108
|
-
|
|
109
|
-
}).on('disconnect', () => {
|
|
110
|
-
|
|
111
|
-
}).on('error', e => {
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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(
|
|
32
|
-
return this.raw(status,
|
|
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.
|
|
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:
|
|
111
|
-
static notFound(msg?:
|
|
112
|
-
return this.
|
|
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 {
|
|
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 =>
|
|
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
|
}))
|