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 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.75",
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/zod-validator": "^0.7.6",
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
@@ -12,4 +12,4 @@ Ability.fromRoutes(routes)
12
12
  Ability.roles = Config.get('roles', {})
13
13
 
14
14
  // @ts-ignore
15
- export const app = createApp({ routes })
15
+ export const app = createApp({ routes, configs: Config.get('rajt', {}) })
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(data?: string, status?: StatusCode) {
32
- return this.raw(status, data, 'text/plain; charset=UTF-8' as BaseMime)
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.raw(204)
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: string): Response
111
- static notFound(msg?: string) {
112
- return this.raw(404, msg)
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 { zValidator } from '@hono/zod-validator'
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 => zValidator(rule.target, rule.schema, (result, c) => {
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
  }))