@martel/calyx 0.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.
@@ -0,0 +1,112 @@
1
+ # Dependency Injection System
2
+
3
+ calyx features a native, compile-safe, high-performance Dependency Injection (DI) system modeled after NestJS. It provides identical configuration syntax to support frictionless migration, but operates with ~25x-30x lower bootstrapping overhead.
4
+
5
+ ## Decoupled Architecture
6
+
7
+ The DI system is composed of:
8
+ 1. **Modules (`@Module`)**: Encapsulated scopes organizing providers and controllers.
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.
11
+
12
+ ---
13
+
14
+ ## Decorators Reference
15
+
16
+ ### `@Module(metadata: ModuleMetadata)`
17
+ Declares a module scope.
18
+ ```typescript
19
+ import { Module } from '@martel/calyx';
20
+
21
+ @Module({
22
+ imports: [DatabaseModule],
23
+ providers: [UsersService],
24
+ exports: [UsersService], // Export to make visible to modules importing this module
25
+ })
26
+ export class UsersModule {}
27
+ ```
28
+
29
+ ### `@Injectable()`
30
+ Marks a class as available for injection. Required on all provider/controller classes using constructor injection.
31
+ ```typescript
32
+ import { Injectable } from '@martel/calyx';
33
+
34
+ @Injectable()
35
+ export class UsersService {}
36
+ ```
37
+
38
+ ### `@Inject(token: InjectionToken)`
39
+ Explicitly injection token override. Useful for injecting non-class tokens (like string-based config values or custom providers).
40
+ ```typescript
41
+ import { Injectable, Inject } from '@martel/calyx';
42
+
43
+ @Injectable()
44
+ export class UsersService {
45
+ constructor(@Inject('DATABASE_CONNECTION') private db: DbConnection) {}
46
+ }
47
+ ```
48
+
49
+ ### `@Optional()`
50
+ Marks a constructor parameter as optional. If the provider token cannot be resolved, the container will inject `undefined` instead of throwing an error.
51
+ ```typescript
52
+ import { Injectable, Optional, Inject } from '@martel/calyx';
53
+
54
+ @Injectable()
55
+ export class UsersService {
56
+ constructor(@Optional() @Inject('ANALYTICS_KEY') private apiKey?: string) {}
57
+ }
58
+ ```
59
+
60
+ ### `@Global()`
61
+ Makes a module's exports globally visible, meaning they can be injected into any module without explicitly importing the parent module.
62
+ ```typescript
63
+ import { Module, Global } from '@martel/calyx';
64
+
65
+ @Global()
66
+ @Module({
67
+ providers: [ConfigService],
68
+ exports: [ConfigService],
69
+ })
70
+ export class ConfigModule {}
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Custom Providers
76
+
77
+ calyx supports standard NestJS custom provider forms:
78
+
79
+ ### Value Providers (`useValue`)
80
+ ```typescript
81
+ {
82
+ provide: 'CONFIG',
83
+ useValue: { port: 3000 }
84
+ }
85
+ ```
86
+
87
+ ### Class Providers (`useClass`)
88
+ ```typescript
89
+ {
90
+ provide: LoggerService,
91
+ useClass: BunLoggerService
92
+ }
93
+ ```
94
+
95
+ ### Factory Providers (`useFactory`)
96
+ ```typescript
97
+ {
98
+ provide: 'DB_CONNECTION',
99
+ useFactory: (config: ConfigService) => new Connection(config),
100
+ inject: [ConfigService]
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Performance Benchmarks
107
+
108
+ Benchmarks measure the instantiation time of a 5-layer deep dependency tree (15 provider modules) over 5,000 iterations:
109
+
110
+ * **calyx**: ~41.45 ms total (avg 8.29 μs per bootstrap)
111
+ * **NestJS**: ~1104.36 ms total (avg 220.87 μs per bootstrap)
112
+ * **Relative Performance**: **calyx is ~26x faster** in DI compilation and instantiation.
@@ -0,0 +1,168 @@
1
+ # Request Lifecycle Pipeline
2
+
3
+ calyx provides a complete NestJS-compatible request lifecycle pipeline consisting of Guards, Interceptors, Pipes, and Exception Filters. It exposes an identical API structure, letting you inject dependencies into lifecycle components using constructor injection, but runs at a fraction of the performance overhead.
4
+
5
+ ---
6
+
7
+ ## Execution Order
8
+
9
+ When an HTTP request is received, calyx processes it through the lifecycle stages in the following order:
10
+
11
+ ```mermaid
12
+ graph TD
13
+ A[Incoming Request] --> B[Guards]
14
+ B -- Allow --> C[Interceptors - Pre-handler]
15
+ C --> D[Pipes - Argument Validation/Transformation]
16
+ D --> E[Controller Handler Method]
17
+ E --> F[Interceptors - Post-handler]
18
+ F --> G[Response Sent]
19
+ B -- Block --> H[Exception Filter]
20
+ D -- Invalid --> H
21
+ E -- Error --> H
22
+ H --> G
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Execution Context & ArgumentsHost
28
+
29
+ Lifecycle components receive metadata about the executing process via:
30
+ * `ArgumentsHost`: Exposes request/response contexts.
31
+ * `ExecutionContext` (extends `ArgumentsHost`): Exposes the target Controller class and the method handler function.
32
+
33
+ ### Reference
34
+ ```typescript
35
+ export interface ArgumentsHost {
36
+ getArgs<T extends any[]>(): T;
37
+ getArgByIndex<T>(index: number): T;
38
+ switchToHttp(): HttpArgumentsHost;
39
+ }
40
+
41
+ export interface ExecutionContext extends ArgumentsHost {
42
+ getClass<T = any>(): Type<T>;
43
+ getHandler(): Function;
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Guards (`@UseGuards`)
50
+
51
+ Guards implement the `CanActivate` interface. They return `boolean` (or a Promise resolving to `boolean`) indicating whether the request should proceed. If `false` is returned, calyx throws a `403 Forbidden` exception.
52
+
53
+ ```typescript
54
+ import { CanActivate, ExecutionContext, UseGuards, Controller, Get } from '@martel/calyx';
55
+
56
+ class AuthGuard implements CanActivate {
57
+ canActivate(context: ExecutionContext): boolean {
58
+ const req = context.switchToHttp().getRequest<Request>();
59
+ return req.headers.get('Authorization') === 'allow-secret';
60
+ }
61
+ }
62
+
63
+ @Controller('admin')
64
+ @UseGuards(AuthGuard) // Applies to all routes in the controller
65
+ export class AdminController {
66
+ @Get()
67
+ getDashboard() {
68
+ return 'Admin Dashboard';
69
+ }
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Interceptors (`@UseInterceptors`)
76
+
77
+ Interceptors implement `NestInterceptor`. They allow you to bind extra logic before and after method execution.
78
+
79
+ ```typescript
80
+ import { NestInterceptor, CallHandler, ExecutionContext, UseInterceptors, Get } from '@martel/calyx';
81
+
82
+ class TransformInterceptor implements NestInterceptor {
83
+ async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
84
+ // 1. Pre-handler logic
85
+ const start = Date.now();
86
+
87
+ // 2. Execute inner handlers/controller method
88
+ const result = await next.handle();
89
+
90
+ // 3. Post-handler logic
91
+ const ms = Date.now() - start;
92
+ return { ...result, executionTimeMs: ms };
93
+ }
94
+ }
95
+
96
+ @Get('data')
97
+ @UseInterceptors(TransformInterceptor)
98
+ getData() {
99
+ return { data: 'payload' };
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Pipes (`@UsePipes`)
106
+
107
+ Pipes implement `PipeTransform`. They are used for validation or data transformation. Pipes can be bound at:
108
+ 1. **Class / Method Level**: Using `@UsePipes()`.
109
+ 2. **Parameter Level**: Inside `@Body()`, `@Query()`, or `@Param()`.
110
+
111
+ ```typescript
112
+ import { PipeTransform, Query, Get, BadRequestException } from '@martel/calyx';
113
+
114
+ class ParseIntPipe implements PipeTransform {
115
+ transform(value: any): number {
116
+ const parsed = Number(value);
117
+ if (isNaN(parsed)) {
118
+ throw new BadRequestException('Validation failed: value must be numeric');
119
+ }
120
+ return parsed;
121
+ }
122
+ }
123
+
124
+ @Get('square')
125
+ square(@Query('num', ParseIntPipe) num: number) {
126
+ // num is guaranteed to be a number type
127
+ return { result: num * num };
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Exception Filters (`@UseFilters` / `@Catch`)
134
+
135
+ Filters catch unhandled exceptions thrown during request execution. Use `@Catch(...Exceptions)` to declare which exceptions the filter catches.
136
+
137
+ ```typescript
138
+ import { ExceptionFilter, Catch, ArgumentsHost, UseFilters, calyxResponse } from '@martel/calyx';
139
+
140
+ class CustomError extends Error {}
141
+
142
+ @Catch(CustomError)
143
+ class TeapotFilter implements ExceptionFilter {
144
+ catch(exception: CustomError, host: ArgumentsHost) {
145
+ const res = host.switchToHttp().getResponse<calyxResponse>();
146
+ res.status(418).json({
147
+ error: 'Teapot',
148
+ message: exception.message,
149
+ });
150
+ }
151
+ }
152
+
153
+ @Get('error')
154
+ @UseFilters(TeapotFilter)
155
+ triggerError() {
156
+ throw new CustomError('Failed operation');
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Performance Benchmarks
163
+
164
+ Overhead benchmarks run the full request lifecycle (with a Class Guard, Method Interceptor, and Parameter Query Pipe) using `autocannon` (100 concurrent connections, 8-second test duration, no pipelining) running in separate OS processes:
165
+
166
+ * **calyx (Bun Native with Pipeline)**: **25,152 req/sec** (avg latency **3.25 ms**)
167
+ * **NestJS (Express with Pipeline)**: **10,274 req/sec** (avg latency **9.28 ms**)
168
+ * **Relative Performance**: Even with all decorators and lifecycle hooks active, **calyx is ~2.4x faster** than NestJS and runs with **3x lower latency**.
@@ -0,0 +1,108 @@
1
+ # Migration Guide: NestJS to calyx
2
+
3
+ calyx is built to offer direct syntax and concept compatibility with NestJS. If you have an existing NestJS application, migrating to calyx is straightforward.
4
+
5
+ This document outlines the steps required to transition your codebase to calyx and compile it to a standalone binary.
6
+
7
+ ---
8
+
9
+ ## 1. Project Configuration
10
+
11
+ ### package.json
12
+ Remove NestJS dependencies and install `@martel/calyx` (with `reflect-metadata` which is required for legacy decorator reflection):
13
+
14
+ ```json
15
+ {
16
+ "dependencies": {
17
+ "@martel/calyx": "^0.1.0",
18
+ "reflect-metadata": "^0.2.2"
19
+ }
20
+ }
21
+ ```
22
+
23
+ ### tsconfig.json
24
+ Ensure your TypeScript options enable legacy decorators:
25
+
26
+ ```json
27
+ {
28
+ "compilerOptions": {
29
+ "experimentalDecorators": true,
30
+ "emitDecoratorMetadata": true,
31
+ "moduleResolution": "Bundler"
32
+ }
33
+ }
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 2. Import Mappings
39
+
40
+ NestJS divides code across several packages (`@nestjs/common`, `@nestjs/core`, etc.). calyx exposes all components from a single package.
41
+
42
+ Replace all NestJS imports with `@martel/calyx`:
43
+
44
+ ```typescript
45
+ // BEFORE (NestJS)
46
+ import { Module, Injectable, Controller, Get, Param } from '@nestjs/common';
47
+ import { NestFactory } from '@nestjs/core';
48
+
49
+ // AFTER (calyx)
50
+ import { Module, Injectable, Controller, Get, Param, calyxFactory } from '@martel/calyx';
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 3. Bootstrap Changes
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:
58
+
59
+ ```typescript
60
+ // BEFORE (NestJS)
61
+ import { NestFactory } from '@nestjs/core';
62
+ import { AppModule } from './app.module';
63
+
64
+ async function bootstrap() {
65
+ const app = await NestFactory.create(AppModule);
66
+ await app.listen(3000);
67
+ }
68
+ bootstrap();
69
+
70
+ // AFTER (calyx)
71
+ import { calyxFactory } from '@martel/calyx';
72
+ import { AppModule } from './app.module';
73
+
74
+ async function bootstrap() {
75
+ const app = await calyxFactory.create(AppModule);
76
+ await app.listen(3000);
77
+ }
78
+ bootstrap();
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 4. Execution Lifecycle Components
84
+
85
+ calyx implements identical decorators and interfaces for lifecycle elements:
86
+
87
+ * **Guards**: Change `import { CanActivate } from '@nestjs/common'` to `@martel/calyx`.
88
+ * **Interceptors**: Change `import { NestInterceptor } from '@nestjs/common'` to `@martel/calyx`.
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
+ * **Pipes**: Change `import { PipeTransform } from '@nestjs/common'` to `@martel/calyx`.
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.*
93
+
94
+ ---
95
+
96
+ ## 5. Compiling to a Standalone Binary
97
+
98
+ One of the major benefits of moving to calyx is the ability to package the entire server, including TypeScript files, routing, DI, and the Bun runtime, into a single, high-performance standalone binary:
99
+
100
+ ```bash
101
+ bun build --compile src/main.ts --outfile build/server
102
+ ```
103
+
104
+ You can then run the output directly on any matching machine with no Node.js, Bun, or `node_modules` dependencies:
105
+
106
+ ```bash
107
+ ./build/server
108
+ ```
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@martel/calyx",
3
+ "version": "0.1.0",
4
+ "description": "High-performance Bun-native NestJS-compatible framework",
5
+ "main": "src/index.ts",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "bun test",
9
+ "benchmark": "bun run benchmarks/index.ts"
10
+ },
11
+ "dependencies": {
12
+ "reflect-metadata": "^0.2.2"
13
+ },
14
+ "devDependencies": {
15
+ "@nestjs/common": "^11.1.27",
16
+ "@nestjs/core": "^11.1.27",
17
+ "@nestjs/platform-express": "^11.1.27",
18
+ "@types/autocannon": "^7.12.7",
19
+ "@types/bun": "latest",
20
+ "autocannon": "^8.0.0",
21
+ "rxjs": "^7.8.2",
22
+ "semantic-release": "^24.0.0",
23
+ "@semantic-release/changelog": "^6.0.3",
24
+ "@semantic-release/git": "^10.0.1",
25
+ "@semantic-release/github": "^10.0.0",
26
+ "@semantic-release/npm": "^12.0.1"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }