rajt 0.0.60 → 0.0.62

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/README.md CHANGED
@@ -1,7 +1,15 @@
1
1
  <h1 align="left">λ Rajt<br/><a href="https://pr.new/attla/rajt"><img align="right" src="https://developer.stackblitz.com/img/start_pr_dark_small.svg" alt="Start new PR in StackBlitz Codeflow"></a><a href="https://npmjs.com/package/rajt"><img align="right" src="https://img.shields.io/npm/v/rajt.svg" alt="npm package"></a></h1>
2
+
3
+ > *Runtime-Agnostic for Javascript and TypeScript*.
4
+
5
+ > *From Hungarian **`[ˈrɒjt]`** (meaning "start") and Russian **`Райт`** (meaning "wright").*
6
+
7
+ <br/>
8
+
9
+ > ⚠️ Rajt is under ALFA development — expect updates, rough edges, and occasional breack changes.
2
10
  <br/>
3
11
 
4
- > This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js, Bun and LLRT runtime) / Cloudflare Workers.
12
+ This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js, Bun and LLRT runtime) / Cloudflare Workers.
5
13
 
6
14
  - 💡 Instant Server Start
7
15
  - ⚡️ Fast Cold Start
package/bin/rajt.js ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { join, dirname } from "node:path";
4
+ import { existsSync } from "node:fs";
5
+
6
+ const __dirname = dirname(new URL(import.meta.url).pathname);
7
+
8
+ const ERR_NODE_VERSION = "18.0.0";
9
+ const MIN_NODE_VERSION = "18.0.0";
10
+
11
+ let rajtProcess;
12
+
13
+ function runRajt() {
14
+ if (process?.versions?.node && semiver(process.versions.node, ERR_NODE_VERSION) < 0) {
15
+ console.error(
16
+ `Rajt requires at least Node.js v${MIN_NODE_VERSION}. You are using v${process.versions.node}. Please update your version of Node.js.
17
+
18
+ Consider using a Node.js version manager such as https://volta.sh or https://github.com/nvm-sh/nvm.`
19
+ );
20
+ process.exitCode = 1;
21
+ return;
22
+ }
23
+
24
+ const isBun = process?.isBun || typeof Bun != 'undefined';
25
+ let tsxPath;
26
+ // const params = isBun ? bunParams() : nodeParams();
27
+
28
+ if (!isBun) {
29
+ const tsxPaths = [
30
+ // join(__dirname, "../node_modules/.bin/tsx"),
31
+ // join(__dirname, "../../.bin/tsx"),
32
+ join(__dirname, "../node_modules/.bin/tsx"),
33
+ join(__dirname, "../../node_modules/.bin/tsx"),
34
+ join(process.cwd(), "node_modules/.bin/tsx"),
35
+ "tsx",
36
+ ];
37
+
38
+ for (const pathOption of tsxPaths) {
39
+ if (pathOption == "tsx" || existsSync(pathOption)) {
40
+ tsxPath = pathOption;
41
+ break;
42
+ }
43
+ }
44
+
45
+ if (!tsxPath) {
46
+ console.error("TypeScript file found but tsx is not available. Please install tsx:");
47
+ console.error(" npm i -D tsx");
48
+ console.error(" or");
49
+ console.error(" bun i -D tsx");
50
+ process.exit(1);
51
+ return;
52
+ }
53
+ }
54
+
55
+ return spawn(
56
+ process.execPath,
57
+ [
58
+ "--no-warnings",
59
+ ...process.execArgv,
60
+ tsxPath,
61
+ join(__dirname, "../src/cli/index.ts"),
62
+ ...process.argv.slice(2),
63
+ ].filter(arg => arg && !arg.includes('experimental-vm-modules') && !arg.includes('loader')),
64
+ {
65
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
66
+ env: {
67
+ ...process.env,
68
+ NODE_ENV: process.env.NODE_ENV || 'development',
69
+ TSX_DISABLE_CACHE: process.env.TSX_DISABLE_CACHE || '1',
70
+ }
71
+ }
72
+ ).on("exit", (code) =>
73
+ process.exit(code == null ? 0 : code)
74
+ ).on("message", (message) => {
75
+ if (process.send) {
76
+ process.send(message);
77
+ }
78
+ }).on("disconnect", () => {
79
+ if (process.disconnect) {
80
+ process.disconnect();
81
+ }
82
+ });
83
+ }
84
+
85
+ var fn = new Intl.Collator(0, { numeric: 1 }).compare;
86
+
87
+ function semiver(a, b, bool) {
88
+ a = a.split(".");
89
+ b = b.split(".");
90
+
91
+ return (
92
+ fn(a[0], b[0]) ||
93
+ fn(a[1], b[1]) ||
94
+ ((b[2] = b.slice(2).join(".")),
95
+ (bool = /[.-]/.test((a[2] = a.slice(2).join(".")))),
96
+ bool == /[.-]/.test(b[2]) ? fn(a[2], b[2]) : bool ? -1 : 1)
97
+ );
98
+ }
99
+
100
+ function directly() {
101
+ try {
102
+ return process.env?.npm_lifecycle_script == 'rajt'
103
+ && (
104
+ process.argv[1]?.endsWith('node_modules/.bin/rajt')
105
+ || process.argv[1]?.endsWith('node_modules/rajt/bin/rajt.js')
106
+ )
107
+ } catch {
108
+ return false
109
+ }
110
+ }
111
+
112
+ if (directly()) {
113
+ rajtProcess = runRajt();
114
+ process.on("SIGINT", () => {
115
+ rajtProcess && rajtProcess.kill();
116
+ });
117
+ process.on("SIGTERM", () => {
118
+ rajtProcess && rajtProcess.kill();
119
+ });
120
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
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.60",
4
+ "version": "0.0.62",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "rajt": "./src/bin/rajt.js"
8
+ "rajt": "./bin/rajt.js"
9
9
  },
