rajt 0.0.75 → 0.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/src/create-app.ts +98 -3
- package/src/dev.ts +1 -1
- package/src/http.ts +19 -1
- package/src/prod.ts +1 -1
- package/src/response.ts +35 -35
- package/src/routes.ts +11 -0
- package/src/types.ts +3 -1
- package/src/validator.ts +8 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rajt",
|
|
3
3
|
"description": "A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.76",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"bin": {
|
|
@@ -42,18 +42,21 @@
|
|
|
42
42
|
"forj": "^0.0.6",
|
|
43
43
|
"t0n": "^0.1",
|
|
44
44
|
"@hono/node-server": "^1.19.9",
|
|
45
|
-
"@hono/
|
|
45
|
+
"@hono/standard-validator": "^0.2.2",
|
|
46
46
|
"@iarna/toml": "^2.2.5",
|
|
47
|
+
"@scalar/hono-api-reference": "^0.9.40",
|
|
47
48
|
"chokidar-cli": "^3.0.0",
|
|
48
49
|
"citty": "^0.1.6",
|
|
49
50
|
"dotenv": "^16.5.0",
|
|
50
51
|
"esbuild": "^0.25.2",
|
|
51
52
|
"hono": "^4.11.7",
|
|
53
|
+
"hono-openapi": "^1.2.0",
|
|
52
54
|
"miniflare": "^4.20251217.0",
|
|
53
55
|
"tiny-glob": "^0.2",
|
|
54
56
|
"tsx": "^4.19.3",
|
|
55
57
|
"ua-parser-js": "^2.0.8",
|
|
56
|
-
"wrangler": "^4.61.0"
|
|
58
|
+
"wrangler": "^4.61.0",
|
|
59
|
+
"zod-openapi": "4"
|
|
57
60
|
},
|
|
58
61
|
"devDependencies": {
|
|
59
62
|
"@cloudflare/workers-types": "^4.20251230.0",
|
package/src/create-app.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import { logger } from 'hono/logger'
|
|
3
3
|
import { matchedRoutes } from 'hono/route'
|
|
4
|
+
import { basicAuth } from 'hono/basic-auth'
|
|
5
|
+
import { openAPIRouteHandler, describeRoute, resolver } from 'hono-openapi'
|
|
6
|
+
import { Scalar } from '@scalar/hono-api-reference'
|
|
4
7
|
import { Envir, Datte } from 't0n'
|
|
5
8
|
import type {
|
|
6
9
|
Env, Context, Next,
|
|
7
10
|
HTTPResponseError,
|
|
8
11
|
ServerOptions,
|
|
12
|
+
DescribeRouteOptions,
|
|
9
13
|
} from './types'
|
|
10
14
|
import { resolve, resolveMiddleware } from './utils/resolve'
|
|
11
15
|
import { getMiddlewares, getHandler } from './register'
|
|
@@ -13,6 +17,7 @@ import request, { GET_REQUEST } from './request'
|
|
|
13
17
|
import response from './response'
|
|
14
18
|
import { isDev } from './utils/environment'
|
|
15
19
|
import { gray } from './utils/colors'
|
|
20
|
+
import packageJson from '../../../package.json'
|
|
16
21
|
|
|
17
22
|
const NFHandler = () => response.notFound()
|
|
18
23
|
const EHandler = async (e: Error | HTTPResponseError) => {
|
|
@@ -60,7 +65,7 @@ const EHandler = async (e: Error | HTTPResponseError) => {
|
|
|
60
65
|
// stack: isDev (? e.stack : undefined
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
68
|
+
export const createApp = <E extends Env>(options?: ServerOptions<E> & { configs: any }) => {
|
|
64
69
|
// const root = options?.root ?? '/'
|
|
65
70
|
const app = options?.app ?? new Hono<E>()
|
|
66
71
|
|
|
@@ -110,12 +115,102 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
|
110
115
|
const routes = options?.routes || []
|
|
111
116
|
for (const route of routes) {
|
|
112
117
|
if (Array.isArray(route)) { // @ts-ignore
|
|
113
|
-
app[route[0]](route[1], ...mw(route[2], route[3]), ...resolve(getHandler(route[3]), route[3]))
|
|
118
|
+
app[route[0]](route[1], describeRoute(route[4]), ...mw(route[2], route[3]), ...resolve(getHandler(route[3]), route[3]))
|
|
114
119
|
} else { // @ts-ignore
|
|
115
|
-
app[route.method](route.path, ...mw(route.middlewares, route.name), ...resolve(route.handle, route.name))
|
|
120
|
+
app[route.method](route.path, describeRoute(route.desc), ...mw(route.middlewares, route.name), ...resolve(route.handle, route.name))
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
|
|
124
|
+
const _docs = options?.configs?.docs ?? {}
|
|
125
|
+
const docs = {
|
|
126
|
+
..._docs,
|
|
127
|
+
disable: !!_docs?.disable,
|
|
128
|
+
path: _docs?.path || '/docs',
|
|
129
|
+
auth: _docs?.auth || {},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (docs.disable) return app
|
|
133
|
+
|
|
134
|
+
if (docs?.auth?.username && docs?.auth?.password) {
|
|
135
|
+
app.use(docs.path +'/*', async (c, next) => {
|
|
136
|
+
const realm = docs.auth?.realm || 'Docs'
|
|
137
|
+
const unauthorized = response.unauthorized(
|
|
138
|
+
null,
|
|
139
|
+
{'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
|
|
140
|
+
)
|
|
141
|
+
if (!c.req.raw.headers.get('Authorization')) return unauthorized
|
|
142
|
+
const auth = basicAuth({ username: docs.auth.username, password: docs.auth.password, realm })
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await auth(c, next)
|
|
146
|
+
} catch {
|
|
147
|
+
return unauthorized
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const appName = Envir.get('APP_NAME', packageJson?.name || 'API Docs')
|
|
153
|
+
const appVersion = Envir.get('APP_VERSION', packageJson?.version || '1.0.0')
|
|
154
|
+
|
|
155
|
+
app.get(
|
|
156
|
+
docs.path +'/openapi',
|
|
157
|
+
openAPIRouteHandler(app, {
|
|
158
|
+
documentation: {
|
|
159
|
+
info: {
|
|
160
|
+
title: appName,
|
|
161
|
+
version: appVersion,
|
|
162
|
+
description: Envir.get('APP_DESCRIPTION', packageJson?.description || ''),
|
|
163
|
+
},
|
|
164
|
+
components: {
|
|
165
|
+
securitySchemes: {
|
|
166
|
+
JWT: {
|
|
167
|
+
type: 'http',
|
|
168
|
+
scheme: 'bearer',
|
|
169
|
+
bearerFormat: 'JWT',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
responses: {
|
|
173
|
+
500: {
|
|
174
|
+
description: 'Internal Server Error',
|
|
175
|
+
content: {
|
|
176
|
+
'application/json': {
|
|
177
|
+
schema: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
m: {
|
|
181
|
+
type: 'array',
|
|
182
|
+
items: { type: 'string' },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
...docs?.responses,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
app.get(
|
|
197
|
+
docs.path,
|
|
198
|
+
Scalar({
|
|
199
|
+
theme: 'saturn',
|
|
200
|
+
url: docs.path +'/openapi',
|
|
201
|
+
showDeveloperTools: 'never',
|
|
202
|
+
telemetry: false,
|
|
203
|
+
documentDownloadType: 'json', //'direct',
|
|
204
|
+
isLoading: true,
|
|
205
|
+
persistAuth: true,
|
|
206
|
+
hideClientButton: true,
|
|
207
|
+
slug: appName.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ appVersion,
|
|
208
|
+
// hideDownloadButton: true,
|
|
209
|
+
// onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
|
|
210
|
+
customCss: `[href="https://www.scalar.com"]{display:none}`,
|
|
211
|
+
})
|
|
212
|
+
)
|
|
213
|
+
|
|
119
214
|
return app
|
|
120
215
|
}
|
|
121
216
|
|
package/src/dev.ts
CHANGED
|
@@ -25,6 +25,6 @@ middlewares.forEach(mw => registerMiddleware(mw.handle))
|
|
|
25
25
|
Ability.fromRoutes(routes)
|
|
26
26
|
Ability.roles = Config.get('roles', {})
|
|
27
27
|
|
|
28
|
-
const app = createApp({ routes })
|
|
28
|
+
const app = createApp({ routes, configs: Config.get('rajt', {}) })
|
|
29
29
|
|
|
30
30
|
export default app
|
package/src/http.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { Ability } from './auth'
|
|
|
5
5
|
import mergeMiddleware from './utils/merge-middleware'
|
|
6
6
|
import type {
|
|
7
7
|
Context, Next,
|
|
8
|
-
IRequest
|
|
8
|
+
IRequest,
|
|
9
|
+
DescribeRouteOptions,
|
|
9
10
|
} from './types'
|
|
10
11
|
|
|
11
12
|
function method(method: string, ...args: any[]): void | ClassDecorator {
|
|
@@ -104,6 +105,13 @@ export function Auth(...args: any[]): void | ClassDecorator {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
function _auth(target: Function | any) {
|
|
108
|
+
if (!target?.d) target.d = {}
|
|
109
|
+
if (!target.d?.security) target.d.security = []
|
|
110
|
+
target.d.security.push({JWT: []})
|
|
111
|
+
|
|
112
|
+
if (!target.d?.responses) target.d.responses = {}
|
|
113
|
+
target.d.responses[401] = {description: 'Unauthorized'}
|
|
114
|
+
|
|
107
115
|
mergeMiddleware(target, async (c: Context, next: Next) => {
|
|
108
116
|
const req = c.get(GET_REQUEST as unknown as string) as IRequest
|
|
109
117
|
const ability = Ability.fromAction(target)
|
|
@@ -114,3 +122,13 @@ function _auth(target: Function | any) {
|
|
|
114
122
|
await next()
|
|
115
123
|
})
|
|
116
124
|
}
|
|
125
|
+
|
|
126
|
+
function _describe(spec: DescribeRouteOptions): ClassDecorator{
|
|
127
|
+
return (target: any) => {
|
|
128
|
+
target.d = spec
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const OpenApi = _describe
|
|
133
|
+
export const Describe = _describe
|
|
134
|
+
export const Desc = _describe
|
package/src/prod.ts
CHANGED
package/src/response.ts
CHANGED
|
@@ -28,13 +28,13 @@ export default class $Response {
|
|
|
28
28
|
return new Response(body ?? null, b)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
static text(
|
|
32
|
-
return this.raw(status,
|
|
31
|
+
static text(str?: string, status?: StatusCode, headers?: HeaderRecord) {
|
|
32
|
+
return this.raw(status, str, 'text/plain; charset=UTF-8' as BaseMime, headers)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
static json<T>(data?: T, status?: StatusCode, headers?: HeaderRecord) {
|
|
36
36
|
if (data == null)
|
|
37
|
-
return this.raw(status)
|
|
37
|
+
return this.raw(status, null, undefined, headers)
|
|
38
38
|
|
|
39
39
|
return this.raw(status, JSON.stringify(data), 'application/json', headers)
|
|
40
40
|
}
|
|
@@ -64,70 +64,70 @@ export default class $Response {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
static ok(): Response
|
|
67
|
-
static ok<T>(data: T): Response
|
|
68
|
-
static ok<T>(data?: T) {
|
|
69
|
-
return this.json(data, 200)
|
|
67
|
+
static ok<T>(data: T, headers?: HeaderRecord): Response
|
|
68
|
+
static ok<T>(data?: T, headers?: HeaderRecord) {
|
|
69
|
+
return this.json(data, 200, headers)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
static created(): Response
|
|
73
|
-
static created<T>(data: T): Response
|
|
74
|
-
static created<T>(data?: T) {
|
|
75
|
-
return this.json(data, 201)
|
|
73
|
+
static created<T>(data: T, headers?: HeaderRecord): Response
|
|
74
|
+
static created<T>(data?: T, headers?: HeaderRecord) {
|
|
75
|
+
return this.json(data, 201, headers)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
static accepted(): Response
|
|
79
|
-
static accepted<T>(data: T): Response
|
|
80
|
-
static accepted<T>(data?: T) {
|
|
81
|
-
return this.json(data, 202)
|
|
79
|
+
static accepted<T>(data: T, headers?: HeaderRecord): Response
|
|
80
|
+
static accepted<T>(data?: T, headers?: HeaderRecord) {
|
|
81
|
+
return this.json(data, 202, headers)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
static deleted() {
|
|
85
|
-
return this.noContent()
|
|
84
|
+
static deleted(headers?: HeaderRecord) {
|
|
85
|
+
return this.noContent(headers)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
static noContent() {
|
|
89
|
-
return this.
|
|
88
|
+
static noContent(headers?: HeaderRecord) {
|
|
89
|
+
return this.json(null, 204, headers)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
static badRequest(): Response
|
|
93
|
-
static badRequest(errors?: Errors, msg?: string) {
|
|
94
|
-
return this.error(errors, msg, 400)
|
|
93
|
+
static badRequest(errors?: Errors, msg?: string, headers?: HeaderRecord) {
|
|
94
|
+
return this.error(errors, msg, 400, headers)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
static unauthorized(): Response
|
|
98
|
-
static unauthorized<T>(data: T): Response
|
|
99
|
-
static unauthorized<T>(data?: T) {
|
|
100
|
-
return this.json(data, 401)
|
|
98
|
+
static unauthorized<T>(data: T, headers?: HeaderRecord): Response
|
|
99
|
+
static unauthorized<T>(data?: T, headers?: HeaderRecord) {
|
|
100
|
+
return this.json(data, 401, headers)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
static forbidden(): Response
|
|
104
|
-
static forbidden<T>(data: T): Response
|
|
105
|
-
static forbidden<T>(data?: T) {
|
|
106
|
-
return this.json(data, 403)
|
|
104
|
+
static forbidden<T>(data: T, headers?: HeaderRecord): Response
|
|
105
|
+
static forbidden<T>(data?: T, headers?: HeaderRecord) {
|
|
106
|
+
return this.json(data, 403, headers)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
static notFound(): Response
|
|
110
|
-
static notFound(msg:
|
|
111
|
-
static notFound(msg?:
|
|
112
|
-
return this.
|
|
110
|
+
static notFound<T>(msg: T, headers?: HeaderRecord): Response
|
|
111
|
+
static notFound<T>(msg?: T, headers?: HeaderRecord) {
|
|
112
|
+
return this.json(msg, 404, headers)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
static conflict(): Response
|
|
116
|
-
static conflict(errors?: Errors, msg?: string) {
|
|
117
|
-
return this.error(errors, msg, 409)
|
|
116
|
+
static conflict(errors?: Errors, msg?: string, headers?: HeaderRecord) {
|
|
117
|
+
return this.error(errors, msg, 409, headers)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
static unsupportedMediaType(): Response
|
|
121
|
-
static unsupportedMediaType(errors?: Errors, msg?: string) {
|
|
122
|
-
return this.error(errors, msg, 415)
|
|
121
|
+
static unsupportedMediaType(errors?: Errors, msg?: string, headers?: HeaderRecord) {
|
|
122
|
+
return this.error(errors, msg, 415, headers)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
static internalError(): Response
|
|
126
|
-
static internalError(errors?: Errors, msg?: string) {
|
|
127
|
-
return this.error(errors, msg, 500)
|
|
126
|
+
static internalError(errors?: Errors, msg?: string, headers?: HeaderRecord) {
|
|
127
|
+
return this.error(errors, msg, 500, headers)
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
static error(errors?: Errors, msg?: string, status?: ContentfulStatusCode) {
|
|
130
|
+
static error(errors?: Errors, msg?: string, status?: ContentfulStatusCode, headers?: HeaderRecord) {
|
|
131
131
|
status ??= 500
|
|
132
132
|
if (!errors && !msg)
|
|
133
133
|
return this.raw(status, msg)
|
|
@@ -136,6 +136,6 @@ export default class $Response {
|
|
|
136
136
|
if (msg) resp.m = msg
|
|
137
137
|
if (errors) resp.e = errors
|
|
138
138
|
|
|
139
|
-
return this.json(resp, status)
|
|
139
|
+
return this.json(resp, status, headers)
|
|
140
140
|
}
|
|
141
141
|
}
|
package/src/routes.ts
CHANGED
|
@@ -64,6 +64,15 @@ export async function getRoutes(
|
|
|
64
64
|
|
|
65
65
|
const m = handle?.m?.toLowerCase()
|
|
66
66
|
const [method, uri] = m ? [m, handle?.p] : [extractHttpVerb(path), extractHttpPath(path)]
|
|
67
|
+
const d = handle?.d || {}
|
|
68
|
+
const desc = {
|
|
69
|
+
summary: handle?.d?.summary || name,
|
|
70
|
+
...d,
|
|
71
|
+
responses: {
|
|
72
|
+
500: {$ref: '#/components/responses/500'},
|
|
73
|
+
...d?.responses,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
67
76
|
routes.push({
|
|
68
77
|
method, path: uri,
|
|
69
78
|
name,
|
|
@@ -71,6 +80,7 @@ export async function getRoutes(
|
|
|
71
80
|
// @ts-ignore
|
|
72
81
|
middlewares,
|
|
73
82
|
handle,
|
|
83
|
+
desc,
|
|
74
84
|
})
|
|
75
85
|
|
|
76
86
|
if (!keys.has(name)) {
|
|
@@ -290,6 +300,7 @@ try {
|
|
|
290
300
|
route.path,
|
|
291
301
|
route.middlewares,
|
|
292
302
|
route.name,
|
|
303
|
+
route.desc,
|
|
293
304
|
])))
|
|
294
305
|
}
|
|
295
306
|
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import type { ResponseHeader } from 'hono/utils/headers'
|
|
8
8
|
// import type { StatusCode } from 'hono/utils/http-status'
|
|
9
9
|
import type { BaseMime } from 'hono/utils/mime'
|
|
10
|
+
import type { DescribeRouteOptions } from 'hono-openapi'
|
|
10
11
|
import z from 'zod'
|
|
11
12
|
import Action from './action'
|
|
12
13
|
import request from './request'
|
|
@@ -28,7 +29,7 @@ export type {
|
|
|
28
29
|
RedirectStatusCode,
|
|
29
30
|
StatusCode,
|
|
30
31
|
} from 'hono/utils/http-status'
|
|
31
|
-
export type { BaseMime }
|
|
32
|
+
export type { BaseMime, DescribeRouteOptions }
|
|
32
33
|
|
|
33
34
|
type PublicMethods<T> = {
|
|
34
35
|
[K in keyof T]: K extends `#${string}` | `$${string}` | symbol | 'prototype' ? never : K
|
|
@@ -52,6 +53,7 @@ export type Route = {
|
|
|
52
53
|
file: string,
|
|
53
54
|
middlewares: Function[],
|
|
54
55
|
handle: Handlers,
|
|
56
|
+
desc: DescribeRouteOptions,
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
// export type ActionType = Function | Handler | Action | (new () => Action)
|
package/src/validator.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZodObject } from 'zod'
|
|
2
|
-
import {
|
|
2
|
+
import { validator } from 'hono-openapi'
|
|
3
3
|
import response from './response'
|
|
4
4
|
import type {
|
|
5
5
|
Rule, Rules,
|
|
@@ -29,16 +29,16 @@ export default class $Validator {
|
|
|
29
29
|
return fn
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
static readonly json = $Validator.fn('json')
|
|
33
|
-
static readonly form = $Validator.fn('form')
|
|
34
|
-
static readonly query = $Validator.fn('query')
|
|
35
|
-
static readonly param = $Validator.fn('param')
|
|
36
|
-
static readonly header = $Validator.fn('header')
|
|
37
|
-
static readonly cookie = $Validator.fn('cookie')
|
|
32
|
+
static readonly json = $Validator.fn('json')!
|
|
33
|
+
static readonly form = $Validator.fn('form')!
|
|
34
|
+
static readonly query = $Validator.fn('query')!
|
|
35
|
+
static readonly param = $Validator.fn('param')!
|
|
36
|
+
static readonly header = $Validator.fn('header')!
|
|
37
|
+
static readonly cookie = $Validator.fn('cookie')!
|
|
38
38
|
|
|
39
39
|
static parse(rules: Rules): Function[] {
|
|
40
40
|
return (Array.isArray(rules) ? rules : [rules]) // @ts-ignore
|
|
41
|
-
.flatMap(rule =>
|
|
41
|
+
.flatMap(rule => validator(rule.target, rule.schema, (result, c) => {
|
|
42
42
|
if (!result.success) // @ts-ignore
|
|
43
43
|
return response.badRequest({ ...result.error.flatten()[rule.eTarget] })
|
|
44
44
|
}))
|