rajt 0.0.57 → 0.0.58

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,7 @@
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>
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
2
  <br/>
3
3
 
4
- > This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js and LLRT) / Cloudflare Workers.
4
+ > This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js, Bun and LLRT runtime) / Cloudflare Workers.
5
5
 
6
6
  - 💡 Instant Server Start
7
7
  - ⚡️ Fast Cold Start
@@ -9,12 +9,15 @@
9
9
 
10
10
  [Read the Docs to Learn More](https://github.com/attla/rajt/blob/main/DOCS.md)
11
11
 
12
- ## Packages
12
+ ## Ecosystem Packages
13
13
 
14
- | Package | Version |
15
- | --------------------------------------------------- | :----------------------------------------------------------------------------- |
16
- | [rajt](https://github.com/attla/rajt) | ![rajt version](https://img.shields.io/npm/v/rajt.svg?label=%20) |
17
- | [create-rajt](https://github.com/attla/create-rajt) | ![create-rajt version](https://img.shields.io/npm/v/create-rajt.svg?label=%20) |
14
+ | Package | Version | Description |
15
+ | -: | :- | :- |
16
+ | [rajt](https://github.com/attla/rajt) | [![rajt version](https://img.shields.io/npm/v/rajt.svg?label=%20)](https://npm.im/rajt) | 🧱 The core of Rajt serverless framework. |
17
+ | [create-rajt](https://github.com/attla/create-rajt) | [![create-rajt version](https://img.shields.io/npm/v/create-rajt.svg?label=%20)](https://npm.im/create-rajt) | 📁 CLI tool for create new Rajt projects, from starter templates. |
18
+ | [forj](https://github.com/attla/forj) | [![forj version](https://img.shields.io/npm/v/forj.svg?label=%20)](https://npm.im/forj) | 🗂️ Just another query builder in typescript |
19
+ | [t0n](https://github.com/attla/t0n) | [![t0n version](https://img.shields.io/npm/v/t0n.svg?label=%20)](https://npm.im/t0n) | 🧰 Collection of elegant typescript resources for web artisans. |
20
+ | [cripta](https://github.com/attla/cripta) | [![cripta version](https://img.shields.io/npm/v/cripta.svg?label=%20)](https://npm.im/cripta) | 🧛 A layer of encryption a little too judicious. |
18
21
 
19
22
  ## License
20
23
 
package/package.json CHANGED
@@ -1,9 +1,12 @@
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.57",
4
+ "version": "0.0.58",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
+ "bin": {
8
+ "rajt": "./src/bin/rajt.js"
9
+ },
7
10
  "exports": {
8
11
  ".": "./src/index.ts",
9
12
  "./auth": "./src/auth/index.ts",
@@ -18,6 +21,7 @@
18
21
  "src"
19
22
  ],
20
23
  "scripts": {
24
+ "rajt": "./src/bin/rajt.js",
21
25
  "dev": "tsx watch src/dev.ts",
22
26
  "aws:build": "bun run --silent cache:routes && bun run --silent aws:export && bun run --silent clean:temp",
23
27
  "cf:build": "bun run --silent cache:routes && bun run --silent cf:export && bun run --silent clean:temp",
@@ -33,7 +37,7 @@
33
37
  "sam:package": "sam package --template-file ../../template-prod.yaml --output-template-file ../../packaged.yaml",
34
38
  "sam:deploy": "sam deploy --template-file ../../packaged.yaml --stack-name rajt-llrt --capabilities CAPABILITY_IAM",
35
39
  "sam:update": "source ../../.env.prod && aws lambda update-function-code --function-name $AWS_NAME --zip-file fileb://../../lambda.zip --region $AWS_REGION --no-cli-pager 2>&1 >/dev/null",
36
- "cf:local": "bun run --silent cf:build && cd ../../dist && bunx wrangler dev",
40
+ "cf:local-": "source ../../.env.dev && bun run --silent cf:build:watch -- cd ../../dist && bunx wrangler dev --port=$PORT --persist-to='../.wrangler/state'",
37
41
  "cf:deploy": "bun run --silent cf:build && cd ../../dist && bunx wrangler deploy",
38
42
  "cache:routes": "tsx src/scripts/cache-routes.ts",
39
43
  "ensure-dirs": "rm -rf ../../dist ../../tmp && mkdir -p ../../tmp && chmod 755 ../../tmp && mkdir -p ../../dist && chmod 755 ../../dist",
@@ -46,22 +50,38 @@
46
50
  "dependencies": {
47
51
  "@aws-sdk/client-dynamodb": "3.817.0",
48
52
  "@aws-sdk/lib-dynamodb": "3.817.0",
49
- "@hono/node-server": "^1.14.1",
50
53
  "@hono/zod-validator": "^0.4.3",
54
+ "cripta": "^0.1",
55
+ "hono": "^4.7.6",
56
+ "pluralize": "^8.0",
57
+ "t0n": "^0.1.5",
58
+ "ua-parser-js": "^2.0.4"
59
+ },
60
+ "devDependencies": {
61
+ "@cloudflare/workers-types": "^4.20251230.0",
62
+ "@hono/node-server": "^1.14.1",
63
+ "@iarna/toml": "^2.2.5",
64
+ "@miniflare/core": "^2.14.4",
65
+ "@miniflare/d1": "^2.14.4",
66
+ "@miniflare/durable-objects": "^2.14.4",
67
+ "@miniflare/kv": "^2.14.4",
68
+ "@miniflare/r2": "^2.14.4",
69
+ "@miniflare/scheduler": "^2.14.4",
70
+ "@miniflare/sites": "^2.14.4",
71
+ "@miniflare/storage-file": "^2.14.4",
72
+ "@miniflare/web-sockets": "^2.14.4",
51
73
  "@types/node": "^20.11.0",
74
+ "@types/pluralize": "^0.0.33",
75
+ "bun-types": "^1.2.14",
52
76
  "chokidar-cli": "^3.0.0",
53
- "cripta": "^0.1",
77
+ "citty": "^0.1.6",
54
78
  "dotenv": "^16.5.0",
55
79
  "esbuild": "^0.25.2",
56
- "hono": "^4.7.6",
57
- "plur": "^5.1.0",
58
- "tsconfig-paths": "^4.2.0",
80
+ "miniflare": "^4.20251217.0",
81
+ "picocolors": "^1.1.1",
59
82
  "tsx": "^4.19.3",
60
83
  "typescript": "^5.8.3",
61
- "ua-parser-js": "^2.0.4"
62
- },
63
- "devDependencies": {
64
- "bun-types": "^1.2.14"
84
+ "wrangler": "^4.56.0"
65
85
  },
66
86
  "engines": {
67
87
  "node": ">=18.0.0"
@@ -0,0 +1,123 @@
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 === undefined || 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
+ }
@@ -0,0 +1,294 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { dirname, join, relative } from 'node:path'
4
+ import { spawn, type ChildProcess } from 'node:child_process'
5
+
6
+ import chokidar from 'chokidar'
7
+ import colors from 'picocolors'
8
+ import { defineCommand } from 'citty'
9
+ import type { ChokidarEventName } from '../../types'
10
+
11
+ import type { Miniflare } from 'miniflare'
12
+ import { build, createMiniflare } from './utils'
13
+ import { getAvailablePort } from '../../../utils/port'
14
+ import shutdown from '../../../utils/shutdown'
15
+
16
+ const __dirname = join(dirname(fileURLToPath(import.meta.url)), '../../../../../../')
17
+
18
+ export default defineCommand({
19
+ meta: {
20
+ name: 'dev',
21
+ description: '💻 Start the localhost server\n',
22
+ },
23
+ args: {
24
+ port: {
25
+ description: 'Port to listen on',
26
+ type: 'number',
27
+ default: 3000,
28
+ },
29
+ host: {
30
+ description: 'Host to forward requests to, defaults to the zone of project',
31
+ type: 'string',
32
+ default: 'localhost',
33
+ },
34
+ platform: {
35
+ alias: 'p',
36
+ description: 'Environment platform',
37
+ type: 'enum',
38
+ options: ['aws', 'cf', 'node'] as const,
39
+ required: true,
40
+ },
41
+ },
42
+ async run({ args }) {
43
+ const platform = args.p || args.platform
44
+ const desiredPort = args.port ? Number(args.port) : 3000
45
+ const host = args.host ? String(args.host) : 'localhost'
46
+ switch (platform) {
47
+ case 'aws':
48
+ return withPort(desiredPort, async (port) => {
49
+ let isBuilding = false
50
+ let lambda: ChildProcess | null = null
51
+
52
+ const buildLambda = async () => {
53
+ if (isBuilding) return
54
+ isBuilding = true
55
+ logger.step('Building lambda')
56
+ try {
57
+ await build(platform)
58
+ if (!lambda) await startLambda()
59
+ } catch (e) {
60
+ logger.error('Build failed:', e)
61
+ process.exit(0)
62
+ } finally {
63
+ isBuilding = false
64
+ }
65
+ }
66
+
67
+ const stopLambda = async () => {
68
+ if (!lambda) return
69
+ logger.step('Stopping lambda process...')
70
+ try {
71
+ if (!lambda?.killed) {
72
+ lambda.kill('SIGTERM')
73
+ await wait(1000)
74
+
75
+ if (!lambda?.killed) { // force kill
76
+ lambda.kill('SIGKILL')
77
+ await wait(1000)
78
+ }
79
+ }
80
+
81
+ lambda = null
82
+ } catch (e) {
83
+ logger.warn('Error stopping lambda:', e)
84
+ }
85
+ }
86
+
87
+ const startLambda = async () => {
88
+ await stopLambda()
89
+
90
+ lambda = spawn(
91
+ 'sam',
92
+ [
93
+ 'local', 'start-api',
94
+ '--warm-containers', 'LAZY',
95
+ '--debug', '--template-file', join(__dirname, 'template-dev.yaml'),
96
+ '--port', args.port,
97
+ ],
98
+ {
99
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
100
+ // stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
101
+ shell: process.platform == 'win32',
102
+ env: {...process.env, DOCKER_HOST: getDockerHost()},
103
+ }
104
+ ).on('exit', code => {
105
+ logger.step(`Lambda process exited with code ${code ?? 0}`)
106
+ if (code !== 0 && code !== null)
107
+ logger.error('Lambda process crashed, waiting for restart...')
108
+
109
+ lambda = null
110
+ })
111
+ .on('message', msg => {
112
+ if (process.send) process.send(msg)
113
+ }).on('disconnect', () => {
114
+ if (process.disconnect) process.disconnect()
115
+ }).on('error', e => {
116
+ logger.error('Lambda process error:', e)
117
+ lambda = null
118
+ })
119
+
120
+ await wait(2000)
121
+
122
+ logger.step('Lambda process started successfully')
123
+ }
124
+
125
+ await buildLambda()
126
+ logger.step(`API running on http://${host}:${port}`)
127
+
128
+ watch(async () => {
129
+ await buildLambda()
130
+ })
131
+
132
+ shutdown(async () => {
133
+ await stopLambda()
134
+ })
135
+ })
136
+ case 'cf':
137
+ return withPort(desiredPort, async (port) => {
138
+ let isBuilding = false
139
+
140
+ const buildWorker = async () => {
141
+ if (isBuilding) return
142
+ isBuilding = true
143
+ logger.step('Building worker')
144
+ try {
145
+ await build(platform)
146
+ await startWorker()
147
+ } catch (e) {
148
+ logger.error('Build failed:', e)
149
+ process.exit(0)
150
+ } finally {
151
+ isBuilding = false
152
+ }
153
+ }
154
+
155
+ let worker: Miniflare | null = null
156
+ const startWorker = async () => {
157
+ if (worker) await worker.dispose()
158
+
159
+ worker = await createMiniflare({ port, host, liveReload: false })
160
+ }
161
+
162
+ await buildWorker()
163
+ logger.step(`API running on http://${host}:${port}`)
164
+
165
+ watch(async () => {
166
+ logger.step('Restarting server')
167
+ await buildWorker()
168
+ logger.step('Server restarted')
169
+ })
170
+ })
171
+ case 'node':
172
+ return withPort(desiredPort, async (port) => {
173
+ logger.step(`API running on http://${host}:${port}`)
174
+
175
+ spawn(
176
+ process.execPath,
177
+ [
178
+ join(__dirname, 'node_modules/.bin/tsx'), 'watch', join(__dirname, 'node_modules/rajt/src/dev.ts'),
179
+ ],
180
+ {
181
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
182
+ env: {...process.env, PORT: args.port},
183
+ }
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
+ })
190
+ })
191
+ default:
192
+ return logger.warn(
193
+ `🟠 Provide a valid platform: ${['aws', 'cf', 'node'].map(p => colors.blue(p)).join(', ')}.\n`
194
+ )
195
+ }
196
+ },
197
+ })
198
+
199
+ function withPort(desiredPort: number, cb: (port: number) => void) {
200
+ getAvailablePort(desiredPort)
201
+ .then((port: number) => {
202
+ if (port != desiredPort)
203
+ logger.warn(`Port ${desiredPort} was in use, using ${port} as a fallback`)
204
+
205
+ cb(port)
206
+ }).catch(e => logger.error('Error finding available port:', e))
207
+ }
208
+
209
+ function getAssetChangeMessage(
210
+ e: ChokidarEventName,
211
+ path: string
212
+ ): string {
213
+ path = relative(__dirname, path)
214
+ switch (e) {
215
+ case 'add':
216
+ return `File ${path} was added`
217
+ case 'addDir':
218
+ return `Directory ${path} was added`
219
+ case 'unlink':
220
+ return `File ${path} was removed`
221
+ case 'unlinkDir':
222
+ return `Directory ${path} was removed`
223
+ case 'change':
224
+ default:
225
+ return `${path} changed`
226
+ }
227
+ }
228
+
229
+ async function watch(cb: (e: ChokidarEventName | string, file: string) => Promise<void>) {
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'),
238
+ join(__dirname, '.env.dev'),
239
+ join(__dirname, '.env.prod'),
240
+ join(__dirname, 'package.json'),
241
+ join(__dirname, 'wrangler.toml'),
242
+ ], {
243
+ ignored: /(^|[/\\])\../, // ignore hidden files
244
+ persistent: true,
245
+ ignoreInitial: true,
246
+ awaitWriteFinish: {
247
+ stabilityThreshold: 200,
248
+ pollInterval: 100,
249
+ },
250
+ })
251
+ let restartTimeout: NodeJS.Timeout | null = null
252
+
253
+ const watcher = (e: ChokidarEventName) => async (file: string) => {
254
+ logger.step(getAssetChangeMessage(e, file))
255
+
256
+ if (restartTimeout)
257
+ clearTimeout(restartTimeout)
258
+
259
+ restartTimeout = setTimeout(async () => {
260
+ await cb(e, file)
261
+ }, 300)
262
+ }
263
+
264
+ codeWatcher.on('change', watcher('change'))
265
+ codeWatcher.on('add', watcher('add'))
266
+ codeWatcher.on('unlink', watcher('unlink'))
267
+ codeWatcher.on('addDir', watcher('addDir'))
268
+ codeWatcher.on('unlinkDir', watcher('unlinkDir'))
269
+
270
+ logger.step('Watching for file changes')
271
+ }
272
+
273
+ async function wait(ms: number) {
274
+ return new Promise(r => setTimeout(r, ms))
275
+ }
276
+
277
+ function getDockerHost() {
278
+ const platform = process.platform
279
+
280
+ if (platform === 'darwin') {
281
+ for (const socket of [
282
+ '/Users/'+ process.env.USER +'/.docker/run/docker.sock',
283
+ '/var/run/docker.sock',
284
+ process.env.DOCKER_HOST
285
+ ]) {
286
+ if (socket && existsSync(socket.replace(/^unix:\/\//, '')))
287
+ return socket.includes('://') ? socket : `unix://${socket}`
288
+ }
289
+
290
+ return 'tcp://localhost:2375'
291
+ }
292
+
293
+ return process.env.DOCKER_HOST || (platform == 'win32' ? 'tcp://localhost:2375' : 'unix:///var/run/docker.sock')
294
+ }