10
10
  "exports": {
11
11
  ".": "./src/index.ts",
@@ -21,6 +21,7 @@
21
21
  "./length": "./src/utils/length.ts"
22
22
  },
23
23
  "files": [
24
+ "bin",
24
25
  "src"
25
26
  ],
26
27
  "scripts": {
@@ -53,9 +54,10 @@
53
54
  "dependencies": {
54
55
  "@hono/zod-validator": "^0.4.3",
55
56
  "cripta": "^0.1",
56
- "forj": "^0.0.2",
57
+ "forj": "^0.0.4",
57
58
  "hono": "^4.7.6",
58
- "t0n": "^0.1.5",
59
+ "t0n": "^0.1",
60
+ "tiny-glob": "^0.2",
59
61
  "ua-parser-js": "^2.0.4"
60
62
  },
61
63
  "devDependencies": {
package/src/action.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import response from './response'
2
2
  import validator from './validator'
3
+ import { GET_REQUEST } from './request'
3
4
  import type { Context, IRequest, IResponse, IValidator, Rules } from './types'
4
5
 
5
6
  export default class Action {
6
7
  static run() {
7
8
  const rules = this.rules(validator)
8
- const h = async (c: Context) => await this.handle(c.get('_'), response)
9
+ const h = async (c: Context) => await this.handle(c.get(GET_REQUEST as unknown as string), response)
9
10
  if (!rules) return [h]
10
11
 
11
12
  const pipe = validator.parse(rules)
@@ -1,10 +1,9 @@
1
1
  import { existsSync } from 'node:fs'
2
- import { fileURLToPath } from 'node:url'
3
2
  import { dirname, join, relative } from 'node:path'
4
3
  import { spawn, type ChildProcess } from 'node:child_process'
5
4
 
6
5
  import chokidar from 'chokidar'
7
- import colors from 'picocolors'
6
+ // import colors from 'picocolors'
8
7
  import { defineCommand } from 'citty'
9
8
  import type { ChokidarEventName } from '../../types'
10
9
 
@@ -13,7 +12,7 @@ import { build, createMiniflare } from './utils'
13
12
  import { getAvailablePort } from '../../../utils/port'
14
13
  import shutdown from '../../../utils/shutdown'
15
14
 
16
- const __dirname = join(dirname(fileURLToPath(import.meta.url)), '../../../../../../')
15
+ const __dirname = join(dirname(new URL(import.meta.url).pathname), '../../../../../../')
17
16
 
18
17
  export default defineCommand({
19
18
  meta: {
@@ -36,7 +35,7 @@ export default defineCommand({
36
35
  description: 'Environment platform',
37
36
  type: 'enum',
38
37
  options: ['aws', 'cf', 'node'] as const,
39
- required: true,
38
+ // required: true,
40
39
  },
41
40
  },
42
41
  async run({ args }) {
@@ -93,7 +92,7 @@ export default defineCommand({
93
92
  'local', 'start-api',
94
93
  '--warm-containers', 'LAZY',
95
94
  '--debug', '--template-file', join(__dirname, 'template-dev.yaml'),
96
- '--port', args.port,
95
+ '--port', String(port),
97
96
  ],
98
97
  {
99
98
  stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
@@ -168,30 +167,43 @@ export default defineCommand({
168
167
  logger.step('Server restarted')
169
168
  })
170
169
  })
170
+ default:
171
171
  case 'node':
172
172
  return withPort(desiredPort, async (port) => {
173
173
  logger.step(`API running on http://${host}:${port}`)
174
+ const isBun = process?.isBun || typeof Bun != 'undefined'
175
+ const params = isBun
176
+ ? ['run', '--port='+ port, '--hot', '--silent', '--no-clear-screen', '--no-summary', join(__dirname, 'node_modules/rajt/src/dev.ts')]
177
+ : [join(__dirname, 'node_modules/.bin/tsx'), 'watch', join(__dirname, 'node_modules/rajt/src/dev-node.ts')]
174
178
 
175
- spawn(
179
+ const child = spawn(
176
180
  process.execPath,
177
- [
178
- join(__dirname, 'node_modules/.bin/tsx'), 'watch', join(__dirname, 'node_modules/rajt/src/dev.ts'),
179
- ],
181
+ params,
180
182
  {
181
- stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
182
- env: {...process.env, PORT: args.port},
183
+ stdio: ['inherit', isBun ? 'pipe' : 'inherit', 'inherit', 'ipc'],
184
+ env: {...process.env, PORT: port},
183
185
  }
184
- ).on('exit', code => process.exit(code ?? 0))
185
- .on('message', msg => {
186
- if (process.send) process.send(msg)
187
- }).on('disconnect', () => {
188
- if (process.disconnect) process.disconnect()
189
- })
186
+ )
187
+
188
+ if (isBun && child?.stdout) {
189
+ child.stdout?.on('data', data => {
190
+ const output = data.toString()
191
+ if (!output.includes('Started development server'))
192
+ process.stdout.write(output)
193
+ })
194
+ }
195
+
196
+ child.on('exit', code => process.exit(code ?? 0))
197
+ .on('message', msg => {
198
+ if (process.send) process.send(msg)
199
+ }).on('disconnect', () => {
200
+ if (process.disconnect) process.disconnect()
201
+ })
190
202
  })
191
- default:
192
- return logger.warn(
193
- `🟠 Provide a valid platform: ${['aws', 'cf', 'node'].map(p => colors.blue(p)).join(', ')}.\n`
194
- )
203
+ // default:
204
+ // return logger.warn(
205
+ // `🟠 Provide a valid platform: ${['aws', 'cf', 'node'].map(p => colors.blue(p)).join(', ')}.\n`
206
+ // )
195
207
  }
196
208
  },
197
209
  })
@@ -228,13 +240,7 @@ function getAssetChangeMessage(
228
240
 
229
241
  async function watch(cb: (e: ChokidarEventName | string, file: string) => Promise<void>) {
230
242
  const codeWatcher = chokidar.watch([
231
- join(__dirname, 'actions/**/*.ts'),
232
- join(__dirname, 'configs/**/*.ts'),
233
- join(__dirname, 'enums/**/*.ts'),
234
- join(__dirname, 'locales/**/*.ts'),
235
- join(__dirname, 'middlewares/**/*.ts'),
236
- join(__dirname, 'models/**/*.ts'),
237
- join(__dirname, 'utils/**/*.ts'),
243
+ join(__dirname, '{actions,features,routes,configs,enums,locales,middlewares,models,utils}/**/*.ts'),
238
244
  join(__dirname, '.env.dev'),
239
245
  join(__dirname, '.env.prod'),
240
246
  join(__dirname, 'package.json'),
@@ -1,14 +1,13 @@
1
1
  import esbuild from 'esbuild'
2
2
  import TOML from '@iarna/toml'
3
3
  import { Miniflare } from 'miniflare'
4
- import { fileURLToPath } from 'node:url'
5
4
  import { mkdirSync, existsSync, readdirSync, rmSync, copyFileSync } from 'node:fs'
6
5
  import { readFile, stat, writeFile } from 'node:fs/promises'
7
6
  import { basename, dirname, join, relative } from 'node:path'
8
7
 
9
8
  import { cacheRoutes } from '../../../routes'
10
9
 
11
- const __dirname = join(dirname(fileURLToPath(import.meta.url)), '../../../../../../')
10
+ const __dirname = join(dirname(new URL(import.meta.url).pathname), '../../../../../../')
12
11
  const __rajt = join(__dirname, 'node_modules/rajt/src')
13
12
 
14
13
  export const formatSize = (bytes: number) => {
@@ -79,7 +78,7 @@ export const build = async (platform: 'aws' | 'cf' | 'node') => {
79
78
  name: 'preserve-class-names',
80
79
  setup(build) {
81
80
  build.onLoad(
82
- { filter: /(actions|features)\/.*\.ts$/ },
81
+ { filter: /(actions|features|routes)\/.*\.ts$/ },
83
82
  async (args) => {
84
83
  const contents = await readFile(args.path, 'utf8')
85
84
  const result = await esbuild.transform(contents, {
package/src/cli/index.ts CHANGED
@@ -25,11 +25,9 @@ console.error = () => {}
25
25
  const directly = () => {
26
26
  try {
27
27
  // @ts-ignore
28
- return typeof vitest == 'undefined'
29
- && (
30
- ![typeof require, typeof module].includes('undefined') && require.main == module
31
- || import.meta.url == `file://${process.argv[1]}`
32
- )
28
+ return typeof vitest == 'undefined'
29
+ && process.env?.npm_lifecycle_script == 'rajt'
30
+ && import.meta.url == `file://${process.argv[1]}`
33
31
  } catch {
34
32
  return false
35
33
  }
package/src/config.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { DataBag } from 't0n'
2
+
3
+ export default class Config {
4
+ static #c: DataBag
5
+
6
+ static {
7
+ this.#c = new DataBag()
8
+ }
9
+
10
+ static all<T = any>(): Record<string, T> {
11
+ return this.#c.all()
12
+ }
13
+
14
+ static has(key: string): boolean {
15
+ return this.#c.has(key)
16
+ }
17
+
18
+ static add<T = any>(data: Record<string, T> = {}) {
19
+ this.#c.add(data)
20
+ }
21
+
22
+ static replace<T = any>(data: Record<string, T> = {}) {
23
+ this.#c.replace(data)
24
+ }
25
+
26
+ static get<T = any>(key: string, defaultValue?: T): T {
27
+ return this.#c.get(key, defaultValue)
28
+ }
29
+
30
+ static set<T = any>(key: string, value: T) {
31
+ this.#c.set(key, value)
32
+ }
33
+
34
+ static remove(key: string) {
35
+ this.#c.remove(key)
36
+ }
37
+
38
+ static clear() {
39
+ this.#c.clear()
40
+ }
41
+
42
+ static get length(): number {
43
+ return this.#c.length
44
+ }
45
+ static get size(): number {
46
+ return this.#c.length
47
+ }
48
+
49
+ static keys(): string[] {
50
+ return this.#c.keys()
51
+ }
52
+
53
+ static values<T = any>(): T[] {
54
+ return this.#c.values()
55
+ }
56
+
57
+ static entries<T = any>(): [string, T][] {
58
+ return this.#c.entries()
59
+ }
60
+
61
+ static toArray<T = any>(): [string, T][] {
62
+ return this.#c.entries()
63
+ }
64
+
65
+ static jsonSerialize<T = any>(): Record<string, T> {
66
+ return this.#c.all()
67
+ }
68
+
69
+ static toJson(options: number = 0): string {
70
+ return this.#c.toJson(options)
71
+ }
72
+
73
+ static [Symbol.iterator](): IterableIterator<[string, any]> {
74
+ return this.#c[Symbol.iterator]()
75
+ }
76
+ }
package/src/create-app.ts CHANGED
@@ -6,7 +6,8 @@ import type { Env, Context, ErrorHandler, NotFoundHandler, Next } from 'hono'
6
6
  // import { createMiddleware } from 'hono/factory'
7
7
  // import type { H, Handler, HandlerResponse } from 'hono/types'
8
8
  import type { HTTPResponseError } from 'hono/types'
9
- import colors from 'picocolors'
9
+ import { createColors } from 'picocolors'
10
+ import { getColorEnabledAsync } from 'hono/utils/color'
10
11
  import { Envir } from 't0n'
11
12
  import type { Routes } from './types'
12
13
  import { BadRequest, Unauthorized } from './exceptions'
@@ -14,9 +15,11 @@ import { resolve, resolveMiddleware } from './utils/resolve'
14
15
  import { getMiddlewares, getHandler } from './register'
15
16
  import { isDev } from './utils/environment'
16
17
  import localDate from './utils/local-date'
17
- import request from './request'
18
+ import request, { GET_REQUEST } from './request'
18
19
  import response from './response'
19
20
 
21
+ const colors = createColors(await getColorEnabledAsync())
22
+
20
23
  type InitFunction<E extends Env = Env> = (app: Hono<E>) => void
21
24
 
22
25
  export type ServerOptions<E extends Env = Env> = Partial<{
@@ -85,12 +88,12 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
85
88
  app.use('*', logger((...args: any[]) => console.log(colors.gray(localDate()), ...args)))
86
89
 
87
90
  app.use(async (c: Context, next: Next) => {
88
- c.set('_', new request(c))
91
+ c.set(GET_REQUEST as unknown as string, new request(c))
89
92
  if (c.env) Envir.add(c.env)
90
93
  await next()
91
94
  })
92
95
  getMiddlewares().forEach(mw => {
93
- const h = async (c: Context, next: Next) => await resolveMiddleware(mw)(c.get('_'), next)
96
+ const h = async (c: Context, next: Next) => await resolveMiddleware(mw)(c.get(GET_REQUEST as unknown as string), next)
94
97
  // @ts-ignore
95
98
  mw?.path ? app.use(String(mw.path), h) : app.use(h)
96
99
  })
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb/types'
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb'
@@ -0,0 +1,16 @@
1
+ import { serve, type ServerType } from '@hono/node-server'
2
+ import app from './dev'
3
+ import shutdown from './utils/shutdown'
4
+
5
+ const fetch = app.fetch
6
+
7
+ const port = process.env?.PORT ? Number(process.env.PORT) : 3000
8
+
9
+ let server: ServerType | null = serve({ fetch, port })
10
+
11
+ shutdown(() => {
12
+ if (server) {
13
+ server?.close()
14
+ server = null
15
+ }
16
+ })
package/src/dev.ts CHANGED
@@ -1,40 +1,30 @@
1
- import { fileURLToPath } from 'node:url'
2
1
  import { dirname, join } from 'node:path'
3
2
  import { config } from 'dotenv'
4
- import { serve, type ServerType } from '@hono/node-server'
5
3
  import createApp from './create-app'
6
- import { getRoutes, getMiddlewares } from './routes'
4
+ import { getRoutes, getMiddlewares, getConfigs } from './routes'
7
5
  import { registerHandler, registerMiddleware } from './register'
8
- import { Ability } from './auth'
9
- import jsonImport from './utils/json-import'
10
- import { setEnv, detectEnvironment } from './utils/environment'
11
- import shutdown from './utils/shutdown'
6
+ import Config from './config'
7
+ import { Ability } from 'rajt/auth'
8
+ import { setEnv, detectEnvironment } from 'rajt/env'
12
9
 
13
10
  setEnv(detectEnvironment())
14
11
 
15
- const __dirname = join(dirname(fileURLToPath(import.meta.url)), '../../../')
12
+ const __dirname = join(dirname(new URL(import.meta.url).pathname), '../../../')
16
13
 
17
14
  config({ path: join(__dirname, '.env.dev') })
18
15
 
16
+ Config.add(await getConfigs())
17
+
19
18
  let routes = await getRoutes()
20
19
  routes.forEach(r => registerHandler(r.name, r.handle))
21
- routes = routes.filter(r => r?.path)
20
+ routes = routes.filter(r => r?.method && r?.path)
22
21
 
23
22
  const middlewares = await getMiddlewares()
24
23
  middlewares.forEach(mw => registerMiddleware(mw.handle))
25
24
 
26
25
  Ability.fromRoutes(routes)
27
- Ability.roles = jsonImport(join(__dirname, 'roles.json'))
28
-
29
- const fetch = createApp({ routes }).fetch
30
-
31
- const port = process.env?.PORT ? Number(process.env.PORT) : 3000
26
+ Ability.roles = Config.get('roles', {})
32
27
 
33
- let server: ServerType | null = serve({ fetch, port })
28
+ const app = createApp({ routes })
34
29
 
35
- shutdown(() => {
36
- if (server) {
37
- server?.close()
38
- server = null
39
- }
40
- })
30
+ export default app
package/src/esbuild.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import esbuild from 'esbuild'
2
2
  import { basename, dirname, join, relative } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
3
  import { mkdirSync, existsSync, readdirSync, rmSync, copyFileSync } from 'node:fs'
5
4
  import { readFile, stat, writeFile } from 'node:fs/promises'
6
5
 
@@ -16,7 +15,7 @@ const platforms = ['aws', 'cf']
16
15
  if (!platform || !platforms.includes(platform))
17
16
  fail()
18
17
 
19
- const __dirname = dirname(fileURLToPath(import.meta.url))
18
+ const __dirname = dirname(new URL(import.meta.url).pathname)
20
19
 
21
20
  const formatSize = (bytes) => {
22
21
  if (bytes < 1024) return `${bytes}b`
@@ -55,7 +54,7 @@ const buildOptions = {
55
54
  name: 'preserve-class-names',
56
55
  setup(build) {
57
56
  build.onLoad(
58
- { filter: /(actions|features)\/.*\.ts$/ },
57
+ { filter: /(actions|features|routes)\/.*\.ts$/ },
59
58
  async (args) => {
60
59
  const contents = await readFile(args.path, 'utf8')
61
60
  const result = await esbuild.transform(contents, {
package/src/http.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Context, Next } from 'hono'
2
2
  import { MiddlewareType } from './middleware'
3
3
  import response from './response'
4
+ import { GET_REQUEST } from './request'
4
5
  import { Ability } from './auth'
5
6
  import mergeMiddleware from './utils/merge-middleware'
6
7
  import { IRequest } from './types'
@@ -102,7 +103,7 @@ export function Auth(...args: any[]): void | ClassDecorator {
102
103
 
103
104
  function _auth(target: Function | any) {
104
105
  mergeMiddleware(target, async (c: Context, next: Next) => {
105
- const req = c.get('_') as IRequest
106
+ const req = c.get(GET_REQUEST as unknown as string) as IRequest
106
107
  const ability = Ability.fromAction(target)
107
108
 
108
109
  if (!req?.user || !ability || req.cant(ability))
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export { default as Action } from './action'
2
2
  export { default as Middleware } from './middleware'
3
3
  export { default as Response } from './response'
4
- export { Enum } from 't0n'
4
+ export { default as Config } from './config'
5
+
6
+ export { Enum, Envir } from 't0n'
7
+ export type { EnumStatic, EnumValue, EnumType } from 't0n'
package/src/prod.ts CHANGED
@@ -1,16 +1,15 @@
1
- import createApp from './create-app'
1
+ import Config from './config'
2
2
  import { Ability } from './auth'
3
+ import createApp from './create-app'
3
4
 
4
5
  // @ts-ignore
5
- await import('../../../tmp/import-routes.mjs')
6
-
6
+ import '../../../tmp/import-routes.mjs'
7
7
  // @ts-ignore
8
- const routes = (await import('../../../tmp/routes.json')).default
8
+ import routes from '../../../tmp/routes.json'
9
9
 
10
- // @ts-ignore
11
- Ability.roles = (await import('../../../roles.json')).default
12
10
  // @ts-ignore
13
11
  Ability.fromRoutes(routes)
12
+ Ability.roles = Config.get('roles', {})
14
13
 
15
14
  // @ts-ignore
16
15
  export const app = createApp({ routes })
package/src/request.ts CHANGED
@@ -17,6 +17,8 @@ const cookieWrapper = (c: Context) => ({
17
17
  delete: (name: string, opt?: CookieOptions) => deleteCookie(c, name, opt)
18
18
  })
19
19
 
20
+ export const GET_REQUEST: unique symbol = Symbol()
21
+
20
22
  export default class $Request {
21
23
  #c!: Context
22
24
  #cookie: ReturnType<typeof cookieWrapper>
package/src/routes.ts CHANGED
@@ -1,16 +1,17 @@
1
- import { existsSync, readdirSync, statSync } from 'node:fs'
1
+ import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
2
2
  import { dirname, join, resolve } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { Route } from './types'
5
- import { isAnonFn } from './utils/func'
6
3
 
7
- import { writeFileSync } from 'node:fs'
4
+ import glob from 'tiny-glob'
8
5
  import { config } from 'dotenv'
6
+
7
+ import IMPORT from './utils/import'
8
+ import { isAnonFn } from './utils/func'
9
9
  import ensureDir from './utils/ensuredir'
10
10
  import versionSHA from './utils/version-sha'
11
+ import type { Route } from './types'
11
12
 
12
- const __filename = fileURLToPath(import.meta.url)
13
- const __dirname = dirname(__filename)
13
+ const __filename = new URL(import.meta.url).pathname
14
+ const __root = resolve(dirname(__filename), '../../..')
14
15
 
15
16
  const importName = (name?: string) => (name || 'Fn'+ Math.random().toString(36).substring(2)).replace(/\.ts$/, '')
16
17
  const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string[] = []): Promise<void> => {
@@ -21,7 +22,7 @@ const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string
21
22
  const indexFile = join(dir, 'index.ts')
22
23
 
23
24
  if (existsSync(indexFile)) {
24
- const mod = await import(indexFile)
25
+ const mod = await IMPORT(indexFile)
25
26
  const group = mod.default
26
27
 
27
28
  !isAnonFn(group) && group?.mw?.length && currentMw.push(group?.name)
@@ -35,28 +36,30 @@ const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string
35
36
  if (stat.isDirectory()) {
36
37
  await walk(fullPath, baseDir, fn, currentMw)
37
38
  } else if (file != 'index.ts' && file.endsWith('.ts') && !file.endsWith('.d.ts')) {
38
- const mod = await import(fullPath)
39
+ const mod = await IMPORT(fullPath)
39
40
  fn(fullPath, baseDir, mod.default, currentMw)
40
41
  }
41
42
  }
42
43
  }
43
44
 
44
45
  export async function getRoutes(
45
- dirs: string[] = ['actions', 'features']
46
+ dirs: string[] = ['actions', 'features', 'routes']
46
47
  ): Promise<Route[]> {
47
48
  const routes: Route[] = []
48
49
  let mw: string[] = []
49
50
 
50
51
  await Promise.all(dirs.map(dir => walk(
51
- resolve(__dirname, '../../..', dir),
52
+ resolve(__root, dir),
52
53
  dir,
53
- (fullPath: string, baseDir: string, handle: any, middlewares: string[]) => {
54
+ (path: string, baseDir: string, handle: any, middlewares: string[]) => {
54
55
  const name = importName(handle?.name)
55
- const file = baseDir + fullPath.split(baseDir)[1]
56
+ path = path.split(baseDir)[1]
57
+ const file = baseDir + path
56
58
 
59
+ const m = handle?.m?.toLowerCase()
60
+ const [method, uri] = m ? [m, handle?.p] : [extractHttpVerb(path), extractHttpPath(path)]
57
61
  routes.push({
58
- method: handle?.m?.toLowerCase() || '',
59
- path: handle?.p || '',
62
+ method, path: uri,
60
63
  name,
61
64
  file,
62
65
  // @ts-ignore
@@ -69,6 +72,27 @@ export async function getRoutes(
69
72
  return sortRoutes(routes)
70
73
  }
71
74
 
75
+ function extractHttpVerb(file: string) {
76
+ if (!file) return 'get'
77
+ const match = file.match(/\.(get|post|put|patch|delete|head|options)\.(?=[jt]s$)/i)
78
+ return match && match[1] ? match[1].toLowerCase() : 'get'
79
+ }
80
+
81
+ function extractHttpPath(file: string) {
82
+ const route = '/'+ file.replace(/\\/g, '/')
83
+ // .replace(/^(actions|features|routes)\//, '')
84
+ .replace(/\.[jt]s$/, '')
85
+ .replace(/\.(get|post|put|patch|delete|head|options)$/i, '')
86
+ .replace(/\/index$/, '')
87
+ .split('/')
88
+ .filter(Boolean)
89
+ .filter(part => !(part.startsWith('(') && part.endsWith(')')))
90
+ .map(part => part.startsWith('[') && part.endsWith(']') ? ':'+ part.slice(1, -1) : part)
91
+ .join('/')
92
+
93
+ return route == '/' ? '/' : route.replace(/\/$/, '')
94
+ }
95
+
72
96
  function sortRoutes(routes: Route[]) {
73
97
  const metas = new Map<string, { score: number, segmentsCount: number }>()
74
98
 
@@ -109,7 +133,7 @@ export async function getMiddlewares(
109
133
  const mw: Route[] = []
110
134
 
111
135
  await Promise.all(dirs.map(dir => walk(
112
- resolve(__dirname, '../../..', dir),
136
+ resolve(__root, dir),
113
137
  dir,
114
138
  (fullPath: string, baseDir: string, handle: any) => {
115
139
  // @ts-ignore
@@ -124,6 +148,36 @@ export async function getMiddlewares(
124
148
  return mw
125
149
  }
126
150
 
151
+ function extractName(file: string) {
152
+ return file.replace(/\.[^/.]+$/, '').split('/').slice(1).join('.')
153
+ }
154
+
155
+ export async function getConfigs(
156
+ dirs: string[] = ['configs']
157
+ ): Promise<Record<string, any>> {
158
+ if (!dirs?.length) return {}
159
+ const configs: Record<string, any> = {}
160
+
161
+ const files = await glob(join(__root, dirs?.length > 1 ? `{${dirs.join(',')}}` : dirs[0], '/**/*.{ts,js,json}'))
162
+
163
+ for (const file of files) {
164
+ const mod = await IMPORT(join(__root, file))
165
+ const keyPath = extractName(file).split('.')
166
+
167
+ keyPath.reduce((acc, key, index) => {
168
+ if (index == keyPath.length - 1) {
169
+ acc[key] = mod.default
170
+ } else if (!acc[key] || typeof acc[key] != 'object') {
171
+ acc[key] = {}
172
+ }
173
+
174
+ return acc[key]
175
+ }, configs)
176
+ }
177
+
178
+ return configs
179
+ }
180
+
127
181
  const env = Object.entries(
128
182
  config({ path: '../../.env.prod' })?.parsed || {}
129
183
  ).filter(([key, val]) => key?.toLowerCase().indexOf('aws') != 0) // prevent AWS credentials
@@ -132,18 +186,46 @@ const version = versionSHA('../../.git') // @ts-ignore
132
186
  env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version]) // @ts-ignore
133
187
  env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
134
188
 
189
+ const IDENTIFIER_RE = /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u
190
+ function stringifyToJS(value: unknown): string {
191
+ if (value === null) return 'null'
192
+ if (value === undefined) return 'undefined'
193
+
194
+ const type = typeof value
195
+
196
+ if (type == 'string') return JSON.stringify(value)
197
+ if (type == 'number' || type == 'boolean') return String(value)
198
+ if (type == 'bigint') return `${value}n`
199
+ if (type == 'function') return value.toString()
200
+
201
+ if (Array.isArray(value))
202
+ return `[${value.map(stringifyToJS).join(',')}]`
203
+
204
+ if (type == 'object') {
205
+ const entries = Object.entries(value as Record<string, unknown>)
206
+ .map(([key, val]) => `${IDENTIFIER_RE.test(key) ? key : JSON.stringify(key)}:${stringifyToJS(val)}`)
207
+
208
+ return `{${entries.join(',')}}`
209
+ }
210
+
211
+ return 'undefined'
212
+ }
213
+
135
214
  export async function cacheRoutes() {
136
- const rolePath = join(__dirname, '../../../roles.json')
215
+ const rolePath = join(__root, 'configs/roles.ts')
216
+ ensureDir(rolePath)
137
217
  if (!existsSync(rolePath))
138
- writeFileSync(rolePath, '{}')
218
+ writeFileSync(rolePath, `export default {\n\n}`)
139
219
 
140
220
  const routes = await getRoutes()
141
221
  const middlewares = await getMiddlewares()
222
+ const configs = Object.entries(await getConfigs())
142
223
 
143
- const iPath = join(__dirname, '../../../tmp/import-routes.mjs')
224
+ const iPath = join(__root, 'tmp/import-routes.mjs')
144
225
  ensureDir(iPath)
145
226
  writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
146
- ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key + ':' + JSON.stringify(val)).join(',')}})` : ''}
227
+ ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
228
+ ${configs?.length ? `import Config from '../node_modules/rajt/src/config'\nConfig.add({${configs.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
147
229
 
148
230
  import { registerHandler, registerMiddleware } from '../node_modules/rajt/src/register'
149
231
 
@@ -169,7 +251,7 @@ try {
169
251
  }
170
252
  `)
171
253
 
172
- const rPath = join(__dirname, '../../../tmp/routes.json')
254
+ const rPath = join(__root, 'tmp/routes.json')
173
255
  ensureDir(rPath)
174
256
  writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
175
257
  route.method,
@@ -0,0 +1,3 @@
1
+ export default async function importUncached(path: string) {
2
+ return import(path +'?v='+ Date.now())
3
+ }
@@ -1,10 +1,8 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
  import { dirname, join } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
3
 
5
4
  export default function jsonImport<T = any>(filePath: string, defaultValue: T = {} as T): T {
6
- const __filename = fileURLToPath(import.meta.url)
7
- const __dirname = dirname(__filename)
5
+ const __dirname = dirname(new URL(import.meta.url).pathname)
8
6
 
9
7
  try {
10
8
  const fullPath = join(__dirname, filePath)
@@ -13,17 +13,18 @@ export interface ILogger {
13
13
  // custom
14
14
  step(...data: any[]): void;
15
15
  substep(...data: any[]): void;
16
+ stepWarn(...data: any[]): void;
16
17
  ln(): void;
17
18
  }
18
19
 
19
20
  export const logger = {
20
- step(...args: any[]) {
21
- if (args?.length && args.length < 2) return _console.log(colors.blue('⁕') +` ${args[0]}\n`)
21
+ $step(color: Function, ...args: any[]) {
22
+ if (args?.length && args.length < 2) return _console.log(color('⁕') +` ${args[0]}\n`)
22
23
  const length = args.length - 1
23
24
  args.forEach((arg, index) => {
24
25
  switch (index) {
25
26
  case 0:
26
- return _console.log(colors.blue('⁕') + ' ' + arg)
27
+ return _console.log(color('⁕') + ' ' + arg)
27
28
  // return _console.log(colors.blue('⁕') +` ${arg} \n`)
28
29
  case length:
29
30
  return _console.log(` ${colors.gray('⁕')} ${arg}\n`)
@@ -31,6 +32,12 @@ export const logger = {
31
32
  return _console.log(` ${colors.gray('⁕')} ` + arg)
32
33
  }
33
34
  })
35
+ },
36
+ step(...args: any[]) {
37
+ this.$step(colors.blue, ...args)
38
+ },
39
+ stepWarn(...args: any[]) {
40
+ this.$step(colors.yellow, ...args)
34
41
  },
35
42
  substep(...args: any[]) {
36
43
  args.forEach(arg => _console.log(` ${colors.gray('⁕')} ` + arg))
package/src/bin/rajt.js DELETED
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
3
- import { join, dirname } from "node:path";
4
- import { existsSync } from "node:fs";
5
- import { fileURLToPath } from "node:url";
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = dirname(__filename);
9
-
10
- const ERR_NODE_VERSION = "18.0.0";
11
- const MIN_NODE_VERSION = "18.0.0";
12
-
13
- let rajtProcess;
14
-
15
- // Executes ../src/cli/index.ts
16
- function runRajt() {
17
- if (semiver(process.versions.node, ERR_NODE_VERSION) < 0) {
18
- console.error(
19
- `Rajt requires at least Node.js v${MIN_NODE_VERSION}. You are using v${process.versions.node}. Please update your version of Node.js.
20
-
21
- Consider using a Node.js version manager such as https://volta.sh or https://github.com/nvm-sh/nvm.`
22
- );
23
- process.exitCode = 1;
24
- return;
25
- }
26
-
27
- const tsxPaths = [
28
- // join(__dirname, "../node_modules/.bin/tsx"),
29
- // join(__dirname, "../../.bin/tsx"),
30
- join(__dirname, "../node_modules/.bin/tsx"),
31
- join(__dirname, "../../node_modules/.bin/tsx"),
32
- join(process.cwd(), "node_modules/.bin/tsx"),
33
- "tsx",
34
- ];
35
-
36
- let tsxPath;
37
- for (const pathOption of tsxPaths) {
38
- if (pathOption == "tsx" || existsSync(pathOption)) {
39
- tsxPath = pathOption;
40
- break;
41
- }
42
- }
43
-
44
- if (!tsxPath) {
45
- console.error("TypeScript file found but tsx is not available. Please install tsx:");
46
- console.error(" npm i -D tsx");
47
- console.error(" or");
48
- console.error(" bun i -D tsx");
49
- process.exit(1);
50
- return;
51
- }
52
-
53
- return spawn(
54
- process.execPath,
55
- [
56
- "--no-warnings",
57
- ...process.execArgv,
58
- tsxPath,
59
- join(__dirname, "../rajt/src/cli/index.ts"),
60
- ...process.argv.slice(2),
61
- ].filter(arg =>
62
- !arg.includes('experimental-vm-modules') &&
63
- !arg.includes('loader')
64
- ),
65
- {
66
- stdio: ["inherit", "inherit", "inherit", "ipc"],
67
- env: {
68
- ...process.env,
69
- NODE_ENV: process.env.NODE_ENV || 'development',
70
- TSX_DISABLE_CACHE: process.env.TSX_DISABLE_CACHE || '1',
71
- }
72
- }
73
- )
74
- .on("exit", (code) =>
75
- process.exit(code == null ? 0 : code)
76
- )
77
- .on("message", (message) => {
78
- if (process.send) {
79
- process.send(message);
80
- }
81
- })
82
- .on("disconnect", () => {
83
- if (process.disconnect) {
84
- process.disconnect();
85
- }
86
- });
87
- }
88
-
89
- var fn = new Intl.Collator(0, { numeric: 1 }).compare;
90
-
91
- function semiver(a, b, bool) {
92
- a = a.split(".");
93
- b = b.split(".");
94
-
95
- return (
96
- fn(a[0], b[0]) ||
97
- fn(a[1], b[1]) ||
98
- ((b[2] = b.slice(2).join(".")),
99
- (bool = /[.-]/.test((a[2] = a.slice(2).join(".")))),
100
- bool == /[.-]/.test(b[2]) ? fn(a[2], b[2]) : bool ? -1 : 1)
101
- );
102
- }
103
-
104
- function directly() {
105
- try {
106
- return ![typeof require, typeof module].includes('undefined') && require.main == module
107
- || import.meta.url == `file://${process.argv[1]}`
108
- || process.argv[1].endsWith(process.env?.npm_package_bin_rajt)
109
- || import.meta?.url?.endsWith(process.env?.npm_package_bin_rajt)
110
- } catch {
111
- return false
112
- }
113
- }
114
-
115
- if (directly()) {
116
- rajtProcess = runRajt();
117
- process.on("SIGINT", () => {
118
- rajtProcess && rajtProcess.kill();
119
- });
120
- process.on("SIGTERM", () => {
121
- rajtProcess && rajtProcess.kill();
122
- });
123
- }