rajt 0.0.98 → 0.0.100
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 +9 -7
- package/src/auth/ability.ts +1 -1
- package/src/auth/index.ts +1 -0
- package/src/auth/middlewares.ts +23 -0
- package/src/cli/commands/dev.ts +21 -14
- package/src/cli/commands/migrate.ts +1 -1
- package/src/cli/utils.ts +82 -40
- package/src/create-app.ts +15 -16
- package/src/dev.ts +3 -4
- package/src/http.ts +27 -16
- package/src/middleware.ts +4 -0
- package/src/{oas.ts → open-api/register.ts} +3 -41
- package/src/open-api/spec.ts +41 -0
- package/src/prod.ts +1 -1
- package/src/register.ts +0 -3
- package/src/request.ts +10 -6
- package/src/routes.ts +40 -20
- package/src/types.ts +5 -2
- package/src/utils/resolve.ts +33 -18
- package/src/validator.ts +8 -8
- package/src/utils/json-import.ts +0 -14
- package/src/utils/merge-middleware.ts +0 -9
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.100",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"files": ["bin", "src"],
|
|
@@ -37,27 +37,28 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@hono/node-server": "^1.19.9",
|
|
39
39
|
"@hono/standard-validator": "^0.2.2",
|
|
40
|
+
"@hono/zod-validator": "^0.7.6",
|
|
40
41
|
"@scalar/hono-api-reference": "^0.9.40",
|
|
41
42
|
"chokidar": "^3.5.2",
|
|
42
43
|
"citty": "^0.1.6",
|
|
43
44
|
"consola": "^3.4.2",
|
|
44
|
-
"cripta": "
|
|
45
|
+
"cripta": "^0.1.11",
|
|
45
46
|
"dotenv": "^16.5.0",
|
|
46
47
|
"esbuild": "^0.25.2",
|
|
47
|
-
"forj": "
|
|
48
|
+
"forj": "^0.1.8",
|
|
48
49
|
"hono": "^4.11.7",
|
|
49
|
-
"hono-openapi": "^1.
|
|
50
|
+
"hono-openapi": "^1.3.0",
|
|
50
51
|
"localflare-api": "^0.4.2",
|
|
51
52
|
"localflare-core": "^0.4.2",
|
|
52
53
|
"miniflare": "^4.20260301.1",
|
|
53
54
|
"pathe": "^2.0",
|
|
54
55
|
"quansync": "^0.2.11",
|
|
55
|
-
"t0n": "
|
|
56
|
+
"t0n": "^0.1.12",
|
|
56
57
|
"tiny-glob": "^0.2",
|
|
57
58
|
"tsx": "^4.19.4",
|
|
58
59
|
"wrangler": "^4.61.0",
|
|
59
60
|
"zod": "^4.3.6",
|
|
60
|
-
"zod-openapi": "4"
|
|
61
|
+
"zod-openapi": "^5.4.6"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@cloudflare/workers-types": "^4.20260113.0",
|
|
@@ -70,7 +71,8 @@
|
|
|
70
71
|
"node": ">=18.0.0"
|
|
71
72
|
},
|
|
72
73
|
"resolutions": {
|
|
73
|
-
"@smithy/types": "^4.3.0"
|
|
74
|
+
"@smithy/types": "^4.3.0",
|
|
75
|
+
"zod": "^4.3.6"
|
|
74
76
|
},
|
|
75
77
|
"publishConfig": {
|
|
76
78
|
"registry": "https://registry.npmjs.org"
|
package/src/auth/ability.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class Ability {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
static fromAction(target: any): string {
|
|
22
|
-
return !target ? '' : this.format(target.name.length > 3 ? target.name : (target?.p || ''))
|
|
22
|
+
return !target ? '' : this.format(typeof target == 'string' ? target : (target.name.length > 3 ? target.name : (target?.p || '')))
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
static format(path: string) {
|
package/src/auth/index.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Ability } from './ability'
|
|
2
|
+
import response from '../response'
|
|
3
|
+
import { GET_REQUEST } from '../request'
|
|
4
|
+
import Config from '../config'
|
|
5
|
+
import { verbAlias } from '../http'
|
|
6
|
+
import type {
|
|
7
|
+
Context, Next,
|
|
8
|
+
IRequest,
|
|
9
|
+
} from '../types'
|
|
10
|
+
|
|
11
|
+
export async function Autorized(c: Context, next: Next) {
|
|
12
|
+
const req = c.get(GET_REQUEST as unknown as string) as IRequest
|
|
13
|
+
const ability = Ability.fromAction(Config.get(`routes.${req.routePath}$`+ verbAlias[req.method.toLowerCase()]))
|
|
14
|
+
|
|
15
|
+
if (!req?.user || !ability || req.cant(ability))
|
|
16
|
+
return response.unauthorized()
|
|
17
|
+
|
|
18
|
+
await next()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// export function Unautorized() {
|
|
22
|
+
|
|
23
|
+
// }
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn, type ChildProcess } from 'node:child_process'
|
|
|
3
3
|
|
|
4
4
|
import { defineCommand } from 'citty'
|
|
5
5
|
import type { Miniflare } from 'miniflare'
|
|
6
|
+
import type { WranglerConfig } from 'localflare-core'
|
|
6
7
|
import {
|
|
7
8
|
build, wait, watch, normalizePlatform, platformError, getRuntime,
|
|
8
9
|
wranglerConfig, createMiniflare, localflareManifest,
|
|
@@ -161,21 +162,12 @@ export default defineCommand({
|
|
|
161
162
|
})
|
|
162
163
|
case 'cf':
|
|
163
164
|
return withPort(desiredPort, async (port) => {
|
|
164
|
-
|
|
165
|
-
let worker: Miniflare | null = null
|
|
166
|
-
let localflare: Miniflare | null = null
|
|
167
|
-
const startWorker = async () => {
|
|
168
|
-
if (worker) await worker.dispose()
|
|
169
|
-
if (localflare) await localflare.dispose()
|
|
165
|
+
started(port)
|
|
170
166
|
|
|
171
|
-
|
|
167
|
+
let localflare: Miniflare | null = null
|
|
168
|
+
const startLocalflare = async (workerConfig: WranglerConfig) => {
|
|
169
|
+
if (localflare) return
|
|
172
170
|
|
|
173
|
-
const workerConfig = await wranglerConfig()
|
|
174
|
-
workerConfig.host = host
|
|
175
|
-
workerConfig.liveReload = false
|
|
176
|
-
|
|
177
|
-
worker = createMiniflare({ ...workerConfig, port })
|
|
178
|
-
await worker.ready
|
|
179
171
|
localflare = createMiniflare({
|
|
180
172
|
...workerConfig,
|
|
181
173
|
vars: {
|
|
@@ -185,8 +177,23 @@ export default defineCommand({
|
|
|
185
177
|
main: '.rajt/localfire.js',
|
|
186
178
|
port: 8788,
|
|
187
179
|
inspectorPort: 9230,
|
|
188
|
-
|
|
180
|
+
})
|
|
181
|
+
|
|
189
182
|
await localflare.ready
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let worker: Miniflare | null = null
|
|
186
|
+
const startWorker = async () => {
|
|
187
|
+
if (worker) await worker.dispose()
|
|
188
|
+
|
|
189
|
+
const workerConfig = await wranglerConfig() // @ts-ignore
|
|
190
|
+
workerConfig.host = host // @ts-ignore
|
|
191
|
+
workerConfig.liveReload = false
|
|
192
|
+
|
|
193
|
+
worker = createMiniflare({ ...workerConfig, port })
|
|
194
|
+
await worker.ready
|
|
195
|
+
|
|
196
|
+
if (!localflare) await startLocalflare(workerConfig)
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
await startApp(startWorker)
|
package/src/cli/utils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
1
2
|
import esbuild from 'esbuild'
|
|
2
3
|
import { Miniflare } from 'miniflare'
|
|
3
4
|
import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync } from 'node:fs'
|
|
@@ -69,6 +70,52 @@ const nodeModules = [
|
|
|
69
70
|
'async_hooks', 'console', 'fsevents',
|
|
70
71
|
].flatMap(lib => ['node:'+ lib, lib])
|
|
71
72
|
|
|
73
|
+
const printer = ts.createPrinter()
|
|
74
|
+
function stripDecorators(source: string) {
|
|
75
|
+
const sourceFile = ts.createSourceFile(
|
|
76
|
+
'tmp.ts', source, ts.ScriptTarget.ESNext, false
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
|
|
80
|
+
const visit: ts.Visitor = (node) => {
|
|
81
|
+
if (ts.isClassDeclaration(node) && node.modifiers?.length) {
|
|
82
|
+
let hasDecorator = false
|
|
83
|
+
const modifiers = []
|
|
84
|
+
|
|
85
|
+
for (const m of node.modifiers) {
|
|
86
|
+
if (m.kind === ts.SyntaxKind.Decorator) {
|
|
87
|
+
hasDecorator = true
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
modifiers.push(m)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (hasDecorator) {
|
|
94
|
+
return ts.factory.updateClassDeclaration(
|
|
95
|
+
node,
|
|
96
|
+
modifiers.length ? modifiers : undefined,
|
|
97
|
+
node.name,
|
|
98
|
+
node.typeParameters,
|
|
99
|
+
node.heritageClauses,
|
|
100
|
+
node.members
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return ts.visitEachChild(node, visit, context)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (node) => ts.visitNode(node, visit) as ts.SourceFile
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = ts.transform(sourceFile, [transformer])
|
|
112
|
+
const code = printer.printFile(result.transformed[0])
|
|
113
|
+
|
|
114
|
+
result.dispose()
|
|
115
|
+
|
|
116
|
+
return code
|
|
117
|
+
}
|
|
118
|
+
|
|
72
119
|
const dist = '.rajt/dist'
|
|
73
120
|
export const build = async (platform: Platform) => {
|
|
74
121
|
const startTime = Date.now()
|
|
@@ -86,6 +133,10 @@ export const build = async (platform: Platform) => {
|
|
|
86
133
|
|
|
87
134
|
if (['bun', 'vercel'].includes(platform)) platform = 'cf'
|
|
88
135
|
|
|
136
|
+
const USE_STRICT_RE = /(["'`])\s*use strict\s*\1;?/g
|
|
137
|
+
const decoder = new TextDecoder()
|
|
138
|
+
const encoder = new TextEncoder()
|
|
139
|
+
|
|
89
140
|
// @ts-ignore
|
|
90
141
|
platform = platform != 'node' ? '-'+ platform : ''
|
|
91
142
|
const opts = {
|
|
@@ -112,54 +163,45 @@ export const build = async (platform: Platform) => {
|
|
|
112
163
|
// define: {
|
|
113
164
|
// 'process.env.NODE_ENV': '"development"'
|
|
114
165
|
// },
|
|
115
|
-
// loader: {
|
|
116
|
-
// '.ts': 'ts',
|
|
117
|
-
// '.js': 'js'
|
|
118
|
-
// },
|
|
119
166
|
// tsconfig: join(_root, 'tsconfig.json'),
|
|
120
167
|
// sourcemap: true,
|
|
121
168
|
// logLevel: 'info',
|
|
122
169
|
plugins: [
|
|
123
170
|
{
|
|
124
|
-
name: 'rajt
|
|
171
|
+
name: 'rajt',
|
|
125
172
|
setup(build) {
|
|
126
173
|
build.onResolve({ filter: /\.rajt[\/\\]/ }, args => ({ path: join(_root, args.path) }))
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const result = await esbuild.transform(contents, {
|
|
137
|
-
loader: 'ts',
|
|
138
|
-
minify: true,
|
|
139
|
-
keepNames: true
|
|
140
|
-
})
|
|
141
|
-
return { contents: result.code, loader: 'ts' }
|
|
142
|
-
}
|
|
143
|
-
)
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: 'remove-use-strict',
|
|
148
|
-
setup(build) {
|
|
174
|
+
|
|
175
|
+
// strip decorators
|
|
176
|
+
build.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
177
|
+
const source = await readFile(args.path, 'utf8')
|
|
178
|
+
return { contents: stripDecorators(source), loader: 'ts' }
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// remove "use strict"
|
|
182
|
+
const write = build.initialOptions.write
|
|
149
183
|
build.onEnd(async (result) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
184
|
+
const files = result.outputFiles
|
|
185
|
+
if (!files) return
|
|
186
|
+
|
|
187
|
+
const tasks: Promise<void>[] = []
|
|
188
|
+
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
if (!file.path.endsWith('.js')) continue
|
|
191
|
+
|
|
192
|
+
let code = decoder.decode(file.contents)
|
|
193
|
+
|
|
194
|
+
if (USE_STRICT_RE.test(code))
|
|
195
|
+
code = code.replace(USE_STRICT_RE, '')
|
|
196
|
+
|
|
197
|
+
if (write) {
|
|
198
|
+
file.contents = encoder.encode(code)
|
|
199
|
+
} else {
|
|
200
|
+
tasks.push(writeFile(file.path, code))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (tasks.length) await Promise.all(tasks)
|
|
163
205
|
})
|
|
164
206
|
}
|
|
165
207
|
},
|
package/src/create-app.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import { logger } from 'hono/logger'
|
|
3
3
|
import { matchedRoutes } from 'hono/route'
|
|
4
|
-
import { describeRoute } from 'hono-openapi'
|
|
5
4
|
import { Envir, Datte } from 't0n'
|
|
6
5
|
import type {
|
|
7
6
|
Env, Context, Next,
|
|
@@ -9,11 +8,13 @@ import type {
|
|
|
9
8
|
ServerOptions,
|
|
10
9
|
} from './types'
|
|
11
10
|
import { resolve, resolveMiddleware } from './utils/resolve'
|
|
12
|
-
import { getMiddlewares
|
|
11
|
+
import { getMiddlewares } from './register'
|
|
13
12
|
import request, { GET_REQUEST } from './request'
|
|
14
13
|
import response from './response'
|
|
15
14
|
import { isDev } from './utils/environment'
|
|
16
15
|
import { gray } from 't0n/color'
|
|
16
|
+
import { Route } from './types'
|
|
17
|
+
import { getVerb } from './http'
|
|
17
18
|
|
|
18
19
|
const NFHandler = () => response.notFound()
|
|
19
20
|
const EHandler = async (e: Error | HTTPResponseError) => {
|
|
@@ -96,11 +97,14 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
|
96
97
|
if (c.env) Envir.add(c.env)
|
|
97
98
|
await next()
|
|
98
99
|
})
|
|
99
|
-
|
|
100
|
+
|
|
101
|
+
const middlewares = getMiddlewares()
|
|
102
|
+
for (const mw of middlewares) {
|
|
100
103
|
const h = async (c: Context, next: Next) => await resolveMiddleware(mw)(c.get(GET_REQUEST as unknown as string), next)
|
|
101
104
|
// @ts-ignore
|
|
102
105
|
mw?.path ? app.use(String(mw.path), h) : app.use(h)
|
|
103
|
-
}
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
// @ts-ignore
|
|
105
109
|
app.onError(options?.onError || EHandler)
|
|
106
110
|
// @ts-ignore
|
|
@@ -108,24 +112,19 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
|
108
112
|
|
|
109
113
|
if (options?.init) options.init(app)
|
|
110
114
|
|
|
111
|
-
const routes = options?.routes || []
|
|
112
|
-
|
|
115
|
+
const routes = options?.routes || [] // @ts-ignore
|
|
116
|
+
const routeRegister = options?.routeRegister ? options.routeRegister : (_: Hono, route: Route) => { // @ts0ignore
|
|
113
117
|
if (Array.isArray(route)) { // @ts-ignore
|
|
114
|
-
|
|
118
|
+
_[getVerb[route[0]]](route[1], ...resolve(...route[2], route[3]))
|
|
115
119
|
} else { // @ts-ignore
|
|
116
|
-
|
|
120
|
+
_[route.method](route.path, ...resolve(...route.middlewares), route.handle)
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
for (const route of routes)
|
|
125
|
+
routeRegister(app, route)
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
return objs.flatMap(obj => {
|
|
125
|
-
if (typeof obj != 'string') return null
|
|
126
|
-
// @ts-ignore
|
|
127
|
-
return getHandler(obj)?.mw || null
|
|
128
|
-
}).flat().filter(Boolean)
|
|
127
|
+
return app
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
export default createApp
|
package/src/dev.ts
CHANGED
|
@@ -3,16 +3,15 @@ import { config } from 'dotenv'
|
|
|
3
3
|
import { getRoutes, getMiddlewares, getConfigs } from './routes'
|
|
4
4
|
import { registerHandler, registerMiddleware } from './register'
|
|
5
5
|
import Config from './config'
|
|
6
|
-
import { registerOpenAPI } from './
|
|
6
|
+
import { registerOpenAPI } from './open-api/register'
|
|
7
7
|
import createApp from './create-app'
|
|
8
8
|
import { Ability } from 'rajt/auth'
|
|
9
9
|
import { setEnv, detectEnvironment } from 'rajt/env'
|
|
10
|
+
import { _root } from './utils/paths'
|
|
10
11
|
|
|
11
12
|
setEnv(detectEnvironment())
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
config({ path: join(__dirname, '.env.dev') })
|
|
14
|
+
config({ path: join(_root, '.env.dev') })
|
|
16
15
|
|
|
17
16
|
Config.add(await getConfigs())
|
|
18
17
|
|
package/src/http.ts
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { GET_REQUEST } from './request'
|
|
4
|
-
import { Ability } from './auth'
|
|
5
|
-
import mergeMiddleware from './utils/merge-middleware'
|
|
1
|
+
import { Autorized } from './auth'
|
|
2
|
+
import { type MiddlewareType, mergeMiddleware } from './middleware'
|
|
6
3
|
import type {
|
|
7
|
-
Context, Next,
|
|
8
|
-
IRequest,
|
|
9
4
|
DescribeRouteOptions,
|
|
10
5
|
} from './types'
|
|
11
6
|
|
|
7
|
+
export const verbAlias = {
|
|
8
|
+
get: 0,
|
|
9
|
+
post: 1,
|
|
10
|
+
put: 2,
|
|
11
|
+
patch: 3,
|
|
12
|
+
delete: 4,
|
|
13
|
+
head: 5,
|
|
14
|
+
options: 6,
|
|
15
|
+
connect: 7,
|
|
16
|
+
trace: 8,
|
|
17
|
+
} as Record<string, number>
|
|
18
|
+
|
|
19
|
+
export const getVerb = [
|
|
20
|
+
'get',
|
|
21
|
+
'post',
|
|
22
|
+
'put',
|
|
23
|
+
'patch',
|
|
24
|
+
'delete',
|
|
25
|
+
'head',
|
|
26
|
+
'options',
|
|
27
|
+
'connect',
|
|
28
|
+
'trace',
|
|
29
|
+
]
|
|
30
|
+
|
|
12
31
|
function method(method: string, ...args: any[]): void | ClassDecorator {
|
|
13
32
|
if (args.length == 1 && typeof args[0] == 'function')
|
|
14
33
|
return _method(method, '/', args[0])
|
|
@@ -112,15 +131,7 @@ function _auth(target: Function | any) {
|
|
|
112
131
|
if (!target.d?.responses) target.d.responses = {}
|
|
113
132
|
target.d.responses[401] = {description: 'Unauthorized'}
|
|
114
133
|
|
|
115
|
-
mergeMiddleware(target,
|
|
116
|
-
const req = c.get(GET_REQUEST as unknown as string) as IRequest
|
|
117
|
-
const ability = Ability.fromAction(target)
|
|
118
|
-
|
|
119
|
-
if (!req?.user || !ability || req.cant(ability))
|
|
120
|
-
return response.unauthorized()
|
|
121
|
-
|
|
122
|
-
await next()
|
|
123
|
-
})
|
|
134
|
+
mergeMiddleware(target, Autorized)
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
function _describe(spec: DescribeRouteOptions): ClassDecorator{
|
package/src/middleware.ts
CHANGED
|
@@ -15,3 +15,7 @@ export class Middleware {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const toHonoMiddleware = (mw: MiddlewareHandler) => async (req: IRequest, next: Next) => await mw(req.cx, next)
|
|
18
|
+
|
|
19
|
+
export function mergeMiddleware(target: Function | any, ...handlers: MiddlewareType[]) {
|
|
20
|
+
target.mw = [...target?.mw, ...handlers.flat()]
|
|
21
|
+
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { STATUS_CODES } from 'node:http'
|
|
2
1
|
import { basicAuth } from 'hono/basic-auth'
|
|
3
2
|
import { Scalar } from '@scalar/hono-api-reference'
|
|
4
|
-
import { generateSpecs, resolver } from 'hono-openapi'
|
|
5
3
|
import { Envir } from 't0n'
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import type { Hono } from './types'
|
|
4
|
+
import response from '../response'
|
|
5
|
+
import { getHandler } from '../register'
|
|
6
|
+
import type { Hono } from '../types'
|
|
10
7
|
|
|
11
8
|
export function config(opts: any) {
|
|
12
9
|
const docs = opts?.docs ?? {}
|
|
@@ -68,38 +65,3 @@ export function registerOpenAPI(app: Hono, conf: any) {
|
|
|
68
65
|
})
|
|
69
66
|
)
|
|
70
67
|
}
|
|
71
|
-
|
|
72
|
-
export async function generateOpenAPI(app: Hono, conf: any) {
|
|
73
|
-
const opts = config(conf)
|
|
74
|
-
if (opts.disable) return {}
|
|
75
|
-
|
|
76
|
-
return await generateSpecs(app, {
|
|
77
|
-
documentation: {
|
|
78
|
-
info: {
|
|
79
|
-
title: opts.appName,
|
|
80
|
-
version: opts.appVersion,
|
|
81
|
-
description: Envir.get('APP_DESCRIPTION', ''),
|
|
82
|
-
},
|
|
83
|
-
components: {
|
|
84
|
-
securitySchemes: {
|
|
85
|
-
JWT: {
|
|
86
|
-
type: 'http',
|
|
87
|
-
scheme: 'bearer',
|
|
88
|
-
bearerFormat: 'JWT',
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
responses: {
|
|
92
|
-
500: {
|
|
93
|
-
description: STATUS_CODES[500],
|
|
94
|
-
content: { // @ts-ignore
|
|
95
|
-
'application/json': await resolver(z.object({
|
|
96
|
-
m: z.array(z.string()),
|
|
97
|
-
})).toOpenAPISchema(),
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
...opts?.responses,
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
})
|
|
105
|
-
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { STATUS_CODES } from 'node:http'
|
|
2
|
+
import { generateSpecs, resolver } from 'hono-openapi'
|
|
3
|
+
import { array, object, string } from 'zod/mini'
|
|
4
|
+
import { Envir } from 't0n'
|
|
5
|
+
import type { Hono } from '../types'
|
|
6
|
+
import { config } from './register'
|
|
7
|
+
|
|
8
|
+
export async function generateOpenAPI(app: Hono, conf: any) {
|
|
9
|
+
const opts = config(conf)
|
|
10
|
+
if (opts.disable) return {}
|
|
11
|
+
|
|
12
|
+
return await generateSpecs(app, {
|
|
13
|
+
documentation: {
|
|
14
|
+
info: {
|
|
15
|
+
title: opts.appName,
|
|
16
|
+
version: opts.appVersion,
|
|
17
|
+
description: Envir.get('APP_DESCRIPTION', ''),
|
|
18
|
+
},
|
|
19
|
+
components: {
|
|
20
|
+
securitySchemes: {
|
|
21
|
+
JWT: {
|
|
22
|
+
type: 'http',
|
|
23
|
+
scheme: 'bearer',
|
|
24
|
+
bearerFormat: 'JWT',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
responses: {
|
|
28
|
+
500: {
|
|
29
|
+
description: STATUS_CODES[500],
|
|
30
|
+
content: { // @ts-ignore
|
|
31
|
+
'application/json': await resolver(object({
|
|
32
|
+
m: array(string()),
|
|
33
|
+
})).toOpenAPISchema(),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
...opts?.responses,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
}
|
package/src/prod.ts
CHANGED
package/src/register.ts
CHANGED
package/src/request.ts
CHANGED
|
@@ -27,7 +27,7 @@ export default class $Request {
|
|
|
27
27
|
#u: Authnz<any> | null = null
|
|
28
28
|
|
|
29
29
|
#host: string
|
|
30
|
-
#routePath
|
|
30
|
+
#routePath?: string
|
|
31
31
|
#matchedRoutes: RouterRoute[]
|
|
32
32
|
|
|
33
33
|
constructor(c: Context) {
|
|
@@ -38,7 +38,6 @@ export default class $Request {
|
|
|
38
38
|
const url = new URL(c.req.raw.url)
|
|
39
39
|
this.#host = url.protocol +'//'+ url.host
|
|
40
40
|
|
|
41
|
-
this.#routePath = routePath(c)
|
|
42
41
|
this.#matchedRoutes = matchedRoutes(c)
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -91,10 +90,6 @@ export default class $Request {
|
|
|
91
90
|
return this.#c.req.header('user-agent')
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
get routePath() {
|
|
95
|
-
return this.#routePath
|
|
96
|
-
}
|
|
97
|
-
|
|
98
93
|
get url() {
|
|
99
94
|
return this.#c.req.raw.url
|
|
100
95
|
}
|
|
@@ -116,6 +111,15 @@ export default class $Request {
|
|
|
116
111
|
return this.#c.req.raw.method
|
|
117
112
|
}
|
|
118
113
|
|
|
114
|
+
get routePath() {
|
|
115
|
+
this.#routePath ??= routePath(this.#c)
|
|
116
|
+
return this.#routePath
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get routeIndex() {
|
|
120
|
+
return this.#c.req.routeIndex
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
get matchedRoutes() {
|
|
120
124
|
return this.#matchedRoutes
|
|
121
125
|
}
|
package/src/routes.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { join, resolve, relative } from 'pathe'
|
|
3
3
|
|
|
4
|
+
import { IMPORT } from 't0n'
|
|
4
5
|
import glob from 'tiny-glob'
|
|
5
6
|
import { config } from 'dotenv'
|
|
6
|
-
|
|
7
|
-
import {
|
|
7
|
+
import { describeRoute, resolver } from 'hono-openapi'
|
|
8
|
+
import { mimes } from 'hono/utils/mime'
|
|
9
|
+
import { STATUS_CODES } from 'node:http'
|
|
8
10
|
import { registerHandler, registerMiddleware } from './register'
|
|
9
11
|
import createApp from './create-app'
|
|
10
12
|
import { isAnonFn } from './utils/func'
|
|
11
13
|
import ensureDir from './utils/ensuredir'
|
|
12
14
|
import versionSHA from './utils/version-sha'
|
|
13
|
-
import type { Routes, StandardSchemaV1 } from './types'
|
|
14
15
|
import { rn, substep, warn } from './utils/log'
|
|
15
|
-
import { _root } from './utils/paths'
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import { resolver } from 'hono-openapi'
|
|
20
|
-
import { mimes } from 'hono/utils/mime'
|
|
21
|
-
import { STATUS_CODES } from 'node:http'
|
|
22
|
-
|
|
16
|
+
import { _root, _rajt } from './utils/paths'
|
|
17
|
+
import { generateOpenAPI } from './open-api/spec'
|
|
18
|
+
import { verbAlias } from './http'
|
|
19
|
+
import { resolve as _resolve } from './utils/resolve'
|
|
23
20
|
import { highlightedMethod, highlightedURI } from './cli/utils'
|
|
24
21
|
|
|
22
|
+
import type * as z from 'zod'
|
|
23
|
+
import type { Routes, StandardSchemaV1 } from './types'
|
|
24
|
+
|
|
25
25
|
const importName = (name?: string) => (name || 'Fn'+ Math.random().toString(36).substring(2)).replace(/\.ts$/, '')
|
|
26
26
|
const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string[] = []): Promise<void> => {
|
|
27
27
|
if (!existsSync(dir)) return
|
|
@@ -61,7 +61,7 @@ function isZodSchema(obj: any): obj is z.ZodType {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function ResolveDescribeSchema(obj: any, deep: boolean = false) {
|
|
64
|
-
if (!obj || typeof obj
|
|
64
|
+
if (!obj || typeof obj != 'object') return obj
|
|
65
65
|
if (isZodSchema(obj))
|
|
66
66
|
return { content: {'application/json': { schema: resolver(obj as unknown as StandardSchemaV1) }} }
|
|
67
67
|
|
|
@@ -134,12 +134,16 @@ export async function getRoutes(
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
const mw = (handle.mw?.length ? [...handle.mw, ...middlewares] : middlewares).flatMap(obj => {
|
|
138
|
+
return typeof obj == 'string' ? obj : obj?.name || null
|
|
139
|
+
}).filter(Boolean) as Function[]
|
|
140
|
+
|
|
137
141
|
routes.push({
|
|
138
142
|
method, path: uri,
|
|
139
143
|
name,
|
|
140
144
|
file,
|
|
141
145
|
// @ts-ignore
|
|
142
|
-
middlewares,
|
|
146
|
+
middlewares: mw,
|
|
143
147
|
handle,
|
|
144
148
|
desc,
|
|
145
149
|
})
|
|
@@ -339,12 +343,25 @@ export async function cacheRoutes() {
|
|
|
339
343
|
|
|
340
344
|
const middlewares = await getMiddlewares()
|
|
341
345
|
const configs = await getConfigs()
|
|
346
|
+
const handlers = [
|
|
347
|
+
['auth/middlewares', 'Autorized', 'Autorized'],
|
|
348
|
+
]
|
|
342
349
|
|
|
343
|
-
|
|
344
|
-
|
|
350
|
+
for (const r of routes)
|
|
351
|
+
registerHandler(r.name, r.handle)
|
|
352
|
+
|
|
353
|
+
for (const mw of middlewares)
|
|
354
|
+
registerMiddleware(mw.handle)
|
|
355
|
+
|
|
356
|
+
for (const h of handlers) {
|
|
357
|
+
const mod = await IMPORT(join(_rajt, h[0]))
|
|
358
|
+
registerHandler(h[1], mod[h[1]])
|
|
359
|
+
}
|
|
345
360
|
|
|
346
361
|
// @ts-ignore
|
|
347
|
-
const openApi = await generateOpenAPI(createApp({ routes
|
|
362
|
+
const openApi = await generateOpenAPI(createApp({ routes, routeRegister: (app: Hono, route: Route) => {
|
|
363
|
+
app[route.method](route.path, describeRoute(route.desc), ..._resolve(...route.middlewares), route.handle)
|
|
364
|
+
} }), configs?.rajt || {})
|
|
348
365
|
|
|
349
366
|
const iPath = join(_root, '.rajt/imports.mjs')
|
|
350
367
|
ensureDir(iPath)
|
|
@@ -353,13 +370,18 @@ export async function cacheRoutes() {
|
|
|
353
370
|
copyFileSync(localfireEntry, join(_root, '.rajt/localfire.js'))
|
|
354
371
|
|
|
355
372
|
const _rajtDir = await dependencyPath('rajt')
|
|
373
|
+
|
|
374
|
+
stringifyToJS(Object.fromEntries(routes.map(r => ([r.path + r.method, r.name]))))
|
|
375
|
+
|
|
356
376
|
writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
|
|
357
377
|
${env?.length ? `import { Envir } from '${await dependencyPath('t0n')}/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
|
|
358
378
|
${Object.entries(configs)?.length ? `import Config from '${_rajtDir}/src/config'\nConfig.add(${stringifyToJS(configs)})` : ''}
|
|
359
379
|
|
|
360
380
|
import { registerHandler, registerMiddleware } from '${_rajtDir}/src/register'
|
|
381
|
+
${handlers.map(([file, name, _export]) => `\nimport ${_export ? `{ ${name} }` : name} from '${_rajtDir}/src/${file}'\nregisterHandler('${name}', ${name})`).join('\n')}
|
|
361
382
|
|
|
362
383
|
${Object.entries(openApi)?.length ? `registerHandler('RAJT_OPENAPI', ${stringifyToJS(openApi)})` : ''}
|
|
384
|
+
Config.set('routes', ${stringifyToJS(Object.fromEntries(routes.map(r => ([r.path+'$'+verbAlias[r.method], r.name]))))})
|
|
363
385
|
|
|
364
386
|
${routes.map(r => `import ${r.name} from '../${normalizeImportPath(r.file)}'`).join('\n')}
|
|
365
387
|
${middlewares.map(r => `import ${r.name} from '../${normalizeImportPath(r.file)}'`).join('\n')}
|
|
@@ -368,9 +390,7 @@ try {
|
|
|
368
390
|
const handlers = {${routes.map(r => r.name).join()}}
|
|
369
391
|
|
|
370
392
|
for (const [name, handler] of Object.entries(handlers)) {
|
|
371
|
-
|
|
372
|
-
registerHandler(name, handler)
|
|
373
|
-
}
|
|
393
|
+
registerHandler(name, handler)
|
|
374
394
|
}
|
|
375
395
|
|
|
376
396
|
const middlewares = {${middlewares.map(r => r.name).join()}}
|
|
@@ -386,7 +406,7 @@ try {
|
|
|
386
406
|
const rPath = join(_root, '.rajt/routes.json')
|
|
387
407
|
ensureDir(rPath)
|
|
388
408
|
writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
|
|
389
|
-
route.method,
|
|
409
|
+
verbAlias[route.method],
|
|
390
410
|
route.path,
|
|
391
411
|
route.middlewares,
|
|
392
412
|
route.name,
|
package/src/types.ts
CHANGED
|
@@ -10,7 +10,8 @@ import { mimes, type BaseMime } from 'hono/utils/mime'
|
|
|
10
10
|
import type { OpenAPIV3_1, OpenAPIV3 } from 'openapi-types'
|
|
11
11
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
12
12
|
import type { DescribeRouteOptions as RawDescribeRouteOptions, ResolverReturnType } from 'hono-openapi'
|
|
13
|
-
import z from 'zod'
|
|
13
|
+
import type * as z from 'zod'
|
|
14
|
+
import type zm from 'zod/mini'
|
|
14
15
|
import Action from './action'
|
|
15
16
|
import request from './request'
|
|
16
17
|
import response from './response'
|
|
@@ -33,6 +34,7 @@ export type {
|
|
|
33
34
|
} from 'hono/utils/http-status'
|
|
34
35
|
export type { BaseMime, StandardSchemaV1 }
|
|
35
36
|
|
|
37
|
+
export type zObject = z.ZodTypeAny | zm.ZodMiniObject
|
|
36
38
|
type PublicMethods<T> = {
|
|
37
39
|
[K in keyof T]: K extends `#${string}` | `$${string}` | symbol | 'prototype' ? never : K
|
|
38
40
|
}[keyof T]
|
|
@@ -42,11 +44,12 @@ export type IResponse = Pick<typeof response, PublicMethods<typeof response>>
|
|
|
42
44
|
|
|
43
45
|
export type IValidator = Pick<typeof validator, PublicMethods<typeof validator>>
|
|
44
46
|
export type Rule = {
|
|
45
|
-
schema:
|
|
47
|
+
schema: zObject
|
|
46
48
|
target: keyof ValidationTargets
|
|
47
49
|
eTarget?: 'fieldErrors' | 'formErrors'
|
|
48
50
|
}
|
|
49
51
|
export type Rules = Rule[] | Rule | null
|
|
52
|
+
export type RuleFn = (schema: zObject) => Rule
|
|
50
53
|
|
|
51
54
|
export type StandardSchema = StandardSchemaV1 | OpenAPIV3_1.ReferenceObject
|
|
52
55
|
export type DescribeRouteOptions = Omit<RawDescribeRouteOptions, 'responses'> & {
|
package/src/utils/resolve.ts
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { getHandler } from '../register'
|
|
2
|
+
|
|
3
|
+
export function resolve(...objs: any[]) {
|
|
4
|
+
const _ = []
|
|
5
|
+
for (let obj of objs) {
|
|
6
|
+
if (typeof obj == 'string')
|
|
7
|
+
obj = getHandler(obj)
|
|
8
|
+
|
|
9
|
+
if (typeof obj == 'function' && obj?.length == 2) {
|
|
10
|
+
|
|
11
|
+
} else if (obj?.run) {
|
|
12
|
+
_.push(...obj.run())
|
|
13
|
+
continue
|
|
14
|
+
} else if (obj?.handle) {
|
|
15
|
+
obj = obj.handle
|
|
16
|
+
} else if (obj?.factory) {
|
|
17
|
+
obj = obj?.opts ? obj.factory(...Array.isArray(obj.opts) ? obj.opts : [obj.opts]) : obj.factory()
|
|
18
|
+
} else {
|
|
19
|
+
const instance = new obj()
|
|
20
|
+
if (obj?.prototype?.run) {
|
|
21
|
+
_.push(...instance.run())
|
|
22
|
+
continue
|
|
23
|
+
} else if (obj?.prototype?.handle) {
|
|
24
|
+
obj = instance.handle
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(`Invalid action "${obj?.name || String(obj)}" - unsupported type`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
obj && _.push(obj)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return _
|
|
19
34
|
}
|
|
20
35
|
|
|
21
36
|
export function resolveMiddleware(obj: any) {
|
package/src/validator.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { validator } from 'hono-openapi'
|
|
1
|
+
import { zValidator } from '@hono/zod-validator'
|
|
3
2
|
import response from './response'
|
|
4
3
|
import type {
|
|
5
|
-
Rule, Rules,
|
|
4
|
+
Rule, Rules, RuleFn,
|
|
6
5
|
ValidationTargets,
|
|
6
|
+
zObject,
|
|
7
7
|
} from './types'
|
|
8
8
|
|
|
9
9
|
export default class $Validator {
|
|
10
|
-
private static cache = new Map<string,
|
|
10
|
+
private static cache = new Map<string, RuleFn>()
|
|
11
11
|
|
|
12
12
|
private static createRule<T extends keyof ValidationTargets>(
|
|
13
13
|
target: T,
|
|
14
|
-
schema:
|
|
14
|
+
schema: zObject
|
|
15
15
|
): Rule {
|
|
16
16
|
return {
|
|
17
17
|
target,
|
|
@@ -24,7 +24,7 @@ export default class $Validator {
|
|
|
24
24
|
if (this.cache.has(target))
|
|
25
25
|
return this.cache.get(target)
|
|
26
26
|
|
|
27
|
-
const fn = (schema:
|
|
27
|
+
const fn = (schema: zObject) => this.createRule(target, schema)
|
|
28
28
|
this.cache.set(target, fn)
|
|
29
29
|
return fn
|
|
30
30
|
}
|
|
@@ -38,9 +38,9 @@ export default class $Validator {
|
|
|
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 => zValidator(rule.target, rule.schema, (result, c) => {
|
|
42
42
|
if (!result.success) // @ts-ignore
|
|
43
|
-
return response.badRequest(result.error)
|
|
43
|
+
return response.badRequest({ ...result.error.flatten()[rule.eTarget] })
|
|
44
44
|
}))
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/utils/json-import.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
|
-
import { dirname, join } from 'pathe'
|
|
3
|
-
|
|
4
|
-
export default function jsonImport<T = any>(filePath: string, defaultValue: T = {} as T): T {
|
|
5
|
-
const __dirname = dirname(new URL(import.meta.url).pathname)
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const fullPath = join(__dirname, filePath)
|
|
9
|
-
const fileContent = readFileSync(fullPath, 'utf-8')
|
|
10
|
-
return JSON.parse(fileContent) as T
|
|
11
|
-
} catch (error) {
|
|
12
|
-
return defaultValue
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { MiddlewareHandler } from 'hono'
|
|
2
|
-
import { MiddlewareType } from '../middleware'
|
|
3
|
-
import { resolveMiddleware } from './resolve'
|
|
4
|
-
|
|
5
|
-
export default function mergeMiddleware(target: Function | any, ...handlers: MiddlewareType[]) {
|
|
6
|
-
const existingMiddlewares: MiddlewareHandler[] = target?.mw || []
|
|
7
|
-
const allMiddlewares = [...existingMiddlewares, ...handlers.flat().map(handler => resolveMiddleware(handler))]
|
|
8
|
-
target.mw = allMiddlewares
|
|
9
|
-
}
|