rajt 0.0.77 → 0.0.79

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.77",
4
+ "version": "0.0.79",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "bin": {
@@ -58,6 +58,14 @@ export const formatTime = (ms: number) => {
58
58
  return `${(ms / 1000).toFixed(2)}s`
59
59
  }
60
60
 
61
+ const nodeModules = [
62
+ 'crypto', 'buffer', 'http', 'fs', 'path', 'events', 'stream', 'util',
63
+ 'url', 'querystring', 'os', 'child_process', 'cluster', 'dns', 'net',
64
+ 'tls', 'https', 'zlib', 'readline', 'repl', 'vm', 'module', 'assert',
65
+ 'timers', 'string_decoder', 'punycode', 'perf_hooks', 'dgram', 'tty',
66
+ 'worker_threads', 'wasi'
67
+ ].flatMap(lib => ['node:'+ lib, lib])
68
+
61
69
  const dist = '.rajt/dist'
62
70
  export const build = async (platform: Platform) => {
63
71
  const startTime = Date.now()
@@ -99,8 +107,7 @@ export const build = async (platform: Platform) => {
99
107
  '@aws-sdk', '@smithy',
100
108
  ...(isCF ? [
101
109
  'cloudflare:workers',
102
- 'node:crypto', 'crypto',
103
- 'node:buffer', 'buffer',
110
+ ...nodeModules,
104
111
  ] : []),
105
112
  ],
106
113
  metafile: true,
package/src/create-app.ts CHANGED
@@ -1,15 +1,12 @@
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
+ import { describeRoute } from 'hono-openapi'
7
5
  import { Envir, Datte } from 't0n'
8
6
  import type {
9
7
  Env, Context, Next,
10
8
  HTTPResponseError,
11
9
  ServerOptions,
12
- DescribeRouteOptions,
13
10
  } from './types'
14
11
  import { resolve, resolveMiddleware } from './utils/resolve'
15
12
  import { getMiddlewares, getHandler } from './register'
@@ -17,7 +14,6 @@ import request, { GET_REQUEST } from './request'
17
14
  import response from './response'
18
15
  import { isDev } from './utils/environment'
19
16
  import { gray } from './utils/colors'
20
- import packageJson from '../../../package.json'
21
17
 
22
18
  const NFHandler = () => response.notFound()
23
19
  const EHandler = async (e: Error | HTTPResponseError) => {
@@ -65,7 +61,7 @@ const EHandler = async (e: Error | HTTPResponseError) => {
65
61
  // stack: isDev (? e.stack : undefined
66
62
  }
67
63
 
68
- export const createApp = <E extends Env>(options?: ServerOptions<E> & { configs: any }) => {
64
+ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
69
65
  // const root = options?.root ?? '/'
70
66
  const app = options?.app ?? new Hono<E>()
71
67
 
@@ -121,96 +117,6 @@ export const createApp = <E extends Env>(options?: ServerOptions<E> & { configs:
121
117
  }
122
118
  }
123
119
 
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
-
214
120
  return app
215
121
  }
216
122
 
package/src/dev.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { dirname, join } from 'node:path'
2
2
  import { config } from 'dotenv'
3
- import createApp from './create-app'
4
3
  import { getRoutes, getMiddlewares, getConfigs } from './routes'
5
4
  import { registerHandler, registerMiddleware } from './register'
6
5
  import Config from './config'
6
+ import OAS from './oas'
7
+ import createApp from './create-app'
7
8
  import { Ability } from 'rajt/auth'
8
9
  import { setEnv, detectEnvironment } from 'rajt/env'
9
10
 
@@ -25,6 +26,8 @@ middlewares.forEach(mw => registerMiddleware(mw.handle))
25
26
  Ability.fromRoutes(routes)
26
27
  Ability.roles = Config.get('roles', {})
27
28
 
28
- const app = createApp({ routes, configs: Config.get('rajt', {}) })
29
+ // @ts-ignore
30
+ const app = createApp({ routes })
31
+ OAS.register(app, Config.get('rajt', {}))
29
32
 
30
33
  export default app
package/src/oas.ts ADDED
@@ -0,0 +1,110 @@
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
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/prod.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import Config from './config'
2
+ import OAS from './oas'
2
3
  import { Ability } from './auth'
3
4
  import createApp from './create-app'
4
5
 
@@ -12,4 +13,7 @@ Ability.fromRoutes(routes)
12
13
  Ability.roles = Config.get('roles', {})
13
14
 
14
15
  // @ts-ignore
15
- export const app = createApp({ routes, configs: Config.get('rajt', {}) })
16
+ const app = createApp({ routes })
17
+ OAS.register(app, Config.get('rajt', {}))
18
+
19
+ export { app }
package/src/routes.ts CHANGED
@@ -5,12 +5,20 @@ import glob from 'tiny-glob'
5
5
  import { config } from 'dotenv'
6
6
 
7
7
  import { IMPORT } from 't0n'
8
+ import { registerHandler, registerMiddleware } from './register'
9
+ import createApp from './create-app'
8
10
  import { isAnonFn } from './utils/func'
9
11
  import ensureDir from './utils/ensuredir'
10
12
  import versionSHA from './utils/version-sha'
11
- import type { Routes } from './types'
13
+ import type { Routes, StandardSchemaV1 } from './types'
12
14
  import { substep, warn } from './utils/log'
13
15
 
16
+ import OAS from './oas'
17
+ import z from 'zod'
18
+ import { resolver } from 'hono-openapi'
19
+ import { mimes } from 'hono/utils/mime'
20
+ import { STATUS_CODES } from 'node:http'
21
+
14
22
  const __filename = new URL(import.meta.url).pathname
15
23
  const __root = resolve(dirname(__filename), '../../..')
16
24
 
@@ -43,6 +51,50 @@ const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string
43
51
  }
44
52
  }
45
53
 
54
+ function isZodSchema(obj: any): obj is z.ZodType {
55
+ return (
56
+ obj &&
57
+ typeof obj == 'object' &&
58
+ ('_def' in obj || '_type' in obj) &&
59
+ (obj.safeParse !== undefined || obj.parse !== undefined)
60
+ )
61
+ }
62
+
63
+ function ResolveDescribeSchema(obj: any, deep: boolean = false) {
64
+ if (!obj || typeof obj !== 'object') return obj
65
+ if (isZodSchema(obj))
66
+ return { content: {'application/json': { schema: resolver(obj as unknown as StandardSchemaV1) }} }
67
+
68
+ if (obj.content && typeof obj.content == 'object') {
69
+ for (const mediaType in obj.content) {
70
+ const contentItem = obj.content[mediaType]
71
+ if (contentItem?.schema && isZodSchema(contentItem.schema))
72
+ contentItem.schema = resolver(contentItem.schema)
73
+
74
+ if (mediaType in mimes) {
75
+ obj.content[mimes[mediaType]] = contentItem
76
+ delete obj.content[mediaType]
77
+ }
78
+ }
79
+
80
+ return obj
81
+ }
82
+
83
+ for (const key in obj) {
84
+ if (obj[key] && typeof obj[key] == 'object') {
85
+ obj[key] = ResolveDescribeSchema(obj[key], true)
86
+
87
+ if (!deep && !obj[key]?.description) {
88
+ const desription = (new Response(null, { status: Number(key) })).statusText || STATUS_CODES[key]
89
+ if (desription) obj[key].description = desription
90
+ }
91
+
92
+ }
93
+ }
94
+
95
+ return obj
96
+ }
97
+
46
98
  let hasDuplicatedRoutes = false
47
99
  export async function getRoutes(
48
100
  dirs: string[] = ['actions', 'features', 'routes']
@@ -70,9 +122,10 @@ export async function getRoutes(
70
122
  ...d,
71
123
  responses: {
72
124
  500: {$ref: '#/components/responses/500'},
73
- ...d?.responses,
125
+ ...ResolveDescribeSchema(d?.responses),
74
126
  }
75
127
  }
128
+
76
129
  routes.push({
77
130
  method, path: uri,
78
131
  name,
@@ -261,16 +314,24 @@ export async function cacheRoutes() {
261
314
  throw new Error("The app can't build with duplicate routes")
262
315
 
263
316
  const middlewares = await getMiddlewares()
264
- const configs = Object.entries(await getConfigs())
317
+ const configs = await getConfigs()
318
+
319
+ routes.forEach(r => registerHandler(r.name, r.handle))
320
+ middlewares.forEach(mw => registerMiddleware(mw.handle))
321
+
322
+ // @ts-ignore
323
+ const openApi = await OAS.generateSpecs(createApp({ routes }), configs?.rajt || {})
265
324
 
266
325
  const iPath = join(__root, '.rajt/import-routes.mjs')
267
326
  ensureDir(iPath)
268
327
  writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
269
328
  ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
270
- ${configs?.length ? `import Config from '../node_modules/rajt/src/config'\nConfig.add({${configs.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
329
+ ${Object.entries(configs)?.length ? `import Config from '../node_modules/rajt/src/config'\nConfig.add(${stringifyToJS(configs)})` : ''}
271
330
 
272
331
  import { registerHandler, registerMiddleware } from '../node_modules/rajt/src/register'
273
332
 
333
+ ${Object.entries(openApi)?.length ? `registerHandler('RAJT_OPENAPI', ${stringifyToJS(openApi)})` : ''}
334
+
274
335
  ${routes.map(r => `import ${r.name} from '../${normalizeImportPath(r.file)}'`).join('\n')}
275
336
  ${middlewares.map(r => `import ${r.name} from '../${normalizeImportPath(r.file)}'`).join('\n')}
276
337
 
package/src/types.ts CHANGED
@@ -6,17 +6,19 @@ import type {
6
6
  } from 'hono'
7
7
  import type { ResponseHeader } from 'hono/utils/headers'
8
8
  // import type { StatusCode } from 'hono/utils/http-status'
9
- import type { BaseMime } from 'hono/utils/mime'
10
- import type { DescribeRouteOptions } from 'hono-openapi'
9
+ import { mimes, type BaseMime } from 'hono/utils/mime'
10
+ import type { OpenAPIV3_1, OpenAPIV3 } from 'openapi-types'
11
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
12
+ import type { DescribeRouteOptions as RawDescribeRouteOptions, ResolverReturnType } from 'hono-openapi'
11
13
  import z from 'zod'
12
14
  import Action from './action'
13
15
  import request from './request'
14
16
  import response from './response'
15
17
  import validator from './validator'
16
18
 
17
-
18
19
  // export type { H, Handler, HandlerResponse } from 'hono/types'
19
20
  export type {
21
+ Hono,
20
22
  Env, Context, Next,
21
23
  // ErrorHandler, NotFoundHandler,
22
24
  MiddlewareHandler, // TODO: remove..
@@ -29,7 +31,7 @@ export type {
29
31
  RedirectStatusCode,
30
32
  StatusCode,
31
33
  } from 'hono/utils/http-status'
32
- export type { BaseMime, DescribeRouteOptions }
34
+ export type { BaseMime, StandardSchemaV1 }
33
35
 
34
36
  type PublicMethods<T> = {
35
37
  [K in keyof T]: K extends `#${string}` | `$${string}` | symbol | 'prototype' ? never : K
@@ -46,6 +48,26 @@ export type Rule = {
46
48
  }
47
49
  export type Rules = Rule[] | Rule | null
48
50
 
51
+ export type StandardSchema = StandardSchemaV1 | OpenAPIV3_1.ReferenceObject
52
+ export type DescribeRouteOptions = Omit<RawDescribeRouteOptions, 'responses'> & {
53
+ responses?: {
54
+ [key: string | number]: (Omit<OpenAPIV3.ResponseObject, 'description' | 'headers' | 'content' | 'links'> & {
55
+ description?: string,
56
+ headers?: { // TODO maybe dont accept ResolverReturnType
57
+ [header: string]: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.HeaderObject
58
+ },
59
+ content?: {
60
+ [key: BaseMime | keyof typeof mimes]: Omit<OpenAPIV3_1.MediaTypeObject, 'schema'> & {
61
+ schema?: StandardSchema | OpenAPIV3_1.SchemaObject | ResolverReturnType,
62
+ },
63
+ },
64
+ links?: { // TODO maybe dont accept ResolverReturnType
65
+ [link: string]: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.LinkObject
66
+ },
67
+ }) | StandardSchema,
68
+ },
69
+ }
70
+
49
71
  export type Route = {
50
72
  method: string,
51
73
  path: string,
package/src/validator.ts CHANGED
@@ -40,7 +40,7 @@ export default class $Validator {
40
40
  return (Array.isArray(rules) ? rules : [rules]) // @ts-ignore
41
41
  .flatMap(rule => validator(rule.target, rule.schema, (result, c) => {
42
42
  if (!result.success) // @ts-ignore
43
- return response.badRequest({ ...result.error.flatten()[rule.eTarget] })
43
+ return response.badRequest(result.error)
44
44
  }))
45
45
  }
46
46
  }