@martel/calyx 0.1.0 → 1.1.0

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.
@@ -47,7 +47,7 @@ Method arguments can extract parts of the request payload using parameter decora
47
47
  | Decorator | NestJS Match | Description |
48
48
  | :--- | :--- | :--- |
49
49
  | `@Req()` / `@Request()` | `req` | Exposes the raw Bun `Request` object. |
50
- | `@Res()` / `@Response()` | `res` | Exposes the `calyxResponse` wrapper for manual responses. |
50
+ | `@Res()` / `@Response()` | `res` | Exposes the `CalyxResponse` wrapper for manual responses. |
51
51
  | `@Param(key?: string)` | `params[key]` | Extracts path parameters (e.g. `@Param('id')`). |
52
52
  | `@Query(key?: string)` | `query[key]` | Extracts query parameters (e.g. `@Query('limit')`). |
53
53
  | `@Body(key?: string)` | `body[key]` | Extracts request body payload (parsed JSON/urlencoded). |
@@ -104,7 +104,7 @@ legacy() {}
104
104
  If you need full control over the response, inject `@Res()`:
105
105
  ```typescript
106
106
  @Get('manual')
107
- manualResponse(@Res() res: calyxResponse) {
107
+ manualResponse(@Res() res: CalyxResponse) {
108
108
  res.status(202).set('X-Manual', 'true').json({ success: true });
109
109
  }
110
110
  ```
