rajt 0.0.99 → 0.0.101
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/bin/rajt.js +59 -50
- package/package.json +10 -10
- 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 +22 -15
- package/src/cli/commands/migrate.ts +1 -1
- package/src/cli/index.ts +2 -2
- package/src/cli/utils.ts +87 -44
- package/src/create-app.ts +11 -7
- package/src/dev.ts +2 -3
- package/src/http.ts +27 -16
- package/src/middleware.ts +4 -0
- package/src/open-api/register.ts +2 -1
- package/src/register.ts +0 -3
- package/src/request.ts +21 -4
- package/src/routes.ts +44 -20
- package/src/types.ts +4 -1
- package/src/utils/environment.ts +10 -8
- package/src/utils/resolve.ts +31 -26
- package/src/validator.ts +33 -9
- package/src/utils/json-import.ts +0 -14
- package/src/utils/merge-middleware.ts +0 -9
package/bin/rajt.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
3
|
+
import { join, dirname, resolve } from 'node:path';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
6
|
-
const __dirname = dirname(
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
|
|
8
9
|
const ERR_NODE_VERSION = '18.0.0';
|
|
9
10
|
const MIN_NODE_VERSION = '18.0.0';
|
|
10
11
|
|
|
11
|
-
let rajtProcess;
|
|
12
|
-
|
|
13
12
|
function runRajt() {
|
|
14
13
|
if (process?.versions?.node && semiver(process.versions.node, ERR_NODE_VERSION) < 0) {
|
|
15
14
|
console.error(
|
|
@@ -21,58 +20,70 @@ Consider using a Node.js version manager such as https://volta.sh or https://git
|
|
|
21
20
|
return;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
const isBun = process?.isBun || typeof Bun
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
join(process.cwd(), _tsxPath),
|
|
35
|
-
'tsx',
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
for (const pathOption of tsxPaths) {
|
|
39
|
-
if (pathOption == 'tsx' || existsSync(pathOption)) {
|
|
40
|
-
tsxPath = pathOption;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
23
|
+
const isBun = process?.isBun || typeof Bun !== 'undefined';
|
|
24
|
+
const targetScript = resolve(__dirname, '../src/cli/index.ts');
|
|
25
|
+
|
|
26
|
+
let executor = process.execPath;
|
|
27
|
+
let args = [];
|
|
28
|
+
|
|
29
|
+
if (isBun) {
|
|
30
|
+
args = [targetScript, ...process.argv.slice(2)];
|
|
31
|
+
} else {
|
|
32
|
+
const tsxBin = findTsx();
|
|
44
33
|
|
|
45
|
-
if (!
|
|
46
|
-
console.error('
|
|
34
|
+
if (!tsxBin) {
|
|
35
|
+
console.error('Error: "tsx" is not available. Please install tsx:');
|
|
47
36
|
console.error(' npm i -D tsx');
|
|
48
37
|
console.error(' or');
|
|
49
38
|
console.error(' bun i -D tsx');
|
|
50
39
|
process.exit(1);
|
|
51
|
-
return;
|
|
52
40
|
}
|
|
53
|
-
}
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
process.execPath,
|
|
57
|
-
[
|
|
42
|
+
args = [
|
|
58
43
|
'--no-warnings',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
44
|
+
tsxBin,
|
|
45
|
+
targetScript,
|
|
46
|
+
...process.argv.slice(2)
|
|
47
|
+
].filter(arg => !arg.includes('experimental-vm-modules') && !arg.includes('loader'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return execute(executor, args)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findTsx() {
|
|
54
|
+
const relativePaths = [
|
|
55
|
+
join(__dirname, '../node_modules/tsx/dist/cli.mjs'),
|
|
56
|
+
join(process.cwd(), 'node_modules/tsx/dist/cli.mjs'),
|
|
57
|
+
join(__dirname, '../node_modules/.bin/tsx'),
|
|
58
|
+
join(process.cwd(), 'node_modules/.bin/tsx'),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const p of relativePaths) {
|
|
62
|
+
if (existsSync(p)) return p;
|
|
63
|
+
if (existsSync(`${p}.exe`)) return `${p}.exe`;
|
|
64
|
+
if (existsSync(`${p}.cmd`)) return `${p}.cmd`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function execute(command, args) {
|
|
71
|
+
const child = spawn(command, args, {
|
|
72
|
+
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
73
|
+
env: {
|
|
74
|
+
...process.env,
|
|
75
|
+
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
76
|
+
TSX_DISABLE_CACHE: '1',
|
|
71
77
|
}
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
.on('
|
|
75
|
-
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
process.on('SIGINT', () => child.kill('SIGINT'))
|
|
81
|
+
.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
82
|
+
|
|
83
|
+
return child
|
|
84
|
+
.on('exit', code => process.exit(code ?? 0))
|
|
85
|
+
.on('message', msg => process.send?.(msg))
|
|
86
|
+
.on('disconnect', () => process.disconnect?.())
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
var fn = new Intl.Collator(0, { numeric: 1 }).compare;
|
|
@@ -101,7 +112,5 @@ function directly() {
|
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
if (directly()) {
|
|
104
|
-
|
|
105
|
-
process.on('SIGINT', () => rajtProcess && rajtProcess.kill())
|
|
106
|
-
.on('SIGTERM', () => rajtProcess && rajtProcess.kill());
|
|
115
|
+
runRajt()
|
|
107
116
|
}
|
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.101",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"files": ["bin", "src"],
|
|
@@ -35,25 +35,25 @@
|
|
|
35
35
|
"zip": "zip -j ../../lambda.zip ../../.rajt/dist/index.js"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@hono/node-server": "^1.19.
|
|
38
|
+
"@hono/node-server": "^1.19.11",
|
|
39
39
|
"@hono/standard-validator": "^0.2.2",
|
|
40
40
|
"@hono/zod-validator": "^0.7.6",
|
|
41
|
-
"@scalar/hono-api-reference": "^0.
|
|
41
|
+
"@scalar/hono-api-reference": "^0.10.3",
|
|
42
42
|
"chokidar": "^3.5.2",
|
|
43
43
|
"citty": "^0.1.6",
|
|
44
44
|
"consola": "^3.4.2",
|
|
45
|
-
"cripta": "^0.1.
|
|
45
|
+
"cripta": "^0.1.12",
|
|
46
46
|
"dotenv": "^16.5.0",
|
|
47
47
|
"esbuild": "^0.25.2",
|
|
48
|
-
"forj": "^0.1.
|
|
49
|
-
"hono": "^4.
|
|
48
|
+
"forj": "^0.1.10",
|
|
49
|
+
"hono": "^4.12.8",
|
|
50
50
|
"hono-openapi": "^1.3.0",
|
|
51
|
-
"localflare-api": "^0.
|
|
52
|
-
"localflare-core": "^0.
|
|
53
|
-
"miniflare": "^4.
|
|
51
|
+
"localflare-api": "^0.5.0",
|
|
52
|
+
"localflare-core": "^0.5.0",
|
|
53
|
+
"miniflare": "^4.20260312.0",
|
|
54
54
|
"pathe": "^2.0",
|
|
55
55
|
"quansync": "^0.2.11",
|
|
56
|
-
"t0n": "^0.1.
|
|
56
|
+
"t0n": "^0.1.13",
|
|
57
57
|
"tiny-glob": "^0.2",
|
|
58
58
|
"tsx": "^4.19.4",
|
|
59
59
|
"wrangler": "^4.61.0",
|
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,
|
|
@@ -53,7 +54,7 @@ export default defineCommand({
|
|
|
53
54
|
event('Building..')
|
|
54
55
|
}
|
|
55
56
|
const fn = async () => {
|
|
56
|
-
building && await build(platform)
|
|
57
|
+
building && await build(platform, 'dev')
|
|
57
58
|
await start()
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -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/index.ts
CHANGED
|
@@ -19,8 +19,8 @@ import make from './commands/make'
|
|
|
19
19
|
const directly = () => {
|
|
20
20
|
try {
|
|
21
21
|
// @ts-ignore
|
|
22
|
-
return typeof vitest
|
|
23
|
-
&& import.meta.url
|
|
22
|
+
return typeof vitest === 'undefined'
|
|
23
|
+
&& import.meta.url?.endsWith(process.argv[1].replace(/\\/g, '/'))
|
|
24
24
|
} catch {
|
|
25
25
|
return false
|
|
26
26
|
}
|
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,8 +70,54 @@ 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
|
-
export const build = async (platform: Platform) => {
|
|
120
|
+
export const build = async (platform: Platform, env: string = 'prd') => {
|
|
74
121
|
const startTime = Date.now()
|
|
75
122
|
|
|
76
123
|
const isCF = platform == 'cf'
|
|
@@ -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 = {
|
|
@@ -109,57 +160,49 @@ export const build = async (platform: Platform) => {
|
|
|
109
160
|
],
|
|
110
161
|
metafile: true,
|
|
111
162
|
write: false,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// '.ts': 'ts',
|
|
117
|
-
// '.js': 'js'
|
|
118
|
-
// },
|
|
163
|
+
define: {
|
|
164
|
+
'process.env.RAJT_ENV': env,
|
|
165
|
+
// 'process.env.NODE_ENV': '"development"',
|
|
166
|
+
},
|
|
119
167
|
// tsconfig: join(_root, 'tsconfig.json'),
|
|
120
168
|
// sourcemap: true,
|
|
121
169
|
// logLevel: 'info',
|
|
122
170
|
plugins: [
|
|
123
171
|
{
|
|
124
|
-
name: 'rajt
|
|
172
|
+
name: 'rajt',
|
|
125
173
|
setup(build) {
|
|
126
174
|
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) {
|
|
175
|
+
|
|
176
|
+
// strip decorators
|
|
177
|
+
build.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
178
|
+
const source = await readFile(args.path, 'utf8')
|
|
179
|
+
return { contents: stripDecorators(source), loader: 'ts' }
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// remove "use strict"
|
|
183
|
+
const write = build.initialOptions.write
|
|
149
184
|
build.onEnd(async (result) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
const files = result.outputFiles
|
|
186
|
+
if (!files) return
|
|
187
|
+
|
|
188
|
+
const tasks: Promise<void>[] = []
|
|
189
|
+
|
|
190
|
+
for (const file of files) {
|
|
191
|
+
if (!file.path.endsWith('.js')) continue
|
|
192
|
+
|
|
193
|
+
let code = decoder.decode(file.contents)
|
|
194
|
+
|
|
195
|
+
if (USE_STRICT_RE.test(code))
|
|
196
|
+
code = code.replace(USE_STRICT_RE, '')
|
|
197
|
+
|
|
198
|
+
if (write) {
|
|
199
|
+
file.contents = encoder.encode(code)
|
|
200
|
+
} else {
|
|
201
|
+
tasks.push(writeFile(file.path, code))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (tasks.length) await Promise.all(tasks)
|
|
163
206
|
})
|
|
164
207
|
}
|
|
165
208
|
},
|
package/src/create-app.ts
CHANGED
|
@@ -7,13 +7,14 @@ import type {
|
|
|
7
7
|
HTTPResponseError,
|
|
8
8
|
ServerOptions,
|
|
9
9
|
} from './types'
|
|
10
|
-
import { resolve, resolveMiddleware
|
|
11
|
-
import { getMiddlewares
|
|
10
|
+
import { resolve, resolveMiddleware } from './utils/resolve'
|
|
11
|
+
import { getMiddlewares } from './register'
|
|
12
12
|
import request, { GET_REQUEST } from './request'
|
|
13
13
|
import response from './response'
|
|
14
14
|
import { isDev } from './utils/environment'
|
|
15
15
|
import { gray } from 't0n/color'
|
|
16
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
|
|
@@ -109,11 +113,11 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
|
109
113
|
if (options?.init) options.init(app)
|
|
110
114
|
|
|
111
115
|
const routes = options?.routes || [] // @ts-ignore
|
|
112
|
-
const routeRegister = options?.routeRegister ? options.routeRegister : (_: Hono, route: Route) => {
|
|
116
|
+
const routeRegister = options?.routeRegister ? options.routeRegister : (_: Hono, route: Route) => {
|
|
113
117
|
if (Array.isArray(route)) { // @ts-ignore
|
|
114
|
-
_[route[0]](route[1], ...
|
|
118
|
+
_[getVerb[route[0]]](route[1], ...resolve(...route[2], route[3]))
|
|
115
119
|
} else { // @ts-ignore
|
|
116
|
-
_[route.method](route.path, ...
|
|
120
|
+
_[route.method](route.path, ...resolve(...route.middlewares, route.handle))
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
package/src/dev.ts
CHANGED
|
@@ -7,12 +7,11 @@ 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
|
+
}
|
package/src/open-api/register.ts
CHANGED
|
@@ -61,7 +61,8 @@ export function registerOpenAPI(app: Hono, conf: any) {
|
|
|
61
61
|
slug: opts.appName?.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ opts.appVersion,
|
|
62
62
|
// hideDownloadButton: true,
|
|
63
63
|
// onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
|
|
64
|
-
customCss: `[href="https://www.scalar.com"]{display:none}`,
|
|
64
|
+
customCss: `[href="https://www.scalar.com"],.scalar-mcp-layer{display:none!important}`,
|
|
65
|
+
// favicon: 'https://example.com/favicon.png',
|
|
65
66
|
})
|
|
66
67
|
)
|
|
67
68
|
}
|
package/src/register.ts
CHANGED
package/src/request.ts
CHANGED
|
@@ -27,7 +27,8 @@ export default class $Request {
|
|
|
27
27
|
#u: Authnz<any> | null = null
|
|
28
28
|
|
|
29
29
|
#host: string
|
|
30
|
-
#
|
|
30
|
+
#protocol: string
|
|
31
|
+
#routePath?: string
|
|
31
32
|
#matchedRoutes: RouterRoute[]
|
|
32
33
|
|
|
33
34
|
constructor(c: Context) {
|
|
@@ -37,8 +38,8 @@ export default class $Request {
|
|
|
37
38
|
|
|
38
39
|
const url = new URL(c.req.raw.url)
|
|
39
40
|
this.#host = url.protocol +'//'+ url.host
|
|
41
|
+
this.#protocol = url.protocol
|
|
40
42
|
|
|
41
|
-
this.#routePath = routePath(c)
|
|
42
43
|
this.#matchedRoutes = matchedRoutes(c)
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -69,6 +70,13 @@ export default class $Request {
|
|
|
69
70
|
return this.has(prop, value)
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
createToken(data: any, exp: number) {
|
|
74
|
+
return Token.create(this, data, exp)
|
|
75
|
+
}
|
|
76
|
+
parseToken(token: string) {
|
|
77
|
+
return Token.parse(this, token)
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
get cx() {
|
|
73
81
|
return this.#c
|
|
74
82
|
}
|
|
@@ -91,8 +99,8 @@ export default class $Request {
|
|
|
91
99
|
return this.#c.req.header('user-agent')
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
get
|
|
95
|
-
return this.#
|
|
102
|
+
get protocol() {
|
|
103
|
+
return this.#protocol
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
get url() {
|
|
@@ -116,6 +124,15 @@ export default class $Request {
|
|
|
116
124
|
return this.#c.req.raw.method
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
get routePath() {
|
|
128
|
+
this.#routePath ??= routePath(this.#c)
|
|
129
|
+
return this.#routePath
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get routeIndex() {
|
|
133
|
+
return this.#c.req.routeIndex
|
|
134
|
+
}
|
|
135
|
+
|
|
119
136
|
get matchedRoutes() {
|
|
120
137
|
return this.#matchedRoutes
|
|
121
138
|
}
|
package/src/routes.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
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, validator } 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'
|
|
12
|
+
import _response from './response'
|
|
13
|
+
import _validator from './validator'
|
|
10
14
|
import { isAnonFn } from './utils/func'
|
|
11
15
|
import ensureDir from './utils/ensuredir'
|
|
12
16
|
import versionSHA from './utils/version-sha'
|
|
13
|
-
import type { Routes, StandardSchemaV1 } from './types'
|
|
14
17
|
import { rn, substep, warn } from './utils/log'
|
|
15
|
-
import { _root } from './utils/paths'
|
|
18
|
+
import { _root, _rajt } from './utils/paths'
|
|
16
19
|
import { generateOpenAPI } from './open-api/spec'
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import { mimes } from 'hono/utils/mime'
|
|
20
|
-
import { STATUS_CODES } from 'node:http'
|
|
21
|
-
import { mw, resolve as _resolve } from './utils/resolve'
|
|
22
|
-
|
|
20
|
+
import { verbAlias } from './http'
|
|
21
|
+
import { resolve as _resolve } from './utils/resolve'
|
|
23
22
|
import { highlightedMethod, highlightedURI } from './cli/utils'
|
|
24
23
|
|
|
24
|
+
import type * as z from 'zod'
|
|
25
|
+
import type { Routes, Rule, StandardSchemaV1 } from './types'
|
|
26
|
+
|
|
25
27
|
const importName = (name?: string) => (name || 'Fn'+ Math.random().toString(36).substring(2)).replace(/\.ts$/, '')
|
|
26
28
|
const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string[] = []): Promise<void> => {
|
|
27
29
|
if (!existsSync(dir)) return
|
|
@@ -61,7 +63,7 @@ function isZodSchema(obj: any): obj is z.ZodType {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
function ResolveDescribeSchema(obj: any, deep: boolean = false) {
|
|
64
|
-
if (!obj || typeof obj
|
|
66
|
+
if (!obj || typeof obj != 'object') return obj
|
|
65
67
|
if (isZodSchema(obj))
|
|
66
68
|
return { content: {'application/json': { schema: resolver(obj as unknown as StandardSchemaV1) }} }
|
|
67
69
|
|
|
@@ -134,12 +136,16 @@ export async function getRoutes(
|
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
const mw = (handle.mw?.length ? [...handle.mw, ...middlewares] : middlewares).flatMap(obj => {
|
|
140
|
+
return typeof obj == 'string' ? obj : obj?.name || null
|
|
141
|
+
}).filter(Boolean) as Function[]
|
|
142
|
+
|
|
137
143
|
routes.push({
|
|
138
144
|
method, path: uri,
|
|
139
145
|
name,
|
|
140
146
|
file,
|
|
141
147
|
// @ts-ignore
|
|
142
|
-
middlewares,
|
|
148
|
+
middlewares: mw,
|
|
143
149
|
handle,
|
|
144
150
|
desc,
|
|
145
151
|
})
|
|
@@ -339,13 +345,28 @@ export async function cacheRoutes() {
|
|
|
339
345
|
|
|
340
346
|
const middlewares = await getMiddlewares()
|
|
341
347
|
const configs = await getConfigs()
|
|
348
|
+
const handlers = [
|
|
349
|
+
['auth/middlewares', 'Autorized', 'Autorized'],
|
|
350
|
+
]
|
|
342
351
|
|
|
343
|
-
|
|
344
|
-
|
|
352
|
+
for (const r of routes)
|
|
353
|
+
registerHandler(r.name, r.handle)
|
|
345
354
|
|
|
355
|
+
for (const mw of middlewares)
|
|
356
|
+
registerMiddleware(mw.handle)
|
|
357
|
+
|
|
358
|
+
for (const h of handlers) {
|
|
359
|
+
const mod = await IMPORT(join(_rajt, h[0]))
|
|
360
|
+
registerHandler(h[1], mod[h[1]])
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_validator.setParser((rule: Rule) => validator(rule.target, rule.schema, (result, c) => {
|
|
364
|
+
if (!result.success) // @ts-ignore
|
|
365
|
+
return _response.badRequest(result.error)
|
|
366
|
+
}))
|
|
346
367
|
// @ts-ignore
|
|
347
368
|
const openApi = await generateOpenAPI(createApp({ routes, routeRegister: (app: Hono, route: Route) => {
|
|
348
|
-
app[route.method](route.path, describeRoute(route.desc), ...
|
|
369
|
+
app[route.method](route.path, describeRoute(route.desc), ..._resolve(...route.middlewares, route.handle))
|
|
349
370
|
} }), configs?.rajt || {})
|
|
350
371
|
|
|
351
372
|
const iPath = join(_root, '.rajt/imports.mjs')
|
|
@@ -355,13 +376,18 @@ export async function cacheRoutes() {
|
|
|
355
376
|
copyFileSync(localfireEntry, join(_root, '.rajt/localfire.js'))
|
|
356
377
|
|
|
357
378
|
const _rajtDir = await dependencyPath('rajt')
|
|
379
|
+
|
|
380
|
+
stringifyToJS(Object.fromEntries(routes.map(r => ([r.path + r.method, r.name]))))
|
|
381
|
+
|
|
358
382
|
writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
|
|
359
|
-
${env?.length ? `import { Envir } from '${await dependencyPath('t0n')}/
|
|
383
|
+
${env?.length ? `import { Envir } from '${await dependencyPath('t0n')}/src/envir'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
|
|
360
384
|
${Object.entries(configs)?.length ? `import Config from '${_rajtDir}/src/config'\nConfig.add(${stringifyToJS(configs)})` : ''}
|
|
361
385
|
|
|
362
386
|
import { registerHandler, registerMiddleware } from '${_rajtDir}/src/register'
|
|
387
|
+
${handlers.map(([file, name, _export]) => `\nimport ${_export ? `{ ${name} }` : name} from '${_rajtDir}/src/${file}'\nregisterHandler('${name}', ${name})`).join('\n')}
|
|
363
388
|
|
|
364
389
|
${Object.entries(openApi)?.length ? `registerHandler('RAJT_OPENAPI', ${stringifyToJS(openApi)})` : ''}
|
|
390
|
+
Config.set('routes', ${stringifyToJS(Object.fromEntries(routes.map(r => ([r.path+'$'+verbAlias[r.method], r.name]))))})
|
|
365
391
|
|
|
366
392
|
${routes.map(r => `import ${r.name} from '../${normalizeImportPath(r.file)}'`).join('\n')}
|
|
367
393
|
${middlewares.map(r => `import ${r.name} from '../${normalizeImportPath(r.file)}'`).join('\n')}
|
|
@@ -370,9 +396,7 @@ try {
|
|
|
370
396
|
const handlers = {${routes.map(r => r.name).join()}}
|
|
371
397
|
|
|
372
398
|
for (const [name, handler] of Object.entries(handlers)) {
|
|
373
|
-
|
|
374
|
-
registerHandler(name, handler)
|
|
375
|
-
}
|
|
399
|
+
registerHandler(name, handler)
|
|
376
400
|
}
|
|
377
401
|
|
|
378
402
|
const middlewares = {${middlewares.map(r => r.name).join()}}
|
|
@@ -388,7 +412,7 @@ try {
|
|
|
388
412
|
const rPath = join(_root, '.rajt/routes.json')
|
|
389
413
|
ensureDir(rPath)
|
|
390
414
|
writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
|
|
391
|
-
route.method,
|
|
415
|
+
verbAlias[route.method],
|
|
392
416
|
route.path,
|
|
393
417
|
route.middlewares,
|
|
394
418
|
route.name,
|
package/src/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ 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
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/environment.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
const prd = Symbol('prd')
|
|
2
2
|
const dev = Symbol('dev')
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
let env = process.env.RAJT_ENV || prd
|
|
5
6
|
|
|
6
|
-
export const getEnv = () => env
|
|
7
|
+
export const getEnv = () => env // @ts-ignore
|
|
7
8
|
export const setEnv = (e: symbol) => env = e
|
|
8
9
|
|
|
9
|
-
export function detectEnvironment() {
|
|
10
|
+
export function detectEnvironment(): symbol {
|
|
10
11
|
try {
|
|
11
12
|
if (
|
|
12
|
-
process.env?.npm_lifecycle_event
|
|
13
|
-
|| process.env?.npm_lifecycle_script
|
|
13
|
+
process.env?.npm_lifecycle_event === 'dev'
|
|
14
|
+
|| process.env?.npm_lifecycle_script === 'rajt'
|
|
14
15
|
|| process.env?.AWS_SAM_LOCAL
|
|
15
16
|
// || process?.argv?.includes('--dev')
|
|
16
17
|
|| process?.argv?.some(arg => ['-port', '-platform', '--dev', '--development', '--watch'].includes(arg))
|
|
@@ -24,10 +25,11 @@ export function detectEnvironment() {
|
|
|
24
25
|
return prd
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export const isEnv = (e: symbol) => env
|
|
28
|
+
export const isEnv = (e: symbol) => env === e
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
export const
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
export const isDev = () => env === dev
|
|
32
|
+
export const isProd = () => env === prd
|
|
31
33
|
|
|
32
34
|
export const isDevelopment = isDev
|
|
33
35
|
export const isProduction = isProd
|
package/src/utils/resolve.ts
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import { getHandler } from '../register'
|
|
2
2
|
|
|
3
|
-
export function resolve(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 _
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
export function resolveMiddleware(obj: any) {
|
|
@@ -35,11 +48,3 @@ export function resolveMiddleware(obj: any) {
|
|
|
35
48
|
|
|
36
49
|
throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
|
|
37
50
|
}
|
|
38
|
-
|
|
39
|
-
export function mw(...objs: string[]): Function[] {
|
|
40
|
-
return objs.flatMap(obj => {
|
|
41
|
-
if (typeof obj != 'string') return null
|
|
42
|
-
// @ts-ignore
|
|
43
|
-
return getHandler(obj)?.mw || null
|
|
44
|
-
}).flat().filter(Boolean)
|
|
45
|
-
}
|
package/src/validator.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import type * as z from 'zod'
|
|
2
1
|
import { zValidator } from '@hono/zod-validator'
|
|
3
2
|
import response from './response'
|
|
3
|
+
import type * as z from 'zod'
|
|
4
4
|
import type {
|
|
5
|
-
Rule, Rules,
|
|
5
|
+
Rule, Rules, RuleFn,
|
|
6
6
|
ValidationTargets,
|
|
7
|
+
zObject,
|
|
8
|
+
MiddlewareHandler,
|
|
7
9
|
} from './types'
|
|
8
10
|
|
|
9
11
|
export default class $Validator {
|
|
10
|
-
private static cache = new Map<string,
|
|
12
|
+
private static cache = new Map<string, RuleFn>()
|
|
11
13
|
|
|
12
14
|
private static createRule<T extends keyof ValidationTargets>(
|
|
13
15
|
target: T,
|
|
14
|
-
schema:
|
|
16
|
+
schema: zObject
|
|
15
17
|
): Rule {
|
|
16
18
|
return {
|
|
17
19
|
target,
|
|
@@ -24,7 +26,7 @@ export default class $Validator {
|
|
|
24
26
|
if (this.cache.has(target))
|
|
25
27
|
return this.cache.get(target)
|
|
26
28
|
|
|
27
|
-
const fn = (schema:
|
|
29
|
+
const fn = (schema: zObject) => this.createRule(target, schema)
|
|
28
30
|
this.cache.set(target, fn)
|
|
29
31
|
return fn
|
|
30
32
|
}
|
|
@@ -36,11 +38,33 @@ export default class $Validator {
|
|
|
36
38
|
static readonly header = $Validator.fn('header')!
|
|
37
39
|
static readonly cookie = $Validator.fn('cookie')!
|
|
38
40
|
|
|
41
|
+
static #parser = (rule: Rule) => zValidator(rule.target, rule.schema, (result, c) => {
|
|
42
|
+
if (!result.success) // @ts-ignore
|
|
43
|
+
return response.badRequest(formatZodErrors(result.error.issues || []))
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
static setParser(parser: (rule: Rule) => MiddlewareHandler) {
|
|
47
|
+
this.#parser = parser
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
static parse(rules: Rules): Function[] {
|
|
40
51
|
return (Array.isArray(rules) ? rules : [rules]) // @ts-ignore
|
|
41
|
-
.flatMap(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
.flatMap(this.#parser)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatZodErrors(issues: z.ZodIssue[]): Record<string, string[]> {
|
|
57
|
+
const result: Record<string, string[]> = {}
|
|
58
|
+
|
|
59
|
+
for (const issue of issues) {
|
|
60
|
+
const path = issue.path.join('.')
|
|
61
|
+
const key = path || 'root'
|
|
62
|
+
|
|
63
|
+
if (!result[key])
|
|
64
|
+
result[key] = []
|
|
65
|
+
|
|
66
|
+
result[key].push(issue.message)
|
|
45
67
|
}
|
|
68
|
+
|
|
69
|
+
return result
|
|
46
70
|
}
|
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
|
-
}
|