rajt 0.0.97 → 0.0.99
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 +15 -25
- package/src/auth/token.ts +46 -12
- package/src/cli/commands/build.ts +1 -1
- package/src/cli/commands/make.ts +1 -1
- package/src/cli/commands/migrate.ts +1 -1
- package/src/cli/index.ts +1 -1
- package/src/cli/utils.ts +2 -2
- package/src/config.ts +1 -0
- package/src/create-app.ts +10 -15
- package/src/dev.ts +2 -2
- package/src/open-api/register.ts +67 -0
- package/src/open-api/spec.ts +41 -0
- package/src/prod.ts +2 -2
- package/src/request.ts +6 -4
- package/src/routes.ts +7 -5
- package/src/types.ts +1 -1
- package/src/utils/log.ts +1 -1
- package/src/utils/resolve.ts +10 -0
- package/src/validator.ts +7 -7
- package/src/oas.ts +0 -110
- package/src/utils/colors.ts +0 -102
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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.99",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
|
+
"files": ["bin", "src"],
|
|
7
8
|
"bin": {
|
|
8
9
|
"rajt": "./bin/rajt.js"
|
|
9
10
|
},
|
|
@@ -22,10 +23,6 @@
|
|
|
22
23
|
"./env": "./src/utils/environment.ts",
|
|
23
24
|
"./length": "./src/utils/length.ts"
|
|
24
25
|
},
|
|
25
|
-
"files": [
|
|
26
|
-
"bin",
|
|
27
|
-
"src"
|
|
28
|
-
],
|
|
29
26
|
"scripts": {
|
|
30
27
|
"aws:local": "bun run --silent aws:build && bun run --silent sam:local",
|
|
31
28
|
"aws:package": "bun run --silent aws:build && bun run --silent sam:package",
|
|
@@ -38,51 +35,44 @@
|
|
|
38
35
|
"zip": "zip -j ../../lambda.zip ../../.rajt/dist/index.js"
|
|
39
36
|
},
|
|
40
37
|
"dependencies": {
|
|
41
|
-
"cripta": "^0.1",
|
|
42
|
-
"forj": "0.1.7",
|
|
43
|
-
"t0n": "^0.1",
|
|
44
38
|
"@hono/node-server": "^1.19.9",
|
|
45
39
|
"@hono/standard-validator": "^0.2.2",
|
|
40
|
+
"@hono/zod-validator": "^0.7.6",
|
|
46
41
|
"@scalar/hono-api-reference": "^0.9.40",
|
|
47
42
|
"chokidar": "^3.5.2",
|
|
48
43
|
"citty": "^0.1.6",
|
|
49
44
|
"consola": "^3.4.2",
|
|
45
|
+
"cripta": "^0.1.10",
|
|
50
46
|
"dotenv": "^16.5.0",
|
|
51
47
|
"esbuild": "^0.25.2",
|
|
48
|
+
"forj": "^0.1.8",
|
|
52
49
|
"hono": "^4.11.7",
|
|
53
|
-
"hono-openapi": "^1.
|
|
50
|
+
"hono-openapi": "^1.3.0",
|
|
54
51
|
"localflare-api": "^0.4.2",
|
|
55
52
|
"localflare-core": "^0.4.2",
|
|
56
|
-
"miniflare": "^4.
|
|
53
|
+
"miniflare": "^4.20260301.1",
|
|
57
54
|
"pathe": "^2.0",
|
|
58
55
|
"quansync": "^0.2.11",
|
|
56
|
+
"t0n": "^0.1.12",
|
|
59
57
|
"tiny-glob": "^0.2",
|
|
60
|
-
"tsx": "^4.19.
|
|
61
|
-
"ua-parser-js": "^2.0.8",
|
|
58
|
+
"tsx": "^4.19.4",
|
|
62
59
|
"wrangler": "^4.61.0",
|
|
63
60
|
"zod": "^4.3.6",
|
|
64
|
-
"zod-openapi": "4"
|
|
61
|
+
"zod-openapi": "^5.4.6"
|
|
65
62
|
},
|
|
66
63
|
"devDependencies": {
|
|
67
|
-
"@cloudflare/workers-types": "^4.
|
|
68
|
-
"@miniflare/core": "^2.14.4",
|
|
69
|
-
"@miniflare/d1": "^2.14.4",
|
|
70
|
-
"@miniflare/durable-objects": "^2.14.4",
|
|
71
|
-
"@miniflare/kv": "^2.14.4",
|
|
72
|
-
"@miniflare/r2": "^2.14.4",
|
|
73
|
-
"@miniflare/scheduler": "^2.14.4",
|
|
74
|
-
"@miniflare/sites": "^2.14.4",
|
|
75
|
-
"@miniflare/storage-file": "^2.14.4",
|
|
76
|
-
"@miniflare/web-sockets": "^2.14.4",
|
|
64
|
+
"@cloudflare/workers-types": "^4.20260113.0",
|
|
77
65
|
"@types/node": "^25.1.0",
|
|
78
66
|
"bun-types": "^1.3.8",
|
|
79
67
|
"typescript": "^5.9.3"
|
|
80
68
|
},
|
|
69
|
+
"packageManager": "bun@1.3.10",
|
|
81
70
|
"engines": {
|
|
82
71
|
"node": ">=18.0.0"
|
|
83
72
|
},
|
|
84
73
|
"resolutions": {
|
|
85
|
-
"@smithy/types": "^4.3.0"
|
|
74
|
+
"@smithy/types": "^4.3.0",
|
|
75
|
+
"zod": "^4.3.6"
|
|
86
76
|
},
|
|
87
77
|
"publishConfig": {
|
|
88
78
|
"registry": "https://registry.npmjs.org"
|
|
@@ -111,4 +101,4 @@
|
|
|
111
101
|
"bun",
|
|
112
102
|
"nodejs"
|
|
113
103
|
]
|
|
114
|
-
}
|
|
104
|
+
}
|
package/src/auth/token.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import { Envir } from 't0n'
|
|
2
|
-
import { Token as Factory } from 'cripta'
|
|
3
|
-
import { UAParser } from 'ua-parser-js'
|
|
1
|
+
import { Envir, parseUA } from 't0n'
|
|
2
|
+
import { Token as Factory, sha256 } from 'cripta'
|
|
4
3
|
import type { IRequest } from '@/types'
|
|
5
4
|
|
|
5
|
+
const STABLE_HEADERS = [
|
|
6
|
+
'host',
|
|
7
|
+
'connection',
|
|
8
|
+
'sec-ch-ua',
|
|
9
|
+
'sec-ch-ua-mobile',
|
|
10
|
+
'sec-ch-ua-platform',
|
|
11
|
+
'upgrade-insecure-requests',
|
|
12
|
+
'user-agent',
|
|
13
|
+
'accept',
|
|
14
|
+
'sec-fetch-site',
|
|
15
|
+
'sec-fetch-mode',
|
|
16
|
+
'sec-fetch-user',
|
|
17
|
+
'sec-fetch-dest',
|
|
18
|
+
'accept-encoding',
|
|
19
|
+
'accept-language',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
function headerOrdering(headers: Record<string, string>) {
|
|
23
|
+
const order: string[] = []
|
|
24
|
+
|
|
25
|
+
for (const name in headers) {
|
|
26
|
+
const key = name.toLowerCase()
|
|
27
|
+
|
|
28
|
+
if (STABLE_HEADERS.includes(key))
|
|
29
|
+
order.push(key)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return order
|
|
33
|
+
}
|
|
34
|
+
|
|
6
35
|
export class Token {
|
|
7
36
|
static #cookieName: string = '__auth_'
|
|
8
37
|
static #name: string = 'Authorization'
|
|
@@ -48,8 +77,7 @@ export class Token {
|
|
|
48
77
|
return Factory.parse(token)
|
|
49
78
|
.issuedBy(serveHost)
|
|
50
79
|
.permittedFor(host)
|
|
51
|
-
.withClaim('
|
|
52
|
-
.withClaim('i', this.ip(req))
|
|
80
|
+
.withClaim('_', this.fingerprint(req))
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
static create(req: IRequest, user: any, exp: number = 7200) {
|
|
@@ -61,8 +89,7 @@ export class Token {
|
|
|
61
89
|
.permittedFor(host)
|
|
62
90
|
.issuedAt(time)
|
|
63
91
|
.expiresAt(time + exp)
|
|
64
|
-
.withClaim('
|
|
65
|
-
.withClaim('i', this.ip(req))
|
|
92
|
+
.withClaim('_', this.fingerprint(req))
|
|
66
93
|
.body(user)
|
|
67
94
|
}
|
|
68
95
|
|
|
@@ -90,11 +117,18 @@ export class Token {
|
|
|
90
117
|
}
|
|
91
118
|
}
|
|
92
119
|
|
|
93
|
-
static
|
|
94
|
-
const ua = req
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
120
|
+
static fingerprint(req: IRequest) {
|
|
121
|
+
const ua = parseUA(req.header('user-agent'))
|
|
122
|
+
|
|
123
|
+
const id = sha256(
|
|
124
|
+
// (req.header('accept-language') || '')
|
|
125
|
+
this.ip(req)
|
|
126
|
+
+ ua.browser.name + ua.browser.version.split('.')[0]
|
|
127
|
+
+ ua.os.type + ua.os.name
|
|
128
|
+
+ headerOrdering(req.header()).join('')
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return id.substring(0, 8) + id.substring(56, 64)
|
|
98
132
|
}
|
|
99
133
|
|
|
100
134
|
static ip(req: IRequest) {
|
package/src/cli/commands/make.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Migrator } from 'forj'
|
|
|
4
4
|
import { makeFile, hasExt, camelCase, kebabCase } from '../utils'
|
|
5
5
|
import { _root } from '../../utils/paths'
|
|
6
6
|
import { event, error } from '../../utils/log'
|
|
7
|
-
import { dim } from '
|
|
7
|
+
import { dim } from 't0n/color'
|
|
8
8
|
import * as stub from '../stubs'
|
|
9
9
|
|
|
10
10
|
export default defineCommand({
|
|
@@ -2,7 +2,7 @@ import { defineCommand } from 'citty'
|
|
|
2
2
|
import { spawn } from 'node:child_process'
|
|
3
3
|
import { join } from 'pathe'
|
|
4
4
|
import { Migrator } from 'forj'
|
|
5
|
-
import { gray } from '
|
|
5
|
+
import { gray } from 't0n/color'
|
|
6
6
|
import { getRuntime, cleanDB, cleanDir } from '../utils'
|
|
7
7
|
import { _root } from '../../utils/paths'
|
|
8
8
|
import { wait, info, event, rn, error, log } from '../../utils/log'
|
package/src/cli/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defineCommand, runMain, renderUsage } from 'citty'
|
|
|
2
2
|
import type { ArgsDef, CommandDef } from 'citty'
|
|
3
3
|
import { createConsola } from 'consola'
|
|
4
4
|
import { logo } from '../utils/log'
|
|
5
|
-
import { isColorSupported, gray } from '
|
|
5
|
+
import { isColorSupported, gray } from 't0n/color'
|
|
6
6
|
import { version as rajtVersion } from '../../package.json'
|
|
7
7
|
|
|
8
8
|
import dev from './commands/dev'
|
package/src/cli/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import esbuild from 'esbuild'
|
|
2
2
|
import { Miniflare } from 'miniflare'
|
|
3
|
-
import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync
|
|
3
|
+
import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync } from 'node:fs'
|
|
4
4
|
import { readFile, stat, writeFile } from 'node:fs/promises'
|
|
5
5
|
import { basename, dirname, join, relative } from 'pathe'
|
|
6
6
|
import { createHash, createHmac } from 'node:crypto'
|
|
@@ -9,7 +9,7 @@ import { findWranglerConfig, parseWranglerConfig, WRANGLER_CONFIG_FILES } from '
|
|
|
9
9
|
import type { WranglerConfig, LocalflareManifest } from 'localflare-core'
|
|
10
10
|
|
|
11
11
|
import chokidar from 'chokidar'
|
|
12
|
-
import { gray, bold, italic, purple, yellow, red } from '
|
|
12
|
+
import { gray, bold, italic, purple, yellow, red } from 't0n/color'
|
|
13
13
|
import type { ChokidarEventName, Platform } from './types'
|
|
14
14
|
|
|
15
15
|
import { cacheRoutes } from '../routes'
|
package/src/config.ts
CHANGED
package/src/create-app.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
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,
|
|
8
7
|
HTTPResponseError,
|
|
9
8
|
ServerOptions,
|
|
10
9
|
} from './types'
|
|
11
|
-
import { resolve, resolveMiddleware } from './utils/resolve'
|
|
10
|
+
import { resolve, resolveMiddleware, mw } from './utils/resolve'
|
|
12
11
|
import { getMiddlewares, getHandler } from './register'
|
|
13
12
|
import request, { GET_REQUEST } from './request'
|
|
14
13
|
import response from './response'
|
|
15
14
|
import { isDev } from './utils/environment'
|
|
16
|
-
import { gray } from '
|
|
15
|
+
import { gray } from 't0n/color'
|
|
16
|
+
import { Route } from './types'
|
|
17
17
|
|
|
18
18
|
const NFHandler = () => response.notFound()
|
|
19
19
|
const EHandler = async (e: Error | HTTPResponseError) => {
|
|
@@ -108,24 +108,19 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
|
108
108
|
|
|
109
109
|
if (options?.init) options.init(app)
|
|
110
110
|
|
|
111
|
-
const routes = options?.routes || []
|
|
112
|
-
|
|
111
|
+
const routes = options?.routes || [] // @ts-ignore
|
|
112
|
+
const routeRegister = options?.routeRegister ? options.routeRegister : (_: Hono, route: Route) => { // @ts0ignore
|
|
113
113
|
if (Array.isArray(route)) { // @ts-ignore
|
|
114
|
-
|
|
114
|
+
_[route[0]](route[1], ...mw(route[2], route[3]), ...resolve(getHandler(route[3]), route[3]))
|
|
115
115
|
} else { // @ts-ignore
|
|
116
|
-
|
|
116
|
+
_[route.method](route.path, ...mw(route.middlewares, route.name), ...resolve(route.handle, route.name))
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
for (const route of routes)
|
|
121
|
+
routeRegister(app, route)
|
|
122
122
|
|
|
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)
|
|
123
|
+
return app
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
export default createApp
|
package/src/dev.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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
|
|
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'
|
|
@@ -28,6 +28,6 @@ Ability.roles = Config.get('roles', {})
|
|
|
28
28
|
|
|
29
29
|
// @ts-ignore
|
|
30
30
|
const app = createApp({ routes })
|
|
31
|
-
|
|
31
|
+
registerOpenAPI(app, Config.get('rajt', {}))
|
|
32
32
|
|
|
33
33
|
export default app
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { basicAuth } from 'hono/basic-auth'
|
|
2
|
+
import { Scalar } from '@scalar/hono-api-reference'
|
|
3
|
+
import { Envir } from 't0n'
|
|
4
|
+
import response from '../response'
|
|
5
|
+
import { getHandler } from '../register'
|
|
6
|
+
import type { Hono } from '../types'
|
|
7
|
+
|
|
8
|
+
export function config(opts: any) {
|
|
9
|
+
const docs = opts?.docs ?? {}
|
|
10
|
+
|
|
11
|
+
const appName = Envir.get('APP_NAME', 'API Docs')
|
|
12
|
+
const appVersion = Envir.get('APP_VERSION') || Envir.get('VERSION_HASH') || '1.0.0'
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
...docs,
|
|
16
|
+
disable: !!docs?.disable,
|
|
17
|
+
path: docs?.path || '/docs',
|
|
18
|
+
auth: docs?.auth || {},
|
|
19
|
+
agent: !docs?.agent,
|
|
20
|
+
appName, appVersion,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerOpenAPI(app: Hono, conf: any) {
|
|
25
|
+
const opts = config(conf)
|
|
26
|
+
if (opts.disable) return
|
|
27
|
+
|
|
28
|
+
if (opts?.auth?.username && opts?.auth?.password) {
|
|
29
|
+
app.use(opts.path +'/*', async (c, next) => {
|
|
30
|
+
const realm = opts.auth?.realm || 'Docs'
|
|
31
|
+
const unauthorized = response.unauthorized(
|
|
32
|
+
null,
|
|
33
|
+
{'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
|
|
34
|
+
)
|
|
35
|
+
if (!c.req.raw.headers.get('Authorization')) return unauthorized
|
|
36
|
+
const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await auth(c, next)
|
|
40
|
+
} catch {
|
|
41
|
+
return unauthorized
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
app.get(opts.path + '/openapi', async () => response.json(getHandler('RAJT_OPENAPI')))
|
|
47
|
+
|
|
48
|
+
app.get(
|
|
49
|
+
opts.path,
|
|
50
|
+
Scalar({
|
|
51
|
+
theme: 'saturn',
|
|
52
|
+
url: opts.path +'/openapi',
|
|
53
|
+
showDeveloperTools: 'never',
|
|
54
|
+
telemetry: false,
|
|
55
|
+
documentDownloadType: 'json', //'direct',
|
|
56
|
+
isLoading: true,
|
|
57
|
+
persistAuth: true,
|
|
58
|
+
hideClientButton: true,
|
|
59
|
+
pageTitle: opts.appName,
|
|
60
|
+
agent: { disabled: opts.agent },
|
|
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
|
+
// hideDownloadButton: true,
|
|
63
|
+
// onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
|
|
64
|
+
customCss: `[href="https://www.scalar.com"]{display:none}`,
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Config from './config'
|
|
2
|
-
import
|
|
2
|
+
import { registerOpenAPI } from './open-api/register'
|
|
3
3
|
import { Ability } from './auth'
|
|
4
4
|
import createApp from './create-app'
|
|
5
5
|
|
|
@@ -14,6 +14,6 @@ Ability.roles = Config.get('roles', {})
|
|
|
14
14
|
|
|
15
15
|
// @ts-ignore
|
|
16
16
|
const app = createApp({ routes })
|
|
17
|
-
|
|
17
|
+
registerOpenAPI(app, Config.get('rajt', {}))
|
|
18
18
|
|
|
19
19
|
export { app }
|
package/src/request.ts
CHANGED
|
@@ -78,11 +78,13 @@ export default class $Request {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
get ip(): string | undefined {
|
|
81
|
-
return this.#c.req.header('cf-connecting-ip')
|
|
82
|
-
|| this.#c.req.header('x-forwarded-for')?.split(',')
|
|
81
|
+
return this.#c.req.header('cf-connecting-ip') // cf
|
|
82
|
+
|| this.#c.req.header('x-forwarded-for')?.split(',').at(-1)?.trim() // aws lambda
|
|
83
83
|
|| this.#c.env?.aws?.lambda?.event?.requestContext?.identity?.sourceIp
|
|
84
|
-
|| this.#c.
|
|
85
|
-
|| this.#c.
|
|
84
|
+
|| this.#c.env?.event?.Records[0]?.cf?.request?.clientIp // aws lambda@edge
|
|
85
|
+
|| this.#c.req.header('x-real-ip') // vercel
|
|
86
|
+
|| this.#c.env?.context?.ip // netlify
|
|
87
|
+
|| this.#c.env?.remoteAddr?.hostname // deno
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
get userAgent(): string | undefined {
|
package/src/routes.ts
CHANGED
|
@@ -13,12 +13,12 @@ import versionSHA from './utils/version-sha'
|
|
|
13
13
|
import type { Routes, StandardSchemaV1 } from './types'
|
|
14
14
|
import { rn, substep, warn } from './utils/log'
|
|
15
15
|
import { _root } from './utils/paths'
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import { resolver } from 'hono-openapi'
|
|
16
|
+
import { generateOpenAPI } from './open-api/spec'
|
|
17
|
+
import type * as z from 'zod'
|
|
18
|
+
import { describeRoute, resolver } from 'hono-openapi'
|
|
20
19
|
import { mimes } from 'hono/utils/mime'
|
|
21
20
|
import { STATUS_CODES } from 'node:http'
|
|
21
|
+
import { mw, resolve as _resolve } from './utils/resolve'
|
|
22
22
|
|
|
23
23
|
import { highlightedMethod, highlightedURI } from './cli/utils'
|
|
24
24
|
|
|
@@ -344,7 +344,9 @@ export async function cacheRoutes() {
|
|
|
344
344
|
middlewares.forEach(mw => registerMiddleware(mw.handle))
|
|
345
345
|
|
|
346
346
|
// @ts-ignore
|
|
347
|
-
const openApi = await
|
|
347
|
+
const openApi = await generateOpenAPI(createApp({ routes, routeRegister: (app: Hono, route: Route) => {
|
|
348
|
+
app[route.method](route.path, describeRoute(route.desc), ...mw(route.middlewares, route.name), ..._resolve(route.handle, route.name))
|
|
349
|
+
} }), configs?.rajt || {})
|
|
348
350
|
|
|
349
351
|
const iPath = join(_root, '.rajt/imports.mjs')
|
|
350
352
|
ensureDir(iPath)
|
package/src/types.ts
CHANGED
|
@@ -10,7 +10,7 @@ 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
14
|
import Action from './action'
|
|
15
15
|
import request from './request'
|
|
16
16
|
import response from './response'
|
package/src/utils/log.ts
CHANGED
package/src/utils/resolve.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getHandler } from '../register'
|
|
2
|
+
|
|
1
3
|
export function resolve(obj: any, id: string) {
|
|
2
4
|
if (typeof obj == 'function' && obj?.length == 2)
|
|
3
5
|
return [obj]
|
|
@@ -33,3 +35,11 @@ export function resolveMiddleware(obj: any) {
|
|
|
33
35
|
|
|
34
36
|
throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
|
|
35
37
|
}
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import type * as z from 'zod'
|
|
2
|
+
import { zValidator } from '@hono/zod-validator'
|
|
3
3
|
import response from './response'
|
|
4
4
|
import type {
|
|
5
5
|
Rule, Rules,
|
|
@@ -7,11 +7,11 @@ import type {
|
|
|
7
7
|
} from './types'
|
|
8
8
|
|
|
9
9
|
export default class $Validator {
|
|
10
|
-
private static cache = new Map<string, (schema: ZodObject<any>) => Rule>()
|
|
10
|
+
private static cache = new Map<string, (schema: z.ZodObject<any>) => Rule>()
|
|
11
11
|
|
|
12
12
|
private static createRule<T extends keyof ValidationTargets>(
|
|
13
13
|
target: T,
|
|
14
|
-
schema: ZodObject<any>
|
|
14
|
+
schema: z.ZodObject<any>
|
|
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: ZodObject<any>) => this.createRule(target, schema)
|
|
27
|
+
const fn = (schema: z.ZodObject<any>) => 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/oas.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { STATUS_CODES } from 'node:http'
|
|
2
|
-
import { basicAuth } from 'hono/basic-auth'
|
|
3
|
-
import { Scalar } from '@scalar/hono-api-reference'
|
|
4
|
-
import { generateSpecs, resolver } from 'hono-openapi'
|
|
5
|
-
import { Envir } from 't0n'
|
|
6
|
-
import z from 'zod'
|
|
7
|
-
import response from './response'
|
|
8
|
-
import { getHandler } from './register'
|
|
9
|
-
import type { Hono } from './types'
|
|
10
|
-
|
|
11
|
-
// @ts-ignore TODO: remove dependency of 'package.json'...
|
|
12
|
-
import packageJson from '../../../package.json'
|
|
13
|
-
|
|
14
|
-
export default class OAS {
|
|
15
|
-
static #config(config: any) {
|
|
16
|
-
const docs = config?.docs ?? {}
|
|
17
|
-
|
|
18
|
-
const appName = Envir.get('APP_NAME', packageJson?.name || 'API Docs')
|
|
19
|
-
const appVersion = Envir.get('APP_VERSION', packageJson?.version || '1.0.0')
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
...docs,
|
|
23
|
-
disable: !!docs?.disable,
|
|
24
|
-
path: docs?.path || '/docs',
|
|
25
|
-
auth: docs?.auth || {},
|
|
26
|
-
agent: !docs?.agent,
|
|
27
|
-
appName, appVersion,
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
static register(app: Hono, config: any) {
|
|
32
|
-
const opts = this.#config(config)
|
|
33
|
-
if (opts.disable) return
|
|
34
|
-
|
|
35
|
-
if (opts?.auth?.username && opts?.auth?.password) {
|
|
36
|
-
app.use(opts.path +'/*', async (c, next) => {
|
|
37
|
-
const realm = opts.auth?.realm || 'Docs'
|
|
38
|
-
const unauthorized = response.unauthorized(
|
|
39
|
-
null,
|
|
40
|
-
{'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
|
|
41
|
-
)
|
|
42
|
-
if (!c.req.raw.headers.get('Authorization')) return unauthorized
|
|
43
|
-
const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
await auth(c, next)
|
|
47
|
-
} catch {
|
|
48
|
-
return unauthorized
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
app.get(opts.path + '/openapi', async () => response.json(getHandler('RAJT_OPENAPI')))
|
|
54
|
-
|
|
55
|
-
app.get(
|
|
56
|
-
opts.path,
|
|
57
|
-
Scalar({
|
|
58
|
-
theme: 'saturn',
|
|
59
|
-
url: opts.path +'/openapi',
|
|
60
|
-
showDeveloperTools: 'never',
|
|
61
|
-
telemetry: false,
|
|
62
|
-
documentDownloadType: 'json', //'direct',
|
|
63
|
-
isLoading: true,
|
|
64
|
-
persistAuth: true,
|
|
65
|
-
hideClientButton: true,
|
|
66
|
-
pageTitle: opts.appName,
|
|
67
|
-
agent: { disabled: opts.agent },
|
|
68
|
-
slug: opts.appName?.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ opts.appVersion,
|
|
69
|
-
// hideDownloadButton: true,
|
|
70
|
-
// onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
|
|
71
|
-
customCss: `[href="https://www.scalar.com"]{display:none}`,
|
|
72
|
-
})
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
static async generateSpecs(app: Hono, config: any) {
|
|
77
|
-
const opts = this.#config(config)
|
|
78
|
-
if (opts.disable) return {}
|
|
79
|
-
|
|
80
|
-
return await generateSpecs(app, {
|
|
81
|
-
documentation: {
|
|
82
|
-
info: {
|
|
83
|
-
title: opts.appName,
|
|
84
|
-
version: opts.appVersion,
|
|
85
|
-
description: Envir.get('APP_DESCRIPTION', packageJson?.description || ''),
|
|
86
|
-
},
|
|
87
|
-
components: {
|
|
88
|
-
securitySchemes: {
|
|
89
|
-
JWT: {
|
|
90
|
-
type: 'http',
|
|
91
|
-
scheme: 'bearer',
|
|
92
|
-
bearerFormat: 'JWT',
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
responses: {
|
|
96
|
-
500: {
|
|
97
|
-
description: STATUS_CODES[500],
|
|
98
|
-
content: { // @ts-ignore
|
|
99
|
-
'application/json': await resolver(z.object({
|
|
100
|
-
m: z.array(z.string()),
|
|
101
|
-
})).toOpenAPISchema(),
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
...opts?.responses,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
}
|
package/src/utils/colors.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// ISC License
|
|
2
|
-
|
|
3
|
-
// Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov
|
|
4
|
-
|
|
5
|
-
// Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
-
// purpose with or without fee is hereby granted, provided that the above
|
|
7
|
-
// copyright notice and this permission notice appear in all copies.
|
|
8
|
-
|
|
9
|
-
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
-
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
-
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
-
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
-
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
-
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
-
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
-
//
|
|
17
|
-
// https://github.com/alexeyraspopov/picocolors/commit/6f0a4638348ed20633d623ee973f9c9a96f65104
|
|
18
|
-
|
|
19
|
-
import { getColorEnabledAsync } from 'hono/utils/color'
|
|
20
|
-
|
|
21
|
-
export const enabled = await getColorEnabledAsync()
|
|
22
|
-
export const isColorSupported = enabled
|
|
23
|
-
|
|
24
|
-
// const { env, stdout } = globalThis?.process ?? {}
|
|
25
|
-
// const enabled =
|
|
26
|
-
// env &&
|
|
27
|
-
// !env.NO_COLOR &&
|
|
28
|
-
// (env.FORCE_COLOR || (stdout?.isTTY && !env.CI && env.TERM !== 'dumb'))
|
|
29
|
-
|
|
30
|
-
const replaceClose = (
|
|
31
|
-
str: string,
|
|
32
|
-
close: string,
|
|
33
|
-
replace: string,
|
|
34
|
-
index: number
|
|
35
|
-
): string => {
|
|
36
|
-
const start = str.substring(0, index) + replace
|
|
37
|
-
const end = str.substring(index + close.length)
|
|
38
|
-
const nextIndex = end.indexOf(close)
|
|
39
|
-
return ~nextIndex
|
|
40
|
-
? start + replaceClose(end, close, replace, nextIndex)
|
|
41
|
-
: start + end
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const formatter = (open: string, close: string, replace = open) => {
|
|
45
|
-
if (!enabled) return String
|
|
46
|
-
return (input: string) => {
|
|
47
|
-
const string = '' + input
|
|
48
|
-
const index = string.indexOf(close, open.length)
|
|
49
|
-
return ~index
|
|
50
|
-
? open + replaceClose(string, close, replace, index) + close
|
|
51
|
-
: open + string + close
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const reset = enabled ? (s: string) => `\x1b[0m${s}\x1b[0m` : String
|
|
56
|
-
export const bold = formatter('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m')
|
|
57
|
-
export const dim = formatter('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m')
|
|
58
|
-
export const italic = formatter('\x1b[3m', '\x1b[23m')
|
|
59
|
-
export const underline = formatter('\x1b[4m', '\x1b[24m')
|
|
60
|
-
export const inverse = formatter('\x1b[7m', '\x1b[27m')
|
|
61
|
-
export const hidden = formatter('\x1b[8m', '\x1b[28m')
|
|
62
|
-
export const strikethrough = formatter('\x1b[9m', '\x1b[29m')
|
|
63
|
-
|
|
64
|
-
const endText = '\x1b[39m'
|
|
65
|
-
export const black = formatter('\x1b[30m', endText)
|
|
66
|
-
export const red = formatter('\x1b[31m', endText)
|
|
67
|
-
export const green = formatter('\x1b[32m', endText)
|
|
68
|
-
export const yellow = formatter('\x1b[33m', endText)
|
|
69
|
-
export const blue = formatter('\x1b[34m', endText)
|
|
70
|
-
export const magenta = formatter('\x1b[35m', endText)
|
|
71
|
-
export const purple = formatter('\x1b[38;2;173;127;168m', endText)
|
|
72
|
-
export const cyan = formatter('\x1b[36m', endText)
|
|
73
|
-
export const white = formatter('\x1b[37m', endText)
|
|
74
|
-
export const gray = formatter('\x1b[90m', endText)
|
|
75
|
-
|
|
76
|
-
const endBg = '\x1b[49m'
|
|
77
|
-
export const bgBlack = formatter('\x1b[40m', endBg)
|
|
78
|
-
export const bgRed = formatter('\x1b[41m', endBg)
|
|
79
|
-
export const bgGreen = formatter('\x1b[42m', endBg)
|
|
80
|
-
export const bgYellow = formatter('\x1b[43m', endBg)
|
|
81
|
-
export const bgBlue = formatter('\x1b[44m', endBg)
|
|
82
|
-
export const bgMagenta = formatter('\x1b[45m', endBg)
|
|
83
|
-
export const bgCyan = formatter('\x1b[46m', endBg)
|
|
84
|
-
export const bgWhite = formatter('\x1b[47m', endBg)
|
|
85
|
-
|
|
86
|
-
export const blackBright = formatter("\x1b[90m", endText)
|
|
87
|
-
export const redBright = formatter("\x1b[91m", endText)
|
|
88
|
-
export const greenBright = formatter("\x1b[92m", endText)
|
|
89
|
-
export const yellowBright = formatter("\x1b[93m", endText)
|
|
90
|
-
export const blueBright = formatter("\x1b[94m", endText)
|
|
91
|
-
export const magentaBright = formatter("\x1b[95m", endText)
|
|
92
|
-
export const cyanBright = formatter("\x1b[96m", endText)
|
|
93
|
-
export const whiteBright = formatter("\x1b[97m", endText)
|
|
94
|
-
|
|
95
|
-
export const bgBlackBright = formatter("\x1b[100m", endBg)
|
|
96
|
-
export const bgRedBright = formatter("\x1b[101m", endBg)
|
|
97
|
-
export const bgGreenBright = formatter("\x1b[102m", endBg)
|
|
98
|
-
export const bgYellowBright = formatter("\x1b[103m", endBg)
|
|
99
|
-
export const bgBlueBright = formatter("\x1b[104m", endBg)
|
|
100
|
-
export const bgMagentaBright = formatter("\x1b[105m", endBg)
|
|
101
|
-
export const bgCyanBright = formatter("\x1b[106m", endBg)
|
|
102
|
-
export const bgWhiteBright = formatter("\x1b[107m", endBg)
|