rajt 0.0.96 → 0.0.98

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 CHANGED
@@ -93,8 +93,8 @@ function semiver(a, b, bool) {
93
93
  function directly() {
94
94
  try {
95
95
  const arg = (process.argv[1] || '')?.replace(/\\/g, '/');
96
- return arg?.endsWith('node_modules/.bin/rajt')
97
- || arg?.endsWith('node_modules/rajt/bin/rajt.js')
96
+ return arg?.endsWith('.bin/rajt')
97
+ || arg?.endsWith('rajt/bin/rajt.js')
98
98
  } catch {
99
99
  return false
100
100
  }
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.96",
4
+ "version": "0.0.98",
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,44 +35,37 @@
38
35
  "zip": "zip -j ../../lambda.zip ../../.rajt/dist/index.js"
39
36
  },
40
37
  "dependencies": {
41
- "cripta": "^0.1",
42
- "forj": "^0.1",
43
- "t0n": "^0.1",
44
38
  "@hono/node-server": "^1.19.9",
45
39
  "@hono/standard-validator": "^0.2.2",
46
40
  "@scalar/hono-api-reference": "^0.9.40",
47
- "chokidar-cli": "^3.0.0",
41
+ "chokidar": "^3.5.2",
48
42
  "citty": "^0.1.6",
43
+ "consola": "^3.4.2",
44
+ "cripta": "~0.1.10",
49
45
  "dotenv": "^16.5.0",
50
46
  "esbuild": "^0.25.2",
47
+ "forj": "~0.1.8",
51
48
  "hono": "^4.11.7",
52
49
  "hono-openapi": "^1.2.0",
53
50
  "localflare-api": "^0.4.2",
54
51
  "localflare-core": "^0.4.2",
55
- "miniflare": "^4.20251217.0",
52
+ "miniflare": "^4.20260301.1",
56
53
  "pathe": "^2.0",
57
54
  "quansync": "^0.2.11",
55
+ "t0n": "~0.1.11",
58
56
  "tiny-glob": "^0.2",
59
- "tsx": "^4.19.3",
60
- "ua-parser-js": "^2.0.8",
57
+ "tsx": "^4.19.4",
61
58
  "wrangler": "^4.61.0",
59
+ "zod": "^4.3.6",
62
60
  "zod-openapi": "4"
63
61
  },
64
62
  "devDependencies": {
65
- "@cloudflare/workers-types": "^4.20251230.0",
66
- "@miniflare/core": "^2.14.4",
67
- "@miniflare/d1": "^2.14.4",
68
- "@miniflare/durable-objects": "^2.14.4",
69
- "@miniflare/kv": "^2.14.4",
70
- "@miniflare/r2": "^2.14.4",
71
- "@miniflare/scheduler": "^2.14.4",
72
- "@miniflare/sites": "^2.14.4",
73
- "@miniflare/storage-file": "^2.14.4",
74
- "@miniflare/web-sockets": "^2.14.4",
75
- "@types/node": "^20.11.0",
76
- "bun-types": "^1.2.14",
63
+ "@cloudflare/workers-types": "^4.20260113.0",
64
+ "@types/node": "^25.1.0",
65
+ "bun-types": "^1.3.8",
77
66
  "typescript": "^5.9.3"
78
67
  },
68
+ "packageManager": "bun@1.3.10",
79
69
  "engines": {
80
70
  "node": ">=18.0.0"
81
71
  },
@@ -13,12 +13,6 @@ export class Ability {
13
13
  static fromRoutes(actions: Routes) {
14
14
  if (!actions?.length) return
15
15
 
16
- const paths = actions?.map(a => Array.isArray(a) ? a[0] + a[1] : a.method + a.path) || []
17
- const items = new Set(paths)
18
-
19
- if (items.size != actions.length)
20
- throw new Error(`Duplicate routes detected: "${paths.filter((path, index) => paths.indexOf(path) != index).join('", "')}"`)
21
-
22
16
  this.#abilities = Array.from(new Set(actions?.map(a => Array.isArray(a) ? a[3] : a.name) || []))
23
17
  .map(a => this.format(a))
24
18
  .filter(Boolean)
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('u', this.userAgent(req))
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('u', this.userAgent(req))
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 userAgent(req: IRequest) {
94
- const ua = req?.userAgent
95
- if (!ua) return 0
96
- const { browser, device, os } = UAParser(ua)
97
- return (browser?.name || '') + (browser?.major || '') + (device?.model || '') + (os?.name || '')
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) {
@@ -1,5 +1,5 @@
1
1
  import { defineCommand } from 'citty'
2
- import { gray } from '../../utils/colors'
2
+ import { gray } from 't0n/color'
3
3
  import { build, normalizePlatform, platformError } from '../utils'
4
4
  import { wait, error, rn } from '../../utils/log'
5
5
 
@@ -1,8 +1,9 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import { defineCommand } from 'citty'
3
3
 
4
- import { _root, normalizePlatform, platformError, getRuntime } from '../utils'
4
+ import { normalizePlatform, platformError, getRuntime } from '../utils'
5
5
  import { error } from '../../utils/log'
6
+ import { _root } from '../../utils/paths'
6
7
  import { platforms } from '../constants'
7
8
 
8
9
  import build from './build'
@@ -4,11 +4,12 @@ import { spawn, type ChildProcess } from 'node:child_process'
4
4
  import { defineCommand } from 'citty'
5
5
  import type { Miniflare } from 'miniflare'
6
6
  import {
7
- _root, build, wait, watch, normalizePlatform, platformError, getRuntime,
7
+ build, wait, watch, normalizePlatform, platformError, getRuntime,
8
8
  wranglerConfig, createMiniflare, localflareManifest,
9
9
  getDockerHost
10
10
  } from '../utils'
11
11
  import { error, event, log, rn, warn } from '../../utils/log'
12
+ import { _root } from '../../utils/paths'
12
13
  import { withPort } from '../../utils/port'
13
14
  import shutdown from '../../utils/shutdown'
14
15
 
@@ -165,7 +166,9 @@ export default defineCommand({
165
166
  let localflare: Miniflare | null = null
166
167
  const startWorker = async () => {
167
168
  if (worker) await worker.dispose()
168
- if (localflare) await localflare.dispose()
169
+ if (localflare) await localflare.dispose()
170
+
171
+ // await wait(500)
169
172
 
170
173
  const workerConfig = await wranglerConfig()
171
174
  workerConfig.host = host
@@ -179,7 +182,7 @@ export default defineCommand({
179
182
  ...workerConfig.vars,
180
183
  LOCALFLARE_MANIFEST: JSON.stringify(localflareManifest(workerConfig)),
181
184
  },
182
- main: 'node_modules/localflare-api/dist/worker/index.js',
185
+ main: '.rajt/localfire.js',
183
186
  port: 8788,
184
187
  inspectorPort: 9230,
185
188
  })
@@ -1,9 +1,10 @@
1
1
  import { defineCommand } from 'citty'
2
2
  import { join, relative } from 'pathe'
3
3
  import { Migrator } from 'forj'
4
- import { _root, makeFile, hasExt, camelCase, kebabCase } from '../utils'
4
+ import { makeFile, hasExt, camelCase, kebabCase } from '../utils'
5
+ import { _root } from '../../utils/paths'
5
6
  import { event, error } from '../../utils/log'
6
- import { dim } from '../../utils/colors'
7
+ import { dim } from 't0n/color'
7
8
  import * as stub from '../stubs'
8
9
 
9
10
  export default defineCommand({
@@ -2,8 +2,9 @@ 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 '../../utils/colors'
6
- import { _root, getRuntime, cleanDB, cleanDir } from '../utils'
5
+ import { gray } from 't0n/color'
6
+ import { getRuntime, cleanDB, cleanDir } from '../utils'
7
+ import { _root } from '../../utils/paths'
7
8
  import { wait, info, event, rn, error, log } from '../../utils/log'
8
9
 
9
10
  export default defineCommand({
@@ -2,9 +2,9 @@ import { join } from 'pathe'
2
2
  import { defineCommand } from 'citty'
3
3
  import { inspectRoutes } from 'hono/dev'
4
4
  import { IMPORT } from 't0n'
5
- import { gray, purple, red, yellow } from '../../utils/colors'
6
- import { __rajt } from '../utils'
5
+ import { _rajt } from '../../utils/paths'
7
6
  import { rn } from '../../utils/log'
7
+ import { highlightedURI, highlightedMethod } from '../utils'
8
8
 
9
9
  export default defineCommand({
10
10
  meta: {
@@ -26,7 +26,7 @@ export default defineCommand({
26
26
  },
27
27
  },
28
28
  async run({ args }) {
29
- const mod = await IMPORT(join(__rajt, 'dev.ts'))
29
+ const mod = await IMPORT(join(_rajt, 'dev.ts'))
30
30
  const app = mod.default
31
31
 
32
32
  const opts = {
@@ -39,28 +39,6 @@ export default defineCommand({
39
39
  let maxMethodLength = 0
40
40
  let maxPathLength = 0
41
41
 
42
- const colorMethod = (method: string, str?: string) => {
43
- const val = str || method
44
-
45
- switch (method) {
46
- case 'HEAD':
47
- case 'OPTIONS':
48
- case 'CONNECT':
49
- case 'TRACE':
50
- return gray(val)
51
- case 'GET':
52
- return purple(val)
53
- case 'POST':
54
- case 'PUT':
55
- case 'PATCH':
56
- return yellow(val)
57
- case 'DELETE':
58
- return red(val)
59
- }
60
-
61
- return val
62
- }
63
-
64
42
  let routes = inspectRoutes(app)
65
43
  .filter(({ method, path, isMiddleware }) => {
66
44
  const key = method + '-' + path
@@ -88,17 +66,12 @@ export default defineCommand({
88
66
  const { method, path } = route
89
67
 
90
68
  let mLength = method.length
91
- let str = colorMethod(method)
69
+ let str = highlightedMethod(method, null, true)
92
70
 
93
- if (method == 'GET') {
71
+ if (method == 'GET')
94
72
  mLength += 5
95
- str += gray('|') + colorMethod('HEAD')
96
- }
97
73
 
98
- console.log(str + ' '.repeat(maxMethodLength - mLength) +' '+ path.replace(
99
- /(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
100
- _ => colorMethod(method, _)
101
- ))
74
+ console.log(str + ' '.repeat(maxMethodLength - mLength) +' '+ highlightedURI(path, method))
102
75
  })
103
76
 
104
77
  rn()
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 '../utils/colors'
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
@@ -3,21 +3,19 @@ import { Miniflare } from 'miniflare'
3
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
- import crypto, { createHash } from 'node:crypto'
6
+ import { createHash, createHmac } from 'node:crypto'
7
7
 
8
8
  import { findWranglerConfig, parseWranglerConfig, WRANGLER_CONFIG_FILES } from 'localflare-core'
9
9
  import type { WranglerConfig, LocalflareManifest } from 'localflare-core'
10
10
 
11
11
  import chokidar from 'chokidar'
12
- import { gray, bold, italic } from '../utils/colors'
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'
16
16
  import { substep, event, error, wait as wwait, warn, log } from '../utils/log'
17
17
  import { platforms } from './constants'
18
-
19
- export const _root = join(dirname(new URL(import.meta.url).pathname), '../../../../')
20
- export const __rajt = join(_root, 'node_modules/rajt/src')
18
+ import { _rajt, _root } from '../utils/paths'
21
19
 
22
20
  export function normalizePlatform(platform: Platform) {
23
21
  platform = platform?.toLowerCase() as Platform
@@ -91,7 +89,7 @@ export const build = async (platform: Platform) => {
91
89
  // @ts-ignore
92
90
  platform = platform != 'node' ? '-'+ platform : ''
93
91
  const opts = {
94
- entryPoints: [join(__rajt, `prod${platform}.ts`)],
92
+ entryPoints: [join(_rajt, `prod${platform}.ts`)],
95
93
  bundle: true,
96
94
  minify: true,
97
95
  outfile: join(_root, dist +'/index.js'),
@@ -122,6 +120,12 @@ export const build = async (platform: Platform) => {
122
120
  // sourcemap: true,
123
121
  // logLevel: 'info',
124
122
  plugins: [
123
+ {
124
+ name: 'rajt-resolver',
125
+ setup(build) {
126
+ build.onResolve({ filter: /\.rajt[\/\\]/ }, args => ({ path: join(_root, args.path) }))
127
+ }
128
+ },
125
129
  {
126
130
  name: 'preserve-class-names',
127
131
  setup(build) {
@@ -199,11 +203,11 @@ export function cleanDir(path: string) {
199
203
  }
200
204
 
201
205
  function durableObjectNamespace(name: string, uniqueKey: string) {
202
- const key = crypto.createHash('sha256').update(uniqueKey).digest()
203
- const nameHmac = crypto.createHmac('sha256', key).update(name).digest().subarray(0, 16)
206
+ const key = createHash('sha256').update(uniqueKey).digest()
207
+ const nameHmac = createHmac('sha256', key).update(name).digest().subarray(0, 16)
204
208
  return Buffer.concat([
205
209
  nameHmac,
206
- crypto.createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16)
210
+ createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16)
207
211
  ]).toString('hex')
208
212
  }
209
213
 
@@ -474,3 +478,30 @@ export const camelCase = (text: string) =>
474
478
  .filter(word => word.length > 0)
475
479
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
476
480
  .join('')
481
+
482
+ export const highlightedMethod = (method: string, str?: string | null, siblings: boolean = false): string => {
483
+ const val = str || method
484
+
485
+ switch (method) {
486
+ case 'HEAD':
487
+ case 'OPTIONS':
488
+ case 'CONNECT':
489
+ case 'TRACE':
490
+ return gray(val)
491
+ case 'GET':
492
+ return purple(val) + (siblings ? gray('|') + highlightedMethod('HEAD') : '')
493
+ case 'POST':
494
+ case 'PUT':
495
+ case 'PATCH':
496
+ return yellow(val)
497
+ case 'DELETE':
498
+ return red(val)
499
+ }
500
+
501
+ return val
502
+ }
503
+
504
+ export const highlightedURI = (uri: string, method: string) => uri.replace(
505
+ /(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
506
+ _ => highlightedMethod(method, _)
507
+ )
package/src/config.ts CHANGED
@@ -39,6 +39,7 @@ export default class Config {
39
39
  this.#c.clear()
40
40
  }
41
41
 
42
+ // @ts-ignore
42
43
  static get length(): number {
43
44
  return this.#c.length
44
45
  }
package/src/create-app.ts CHANGED
@@ -13,7 +13,7 @@ import { getMiddlewares, getHandler } from './register'
13
13
  import request, { GET_REQUEST } from './request'
14
14
  import response from './response'
15
15
  import { isDev } from './utils/environment'
16
- import { gray } from './utils/colors'
16
+ import { gray } from 't0n/color'
17
17
 
18
18
  const NFHandler = () => response.notFound()
19
19
  const EHandler = async (e: Error | HTTPResponseError) => {
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 OAS from './oas'
6
+ import { registerOpenAPI } from './oas'
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
- OAS.register(app, Config.get('rajt', {}))
31
+ registerOpenAPI(app, Config.get('rajt', {}))
32
32
 
33
33
  export default app
package/src/oas.ts CHANGED
@@ -8,103 +8,98 @@ import response from './response'
8
8
  import { getHandler } from './register'
9
9
  import type { Hono } from './types'
10
10
 
11
- // @ts-ignore TODO: remove dependency of 'package.json'...
12
- import packageJson from '../../../package.json'
11
+ export function config(opts: any) {
12
+ const docs = opts?.docs ?? {}
13
13
 
14
- export default class OAS {
15
- static #config(config: any) {
16
- const docs = config?.docs ?? {}
14
+ const appName = Envir.get('APP_NAME', 'API Docs')
15
+ const appVersion = Envir.get('APP_VERSION') || Envir.get('VERSION_HASH') || '1.0.0'
17
16
 
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
- }
17
+ return {
18
+ ...docs,
19
+ disable: !!docs?.disable,
20
+ path: docs?.path || '/docs',
21
+ auth: docs?.auth || {},
22
+ agent: !docs?.agent,
23
+ appName, appVersion,
29
24
  }
25
+ }
30
26
 
31
- static register(app: Hono, config: any) {
32
- const opts = this.#config(config)
33
- if (opts.disable) return
27
+ export function registerOpenAPI(app: Hono, conf: any) {
28
+ const opts = config(conf)
29
+ if (opts.disable) return
34
30
 
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 })
31
+ if (opts?.auth?.username && opts?.auth?.password) {
32
+ app.use(opts.path +'/*', async (c, next) => {
33
+ const realm = opts.auth?.realm || 'Docs'
34
+ const unauthorized = response.unauthorized(
35
+ null,
36
+ {'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
37
+ )
38
+ if (!c.req.raw.headers.get('Authorization')) return unauthorized
39
+ const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
44
40
 
45
- try {
46
- await auth(c, next)
47
- } catch {
48
- return unauthorized
49
- }
50
- })
51
- }
41
+ try {
42
+ await auth(c, next)
43
+ } catch {
44
+ return unauthorized
45
+ }
46
+ })
47
+ }
52
48
 
53
- app.get(opts.path + '/openapi', async () => response.json(getHandler('RAJT_OPENAPI')))
49
+ app.get(opts.path + '/openapi', async () => response.json(getHandler('RAJT_OPENAPI')))
54
50
 
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
- }
51
+ app.get(
52
+ opts.path,
53
+ Scalar({
54
+ theme: 'saturn',
55
+ url: opts.path +'/openapi',
56
+ showDeveloperTools: 'never',
57
+ telemetry: false,
58
+ documentDownloadType: 'json', //'direct',
59
+ isLoading: true,
60
+ persistAuth: true,
61
+ hideClientButton: true,
62
+ pageTitle: opts.appName,
63
+ agent: { disabled: opts.agent },
64
+ slug: opts.appName?.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ opts.appVersion,
65
+ // hideDownloadButton: true,
66
+ // onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
67
+ customCss: `[href="https://www.scalar.com"]{display:none}`,
68
+ })
69
+ )
70
+ }
75
71
 
76
- static async generateSpecs(app: Hono, config: any) {
77
- const opts = this.#config(config)
78
- if (opts.disable) return {}
72
+ export async function generateOpenAPI(app: Hono, conf: any) {
73
+ const opts = config(conf)
74
+ if (opts.disable) return {}
79
75
 
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
- },
76
+ return await generateSpecs(app, {
77
+ documentation: {
78
+ info: {
79
+ title: opts.appName,
80
+ version: opts.appVersion,
81
+ description: Envir.get('APP_DESCRIPTION', ''),
82
+ },
83
+ components: {
84
+ securitySchemes: {
85
+ JWT: {
86
+ type: 'http',
87
+ scheme: 'bearer',
88
+ bearerFormat: 'JWT',
94
89
  },
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
- },
90
+ },
91
+ responses: {
92
+ 500: {
93
+ description: STATUS_CODES[500],
94
+ content: { // @ts-ignore
95
+ 'application/json': await resolver(z.object({
96
+ m: z.array(z.string()),
97
+ })).toOpenAPISchema(),
103
98
  },
104
- ...opts?.responses,
105
99
  },
100
+ ...opts?.responses,
106
101
  },
107
102
  },
108
- })
109
- }
103
+ },
104
+ })
110
105
  }
package/src/prod.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import Config from './config'
2
- import OAS from './oas'
2
+ import { registerOpenAPI } from './oas'
3
3
  import { Ability } from './auth'
4
4
  import createApp from './create-app'
5
5
 
6
6
  // @ts-ignore
7
- import '../../../.rajt/imports.mjs'
7
+ import '.rajt/imports.mjs'
8
8
  // @ts-ignore
9
- import routes from '../../../.rajt/routes.json'
9
+ import routes from '.rajt/routes.json'
10
10
 
11
11
  // @ts-ignore
12
12
  Ability.fromRoutes(routes)
@@ -14,6 +14,6 @@ Ability.roles = Config.get('roles', {})
14
14
 
15
15
  // @ts-ignore
16
16
  const app = createApp({ routes })
17
- OAS.register(app, Config.get('rajt', {}))
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(',')[0]?.trim()
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.req.header('x-real-ip')
85
- || this.#c.env?.remoteAddr?.hostname
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
@@ -1,5 +1,5 @@
1
- import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
2
- import { dirname, join, resolve } from 'pathe'
1
+ import { copyFileSync, existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
2
+ import { join, resolve, relative } from 'pathe'
3
3
 
4
4
  import glob from 'tiny-glob'
5
5
  import { config } from 'dotenv'
@@ -11,16 +11,16 @@ import { isAnonFn } from './utils/func'
11
11
  import ensureDir from './utils/ensuredir'
12
12
  import versionSHA from './utils/version-sha'
13
13
  import type { Routes, StandardSchemaV1 } from './types'
14
- import { substep, warn } from './utils/log'
14
+ import { rn, substep, warn } from './utils/log'
15
+ import { _root } from './utils/paths'
15
16
 
16
- import OAS from './oas'
17
+ import { generateOpenAPI } from './oas'
17
18
  import z from 'zod'
18
19
  import { resolver } from 'hono-openapi'
19
20
  import { mimes } from 'hono/utils/mime'
20
21
  import { STATUS_CODES } from 'node:http'
21
22
 
22
- const __filename = new URL(import.meta.url).pathname
23
- const __root = resolve(dirname(__filename), '../../..')
23
+ import { highlightedMethod, highlightedURI } from './cli/utils'
24
24
 
25
25
  const importName = (name?: string) => (name || 'Fn'+ Math.random().toString(36).substring(2)).replace(/\.ts$/, '')
26
26
  const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string[] = []): Promise<void> => {
@@ -105,9 +105,17 @@ export async function getRoutes(
105
105
  let length = 0
106
106
  const keys: Set<string> = new Set()
107
107
  const bag: Record<string, string[]> = {}
108
+ const _route = (key: string, _: string) => {
109
+ if (!keys.has(key)) {
110
+ keys.add(key)
111
+ } else {
112
+ ;(bag[key] ||= []).push(_)
113
+ length++
114
+ }
115
+ }
108
116
 
109
117
  await Promise.all(dirs.map(dir => walk(
110
- resolve(__root, dir),
118
+ resolve(_root, dir),
111
119
  dir,
112
120
  (path: string, baseDir: string, handle: any, middlewares: string[]) => {
113
121
  const name = importName(handle?.name)
@@ -136,21 +144,26 @@ export async function getRoutes(
136
144
  desc,
137
145
  })
138
146
 
139
- if (!keys.has(name)) {
140
- keys.add(name)
141
- } else {
142
- ;(bag[name] ||= []).push(file)
143
- length++
144
- }
147
+ const routeKey = method +'|'+ uri
148
+ _route(name, file)
149
+ _route(routeKey, file)
145
150
  }
146
151
  )))
147
152
 
148
153
  if (length) {
149
154
  hasDuplicatedRoutes = true
150
155
  Object.entries(bag).forEach(([name, paths]) => {
151
- warn(`Route "${name}" has `+ (paths.length > 1 ? `registered ${paths.length} times:` : 'already been registered:'))
156
+ if (name.includes('|')) {
157
+ let [method, uri] = name.split('|')
158
+ method = method.toUpperCase()
159
+ name = `${highlightedMethod(method, null, true)} "${highlightedURI(uri, method)}"`
160
+ }
161
+
162
+ warn(`Route ${name} has `+ (paths.length > 1 ? `registered ${paths.length} times:` : 'already been registered:'))
152
163
  substep(...paths)
153
164
  })
165
+
166
+ rn()
154
167
  }
155
168
 
156
169
  return sortRoutes(routes)
@@ -224,7 +237,7 @@ export async function getMiddlewares(
224
237
  const mw: Routes = []
225
238
 
226
239
  await Promise.all(dirs.map(dir => walk(
227
- resolve(__root, dir),
240
+ resolve(_root, dir),
228
241
  dir,
229
242
  (fullPath: string, baseDir: string, handle: any) => {
230
243
  // @ts-ignore
@@ -246,15 +259,15 @@ function extractName(file: string) {
246
259
  export async function getConfigs(
247
260
  dirs: string[] = ['configs']
248
261
  ): Promise<Record<string, any>> {
249
- dirs = dirs.filter(dir => existsSync(join(__root, dir)))
262
+ dirs = dirs.filter(dir => existsSync(join(_root, dir)))
250
263
  if (!dirs?.length) return {}
251
264
  const configs: Record<string, any> = {}
252
265
 
253
- const files = (await glob(join(__root, dirs?.length > 1 ? `{${dirs.join(',')}}` : dirs[0], '/**/*.{ts,js,cjs,mjs,json}')))
266
+ const files = (await glob(join(_root, dirs?.length > 1 ? `{${dirs.join(',')}}` : dirs[0], '/**/*.{ts,js,cjs,mjs,json}')))
254
267
  .filter(file => !file.includes('.d.'))
255
268
 
256
269
  for (const file of files) {
257
- const mod = await IMPORT(join(__root, file))
270
+ const mod = await IMPORT(join(_root, file))
258
271
  const keyPath = extractName(file).split('.')
259
272
 
260
273
  keyPath.reduce((acc, key, index) => {
@@ -296,16 +309,26 @@ function stringifyToJS(value: unknown): string {
296
309
  return 'undefined'
297
310
  }
298
311
 
312
+ export async function dependencyEntry(lib: string, root: string) {
313
+ const path = await import.meta.resolve(lib)
314
+ return relative(root, path.replace('file://', ''))
315
+ }
316
+
317
+ async function dependencyPath(lib: string) {
318
+ const entry = await dependencyEntry(lib, join(_root, '.rajt'))
319
+ return entry.substring(0, entry.lastIndexOf(lib) + lib.length)
320
+ }
321
+
299
322
  export async function cacheRoutes() {
300
323
  const env = Object.entries(
301
- config({ path: '../../.env.prod' })?.parsed || {}
324
+ config({ path: join(_root, '.env.prod') })?.parsed || {}
302
325
  ).filter(([key, val]) => key?.toLowerCase().indexOf('aws') != 0) // prevent AWS credentials
303
326
 
304
327
  const version = versionSHA('../../.git') // @ts-ignore
305
328
  env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version]) // @ts-ignore
306
329
  env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
307
330
 
308
- const rolePath = join(__root, 'configs/roles.ts')
331
+ const rolePath = join(_root, 'configs/roles.ts')
309
332
  ensureDir(rolePath)
310
333
  if (!existsSync(rolePath))
311
334
  writeFileSync(rolePath, `export default {\n\n}`)
@@ -321,15 +344,20 @@ export async function cacheRoutes() {
321
344
  middlewares.forEach(mw => registerMiddleware(mw.handle))
322
345
 
323
346
  // @ts-ignore
324
- const openApi = await OAS.generateSpecs(createApp({ routes }), configs?.rajt || {})
347
+ const openApi = await generateOpenAPI(createApp({ routes }), configs?.rajt || {})
325
348
 
326
- const iPath = join(__root, '.rajt/imports.mjs')
349
+ const iPath = join(_root, '.rajt/imports.mjs')
327
350
  ensureDir(iPath)
351
+
352
+ const localfireEntry = await dependencyEntry('localflare-api', _root)
353
+ copyFileSync(localfireEntry, join(_root, '.rajt/localfire.js'))
354
+
355
+ const _rajtDir = await dependencyPath('rajt')
328
356
  writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
329
- ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
330
- ${Object.entries(configs)?.length ? `import Config from '../node_modules/rajt/src/config'\nConfig.add(${stringifyToJS(configs)})` : ''}
357
+ ${env?.length ? `import { Envir } from '${await dependencyPath('t0n')}/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
358
+ ${Object.entries(configs)?.length ? `import Config from '${_rajtDir}/src/config'\nConfig.add(${stringifyToJS(configs)})` : ''}
331
359
 
332
- import { registerHandler, registerMiddleware } from '../node_modules/rajt/src/register'
360
+ import { registerHandler, registerMiddleware } from '${_rajtDir}/src/register'
333
361
 
334
362
  ${Object.entries(openApi)?.length ? `registerHandler('RAJT_OPENAPI', ${stringifyToJS(openApi)})` : ''}
335
363
 
@@ -355,7 +383,7 @@ try {
355
383
  }
356
384
  `)
357
385
 
358
- const rPath = join(__root, '.rajt/routes.json')
386
+ const rPath = join(_root, '.rajt/routes.json')
359
387
  ensureDir(rPath)
360
388
  writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
361
389
  route.method,
package/src/utils/log.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { blue, bold, gray, green, magenta, red, yellow, white } from './colors'
1
+ import { blue, bold, gray, green, magenta, red, yellow, white } from 't0n/color'
2
2
 
3
3
  const _step = (color: Function, ...msg: any[]) => {
4
4
  const length = msg.length
@@ -0,0 +1,5 @@
1
+ import { dirname, join } from 'pathe'
2
+ import { Envir } from 't0n'
3
+
4
+ export const _rajt = join(dirname(new URL(import.meta.url).pathname), '..')
5
+ export const _root = Envir.get('npm_config_local_prefix') || Envir.get('PWD') || join(_rajt, '../../../')
package/src/validator.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ZodObject } from 'zod'
1
+ import type { ZodObject } from 'zod'
2
2
  import { validator } from 'hono-openapi'
3
3
  import response from './response'
4
4
  import type {
@@ -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)