@@ -7,7 +7,7 @@ calyx features a native, compile-safe, high-performance Dependency Injection (DI
7
7
  The DI system is composed of:
8
8
  1. **Modules (`@Module`)**: Encapsulated scopes organizing providers and controllers.
9
9
  2. **Providers (`@Injectable`)**: Classes, factories, or static values that can be injected into constructor functions.
10
- 3. **Container (`calyxContainer`)**: The core registry that parses metadata, detects circular dependencies, and instantiates services.
10
+ 3. **Container (`CalyxContainer`)**: The core registry that parses metadata, detects circular dependencies, and instantiates services.
11
11
 
12
12
  ---
13
13
 
package/docs/lifecycle.md CHANGED
@@ -135,14 +135,14 @@ square(@Query('num', ParseIntPipe) num: number) {
135
135
  Filters catch unhandled exceptions thrown during request execution. Use `@Catch(...Exceptions)` to declare which exceptions the filter catches.
136
136
 
137
137
  ```typescript
138
- import { ExceptionFilter, Catch, ArgumentsHost, UseFilters, calyxResponse } from '@martel/calyx';
138
+ import { ExceptionFilter, Catch, ArgumentsHost, UseFilters, CalyxResponse } from '@martel/calyx';
139
139
 
140
140
  class CustomError extends Error {}
141
141
 
142
142
  @Catch(CustomError)
143
143
  class TeapotFilter implements ExceptionFilter {
144
144
  catch(exception: CustomError, host: ArgumentsHost) {
145
- const res = host.switchToHttp().getResponse<calyxResponse>();
145
+ const res = host.switchToHttp().getResponse<CalyxResponse>();
146
146
  res.status(418).json({
147
147
  error: 'Teapot',
148
148
  message: exception.message,
package/docs/migration.md CHANGED
@@ -47,14 +47,14 @@ import { Module, Injectable, Controller, Get, Param } from '@nestjs/common';
47
47
  import { NestFactory } from '@nestjs/core';
48
48
 
49
49
  // AFTER (calyx)
50
- import { Module, Injectable, Controller, Get, Param, calyxFactory } from '@martel/calyx';
50
+ import { Module, Injectable, Controller, Get, Param, CalyxFactory } from '@martel/calyx';
51
51
  ```
52
52
 
53
53
  ---
54
54
 
55
55
  ## 3. Bootstrap Changes
56
56
 
57
- In NestJS, applications are bootstrapped using `NestFactory.create`. In calyx, use `calyxFactory.create` which returns a `calyxApplication` instance backed by Bun's native HTTP server:
57
+ In NestJS, applications are bootstrapped using `NestFactory.create`. In calyx, use `CalyxFactory.create` which returns a `CalyxApplication` instance backed by Bun's native HTTP server:
58
58
 
59
59
  ```typescript
60
60
  // BEFORE (NestJS)
@@ -68,11 +68,11 @@ async function bootstrap() {
68
68
  bootstrap();
69
69
 
70
70
  // AFTER (calyx)
71
- import { calyxFactory } from '@martel/calyx';
71
+ import { CalyxFactory } from '@martel/calyx';
72
72
  import { AppModule } from './app.module';
73
73
 
74
74
  async function bootstrap() {
75
- const app = await calyxFactory.create(AppModule);
75
+ const app = await CalyxFactory.create(AppModule);
76
76
  await app.listen(3000);
77
77
  }
78
78
  bootstrap();
@@ -89,7 +89,7 @@ calyx implements identical decorators and interfaces for lifecycle elements:
89
89
  * *Note: In NestJS, `next.handle()` returns an RxJS `Observable`. In calyx, it returns a standard native `Promise` or an `Observable` based on developer preference. If migrating RxJS interceptors, you can map the result or rewrite to simple async-await.*
90
90
  * **Pipes**: Change `import { PipeTransform } from '@nestjs/common'` to `@martel/calyx`.
91
91
  * **Exception Filters**: Change `import { ExceptionFilter, Catch } from '@nestjs/common'` to `@martel/calyx`.
92
- * *Note: Expose `host.switchToHttp().getResponse()` which returns calyx's Express-compatible `calyxResponse` wrapper to make calls like `res.status(code).json(body)` compatible without modifications.*
92
+ * *Note: Expose `host.switchToHttp().getResponse()` which returns calyx's Express-compatible `CalyxResponse` wrapper to make calls like `res.status(code).json(body)` compatible without modifications.*
93
93
 
94
94
  ---
95
95
 
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@martel/calyx",
3
- "version": "0.1.0",
3
+ "version": "1.1.0",
4
4
  "description": "High-performance Bun-native NestJS-compatible framework",
5
5
  "main": "src/index.ts",
6
+ "bin": {
7
+ "calyx": "./src/cli/index.ts"
8
+ },
6
9
  "type": "module",
7
10
  "scripts": {
8
11
  "test": "bun test",
@@ -19,13 +22,14 @@
19
22
  "@types/bun": "latest",
20
23
  "autocannon": "^8.0.0",
21
24
  "rxjs": "^7.8.2",
22
- "semantic-release": "^24.0.0",
25
+ "semantic-release": "^25.0.0",
23
26
  "@semantic-release/changelog": "^6.0.3",
24
27
  "@semantic-release/git": "^10.0.1",
25
- "@semantic-release/github": "^10.0.0",
26
- "@semantic-release/npm": "^12.0.1"
28
+ "@semantic-release/github": "^12.0.0",
29
+ "@semantic-release/npm": "^13.0.0"
27
30
  },
28
31
  "publishConfig": {
29
- "access": "public"
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
30
34
  }
31
35
  }
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env bun
2
+ import { spawnSync } from 'child_process';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+
6
+ const args = Bun.argv.slice(2);
7
+ const command = args[0];
8
+
9
+ if (!command) {
10
+ printHelp();
11
+ process.exit(0);
12
+ }
13
+
14
+ switch (command) {
15
+ case 'info':
16
+ runInfo();
17
+ break;
18
+ case 'start':
19
+ runStart(args.slice(1));
20
+ break;
21
+ case 'build':
22
+ runBuild(args.slice(1));
23
+ break;
24
+ case 'new':
25
+ runNew(args[1]);
26
+ break;
27
+ case 'generate':
28
+ case 'g':
29
+ runGenerate(args[1], args[2]);
30
+ break;
31
+ default:
32
+ console.log(`Unknown command: ${command}`);
33
+ printHelp();
34
+ process.exit(1);
35
+ }
36
+
37
+ function printHelp() {
38
+ console.log(`
39
+ Calyx CLI - Bun-native NestJS-compatible Framework CLI
40
+
41
+ Usage:
42
+ calyx <command> [options]
43
+
44
+ Commands:
45
+ new <name> Scaffold a new Calyx application
46
+ generate, g <type> <name> Generate a new schematic element (module, controller, service)
47
+ start Start the application
48
+ -w, --watch Enable hot reload / watch mode
49
+ build Bundle the application using Bun compiler
50
+ info Display system and environment details
51
+ `);
52
+ }
53
+
54
+ function runInfo() {
55
+ console.log(`
56
+ Calyx CLI Information:
57
+ Calyx Version: 0.1.0
58
+ Bun Version: ${Bun.version}
59
+ OS Platform: ${process.platform}
60
+ Node Compatibility: Yes
61
+ `);
62
+ }
63
+
64
+ function runStart(cmdArgs: string[]) {
65
+ const isWatch = cmdArgs.includes('--watch') || cmdArgs.includes('-w');
66
+ const mainPath = 'src/main.ts';
67
+
68
+ if (!existsSync(mainPath)) {
69
+ console.error(`Error: Cannot find entrypoint "${mainPath}". Make sure you are in a Calyx project root.`);
70
+ process.exit(1);
71
+ }
72
+
73
+ const spawnArgs = isWatch ? ['--watch', mainPath] : [mainPath];
74
+ console.log(`Starting Calyx application... (${isWatch ? 'Watch Mode' : 'Standard Mode'})`);
75
+
76
+ const proc = spawnSync('bun', spawnArgs, { stdio: 'inherit' });
77
+ process.exit(proc.status ?? 0);
78
+ }
79
+
80
+ function runBuild(cmdArgs: string[]) {
81
+ const mainPath = 'src/main.ts';
82
+ if (!existsSync(mainPath)) {
83
+ console.error(`Error: Cannot find entrypoint "${mainPath}". Make sure you are in a Calyx project root.`);
84
+ process.exit(1);
85
+ }
86
+
87
+ console.log('Building Calyx application using bun build...');
88
+ const proc = spawnSync('bun', ['build', mainPath, '--outdir', './dist', '--target', 'bun'], { stdio: 'inherit' });
89
+ if (proc.status === 0) {
90
+ console.log('Build completed successfully. Output at ./dist/main.js');
91
+ }
92
+ process.exit(proc.status ?? 0);
93
+ }
94
+
95
+ function runNew(name: string) {
96
+ if (!name) {
97
+ console.error('Error: Please specify the project name. Example: calyx new my-app');
98
+ process.exit(1);
99
+ }
100
+
101
+ if (existsSync(name)) {
102
+ console.error(`Error: Directory "${name}" already exists.`);
103
+ process.exit(1);
104
+ }
105
+
106
+ console.log(`Scaffolding new Calyx application in "${name}"...`);
107
+
108
+ mkdirSync(name, { recursive: true });
109
+ mkdirSync(join(name, 'src'), { recursive: true });
110
+
111
+ // Write package.json
112
+ const packageJson = {
113
+ name,
114
+ version: '0.0.1',
115
+ description: 'Calyx application',
116
+ type: 'module',
117
+ scripts: {
118
+ "start": "calyx start",
119
+ "start:dev": "calyx start --watch",
120
+ "build": "calyx build"
121
+ },
122
+ dependencies: {
123
+ "@martel/calyx": "link:../..", // link back to Calyx package parent root for local tests
124
+ "reflect-metadata": "^0.2.2"
125
+ }
126
+ };
127
+ writeFileSync(join(name, 'package.json'), JSON.stringify(packageJson, null, 2));
128
+
129
+ // Write tsconfig.json
130
+ const tsconfigJson = {
131
+ compilerOptions: {
132
+ module: "ESNext",
133
+ target: "ESNext",
134
+ moduleResolution: "bundler",
135
+ esModuleInterop: true,
136
+ experimentalDecorators: true,
137
+ emitDecoratorMetadata: true,
138
+ strict: true,
139
+ skipLibCheck: true
140
+ }
141
+ };
142
+ writeFileSync(join(name, 'tsconfig.json'), JSON.stringify(tsconfigJson, null, 2));
143
+
144
+ // Write src/app.service.ts
145
+ const appService = `import { Injectable } from '@martel/calyx';
146
+
147
+ @Injectable()
148
+ export class AppService {
149
+ getHello(): string {
150
+ return 'Hello World!';
151
+ }
152
+ }
153
+ `;
154
+ writeFileSync(join(name, 'src/app.service.ts'), appService);
155
+
156
+ // Write src/app.controller.ts
157
+ const appController = `import { Controller, Get } from '@martel/calyx';
158
+ import { AppService } from './app.service';
159
+
160
+ @Controller()
161
+ export class AppController {
162
+ constructor(private readonly appService: AppService) {}
163
+
164
+ @Get()
165
+ getHello(): string {
166
+ return this.appService.getHello();
167
+ }
168
+ }
169
+ `;
170
+ writeFileSync(join(name, 'src/app.controller.ts'), appController);
171
+
172
+ // Write src/app.module.ts
173
+ const appModule = `import { Module } from '@martel/calyx';
174
+ import { AppController } from './app.controller';
175
+ import { AppService } from './app.service';
176
+
177
+ @Module({
178
+ controllers: [AppController],
179
+ providers: [AppService],
180
+ })
181
+ export class AppModule {}
182
+ `;
183
+ writeFileSync(join(name, 'src/app.module.ts'), appModule);
184
+
185
+ // Write src/main.ts
186
+ const mainTs = `import 'reflect-metadata';
187
+ import { CalyxFactory } from '@martel/calyx';
188
+ import { AppModule } from './app.module';
189
+
190
+ async function bootstrap() {
191
+ const app = await CalyxFactory.create(AppModule);
192
+ await app.listen(3000);
193
+ console.log('Application is running on http://localhost:3000');
194
+ }
195
+ bootstrap();
196
+ `;
197
+ writeFileSync(join(name, 'src/main.ts'), mainTs);
198
+
199
+ console.log('Installing dependencies...');
200
+ spawnSync('bun', ['install'], { cwd: name, stdio: 'inherit' });
201
+
202
+ console.log(`
203
+ Calyx application successfully created!
204
+
205
+ To start running your app:
206
+ cd ${name}
207
+ bun run start:dev
208
+ `);
209
+ }
210
+
211
+ function runGenerate(schematic: string, rawName: string) {
212
+ if (!schematic || !rawName) {
213
+ console.error('Error: Please specify schematic type and name. Example: calyx g controller users');
214
+ process.exit(1);
215
+ }
216
+
217
+ const name = rawName.toLowerCase();
218
+ const pascalName = rawName.charAt(0).toUpperCase() + rawName.slice(1);
219
+ const type = schematic.toLowerCase();
220
+
221
+ const srcDir = existsSync('src') ? 'src' : '.';
222
+ const targetDir = join(srcDir, name);
223
+
224
+ if (!existsSync(targetDir)) {
225
+ mkdirSync(targetDir, { recursive: true });
226
+ }
227
+
228
+ switch (type) {
229
+ case 'module': {
230
+ const filePath = join(targetDir, `${name}.module.ts`);
231
+ const content = `import { Module } from '@martel/calyx';
232
+
233
+ @Module({})
234
+ export class ${pascalName}Module {}
235
+ `;
236
+ writeFileSync(filePath, content);
237
+ console.log(`CREATE ${filePath}`);
238
+ break;
239
+ }
240
+ case 'controller': {
241
+ const filePath = join(targetDir, `${name}.controller.ts`);
242
+ const content = `import { Controller, Get } from '@martel/calyx';
243
+
244
+ @Controller('${name}')
245
+ export class ${pascalName}Controller {
246
+ @Get()
247
+ findAll() {
248
+ return 'This action returns all ${name}';
249
+ }
250
+ }
251
+ `;
252
+ writeFileSync(filePath, content);
253
+ console.log(`CREATE ${filePath}`);
254
+ autoRegisterInModule(name, pascalName, 'controller');
255
+ break;
256
+ }
257
+ case 'service': {
258
+ const filePath = join(targetDir, `${name}.service.ts`);
259
+ const content = `import { Injectable } from '@martel/calyx';
260
+
261
+ @Injectable()
262
+ export class ${pascalName}Service {}
263
+ `;
264
+ writeFileSync(filePath, content);
265
+ console.log(`CREATE ${filePath}`);
266
+ autoRegisterInModule(name, pascalName, 'service');
267
+ break;
268
+ }
269
+ default:
270
+ console.error(`Error: Unknown schematic type "${type}". Supported: module, controller, service`);
271
+ process.exit(1);
272
+ }
273
+ }
274
+
275
+ function autoRegisterInModule(name: string, pascalName: string, type: 'controller' | 'service') {
276
+ const srcDir = existsSync('src') ? 'src' : '.';
277
+ const modulePath = join(srcDir, name, `${name}.module.ts`);
278
+ const rootModulePath = join(srcDir, `app.module.ts`);
279
+
280
+ const pathToCheck = existsSync(modulePath) ? modulePath : (existsSync(rootModulePath) ? rootModulePath : null);
281
+ if (!pathToCheck) return;
282
+
283
+ let content = readFileSync(pathToCheck, 'utf-8');
284
+
285
+ // Insert import statement
286
+ const importName = `${pascalName}${type === 'controller' ? 'Controller' : 'Service'}`;
287
+ const importRelativePath = pathToCheck === rootModulePath ? `./${name}/${name}.${type}` : `./${name}.${type}`;
288
+ const importStatement = `import { ${importName} } from '${importRelativePath}';\n`;
289
+
290
+ // Find last import line and insert after it
291
+ const lines = content.split('\n');
292
+ let lastImportIdx = -1;
293
+ for (let i = 0; i < lines.length; i++) {
294
+ if (lines[i].trim().startsWith('import ')) {
295
+ lastImportIdx = i;
296
+ }
297
+ }
298
+ lines.splice(lastImportIdx + 1, 0, importStatement.trim());
299
+ content = lines.join('\n');
300
+
301
+ // Add item into metadata decorator
302
+ const arrayName = type === 'controller' ? 'controllers' : 'providers';
303
+ const arrayRegex = new RegExp(`(${arrayName}\\s*:\\s*\\[)([^\\]]*)(\\])`);
304
+
305
+ if (arrayRegex.test(content)) {
306
+ content = content.replace(arrayRegex, (match, prefix, list, suffix) => {
307
+ const trimmedList = list.trim();
308
+ const newList = trimmedList ? `${trimmedList}, ${importName}` : importName;
309
+ return `${prefix}${newList}${suffix}`;
310
+ });
311
+ } else {
312
+ // Add the metadata property if it doesn't exist
313
+ const decoratorRegex = /(@Module\(\s*\{)([\s\S]*?)(\}\s*\))/;
314
+ content = content.replace(decoratorRegex, (match, prefix, body, suffix) => {
315
+ const trimmedBody = body.trim();
316
+ const newBody = trimmedBody ? `${trimmedBody},\n ${arrayName}: [${importName}]` : ` ${arrayName}: [${importName}]`;
317
+ return `${prefix}\n${newBody}\n${suffix}`;
318
+ });
319
+ }
320
+
321
+ writeFileSync(pathToCheck, content);
322
+ console.log(`UPDATE ${pathToCheck} (Registered ${importName})`);
323
+ }