rajt 0.0.59 → 0.0.61

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,12 @@
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
+
2
7
  <br/>
3
8
 
4
- > This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js, Bun and LLRT runtime) / Cloudflare Workers.
9
+ This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js, Bun and LLRT runtime) / Cloudflare Workers.
5
10
 
6
11
  - 💡 Instant Server Start
7
12
  - ⚡️ Fast Cold Start
@@ -56,7 +56,7 @@ Consider using a Node.js version manager such as https://volta.sh or https://git
56
56
  "--no-warnings",
57
57
  ...process.execArgv,
58
58
  tsxPath,
59
- join(__dirname, "../rajt/src/cli/index.ts"),
59
+ join(__dirname, "../cli/index.ts"),
60
60
  ...process.argv.slice(2),
61
61
  ].filter(arg =>
62
62
  !arg.includes('experimental-vm-modules') &&
@@ -103,10 +103,8 @@ function semiver(a, b, bool) {
103
103
 
104
104
  function directly() {
105
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)
106
+ return process.env?.npm_lifecycle_script == 'rajt'
107
+ && process.argv[1]?.endsWith('node_modules/.bin/rajt')
110
108
  } catch {
111
109
  return false
112
110
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
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.59",
4
+ "version": "0.0.61",
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",
12
12
  "./auth": "./src/auth/index.ts",
13
13
  "./d1": "./src/db/d1.ts",
14
- "./d1/types": "./src/db.d.ts",
14
+ "./d1/types": "./src/db/d1.d.ts",
15
15
  "./dynamodb": "./src/db/dynamodb.ts",
16
16
  "./dynamodb/types": "./src/db/dynamodb.d.ts",
17
17
  "./enum": "./src/enum.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,9 @@
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",
59
60
  "ua-parser-js": "^2.0.4"
60
61
  },
61
62
  "devDependencies": {
@@ -79,6 +80,7 @@
79
80
  "esbuild": "^0.25.2",
80
81
  "miniflare": "^4.20251217.0",
81
82
  "picocolors": "^1.1.1",
83
+ "tiny-glob": "^0.2",
82
84
  "tsx": "^4.19.3",
83
85
  "typescript": "^5.8.3",
84
86
  "wrangler": "^4.56.0"
@@ -228,13 +228,7 @@ function getAssetChangeMessage(
228
228
 
229
229
  async function watch(cb: (e: ChokidarEventName | string, file: string) => Promise<void>) {
230
230
  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'),
231
+ join(__dirname, '{actions,features,routes,configs,enums,locales,middlewares,models,utils}/**/*.ts'),
238
232
  join(__dirname, '.env.dev'),
239
233
  join(__dirname, '.env.prod'),
240
234
  join(__dirname, 'package.json'),
@@ -79,7 +79,7 @@ export const build = async (platform: 'aws' | 'cf' | 'node') => {
79
79
  name: 'preserve-class-names',
80
80
  setup(build) {
81
81
  build.onLoad(
82
- { filter: /(actions|features)\/.*\.ts$/ },
82
+ { filter: /(actions|features|routes)\/.*\.ts$/ },
83
83
  async (args) => {
84
84
  const contents = await readFile(args.path, 'utf8')
85
85
  const result = await esbuild.transform(contents, {
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
+ }
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb/types'
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb'
package/src/dev.ts CHANGED
@@ -3,8 +3,9 @@ import { dirname, join } from 'node:path'
3
3
  import { config } from 'dotenv'
4
4
  import { serve, type ServerType } from '@hono/node-server'
5
5
  import createApp from './create-app'
6
- import { getRoutes, getMiddlewares } from './routes'
6
+ import { getRoutes, getMiddlewares, getConfigs } from './routes'
7
7
  import { registerHandler, registerMiddleware } from './register'
8
+ import Config from './config'
8
9
  import { Ability } from './auth'
9
10
  import jsonImport from './utils/json-import'
10
11
  import { setEnv, detectEnvironment } from './utils/environment'
@@ -16,15 +17,17 @@ const __dirname = join(dirname(fileURLToPath(import.meta.url)), '../../../')
16
17
 
17
18
  config({ path: join(__dirname, '.env.dev') })
18
19
 
20
+ Config.add(await getConfigs())
21
+
19
22
  let routes = await getRoutes()
20
23
  routes.forEach(r => registerHandler(r.name, r.handle))
21
- routes = routes.filter(r => r?.path)
24
+ routes = routes.filter(r => r?.method && r?.path)
22
25
 
23
26
  const middlewares = await getMiddlewares()
24
27
  middlewares.forEach(mw => registerMiddleware(mw.handle))
25
28
 
26
29
  Ability.fromRoutes(routes)
27
- Ability.roles = jsonImport(join(__dirname, 'roles.json'))
30
+ Ability.roles = Config.get('roles', {})
28
31
 
29
32
  const fetch = createApp({ routes }).fetch
30
33
 
package/src/esbuild.mjs CHANGED
@@ -55,7 +55,7 @@ const buildOptions = {
55
55
  name: 'preserve-class-names',
56
56
  setup(build) {
57
57
  build.onLoad(
58
- { filter: /(actions|features)\/.*\.ts$/ },
58
+ { filter: /(actions|features|routes)\/.*\.ts$/ },
59
59
  async (args) => {
60
60
  const contents = await readFile(args.path, 'utf8')
61
61
  const result = await esbuild.transform(contents, {
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
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
+ export { Enum, Envir } 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/routes.ts CHANGED
@@ -1,16 +1,18 @@
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
3
  import { fileURLToPath } from 'node:url'
4
- import { Route } from './types'
5
- import { isAnonFn } from './utils/func'
6
4
 
7
- import { writeFileSync } from 'node:fs'
5
+ import glob from 'tiny-glob'
8
6
  import { config } from 'dotenv'
7
+
8
+ import IMPORT from './utils/import'
9
+ import { isAnonFn } from './utils/func'
9
10
  import ensureDir from './utils/ensuredir'
10
11
  import versionSHA from './utils/version-sha'
12
+ import type { Route } from './types'
11
13
 
12
14
  const __filename = fileURLToPath(import.meta.url)
13
- const __dirname = dirname(__filename)
15
+ const __root = resolve(dirname(__filename), '../../..')
14
16
 
15
17
  const importName = (name?: string) => (name || 'Fn'+ Math.random().toString(36).substring(2)).replace(/\.ts$/, '')
16
18
  const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string[] = []): Promise<void> => {
@@ -21,7 +23,7 @@ const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string
21
23
  const indexFile = join(dir, 'index.ts')
22
24
 
23
25
  if (existsSync(indexFile)) {
24
- const mod = await import(indexFile)
26
+ const mod = await IMPORT(indexFile)
25
27
  const group = mod.default
26
28
 
27
29
  !isAnonFn(group) && group?.mw?.length && currentMw.push(group?.name)
@@ -35,28 +37,30 @@ const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string
35
37
  if (stat.isDirectory()) {
36
38
  await walk(fullPath, baseDir, fn, currentMw)
37
39
  } else if (file != 'index.ts' && file.endsWith('.ts') && !file.endsWith('.d.ts')) {
38
- const mod = await import(fullPath)
40
+ const mod = await IMPORT(fullPath)
39
41
  fn(fullPath, baseDir, mod.default, currentMw)
40
42
  }
41
43
  }
42
44
  }
43
45
 
44
46
  export async function getRoutes(
45
- dirs: string[] = ['actions', 'features']
47
+ dirs: string[] = ['actions', 'features', 'routes']
46
48
  ): Promise<Route[]> {
47
49
  const routes: Route[] = []
48
50
  let mw: string[] = []
49
51
 
50
52
  await Promise.all(dirs.map(dir => walk(
51
- resolve(__dirname, '../../..', dir),
53
+ resolve(__root, dir),
52
54
  dir,
53
- (fullPath: string, baseDir: string, handle: any, middlewares: string[]) => {
55
+ (path: string, baseDir: string, handle: any, middlewares: string[]) => {
54
56
  const name = importName(handle?.name)
55
- const file = baseDir + fullPath.split(baseDir)[1]
57
+ path = path.split(baseDir)[1]
58
+ const file = baseDir + path
56
59
 
60
+ const m = handle?.m?.toLowerCase()
61
+ const [method, uri] = m ? [m, handle?.p] : [extractHttpVerb(path), extractHttpPath(path)]
57
62
  routes.push({
58
- method: handle?.m?.toLowerCase() || '',
59
- path: handle?.p || '',
63
+ method, path: uri,
60
64
  name,
61
65
  file,
62
66
  // @ts-ignore
@@ -69,6 +73,27 @@ export async function getRoutes(
69
73
  return sortRoutes(routes)
70
74
  }
71
75
 
76
+ function extractHttpVerb(file: string) {
77
+ if (!file) return 'get'
78
+ const match = file.match(/\.(get|post|put|patch|delete|head|options)\.(?=[jt]s$)/i)
79
+ return match && match[1] ? match[1].toLowerCase() : 'get'
80
+ }
81
+
82
+ function extractHttpPath(file: string) {
83
+ const route = '/'+ file.replace(/\\/g, '/')
84
+ // .replace(/^(actions|features|routes)\//, '')
85
+ .replace(/\.[jt]s$/, '')
86
+ .replace(/\.(get|post|put|patch|delete|head|options)$/i, '')
87
+ .replace(/\/index$/, '')
88
+ .split('/')
89
+ .filter(Boolean)
90
+ .filter(part => !(part.startsWith('(') && part.endsWith(')')))
91
+ .map(part => part.startsWith('[') && part.endsWith(']') ? ':'+ part.slice(1, -1) : part)
92
+ .join('/')
93
+
94
+ return route == '/' ? '/' : route.replace(/\/$/, '')
95
+ }
96
+
72
97
  function sortRoutes(routes: Route[]) {
73
98
  const metas = new Map<string, { score: number, segmentsCount: number }>()
74
99
 
@@ -109,7 +134,7 @@ export async function getMiddlewares(
109
134
  const mw: Route[] = []
110
135
 
111
136
  await Promise.all(dirs.map(dir => walk(
112
- resolve(__dirname, '../../..', dir),
137
+ resolve(__root, dir),
113
138
  dir,
114
139
  (fullPath: string, baseDir: string, handle: any) => {
115
140
  // @ts-ignore
@@ -124,6 +149,36 @@ export async function getMiddlewares(
124
149
  return mw
125
150
  }
126
151
 
152
+ function extractName(file: string) {
153
+ return file.replace(/\.[^/.]+$/, '').split('/').slice(1).join('.')
154
+ }
155
+
156
+ export async function getConfigs(
157
+ dirs: string[] = ['configs']
158
+ ): Promise<Record<string, any>> {
159
+ if (!dirs?.length) return {}
160
+ const configs: Record<string, any> = {}
161
+
162
+ const files = await glob(join(__root, dirs?.length > 1 ? `{${dirs.join(',')}}` : dirs[0], '/**/*.{ts,js,json}'))
163
+
164
+ for (const file of files) {
165
+ const mod = await IMPORT(join(__root, file))
166
+ const keyPath = extractName(file).split('.')
167
+
168
+ keyPath.reduce((acc, key, index) => {
169
+ if (index == keyPath.length - 1) {
170
+ acc[key] = mod.default
171
+ } else if (!acc[key] || typeof acc[key] != 'object') {
172
+ acc[key] = {}
173
+ }
174
+
175
+ return acc[key]
176
+ }, configs)
177
+ }
178
+
179
+ return configs
180
+ }
181
+
127
182
  const env = Object.entries(
128
183
  config({ path: '../../.env.prod' })?.parsed || {}
129
184
  ).filter(([key, val]) => key?.toLowerCase().indexOf('aws') != 0) // prevent AWS credentials
@@ -132,18 +187,46 @@ const version = versionSHA('../../.git') // @ts-ignore
132
187
  env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version]) // @ts-ignore
133
188
  env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
134
189
 
190
+ const IDENTIFIER_RE = /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u
191
+ function stringifyToJS(value: unknown): string {
192
+ if (value === null) return 'null'
193
+ if (value === undefined) return 'undefined'
194
+
195
+ const type = typeof value
196
+
197
+ if (type == 'string') return JSON.stringify(value)
198
+ if (type == 'number' || type == 'boolean') return String(value)
199
+ if (type == 'bigint') return `${value}n`
200
+ if (type == 'function') return value.toString()
201
+
202
+ if (Array.isArray(value))
203
+ return `[${value.map(stringifyToJS).join(',')}]`
204
+
205
+ if (type == 'object') {
206
+ const entries = Object.entries(value as Record<string, unknown>)
207
+ .map(([key, val]) => `${IDENTIFIER_RE.test(key) ? key : JSON.stringify(key)}:${stringifyToJS(val)}`)
208
+
209
+ return `{${entries.join(',')}}`
210
+ }
211
+
212
+ return 'undefined'
213
+ }
214
+
135
215
  export async function cacheRoutes() {
136
- const rolePath = join(__dirname, '../../../roles.json')
216
+ const rolePath = join(__root, 'configs/roles.ts')
217
+ ensureDir(rolePath)
137
218
  if (!existsSync(rolePath))
138
- writeFileSync(rolePath, '{}')
219
+ writeFileSync(rolePath, `export default {\n\n}`)
139
220
 
140
221
  const routes = await getRoutes()
141
222
  const middlewares = await getMiddlewares()
223
+ const configs = Object.entries(await getConfigs())
142
224
 
143
- const iPath = join(__dirname, '../../../tmp/import-routes.mjs')
225
+ const iPath = join(__root, 'tmp/import-routes.mjs')
144
226
  ensureDir(iPath)
145
227
  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(',')}})` : ''}
228
+ ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
229
+ ${configs?.length ? `import Config from '../node_modules/rajt/src/config'\nConfig.add({${configs.map(([key, val]) => key +':'+ stringifyToJS(val)).join(',')}})` : ''}
147
230
 
148
231
  import { registerHandler, registerMiddleware } from '../node_modules/rajt/src/register'
149
232
 
@@ -169,7 +252,7 @@ try {
169
252
  }
170
253
  `)
171
254
 
172
- const rPath = join(__dirname, '../../../tmp/routes.json')
255
+ const rPath = join(__root, 'tmp/routes.json')
173
256
  ensureDir(rPath)
174
257
  writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
175
258
  route.method,
@@ -0,0 +1,3 @@
1
+ export default async function importUncached(path: string) {
2
+ return import(path +'?v='+ Date.now())
3
+ }